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.

This commit is contained in:
2025-12-28 21:22:45 +07:00
parent 8a7addafd7
commit 31886aeaf3

View File

@@ -580,7 +580,8 @@ public class TradingService : ITradingService
} }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
private List<LightSignal> GenerateSignalsForDateRange( private List<LightSignal> GenerateSignalsForDateRange(
List<Candle> candles, List<Candle> candles,
@@ -588,47 +589,72 @@ public class TradingService : ITradingService
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues) Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
{ {
var allSignals = new List<LightSignal>(); var allSignals = new List<LightSignal>();
var seenSignalIdentifiers = new HashSet<string>();
var lightScenario = LightScenario.FromScenario(scenario); var lightScenario = LightScenario.FromScenario(scenario);
// Process each indicator individually to get all signals from each // Use rolling window approach to process candles incrementally
foreach (var lightIndicator in lightScenario.Indicators) // This ensures we capture signals throughout the entire date range, not just recent ones
const int RollingWindowSize = 600;
var rollingWindowCandles = new List<Candle>(RollingWindowSize);
// Process each candle with a rolling window
foreach (var candle in candles)
{ {
// Build the indicator instance // Maintain rolling window
var indicatorInstance = lightIndicator.ToInterface(); if (rollingWindowCandles.Count >= RollingWindowSize)
// Use pre-calculated indicator values if available
List<LightSignal> indicatorSignals;
if (preCalculatedIndicatorValues != null && preCalculatedIndicatorValues.ContainsKey(lightIndicator.Type))
{ {
// Use pre-calculated values to avoid recalculating indicators rollingWindowCandles.RemoveAt(0);
indicatorSignals = indicatorInstance.Run(candles, preCalculatedIndicatorValues[lightIndicator.Type]);
} }
else rollingWindowCandles.Add(candle);
// Only process if we have enough candles for indicators
if (rollingWindowCandles.Count < 2)
{ {
// Normal path: calculate indicators on the fly continue;
indicatorSignals = indicatorInstance.Run(candles);
} }
// Add all signals from this indicator to the collection // Process each indicator individually for this rolling window position
if (indicatorSignals != null && indicatorSignals.Count > 0) foreach (var lightIndicator in lightScenario.Indicators)
{ {
// Filter signals to only include those within the candle date range // Build the indicator instance (create fresh instance for each indicator to avoid state issues)
var firstCandleDate = candles.First().Date; var indicatorInstance = lightIndicator.ToInterface();
var lastCandleDate = candles.Last().Date;
var filteredSignals = indicatorSignals
.Where(s => s.Date >= firstCandleDate && s.Date <= lastCandleDate)
.ToList();
allSignals.AddRange(filteredSignals); // Use pre-calculated indicator values if available
List<LightSignal> 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 // Sort by date and return
return allSignals return allSignals.OrderBy(s => s.Date).ToList();
.GroupBy(s => s.Identifier)
.Select(g => g.First())
.OrderBy(s => s.Date)
.ToList();
} }
} }