Skip to content

Backtesting

How TradePilot simulates a backtest — rebalancing, daily valuation, transaction costs, and the buy-and-hold benchmark. Reference for what the run loop actually does.

A backtest replays a strategy over historical prices to estimate how it would have performed. TradePilot’s engine runs the whole simulation in your browser. For the why of backtesting (and its pitfalls like overfitting), see Learn; this page documents what the engine does.

The run loop

Given a BacktestConfig, the simulator:

  1. Determines rebalance dates — the trading days in [startDate, endDate], every rebalanceFreq days.
  2. At each rebalance date:
    • runs the strategy over the lookback window to rank the universe;
    • keeps the top topN symbols;
    • runs the optimizer to compute target weights (bounded by minWeight / maxWeight, defaults 0.01 / 0.95);
    • records the trades needed to reach those weights.
  3. Values the portfolio every trading day between rebalances, producing a daily equity curve — not just point-to-point at rebalance dates.
  4. Applies transaction costs on each trade if a cost model is configured.
  5. Tracks a benchmark buy-and-hold curve for comparison.
  6. Computes metrics from the daily series.

Daily valuation

The engine values holdings on every trading date in the window (annualizing with periodsPerYear = 252). This gives an honest equity curve and realistic drawdowns, rather than sampling only on rebalance days.

There is a legacy rebalance-date-only mode (periodsPerYear = 52) kept purely so the Python parity test can pin the original behavior. The app always uses daily valuation.

Transaction costs

Costs are optional and expressed in basis points per trade side:

  • costBps — commission / fees
  • slippageBps — slippage

Total friction per trade = (costBps + slippageBps) / 10000 × trade notional. Both default to 0 (frictionless), so leave them off for an idealized run and turn them on to stress a high-turnover strategy.

The benchmark

Every run tracks a buy-and-hold benchmark (default SPY, set via benchmarkSymbol). It’s overlaid on the equity curve and drives the alpha metric. If the benchmark symbol has no price data, the benchmark series is simply null and omitted.

What a run returns

The result (BacktestResultV2) is fully JSON-serializable — daily dates and portfolioValues, per-rebalance weights, the trades list, the metrics object, monthlyReturns, topDrawdowns, and the benchmarkValues. That exact shape is what gets stored when you save a run; see the data model.