Refactoring TradingBotBase.cs + clean architecture (#38)
* Refactoring TradingBotBase.cs + clean architecture * Fix basic tests * Fix tests * Fix workers * Fix open positions * Fix closing position stucking the grain * Fix comments * Refactor candle handling to use IReadOnlyList for chronological order preservation across various components
This commit is contained in:
@@ -129,7 +129,7 @@ public class BacktestExecutor
|
||||
/// <returns>The lightweight backtest result</returns>
|
||||
public async Task<LightBacktest> ExecuteAsync(
|
||||
TradingBotConfig config,
|
||||
HashSet<Candle> candles,
|
||||
IReadOnlyList<Candle> candles,
|
||||
User user,
|
||||
bool save = false,
|
||||
bool withCandles = false,
|
||||
@@ -166,7 +166,9 @@ public class BacktestExecutor
|
||||
|
||||
// Create a fresh TradingBotBase instance for this backtest
|
||||
var tradingBot = CreateTradingBotInstance(config);
|
||||
tradingBot.Account = user.Accounts.First();
|
||||
var account = user.Accounts.First();
|
||||
account.User = user; // Ensure Account.User is set for backtest
|
||||
tradingBot.Account = account;
|
||||
|
||||
var totalCandles = candles.Count;
|
||||
var currentCandle = 0;
|
||||
@@ -220,8 +222,9 @@ public class BacktestExecutor
|
||||
// The signal calculation depends on rolling window state and cannot be pre-calculated effectively
|
||||
|
||||
// Use optimized rolling window approach - TradingBox.GetSignal only needs last 600 candles
|
||||
// Use List<Candle> directly to preserve chronological order and enable incremental updates
|
||||
const int RollingWindowSize = 600; // TradingBox.GetSignal only needs last 600 candles
|
||||
var rollingWindowCandles = new Queue<Candle>(RollingWindowSize);
|
||||
var rollingWindowCandles = new List<Candle>(RollingWindowSize); // Pre-allocate capacity for performance
|
||||
var candlesProcessed = 0;
|
||||
|
||||
// Signal caching optimization - reduce signal update frequency for better performance
|
||||
@@ -253,21 +256,20 @@ public class BacktestExecutor
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Maintain rolling window of last 600 candles to prevent exponential memory growth
|
||||
rollingWindowCandles.Enqueue(candle);
|
||||
if (rollingWindowCandles.Count > RollingWindowSize)
|
||||
// Incremental updates: remove oldest if at capacity, then add newest
|
||||
// This preserves chronological order and avoids expensive HashSet recreation
|
||||
if (rollingWindowCandles.Count >= RollingWindowSize)
|
||||
{
|
||||
rollingWindowCandles.Dequeue(); // Remove oldest candle
|
||||
rollingWindowCandles.RemoveAt(0); // Remove oldest candle (O(n) but only 600 items max)
|
||||
}
|
||||
rollingWindowCandles.Add(candle); // Add newest candle (O(1) amortized)
|
||||
|
||||
tradingBot.LastCandle = candle;
|
||||
|
||||
// Run with optimized backtest path (minimize async calls)
|
||||
var signalUpdateStart = Stopwatch.GetTimestamp();
|
||||
// Convert rolling window to HashSet for TradingBot.UpdateSignals compatibility
|
||||
// NOTE: Recreating HashSet each iteration is necessary to maintain correct enumeration order
|
||||
// Incremental updates break business logic (changes PnL results)
|
||||
var fixedCandles = new HashSet<Candle>(rollingWindowCandles);
|
||||
await tradingBot.UpdateSignals(fixedCandles, preCalculatedIndicatorValues);
|
||||
// Pass List<Candle> directly - no conversion needed, order is preserved
|
||||
await tradingBot.UpdateSignals(rollingWindowCandles, preCalculatedIndicatorValues);
|
||||
signalUpdateTotalTime += Stopwatch.GetElapsedTime(signalUpdateStart);
|
||||
|
||||
var backtestStepStart = Stopwatch.GetTimestamp();
|
||||
@@ -542,7 +544,7 @@ public class BacktestExecutor
|
||||
throw new InvalidOperationException("Bot configuration is not initialized");
|
||||
}
|
||||
|
||||
if (!config.IsForBacktest)
|
||||
if (config.TradingType != TradingType.BacktestFutures)
|
||||
{
|
||||
throw new InvalidOperationException("BacktestExecutor can only be used for backtesting");
|
||||
}
|
||||
@@ -550,7 +552,7 @@ public class BacktestExecutor
|
||||
// Create the trading bot instance
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var logger = scope.ServiceProvider.GetRequiredService<ILogger<TradingBotBase>>();
|
||||
var tradingBot = new TradingBotBase(logger, _scopeFactory, config);
|
||||
var tradingBot = new BacktestFuturesBot(logger, _scopeFactory, config);
|
||||
return tradingBot;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user