Backtesting
A backtest replays historical bars through a strategy, simulates fills, and reports performance. It answers "would this have worked?" — a useful question, as long as you remember it's not the same as "will this work?"
make backtest
# or
uv run python main.py backtest \
--strategy volume_spike --scanner volume \
--symbols NVDA,META,TSLA --start 2024-01-02 --end 2024-04-01 --capital 100000
Options
| Option | Default | Meaning |
|---|---|---|
--strategy | volume_spike | Strategy to run |
--scanner | volume | Universe scanner (none to skip) |
--symbols | a 10-name list | Comma-separated candidates |
--start / --end | last 30 days | Backtest window (YYYY-MM-DD) |
--capital | 100000 | Starting capital |
Reading the report
The engine prints a metrics block, for example:
=== Backtest Results ===
Capital $100,000.00 -> $103,420.00
Total Return 3.42%
Buy & Hold Return 2.10%
Sharpe Ratio 1.24
Max Drawdown 4.80%
Total Trades 37
Win Rate 54.05%
Profit Factor 1.61
...
- Total Return vs Buy & Hold shows whether the strategy beat simply holding the symbols.
- Sharpe / Max Drawdown / Profit Factor describe risk-adjusted quality.
How fills and P&L are simulated is documented in The Engine; how each metric is computed is in Indicators & Analytics.
Net of transaction cost
Backtest metrics are net of transaction cost by default — commission + half-spread
- square-root market impact, charged on both legs of every trade (see
Transaction costs). The report prints the total
cost and the gross final capital alongside. Pass
--grossto disable the charge (for attribution — "how much did costs cost me?"), and tune--commission-bps/--impact-eta. High-turnover strategies degrade sharply once costs are on; that's the point.
Trial journaling
Each run records one trial — the config it evaluated, on this universe and
window — to logs/research_journal.jsonl, and is dual-written into the
trial store (logs/trials.db) so
trials query can report the real campaign-wide trial count on demand — the
gate itself still counts only the current run (see the
open item).
Pass --no-journal to keep a throwaway or reproducibility run out of that total.
Tuning the strategy
Once a backtest runs, search for better parameters with Optimization.