예제 #1
0
        public virtual void Start(bool virtualTrading)
        {
            loggingService.Info("Start Exchange service...");
            Api = InitializeApi();

            if (!virtualTrading && !String.IsNullOrWhiteSpace(Config.KeysPath))
            {
                if (File.Exists(Config.KeysPath))
                {
                    loggingService.Info("Load keys from encrypted file...");
                    Api.LoadAPIKeys(Config.KeysPath);
                }
                else
                {
                    throw new FileNotFoundException("Keys file not found");
                }
            }

            loggingService.Info("Get initial ticker values...");
            IEnumerable <KeyValuePair <string, ExchangeTicker> > exchangeTickers = null;

            for (int retry = 0; retry < INITIAL_TICKERS_RETRY_LIMIT; retry++)
            {
                Task.Run(() => exchangeTickers = Api.GetTickers()).Wait(TimeSpan.FromMilliseconds(INITIAL_TICKERS_TIMEOUT_MILLISECONDS));
                if (exchangeTickers != null)
                {
                    break;
                }
            }
            if (exchangeTickers != null)
            {
                Tickers = new ConcurrentDictionary <string, Ticker>(exchangeTickers.Select(t => new KeyValuePair <string, Ticker>(t.Key, new Ticker
                {
                    Pair      = t.Key,
                    AskPrice  = t.Value.Ask,
                    BidPrice  = t.Value.Bid,
                    LastPrice = t.Value.Last
                })));
                markets = new ConcurrentBag <string>(Tickers.Keys.Select(pair => GetPairMarket(pair)).Distinct().ToList());

                lastTickersUpdate = DateTimeOffset.Now;
                healthCheckService.UpdateHealthCheck(Constants.HealthChecks.TickersUpdated, $"Updates: {Tickers.Count}");
            }
            else if (Tickers != null)
            {
                loggingService.Error("Unable to get initial ticker values");
            }
            else
            {
                throw new Exception("Unable to get initial ticker values");
            }

            ConnectTickersWebsocket();

            loggingService.Info("Exchange service started");
        }
        private void TakeSignalsSnapshot()
        {
            var signals = signalsService.GetAllSignals().Select(s => SignalData.FromSignal(s));

            byte[] signalBytes             = ZeroFormatterSerializer.Serialize(signals);
            string signalsSnapshotFilePath = backtestingService.GetSnapshotFilePath(Constants.SnapshotEntities.Signals);
            var    signalsSnapshotFile     = new FileInfo(signalsSnapshotFilePath);

            signalsSnapshotFile.Directory.Create();
            File.WriteAllBytes(signalsSnapshotFilePath, signalBytes);

            healthCheckService.UpdateHealthCheck(Constants.HealthChecks.BacktestingSignalsSnapshotTaken, $"Signals: {signals.Count()}");
        }
        public void ProcessAllRules()
        {
            if (tradingService.Config.BuyEnabled)
            {
                IEnumerable <ISignal> allSignals = signalsService.GetAllSignals();
                if (allSignals != null)
                {
                    IEnumerable <IRule> enabledRules = signalsService.Rules.Entries.Where(r => r.Enabled);
                    if (enabledRules.Any())
                    {
                        var           groupedSignals = allSignals.Where(s => tradingService.GetPairConfig(s.Pair).BuyEnabled).GroupBy(s => s.Pair).ToDictionary(g => g.Key, g => g.ToDictionary(s => s.Name, s => s));
                        double?       globalRating   = signalsService.GetGlobalRating();
                        List <String> excludedPairs  = GetExcludedPairs();

                        if (signalsService.RulesConfig.ProcessingMode == RuleProcessingMode.FirstMatch)
                        {
                            excludedPairs.AddRange(trailingSignals.Keys);
                        }

                        foreach (IRule rule in enabledRules)
                        {
                            foreach (var group in groupedSignals)
                            {
                                Dictionary <string, ISignal> signals = group.Value;
                                ProcessRule(rule, signals, group.Key, excludedPairs, globalRating);
                            }
                        }
                    }

                    healthCheckService.UpdateHealthCheck(Constants.HealthChecks.SignalRulesProcessed, $"Rules: {enabledRules.Count()}, Trailing signals: {trailingSignals.Count}");
                }
            }
        }
예제 #4
0
        public void ProcessAllRules()
        {
            IEnumerable <IRule> enabledRules = tradingService.Rules?.Entries?.Where(r => r.Enabled) ?? new List <IRule>();
            List <string>       allPairs     = tradingService.Exchange.GetMarketPairs(tradingService.Config.Market).ToList();
            double?globalRating = signalsService.GetGlobalRating();

            foreach (string pair in allPairs)
            {
                IEnumerable <ISignal> signalsByPair = signalsService.GetSignalsByPair(pair);
                if (signalsByPair != null)
                {
                    Dictionary <string, ISignal> signals = signalsByPair.ToDictionary(s => s.Name, s => s);
                    ITradingPair  tradingPair            = tradingService.Account.GetTradingPair(pair);
                    TradingConfig modifiedTradingConfig  = tradingService.Config.Clone() as TradingConfig;
                    PairConfig    modifiedPairConfig     = new PairConfig();
                    pairConfigs.TryGetValue(pair, out PairConfig oldPairConfig);
                    var appliedRules = new List <IRule>();

                    foreach (var rule in enabledRules)
                    {
                        if (rulesService.CheckConditions(rule.Conditions, signals, globalRating, pair, tradingPair))
                        {
                            var modifiers = rule.GetModifiers <TradingRuleModifiers>();
                            if (modifiers != null)
                            {
                                // Base Trading Config
                                modifiedTradingConfig.MaxPairs              = modifiers.MaxPairs ?? modifiedTradingConfig.MaxPairs;
                                modifiedTradingConfig.BuyEnabled            = modifiers.BuyEnabled ?? modifiedTradingConfig.BuyEnabled;
                                modifiedTradingConfig.BuyMaxCost            = modifiers.BuyMaxCost ?? modifiedTradingConfig.BuyMaxCost;
                                modifiedTradingConfig.BuyMultiplier         = modifiers.BuyMultiplier ?? modifiedTradingConfig.BuyMultiplier;
                                modifiedTradingConfig.BuyMinBalance         = modifiers.BuyMinBalance ?? modifiedTradingConfig.BuyMinBalance;
                                modifiedTradingConfig.BuySamePairTimeout    = modifiers.BuySamePairTimeout ?? modifiedTradingConfig.BuySamePairTimeout;
                                modifiedTradingConfig.BuyTrailing           = modifiers.BuyTrailing ?? modifiedTradingConfig.BuyTrailing;
                                modifiedTradingConfig.BuyTrailingStopMargin = modifiers.BuyTrailingStopMargin ?? modifiedTradingConfig.BuyTrailingStopMargin;
                                modifiedTradingConfig.BuyTrailingStopAction = modifiers.BuyTrailingStopAction ?? modifiedTradingConfig.BuyTrailingStopAction;

                                modifiedTradingConfig.BuyDCAEnabled            = modifiers.BuyDCAEnabled ?? modifiedTradingConfig.BuyDCAEnabled;
                                modifiedTradingConfig.BuyDCAMultiplier         = modifiers.BuyDCAMultiplier ?? modifiedTradingConfig.BuyDCAMultiplier;
                                modifiedTradingConfig.BuyDCAMinBalance         = modifiers.BuyDCAMinBalance ?? modifiedTradingConfig.BuyDCAMinBalance;
                                modifiedTradingConfig.BuyDCASamePairTimeout    = modifiers.BuyDCASamePairTimeout ?? modifiedTradingConfig.BuyDCASamePairTimeout;
                                modifiedTradingConfig.BuyDCATrailing           = modifiers.BuyDCATrailing ?? modifiedTradingConfig.BuyDCATrailing;
                                modifiedTradingConfig.BuyDCATrailingStopMargin = modifiers.BuyDCATrailingStopMargin ?? modifiedTradingConfig.BuyDCATrailingStopMargin;
                                modifiedTradingConfig.BuyDCATrailingStopAction = modifiers.BuyDCATrailingStopAction ?? modifiedTradingConfig.BuyDCATrailingStopAction;

                                modifiedTradingConfig.SellEnabled            = modifiers.SellEnabled ?? modifiedTradingConfig.SellEnabled;
                                modifiedTradingConfig.SellMargin             = modifiers.SellMargin ?? modifiedTradingConfig.SellMargin;
                                modifiedTradingConfig.SellTrailing           = modifiers.SellTrailing ?? modifiedTradingConfig.SellTrailing;
                                modifiedTradingConfig.SellTrailingStopMargin = modifiers.SellTrailingStopMargin ?? modifiedTradingConfig.SellTrailingStopMargin;
                                modifiedTradingConfig.SellTrailingStopAction = modifiers.SellTrailingStopAction ?? modifiedTradingConfig.SellTrailingStopAction;
                                modifiedTradingConfig.SellStopLossEnabled    = modifiers.SellStopLossEnabled ?? modifiedTradingConfig.SellStopLossEnabled;
                                modifiedTradingConfig.SellStopLossAfterDCA   = modifiers.SellStopLossAfterDCA ?? modifiedTradingConfig.SellStopLossAfterDCA;
                                modifiedTradingConfig.SellStopLossMinAge     = modifiers.SellStopLossMinAge ?? modifiedTradingConfig.SellStopLossMinAge;
                                modifiedTradingConfig.SellStopLossMargin     = modifiers.SellStopLossMargin ?? modifiedTradingConfig.SellStopLossMargin;

                                modifiedTradingConfig.SellDCAMargin             = modifiers.SellDCAMargin ?? modifiedTradingConfig.SellDCAMargin;
                                modifiedTradingConfig.SellDCATrailing           = modifiers.SellDCATrailing ?? modifiedTradingConfig.SellDCATrailing;
                                modifiedTradingConfig.SellDCATrailingStopMargin = modifiers.SellDCATrailingStopMargin ?? modifiedTradingConfig.SellDCATrailingStopMargin;
                                modifiedTradingConfig.SellDCATrailingStopAction = modifiers.SellDCATrailingStopAction ?? modifiedTradingConfig.SellDCATrailingStopAction;

                                modifiedTradingConfig.RepeatLastDCALevel = modifiers.RepeatLastDCALevel ?? modifiedTradingConfig.RepeatLastDCALevel;
                                modifiedTradingConfig.DCALevels          = modifiers.DCALevels ?? modifiedTradingConfig.DCALevels;

                                // Base Pair Config
                                modifiedPairConfig.SwapEnabled     = modifiers.SwapEnabled ?? modifiedPairConfig.SwapEnabled;
                                modifiedPairConfig.SwapSignalRules = modifiers.SwapSignalRules ?? modifiedPairConfig.SwapSignalRules;
                                modifiedPairConfig.SwapTimeout     = modifiers.SwapTimeout ?? modifiedPairConfig.SwapTimeout;

                                modifiedPairConfig.ArbitrageEnabled        = modifiers.ArbitrageEnabled ?? modifiedPairConfig.ArbitrageEnabled;
                                modifiedPairConfig.ArbitrageMarkets        = modifiers.ArbitrageMarkets ?? modifiedPairConfig.ArbitrageMarkets;
                                modifiedPairConfig.ArbitrageType           = modifiers.ArbitrageType ?? modifiedPairConfig.ArbitrageType;
                                modifiedPairConfig.ArbitrageBuyMultiplier  = modifiers.ArbitrageBuyMultiplier ?? modifiedPairConfig.ArbitrageBuyMultiplier;
                                modifiedPairConfig.ArbitrageSellMultiplier = modifiers.ArbitrageSellMultiplier ?? modifiedPairConfig.ArbitrageSellMultiplier;
                                modifiedPairConfig.ArbitrageSignalRules    = modifiers.ArbitrageSignalRules ?? modifiedPairConfig.ArbitrageSignalRules;

                                if (oldPairConfig != null && !oldPairConfig.ArbitrageEnabled && modifiedPairConfig.ArbitrageEnabled)
                                {
                                    signalsService.ProcessPair(pair, signals);
                                }
                            }

                            appliedRules.Add(rule);

                            if (tradingService.RulesConfig.ProcessingMode == RuleProcessingMode.FirstMatch)
                            {
                                break;
                            }
                        }
                    }

                    pairConfigs[pair] = CreatePairConfig(pair, modifiedTradingConfig, modifiedPairConfig, appliedRules);
                }
            }

            healthCheckService.UpdateHealthCheck(Constants.HealthChecks.TradingRulesProcessed, $"Rules: {enabledRules.Count()}, Pairs: {allPairs.Count}");
        }
예제 #5
0
        private void ProcessTradingPairs()
        {
            int traidingPairsCount = 0;

            foreach (var tradingPair in tradingService.Account.GetTradingPairs())
            {
                IPairConfig pairConfig = tradingService.GetPairConfig(tradingPair.Pair);
                tradingPair.SetCurrentValues(tradingService.GetCurrentPrice(tradingPair.Pair), tradingService.GetCurrentSpread(tradingPair.Pair));
                tradingPair.Metadata.TradingRules  = pairConfig.Rules.ToList();
                tradingPair.Metadata.CurrentRating = tradingPair.Metadata.Signals != null?signalsService.GetRating(tradingPair.Pair, tradingPair.Metadata.Signals) : null;

                tradingPair.Metadata.CurrentGlobalRating = signalsService.GetGlobalRating();

                if (trailingSells.TryGetValue(tradingPair.Pair, out SellTrailingInfo sellTrailingInfo))
                {
                    if (pairConfig.SellEnabled)
                    {
                        if (Math.Round(tradingPair.CurrentMargin, 1) != Math.Round(sellTrailingInfo.LastTrailingMargin, 1))
                        {
                            if (LoggingEnabled)
                            {
                                loggingService.Info($"Continue trailing sell {tradingPair.FormattedName}. Price: {tradingPair.CurrentPrice:0.00000000}, Margin: {tradingPair.CurrentMargin:0.00}");
                            }
                        }

                        if (tradingPair.CurrentMargin <= sellTrailingInfo.TrailingStopMargin || tradingPair.CurrentMargin < (sellTrailingInfo.BestTrailingMargin - sellTrailingInfo.Trailing))
                        {
                            trailingSells.TryRemove(tradingPair.Pair, out SellTrailingInfo p);

                            if (tradingPair.CurrentMargin > 0 || sellTrailingInfo.SellMargin < 0)
                            {
                                if (sellTrailingInfo.TrailingStopAction == SellTrailingStopAction.Sell || tradingPair.CurrentMargin > sellTrailingInfo.TrailingStopMargin)
                                {
                                    PlaceSellOrder(sellTrailingInfo.SellOptions);
                                }
                                else
                                {
                                    if (LoggingEnabled)
                                    {
                                        loggingService.Info($"Stop trailing sell {tradingPair.FormattedName}. Reason: stop margin reached");
                                    }
                                }
                            }
                            else
                            {
                                if (LoggingEnabled)
                                {
                                    loggingService.Info($"Stop trailing sell {tradingPair.FormattedName}. Reason: negative margin");
                                }
                            }
                        }
                        else
                        {
                            sellTrailingInfo.LastTrailingMargin = tradingPair.CurrentMargin;
                            if (tradingPair.CurrentMargin > sellTrailingInfo.BestTrailingMargin)
                            {
                                sellTrailingInfo.BestTrailingMargin = tradingPair.CurrentMargin;
                            }
                        }
                    }
                    else
                    {
                        trailingSells.TryRemove(tradingPair.Pair, out SellTrailingInfo p);
                    }
                }
                else
                {
                    if (pairConfig.SellEnabled && tradingPair.CurrentMargin >= pairConfig.SellMargin)
                    {
                        InitiateSell(new SellOptions(tradingPair.Pair));
                    }
                    else if (pairConfig.SellEnabled && pairConfig.SellStopLossEnabled && tradingPair.CurrentMargin <= pairConfig.SellStopLossMargin && tradingPair.CurrentAge >= pairConfig.SellStopLossMinAge &&
                             (pairConfig.NextDCAMargin == null || !pairConfig.SellStopLossAfterDCA))
                    {
                        if (LoggingEnabled)
                        {
                            loggingService.Info($"Stop loss triggered for {tradingPair.FormattedName}. Margin: {tradingPair.CurrentMargin:0.00}");
                        }
                        PlaceSellOrder(new SellOptions(tradingPair.Pair));
                    }
                    else if (pairConfig.NextDCAMargin != null && pairConfig.BuyEnabled && pairConfig.NextDCAMargin != null &&
                             !trailingBuys.ContainsKey(tradingPair.Pair) && !trailingSells.ContainsKey(tradingPair.Pair))
                    {
                        if (tradingPair.CurrentMargin <= pairConfig.NextDCAMargin)
                        {
                            var buyOptions = new BuyOptions(tradingPair.Pair)
                            {
                                MaxCost        = tradingPair.AverageCostPaid * pairConfig.BuyMultiplier,
                                IgnoreExisting = true
                            };

                            if (tradingService.CanBuy(buyOptions, message: out string message))
                            {
                                if (LoggingEnabled)
                                {
                                    loggingService.Info($"DCA triggered for {tradingPair.FormattedName}. Margin: {tradingPair.CurrentMargin:0.00}, Level: {pairConfig.NextDCAMargin:0.00}, Multiplier: {pairConfig.BuyMultiplier}");
                                }
                                InitiateBuy(buyOptions);
                            }
                        }
                    }
                }

                traidingPairsCount++;
            }

            foreach (var kvp in trailingBuys)
            {
                string          pair            = kvp.Key;
                BuyTrailingInfo buyTrailingInfo = kvp.Value;
                ITradingPair    tradingPair     = tradingService.Account.GetTradingPair(pair);
                IPairConfig     pairConfig      = tradingService.GetPairConfig(pair);
                decimal         currentPrice    = tradingService.GetCurrentPrice(pair);
                decimal         currentMargin   = Utils.CalculateMargin(buyTrailingInfo.InitialPrice, currentPrice);

                if (pairConfig.BuyEnabled)
                {
                    if (Math.Round(currentMargin, 1) != Math.Round(buyTrailingInfo.LastTrailingMargin, 1))
                    {
                        if (LoggingEnabled)
                        {
                            loggingService.Info($"Continue trailing buy {tradingPair?.FormattedName ?? pair}. Price: {currentPrice:0.00000000}, Margin: {currentMargin:0.00}");
                        }
                    }

                    if (currentMargin >= buyTrailingInfo.TrailingStopMargin || currentMargin > (buyTrailingInfo.BestTrailingMargin - buyTrailingInfo.Trailing))
                    {
                        trailingBuys.TryRemove(pair, out BuyTrailingInfo p);

                        if (buyTrailingInfo.TrailingStopAction == BuyTrailingStopAction.Buy || currentMargin < buyTrailingInfo.TrailingStopMargin)
                        {
                            PlaceBuyOrder(buyTrailingInfo.BuyOptions);
                        }
                        else
                        {
                            if (LoggingEnabled)
                            {
                                loggingService.Info($"Stop trailing buy {tradingPair?.FormattedName ?? pair}. Reason: stop margin reached");
                            }
                        }
                    }
                    else
                    {
                        buyTrailingInfo.LastTrailingMargin = currentMargin;
                        if (currentMargin < buyTrailingInfo.BestTrailingMargin)
                        {
                            buyTrailingInfo.BestTrailingMargin = currentMargin;
                        }
                    }
                }
                else
                {
                    trailingBuys.TryRemove(pair, out BuyTrailingInfo p);
                }
            }

            healthCheckService.UpdateHealthCheck(Constants.HealthChecks.TradingPairsProcessed, $"Pairs: {traidingPairsCount}, Trailing buys: {trailingBuys.Count}, Trailing sells: {trailingSells.Count}");
        }
예제 #6
0
        private void LoadNextSnapshots()
        {
            lock (backtestingService.SyncRoot)
            {
                if (loadedSignalSnapshots == 0 && loadedTickerSnapshots == 0)
                {
                    loggingService.Info($"<<<--- Backtesting started. Total signals snapshots: {totalSignalSnapshots}, Total tickers snapshots: {totalTickerSnapshots} --->>>");
                    backtestingTimer = Stopwatch.StartNew();
                }

                if (allSignalSnapshotPaths.TryDequeue(out string currentSignalsSnapshotPath))
                {
                    try
                    {
                        byte[] currentSignalsSnapshotBytes = File.ReadAllBytes(currentSignalsSnapshotPath);
                        IEnumerable <ISignal> data         = ZeroFormatterSerializer.Deserialize <IEnumerable <SignalData> >(currentSignalsSnapshotBytes).Select(s => s.ToSignal()).ToList();
                        currentSignals = data.GroupBy(s => s.Pair).ToDictionary(s => s.Key, s => s.AsEnumerable());
                        loadedSignalSnapshots++;

                        var currentSignalsSnapshotFile = currentSignalsSnapshotPath.Substring(currentSignalsSnapshotPath.Length - 27);
                        currentSignalsSnapshotFile = currentSignalsSnapshotFile.Replace('\\', '-').Replace('/', '-');
                        if (backtestingService.Config.ReplayOutput && loadedSignalSnapshots % 100 == 0)
                        {
                            loggingService.Info($"<<<--- ({loadedSignalSnapshots}/{totalSignalSnapshots}) Load signals snapshot file: {currentSignalsSnapshotFile} --->>>");
                        }
                        healthCheckService.UpdateHealthCheck(Constants.HealthChecks.BacktestingSignalsSnapshotLoaded, $"File: {currentSignalsSnapshotFile}");
                    }
                    catch (Exception ex)
                    {
                        loggingService.Error($"<<<--- Unable to load signals snapshot file: {currentSignalsSnapshotPath} --->>>", ex);
                    }
                }

                if (allTickerSnapshotPaths.TryDequeue(out string currentTickersSnapshotPath))
                {
                    try
                    {
                        byte[] currentTickersSnapshotBytes = File.ReadAllBytes(currentTickersSnapshotPath);
                        IEnumerable <ITicker> data         = ZeroFormatterSerializer.Deserialize <IEnumerable <TickerData> >(currentTickersSnapshotBytes).Select(t => t.ToTicker()).ToList();
                        currentTickers = data.ToDictionary(t => t.Pair, t => t);
                        loadedTickerSnapshots++;

                        var currentTickersSnapshotFile = currentTickersSnapshotPath.Substring(currentTickersSnapshotPath.Length - 27);
                        currentTickersSnapshotFile = currentTickersSnapshotFile.Replace('\\', '-').Replace('/', '-');
                        if (backtestingService.Config.ReplayOutput && loadedTickerSnapshots % 100 == 0)
                        {
                            loggingService.Info($"<<<--- ({loadedTickerSnapshots}/{totalTickerSnapshots}) Load tickers snapshot file: {currentTickersSnapshotFile} --->>>");
                        }
                        healthCheckService.UpdateHealthCheck(Constants.HealthChecks.BacktestingTickersSnapshotLoaded, $"File: {currentTickersSnapshotFile}");
                    }
                    catch (Exception ex)
                    {
                        loggingService.Error($"<<<--- Unable to load tickers snapshot file: {currentTickersSnapshotPath} --->>>", ex);
                    }
                }

                if (currentSignalsSnapshotPath == null && currentTickersSnapshotPath == null)
                {
                    isCompleted = true;
                    backtestingTimer.Stop();
                    loggingService.Info($"<<<--- Backtesting finished in {Math.Round(backtestingTimer.Elapsed.TotalSeconds)} seconds --->>>");
                    backtestingService.Complete(totalSignalSnapshots - loadedSignalSnapshots, totalTickerSnapshots - loadedTickerSnapshots);
                }
            }
        }
        private void ProcessRules()
        {
            if (tradingService.Config.BuyEnabled)
            {
                var allSignals = signalsService.GetAllSignals();
                if (allSignals != null)
                {
                    var enabledRules = signalsService.Rules.Entries.Where(r => r.Enabled);
                    if (enabledRules.Any())
                    {
                        var    groupedSignals = allSignals.Where(s => tradingService.GetPairConfig(s.Pair).BuyEnabled).GroupBy(s => s.Pair).ToDictionary(g => g.Key, g => g.ToDictionary(s => s.Name, s => s));
                        double?globalRating   = signalsService.GetGlobalRating();

                        var excludedPairs = tradingService.Config.ExcludedPairs
                                            .Concat(tradingService.Account.GetTradingPairs().Select(p => p.Pair))
                                            .Concat(tradingService.GetTrailingBuys()).ToList();

                        if (signalsService.RulesConfig.ProcessingMode == RuleProcessingMode.FirstMatch)
                        {
                            excludedPairs.AddRange(trailingSignals.Keys);
                        }

                        foreach (var rule in enabledRules)
                        {
                            foreach (var group in groupedSignals)
                            {
                                string pair = group.Key;
                                Dictionary <string, ISignal> signals    = group.Value;
                                ITradingPair tradingPair                = tradingService.Account.GetTradingPair(pair);
                                IEnumerable <IRuleCondition> conditions = rule.Trailing != null && rule.Trailing.Enabled ? rule.Trailing.StartConditions : rule.Conditions;
                                List <SignalTrailingInfo>    trailingInfoList;

                                if (!excludedPairs.Contains(pair) && (!trailingSignals.TryGetValue(pair, out trailingInfoList) || !trailingInfoList.Any(t => t.Rule == rule)) &&
                                    rulesService.CheckConditions(conditions, signals, globalRating, pair, tradingPair))
                                {
                                    IEnumerable <ISignal> ruleSignals = signals.Where(s => conditions.Any(c => c.Signal == s.Key)).Select(s => s.Value);
                                    if (rule.Trailing != null && rule.Trailing.Enabled)
                                    {
                                        if (trailingInfoList == null)
                                        {
                                            trailingInfoList = new List <SignalTrailingInfo>();
                                            trailingSignals.TryAdd(pair, trailingInfoList);
                                        }

                                        trailingInfoList.Add(new SignalTrailingInfo
                                        {
                                            Rule      = rule,
                                            StartTime = DateTimeOffset.Now
                                        });

                                        string ruleSignalsList = String.Join(", ", ruleSignals.Select(s => s.Name));
                                        if (LoggingEnabled)
                                        {
                                            loggingService.Info($"Start trailing signal for {pair}. Rule: {rule.Name}, Signals: {ruleSignalsList}");
                                        }
                                    }
                                    else
                                    {
                                        InitiateBuy(pair, rule, ruleSignals);
                                    }

                                    if (signalsService.RulesConfig.ProcessingMode == RuleProcessingMode.FirstMatch)
                                    {
                                        excludedPairs.Add(pair);
                                    }
                                }
                            }
                        }
                    }

                    healthCheckService.UpdateHealthCheck(Constants.HealthChecks.SignalRulesProcessed, $"Rules: {enabledRules.Count()}, Trailing signals: {trailingSignals.Count}");
                }
            }
        }
        protected override void Run()
        {
            string requestData = signalReceiver.Config.RequestData
                                 .Replace("%EXCHANGE%", tradingService.Config.Exchange.ToUpper())
                                 .Replace("%MARKET%", tradingService.Config.Market)
                                 .Replace("%PERIOD%", signalReceiver.Config.SignalPeriod <= 240 ? $"|{signalReceiver.Config.SignalPeriod}" : "")
                                 .Replace("%VOLATILITY%", $".{signalReceiver.Config.VolatilityPeriod?[0] ?? 'W'}");

            var requestContent = new StringContent(requestData, Encoding.UTF8, "application/json");

            try
            {
                using (HttpResponseMessage response = httpClient.PostAsync(signalReceiver.Config.RequestUrl, requestContent).Result)
                {
                    string responseContent       = response.Content.ReadAsStringAsync().Result;
                    IEnumerable <JToken> jtokens = JObject.Parse(responseContent).SelectTokens("data[*].d");
                    lock (syncRoot)
                    {
                        List <Signal> historicalSignals = this.GetHistoricalSignals();
                        signals = jtokens.Select(t =>
                        {
                            try
                            {
                                Signal signal = t.ToObject <Signal>(signalsSerializer);
                                if (signal.Pair.EndsWith(tradingService.Config.Market))
                                {
                                    signal.Name = signalReceiver.SignalName;

                                    Signal historicalSignal = historicalSignals?.FirstOrDefault(s => s.Pair == signal.Pair);
                                    if (historicalSignal != null)
                                    {
                                        signal.VolumeChange = this.CalculatePercentageChange(historicalSignal.Volume, signal.Volume);
                                        signal.RatingChange = this.CalculatePercentageChange(historicalSignal.Rating, signal.Rating);
                                    }
                                    return(signal);
                                }
                                else
                                {
                                    return(null);
                                }
                            }
                            catch (Exception ex)
                            {
                                loggingService.Debug("Unable to parse Trading View Crypto Signal", ex);
                                return(null);
                            }
                        }).Where(s => s != null && s.Pair != null).ToList();

                        if (signals.Count > 0)
                        {
                            if ((DateTimeOffset.Now - lastSnapshotDate).TotalMilliseconds > HISTORICAL_SIGNALS_SNAPSHOT_MIN_INTERVAL_MILLISECONDS)
                            {
                                signalsHistory.TryAdd(DateTimeOffset.Now, signals);
                                lastSnapshotDate = DateTimeOffset.Now;
                                this.CleanUpSignalsHistory();
                            }
                            averageRating = signals.Any(s => s.Rating != null) ? signals.Where(s => s.Rating != null).Average(s => s.Rating) : null;
                            healthCheckService.UpdateHealthCheck($"{Constants.HealthChecks.TradingViewCryptoSignalsReceived} [{signalReceiver.SignalName}]", $"Total: {signals.Count()}");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                loggingService.Debug("Unable to retrieve TV Signals", ex);
            }
        }