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:
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user