Fix backtest consistency

This commit is contained in:
2025-11-11 12:15:12 +07:00
parent 2a0fbf9bc0
commit 1792cd2371
10 changed files with 248 additions and 34978 deletions

View File

@@ -230,9 +230,6 @@ public class BacktestExecutor
var fixedCandlesHashSet = new HashSet<Candle>(rollingWindowSize); // Reuse HashSet to avoid allocations
var candlesProcessed = 0;
// Pre-allocate reusable collections to minimize allocations during processing
var tempCandlesList = new List<Candle>(rollingWindowSize);
// Signal caching optimization - reduce signal update frequency for better performance
var signalUpdateSkipCount = 0;
@@ -256,39 +253,26 @@ public class BacktestExecutor
// Process all candles with optimized rolling window approach
_logger.LogInformation("🎯 Starting to process {Count} candles in loop", orderedCandles.Count);
Console.WriteLine("CONSOLE: About to start candle processing loop");
// Optimize: Pre-populate rolling window with initial candles to avoid repeated checks
var initialWindowSize = Math.Min(rollingWindowSize, orderedCandles.Count);
for (int i = 0; i < initialWindowSize; i++)
{
var candle = orderedCandles[i];
rollingCandles.Add(candle);
fixedCandlesHashSet.Add(candle);
}
foreach (var candle in orderedCandles)
{
// Optimized rolling window maintenance - only modify when window is full
if (rollingCandles.Count >= rollingWindowSize)
// Maintain rolling window efficiently using List
rollingCandles.Add(candle);
if (rollingCandles.Count > rollingWindowSize)
{
// Remove oldest candle from both structures efficiently
// Remove oldest candle from both structures
var removedCandle = rollingCandles[0];
rollingCandles.RemoveAt(0);
fixedCandlesHashSet.Remove(removedCandle);
}
// Add new candle to rolling window (skip if already in initial population)
if (!fixedCandlesHashSet.Contains(candle))
{
rollingCandles.Add(candle);
fixedCandlesHashSet.Add(candle);
}
// Add to HashSet for reuse
fixedCandlesHashSet.Add(candle);
tradingBot.LastCandle = candle;
// Smart signal caching - reduce signal update frequency for performance
// RSI and similar indicators don't need updates every candle for 15-minute data
var shouldSkipSignalUpdate = ShouldSkipSignalUpdate(currentCandle, totalCandles, config);
var shouldSkipSignalUpdate = ShouldSkipSignalUpdate(currentCandle, totalCandles);
if (currentCandle <= 5) // Debug first few candles
{
_logger.LogInformation("🔍 Candle {CurrentCandle}: shouldSkip={ShouldSkip}, totalCandles={Total}",
@@ -549,70 +533,24 @@ public class BacktestExecutor
}
/// <summary>
/// Advanced signal caching based on indicator update frequency and timeframe
/// Dynamically adjusts update frequency based on timeframe and indicator characteristics
/// Advanced signal caching based on indicator update frequency
/// Instead of hashing candles, we cache signals based on how often indicators need updates
/// </summary>
private bool ShouldSkipSignalUpdate(int currentCandleIndex, int totalCandles, TradingBotConfig config)
private bool ShouldSkipSignalUpdate(int currentCandleIndex, int totalCandles)
{
// RSI and similar indicators don't need to be recalculated every candle
// For 15-minute candles, we can update signals every 3-5 candles without significant accuracy loss
const int signalUpdateFrequency = 3; // Update signals every N candles
// Always update signals for the first few candles to establish baseline
if (currentCandleIndex < 20)
if (currentCandleIndex < 10)
return false;
// Always update signals near the end to ensure final trades are calculated
if (currentCandleIndex > totalCandles - 20)
if (currentCandleIndex > totalCandles - 10)
return false;
// Adaptive update frequency based on timeframe
// Shorter timeframes can skip more updates as they're more volatile
int signalUpdateFrequency;
switch (config.Timeframe)
{
case Timeframe.OneMinute:
case Timeframe.FiveMinutes:
signalUpdateFrequency = 10; // Update every 10 candles for fast timeframes
break;
case Timeframe.FifteenMinutes:
case Timeframe.ThirtyMinutes:
signalUpdateFrequency = 5; // Update every 5 candles for medium timeframes
break;
case Timeframe.OneHour:
case Timeframe.FourHour:
signalUpdateFrequency = 3; // Update every 3 candles for slower timeframes
break;
case Timeframe.OneDay:
signalUpdateFrequency = 1; // Update every candle for daily (already slow)
break;
default:
signalUpdateFrequency = 5; // Default fallback
break;
}
// Further optimize based on indicator types in the scenario
if (config.Scenario?.Indicators != null)
{
var hasFastIndicators = config.Scenario.Indicators.Any(ind =>
ind.Type == IndicatorType.RsiDivergence ||
ind.Type == IndicatorType.StochRsiTrend ||
ind.Type == IndicatorType.MacdCross);
var hasSlowIndicators = config.Scenario.Indicators.Any(ind =>
ind.Type == IndicatorType.EmaCross ||
ind.Type == IndicatorType.EmaTrend ||
ind.Type == IndicatorType.SuperTrend);
// If we have mostly slow indicators, we can update less frequently
if (!hasFastIndicators && hasSlowIndicators)
{
signalUpdateFrequency = Math.Max(signalUpdateFrequency, 8);
}
// If we have fast indicators, we need more frequent updates
else if (hasFastIndicators && !hasSlowIndicators)
{
signalUpdateFrequency = Math.Min(signalUpdateFrequency, 3);
}
}
// Skip signal updates based on calculated frequency
// Skip signal updates based on frequency
return (currentCandleIndex % signalUpdateFrequency) != 0;
}