Backtest Engine
Short answer
Backtest is built into the Python wheel — use df.lazy().bt.backtest_with_report() after import quantwave. Rust core (quantwave-backtest); same parity story as indicators.
QuantWave ships a Polars-native, clean-room backtest engine (quantwave-backtest) with Python .bt namespace ergonomics. It is vectorbt-inspired research UX built on QuantWave's unique batch ↔ streaming parity — not a fork of vectorbt or polars-backtest.
Why this engine
| Capability | What you get |
|---|---|
| Parity moat | Same signal logic → identical trades/equity in batch (Polars LazyFrame) and streaming (Next<T>) |
| Research depth | Sweeps, walk-forward optimization, Monte Carlo, cross-sectional panels |
| Production realism | Commission, slippage, T+1 execution, stops, shorts, sizing filters |
| Portfolio mode | Shared-capital multi-symbol books via .bt.portfolio_backtest() |
| Reporting | PerformanceMetrics dict + standalone HTML tear sheets |
The Rust core owns simulation math; Python exposes ergonomic Polars namespaces. Every shipped feature has nextest or pytest proof — see the Capability Matrix.
The parity moat
One mathematical truth: Polars plugins and the live streaming engine bind to the same Rust traits.
| Artifact | Location |
|---|---|
| Batch ↔ streaming integration tests | quantwave-backtest — test_batch_vs_streaming_parity_* |
| Guide | Batch & Streaming |
| ML → backtest E2E | ML Features → Backtest |
.bt API surface
All methods are on df.lazy().bt after import quantwave (registers the namespace).
| Method | Purpose |
|---|---|
backtest() |
Trades + equity DataFrames |
backtest_with_report() |
Above + BacktestReport with .metrics() |
backtest_metrics() |
Metrics only — fast path, no trade/equity materialization |
sweep() |
Grid over a pre-built signal column |
sweep_callback() |
Rebuild signals per parameter via build_fn |
walk_forward() |
Rolling out-of-sample folds |
walk_forward_optimize() |
In-fold param sweep, locked OOS evaluation. optimizer="grid" (default, exhaustive) or optimizer="tpe" (optional Bayesian search, n_trials= required) |
cross_sectional_backtest() |
Universe rank long/short (transform= optional) |
portfolio_backtest() |
Shared-capital multi-symbol portfolio simulation |
Portfolio backtest (shared capital)
When multiple symbols trade from one cash pool, use portfolio_backtest() instead of independent per-symbol runs:
report = (
df.lazy()
.bt.portfolio_backtest(
signal="signal",
symbol_col="symbol",
portfolio_mode="shared_capital",
portfolio_allocator="equal_weight",
initial_cash=100_000.0,
)
)
See Portfolio Shared Capital for a full walkthrough.
Quickstart path
- Quickstart — copy-paste script, first trades in 5 minutes
- Capability Matrix — feature inventory with proof links
- Tear Sheets — HTML reports from
BacktestReport
Notebooks
Runnable marimo notebooks (also linked in the site nav):
| Notebook | Focus |
|---|---|
| Backtest Showcase | Full .bt tour — sweeps, WFO, Monte Carlo |
| Portfolio Shared Capital | Pooled-book multi-symbol simulation |
| Strategy Backtest | SuperTrend → signal → backtest E2E |
| PA Flag Breakout | Price-action metadata into trades |
| Backtest Benchmarks | Criterion vs naive Python loop |
| ML Features → Backtest | Feature pipeline parity proof |