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}"); } } }
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}"); }
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}"); }
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); } }