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>
/// 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>
private List<LightSignal> GenerateSignalsForDateRange(
List<Candle> candles,
@@ -588,12 +589,34 @@ public class TradingService : ITradingService
Dictionary<IndicatorType, IndicatorsResultBase> preCalculatedIndicatorValues)
{
var allSignals = new List<LightSignal>();
var seenSignalIdentifiers = new HashSet<string>();
var lightScenario = LightScenario.FromScenario(scenario);
// Process each indicator individually to get all signals from each
// 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<Candle>(RollingWindowSize);
// Process each candle with a rolling window
foreach (var candle in candles)
{
// Maintain rolling window
if (rollingWindowCandles.Count >= RollingWindowSize)
{
rollingWindowCandles.RemoveAt(0);
}
rollingWindowCandles.Add(candle);
// Only process if we have enough candles for indicators
if (rollingWindowCandles.Count < 2)
{
continue;
}
// Process each indicator individually for this rolling window position
foreach (var lightIndicator in lightScenario.Indicators)
{
// Build the indicator instance
// Build the indicator instance (create fresh instance for each indicator to avoid state issues)
var indicatorInstance = lightIndicator.ToInterface();
// Use pre-calculated indicator values if available
@@ -601,34 +624,37 @@ public class TradingService : ITradingService
if (preCalculatedIndicatorValues != null && preCalculatedIndicatorValues.ContainsKey(lightIndicator.Type))
{
// Use pre-calculated values to avoid recalculating indicators
indicatorSignals = indicatorInstance.Run(candles, preCalculatedIndicatorValues[lightIndicator.Type]);
indicatorSignals = indicatorInstance.Run(rollingWindowCandles, preCalculatedIndicatorValues[lightIndicator.Type]);
}
else
{
// Normal path: calculate indicators on the fly
indicatorSignals = indicatorInstance.Run(candles);
indicatorSignals = indicatorInstance.Run(rollingWindowCandles);
}
// Add all signals from this indicator to the collection
// Add new signals from this indicator to the collection
if (indicatorSignals != null && indicatorSignals.Count > 0)
{
// Filter signals to only include those within the candle date range
// Filter signals to only include those within the requested date range
var firstCandleDate = candles.First().Date;
var lastCandleDate = candles.Last().Date;
var filteredSignals = indicatorSignals
.Where(s => s.Date >= firstCandleDate && s.Date <= lastCandleDate)
.ToList();
allSignals.AddRange(filteredSignals);
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();
}
}