From 31886aeaf39fdd05c11f26662b9d84a6a580fd6e Mon Sep 17 00:00:00 2001 From: cryptooda Date: Sun, 28 Dec 2025 21:22:45 +0700 Subject: [PATCH] Refactor signal generation in TradingService to implement a rolling window approach, allowing for incremental processing of candles. This change enhances signal capture across the entire date range and prevents duplicate signals by tracking seen identifiers. --- .../Trading/TradingService.cs | 86 ++++++++++++------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/src/Managing.Application/Trading/TradingService.cs b/src/Managing.Application/Trading/TradingService.cs index 274db3dd..f89d21a3 100644 --- a/src/Managing.Application/Trading/TradingService.cs +++ b/src/Managing.Application/Trading/TradingService.cs @@ -580,7 +580,8 @@ public class TradingService : ITradingService } /// - /// Generates signals for a date range by running each indicator individually and collecting all signals. + /// Generates signals for a date range using a rolling window approach to capture signals throughout the entire range. + /// This is necessary because some indicators (like STC) only process recent candles, so we need to process incrementally. /// private List GenerateSignalsForDateRange( List candles, @@ -588,47 +589,72 @@ public class TradingService : ITradingService Dictionary preCalculatedIndicatorValues) { var allSignals = new List(); + var seenSignalIdentifiers = new HashSet(); var lightScenario = LightScenario.FromScenario(scenario); - // Process each indicator individually to get all signals from each - foreach (var lightIndicator in lightScenario.Indicators) + // Use rolling window approach to process candles incrementally + // This ensures we capture signals throughout the entire date range, not just recent ones + const int RollingWindowSize = 600; + var rollingWindowCandles = new List(RollingWindowSize); + + // Process each candle with a rolling window + foreach (var candle in candles) { - // Build the indicator instance - var indicatorInstance = lightIndicator.ToInterface(); - - // Use pre-calculated indicator values if available - List indicatorSignals; - if (preCalculatedIndicatorValues != null && preCalculatedIndicatorValues.ContainsKey(lightIndicator.Type)) + // Maintain rolling window + if (rollingWindowCandles.Count >= RollingWindowSize) { - // Use pre-calculated values to avoid recalculating indicators - indicatorSignals = indicatorInstance.Run(candles, preCalculatedIndicatorValues[lightIndicator.Type]); + rollingWindowCandles.RemoveAt(0); } - else + rollingWindowCandles.Add(candle); + + // Only process if we have enough candles for indicators + if (rollingWindowCandles.Count < 2) { - // Normal path: calculate indicators on the fly - indicatorSignals = indicatorInstance.Run(candles); + continue; } - // Add all signals from this indicator to the collection - if (indicatorSignals != null && indicatorSignals.Count > 0) + // Process each indicator individually for this rolling window position + foreach (var lightIndicator in lightScenario.Indicators) { - // Filter signals to only include those within the candle date range - var firstCandleDate = candles.First().Date; - var lastCandleDate = candles.Last().Date; - - var filteredSignals = indicatorSignals - .Where(s => s.Date >= firstCandleDate && s.Date <= lastCandleDate) - .ToList(); + // Build the indicator instance (create fresh instance for each indicator to avoid state issues) + var indicatorInstance = lightIndicator.ToInterface(); - allSignals.AddRange(filteredSignals); + // Use pre-calculated indicator values if available + List indicatorSignals; + if (preCalculatedIndicatorValues != null && preCalculatedIndicatorValues.ContainsKey(lightIndicator.Type)) + { + // Use pre-calculated values to avoid recalculating indicators + indicatorSignals = indicatorInstance.Run(rollingWindowCandles, preCalculatedIndicatorValues[lightIndicator.Type]); + } + else + { + // Normal path: calculate indicators on the fly + indicatorSignals = indicatorInstance.Run(rollingWindowCandles); + } + + // Add new signals from this indicator to the collection + if (indicatorSignals != null && indicatorSignals.Count > 0) + { + // Filter signals to only include those within the requested date range + var firstCandleDate = candles.First().Date; + var lastCandleDate = candles.Last().Date; + + foreach (var signal in indicatorSignals) + { + // Only add signals within the date range and avoid duplicates + if (signal.Date >= firstCandleDate && + signal.Date <= lastCandleDate && + !seenSignalIdentifiers.Contains(signal.Identifier)) + { + allSignals.Add(signal); + seenSignalIdentifiers.Add(signal.Identifier); + } + } + } } } - // Remove duplicates based on identifier and sort by date - return allSignals - .GroupBy(s => s.Identifier) - .Select(g => g.First()) - .OrderBy(s => s.Date) - .ToList(); + // Sort by date and return + return allSignals.OrderBy(s => s.Date).ToList(); } } \ No newline at end of file