# Backtest Performance Optimizations This document tracks identified performance optimization opportunities for `BacktestExecutor.cs` based on analysis of the foreach loop that processes thousands of candles. ## Current Performance Baseline - **Processing Rate**: ~1,707 candles/sec - **Execution Time**: ~3.365 seconds for 5,760 candles - **Memory Peak**: ~36.29 MB ## Optimization Opportunities ### 🔴 Priority 1: Reuse HashSet Instead of Recreating (CRITICAL) **Location**: `BacktestExecutor.cs` line 267 **Current Code**: ```csharp var fixedCandles = new HashSet(rollingWindowCandles); ``` **Problem**: Creates a new HashSet 5,760 times (once per candle iteration). This is extremely expensive in terms of: - Memory allocations - GC pressure - CPU cycles for hash calculations **Solution**: Reuse HashSet and update incrementally: ```csharp // Initialize before loop var fixedCandles = new HashSet(RollingWindowSize); // Inside loop (replace lines 255-267): if (rollingWindowCandles.Count >= RollingWindowSize) { var removedCandle = rollingWindowCandles.Dequeue(); fixedCandles.Remove(removedCandle); } rollingWindowCandles.Enqueue(candle); fixedCandles.Add(candle); // fixedCandles is now up-to-date, no need to recreate ``` **Expected Impact**: 20-30% performance improvement --- ### 🟠 Priority 2: Optimize Wallet Balance Tracking **Location**: `BacktestExecutor.cs` line 283 **Current Code**: ```csharp lastWalletBalance = tradingBot.WalletBalances.Values.LastOrDefault(); ``` **Problem**: `LastOrDefault()` on `Dictionary.Values` is O(n) operation, called every 10 candles. **Solution**: Track balance directly or use more efficient structure: ```csharp // Option 1: Cache last balance when wallet updates // Option 2: Use SortedDictionary if order matters // Option 3: Maintain separate variable that updates when wallet changes ``` **Expected Impact**: 2-5% performance improvement --- ### 🟡 Priority 3: Optimize TradingBox.GetSignal Input **Location**: `TradingBox.cs` line 130 **Current Code**: ```csharp var limitedCandles = newCandles.ToList(); // Converts HashSet to List ``` **Problem**: Converts HashSet to List every time `GetSignal` is called. **Solution**: - Modify `TradingBox.GetSignal` to accept `IEnumerable` or `List` - Pass List directly from rolling window instead of HashSet **Expected Impact**: 1-3% performance improvement --- ### 🟢 Priority 4: Cache Progress Percentage Calculation **Location**: `BacktestExecutor.cs` line 297 **Current Code**: ```csharp var currentPercentage = (currentCandle * 100) / totalCandles; ``` **Problem**: Integer division recalculated every iteration (minor but can be optimized). **Solution**: ```csharp // Before loop const double percentageMultiplier = 100.0 / totalCandles; // Inside loop var currentPercentage = (int)(currentCandle * percentageMultiplier); ``` **Expected Impact**: <1% performance improvement (minor optimization) --- ### 🟢 Priority 5: Use Stopwatch for Time Checks **Location**: `BacktestExecutor.cs` line 298 **Current Code**: ```csharp var timeSinceLastUpdate = (DateTime.UtcNow - lastProgressUpdate).TotalMilliseconds; ``` **Problem**: `DateTime.UtcNow` is relatively expensive when called frequently. **Solution**: Use `Stopwatch` for timing: ```csharp var progressStopwatch = Stopwatch.StartNew(); // Then check: progressStopwatch.ElapsedMilliseconds >= progressUpdateIntervalMs ``` **Expected Impact**: <1% performance improvement (minor optimization) --- ## Future Considerations ### Batching Candle Processing If business logic allows, process multiple candles before updating signals to reduce `UpdateSignals()` call frequency. Requires careful validation. ### Object Pooling Reuse List/HashSet instances if possible to reduce GC pressure. May require careful state management. ### Parallel Processing If signals are independent, consider parallel indicator calculations. Requires careful validation to ensure business logic integrity. ## Implementation Checklist - [ ] Priority 1: Reuse HashSet instead of recreating - [ ] Priority 2: Optimize wallet balance tracking - [ ] Priority 3: Optimize TradingBox.GetSignal input - [ ] Priority 4: Cache progress percentage calculation - [ ] Priority 5: Use Stopwatch for time checks - [ ] Run benchmark-backtest-performance.sh to validate improvements - [ ] Ensure business logic validation passes (Final PnL matches baseline) ## Expected Total Impact **Combined Expected Improvement**: 25-40% faster execution **Target Performance**: - Processing Rate: ~2,100-2,400 candles/sec (up from ~1,707) - Execution Time: ~2.0-2.5 seconds (down from ~3.365) - Memory: Similar or slightly reduced ## Notes - Always validate business logic after optimizations - Run benchmarks multiple times to account for system variance - Monitor memory usage to ensure optimizations don't increase GC pressure - Priority 1 (HashSet reuse) should provide the largest performance gain