Skip to content

fix(trading-strategies): call strategy init in BacktestExecutor to mirror TradingSession - #1189

Open
bennycode wants to merge 1 commit into
mainfrom
fix/backtest-executor-strategy-init
Open

fix(trading-strategies): call strategy init in BacktestExecutor to mirror TradingSession#1189
bennycode wants to merge 1 commit into
mainfrom
fix/backtest-executor-strategy-init

Conversation

@bennycode

Copy link
Copy Markdown
Owner

Problem

TradingSession.start() calls strategy.init?.(broker, pair) before the first candle — BacktestExecutor never did. A strategy that warms itself up in init() (seeding indicators from history, loading datasets) runs warm live but cold in backtests, so backtest results silently misrepresent live behavior. Parity between the two executors is the backtester's core promise.

Fix

  • BacktestExecutor.execute() now calls await strategy.init?.(...) before the candle loop, mirroring TradingSession.
  • Lookahead-bias guard: init's market view is fed exclusively from the new optional BacktestConfig.warmupCandles (history from before the backtest window) — never from the exchange. init structurally cannot see the candles it is about to be tested on.

Impact

No existing strategy implements init, so current backtest results are unchanged. Only strategies opting into the hook are affected.

Test plan

  • init fires before the first candle
  • init sees exactly the configured warmup candles (and never the run candles)
  • init sees empty history when no warmup is configured
  • Full suite: 262 tests green, lint clean

…rror TradingSession

TradingSession.start() calls strategy.init() before the first candle, but
BacktestExecutor never did — a strategy that warms up in init() ran warm in
live trading and cold in backtests, silently diverging.

The executor now inits the strategy before the candle loop. Its market view
is fed exclusively from the new optional BacktestConfig.warmupCandles (data
from before the backtest window), never from the exchange, so init cannot
see the candles under test (lookahead-bias guard).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The backtest getRecentCandles stub currently ignores intervalInMillis, which can silently give strategies incorrect warmup data when they request non-1m history in init().

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR brings BacktestExecutor behavior in line with TradingSession by invoking strategy.init?.(market, pair) before processing the first candle, enabling strategies to warm up consistently in both live runs and backtests while avoiding lookahead bias via a dedicated warmup history input.

Changes:

  • Call strategy.init?.(...) at the start of BacktestExecutor.execute() using a warmup-only getRecentCandles implementation.
  • Extend BacktestConfig with optional warmupCandles to supply pre-window history to init().
  • Add tests asserting init is called pre-loop and only sees warmup history.
File summaries
File Description
packages/trading-strategies/src/backtest/BacktestExecutor.ts Adds a pre-loop strategy.init call using warmup-only candle history.
packages/trading-strategies/src/backtest/BacktestExecutor.test.ts Adds coverage for init call timing and warmup isolation.
packages/trading-strategies/src/backtest/BacktestConfig.ts Introduces warmupCandles?: Candle[] with documentation about lookahead bias.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +927 to +938
it('feeds init from warmupCandles only — never the candles under test (lookahead guard)', async () => {
const warmup = [createCandle({close: '95', open: '90', openTimeInISO: '2025-01-01T00:00:00.000Z'})];
const strategy = new InitProbeStrategy();
await new BacktestExecutor({
broker: createMockExchange(),
candles: runCandles,
strategy,
tradingPair,
warmupCandles: warmup,
}).execute();
expect(strategy.seenWarmupCandles).toEqual(warmup);
});
*/
const warmupCandles = this.#config.warmupCandles ?? [];
await strategy.init?.(
{getRecentCandles: async (_pair, count) => (count > 0 ? warmupCandles.slice(-count) : [])},
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants