trader.backtest.engine module

Single-symbol, long-only backtest engine.

The fill model is the load-bearing decision here: a signal computed from bar N’s close fills at bar N+1’s open. Filling at the signal bar’s own close would assume knowledge of a price that had not yet happened, and that lookahead bias inflates mean-reversion results most — which is most of these strategies.

Every result also carries a buy-and-hold baseline over the same bars and the same costs, because a return reported alone is uninterpretable: rsi_revert_14 returning +20.98% reads like a win until the 54.78% it gave up is beside it.

class trader.backtest.engine.BacktestConfig(starting_capital=Decimal('100000'), slippage_bps=5, position_fraction=Decimal('1.0'))[source]

Bases: object

Execution assumptions, identical across strategies so they stay comparable.

Parameters:
  • starting_capital (Decimal)

  • slippage_bps (int)

  • position_fraction (Decimal)

starting_capital: Decimal
slippage_bps: int
position_fraction: Decimal
class trader.backtest.engine.BacktestResult(symbol, strategy_id, starting_value, ending_value, total_return_pct, max_drawdown_pct, win_count, loss_count, benchmark_ending_value, benchmark_return_pct, trades=<factory>)[source]

Bases: object

Everything requirements §12 asks a backtest to report.

Parameters:
  • symbol (str)

  • strategy_id (str)

  • starting_value (Decimal)

  • ending_value (Decimal)

  • total_return_pct (Decimal)

  • max_drawdown_pct (Decimal)

  • win_count (int)

  • loss_count (int)

  • benchmark_ending_value (Decimal)

  • benchmark_return_pct (Decimal)

  • trades (list[SimulatedTrade])

symbol: str
strategy_id: str
starting_value: Decimal
ending_value: Decimal
total_return_pct: Decimal
max_drawdown_pct: Decimal
win_count: int
loss_count: int
benchmark_ending_value: Decimal

buy at the first price the strategy could itself have filled at, hold to the last close. Not optional and without a default on purpose — a result that can be constructed without a baseline is a result someone will report without one, which is the whole failure this field exists to close.

Type:

The do-nothing baseline over the same bars and the same costs

benchmark_return_pct: Decimal
trades: list[SimulatedTrade]
property excess_return_pct: Decimal

How much the strategy added over simply holding. Negative is common.

class trader.backtest.engine.SimulatedTrade(entry_at, entry_price, quantity, exit_at=None, exit_price=None, pnl=None)[source]

Bases: object

One simulated round trip. exit_at is None means still open at the end.

Parameters:
  • entry_at (datetime)

  • entry_price (Decimal)

  • quantity (int)

  • exit_at (datetime | None)

  • exit_price (Decimal | None)

  • pnl (Decimal | None)

entry_at: datetime
entry_price: Decimal
quantity: int
exit_at: datetime | None
exit_price: Decimal | None
pnl: Decimal | None
trader.backtest.engine.run_backtest(strategy, symbol, bars, config)[source]

Simulate strategy over bars, long-only, one position at a time.

Raises:

BacktestError – the window is too short for the strategy to decide once.

Parameters:
Return type:

BacktestResult