Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ htmlcov/
.tox/
.nox/

# mypy
# Type checking
.mypy_cache/

# Jupyter
Expand All @@ -44,3 +44,8 @@ CLAUDE.md
# Validation-generated local artifacts
validation/.zipline/
validation/CORRECTNESS_RESULTS.md
validation/BENCHMARK_RESULTS.md
validation/trade_logs/
validation/lean/workspace/
validation/nautilus/.venv-nautilus/
validation/nautilus/catalog/
36 changes: 25 additions & 11 deletions AGENT.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
# ml4t-backtest

Minimal event-driven backtesting engine.
Event-driven backtesting engine with cross-framework parity validation.

## Structure

| Directory | Purpose |
|-----------|---------|
| src/ml4t/backtest/ | Package root |
| tests/ | 154 tests |
| validation/ | VectorBT/Backtrader matching |
| src/ml4t/backtest/ | Package root (~14.3k lines, 40 modules) |
| tests/ | 1,083 tests |
| validation/ | Cross-framework parity (VBT, Backtrader, Zipline, LEAN) |

## Key Modules

| Module | Purpose |
|--------|---------|
| engine.py | Event loop orchestration |
| broker.py | Order execution, positions |
| strategy.py | Strategy base class |
| datafeed.py | Price + signal iteration |
| Module | Lines | Purpose |
|--------|-------|---------|
| engine.py | 491 | Event loop orchestration |
| broker.py | 1,438 | Order execution, positions |
| config.py | 937 | BacktestConfig (40+ knobs) |
| result.py | 1,025 | BacktestResult container |
| types.py | 578 | Order, Position, Fill, Trade |
| profiles.py | 375 | 6 core + 4 strict profiles |
| calendar.py | 786 | Trading calendar, sessions |

## Subpackages

| Directory | Lines | Purpose |
|-----------|-------|---------|
| core/ | 1,365 | Order book, execution engine, fill engine, risk engine |
| accounting/ | 1,180 | Cash/margin policies, gatekeeper |
| analytics/ | 917 | Metrics, equity, trades, diagnostic bridge |
| execution/ | 1,328 | Fill executor, rebalancer, impact |
| risk/ | 1,876 | Position rules, portfolio limits |
| strategies/ | 417 | Strategy templates |

## Entry Point

```python
from ml4t.backtest import Engine, Strategy
from ml4t.backtest import Engine, Strategy, BacktestConfig, run_backtest
```
87 changes: 43 additions & 44 deletions LIMITATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ Understanding these helps set realistic expectations for backtest results.
- **Assumption**: All trades settle immediately (T+0)
- **Reality**: US equities settle T+2, futures T+1
- **Impact**: Buying power is available immediately after a sale
- **Workaround**: Use `cash_buffer_pct` in BacktestConfig to reserve liquidity
- **Workaround**: Use `settlement_delay=2` in BacktestConfig for T+2 settlement, or `cash_buffer_pct` to reserve liquidity

### No Settlement Queue
- There is no modeling of pending settlements
- Cash from sales is immediately available for new purchases
- This can overestimate trading capacity in high-turnover strategies
### Settlement Delay (Optional)
- `settlement_delay` models T+N settlement (proceeds unavailable for N bars)
- Used by the `lean_strict` profile (T+2 for US equities)
- Does not model partial settlement or settlement failure

## Corporate Actions

Expand Down Expand Up @@ -167,49 +167,48 @@ The following real-world factors are **not** simulated:

## Validation Status

ml4t-backtest has been validated against:
### Scenario-Level Validation (16 scenarios x 4 frameworks)

| Framework | Trade Count | Match Rate | Notes |
|-----------|-------------|------------|-------|
| VectorBT Pro | 119,000+ | 100% | Full feature coverage |
| Backtrader | 119,577 | 100% | LONG positions only for risk rules |
| Zipline | 119,577 | 100% | LONG positions only for risk rules |
| VectorBT OSS | 114,607 | 100% | LONG positions only for risk rules |
| Framework | Scenarios | Status |
|-----------|-----------|--------|
| VectorBT Pro | 16/16 | PASS (exact match) |
| VectorBT OSS | 16/16 | PASS (exact match) |
| Backtrader | 16/16 | PASS (exact match) |
| Zipline | 15/15 | PASS (exact match) |

Scenarios cover: long-only, long/short, stop-loss, take-profit, commission models,
slippage models, trailing stops, bracket orders, short selling, rule combinations,
and 1500-bar stress tests across 9 market regimes.

### Large-Scale Parity (250 assets x 20 years, real data)

| Profile | Trades | Gap | Value Gap |
|---------|--------|-----|-----------|
| zipline_strict | 225,583 | 0 trades (0.00%) | $19 (0.0001%) |
| backtrader_strict | 216,980 | 1 trade (0.0005%) | $503 (0.004%) |
| vectorbt_strict | 210,352 | 91 trades (0.04%) | $0 (0.00%) |
| lean_strict | 226,172 | 589 trades (0.26%) | $7,200 (0.66%) |

### What's Validated

**Core Execution** (119k+ trades across 500 assets × 10 years):
- Entry/exit timing
- Fill prices
- Position tracking
- P&L calculations
- Multi-asset portfolio management
- Cash constraints and margin

**Risk Rules - LONG Positions** (Scenario tests):
- Stop-loss triggers
- Take-profit triggers
- Trailing stop (close-based HWM)
- Bracket orders

### What's NOT Validated (Gaps)

**SHORT Position Risk Rules** (VBT Pro only):
- SHORT stop-loss triggers - VBT Pro validated, others untested
- SHORT take-profit triggers - VBT Pro validated, others untested
- SHORT trailing stop - VBT Pro validated, others untested
- SHORT bracket orders - VBT Pro validated, others untested

**Rule Combinations** (Not tested anywhere):
- TSL + TP when both trigger same bar
- TSL + SL when both trigger same bar
- TSL + TP + SL (triple rule interaction)
- Rule priority in simultaneous breach

**Stress Conditions** (Limited testing):
- Gap openings through stop levels
- High volatility periods
- Extended (1000+ bar) trailing stop tracking
**Core Execution** (210k+ trades per profile on real data):
- Entry/exit timing and fill prices (open, close, stop price)
- Position tracking (long and short)
- P&L calculations and multi-asset portfolio management
- Cash constraints, margin, and buying power

**Risk Rules** (16 scenario tests per framework):
- Stop-loss and take-profit (long and short)
- Trailing stop with close-based HWM (long and short)
- Bracket orders (SL + TP)
- Rule combinations (TSL+TP, TSL+SL, TSL+TP+SL)
- 1500-bar stress test across 9 market regimes with gap events

### Remaining Gaps

- LEAN parity gap (+589 trades) under investigation (buying power reservation model)
- Per-share commission and volume-based slippage not cross-validated
- VWAP and MID execution prices not cross-validated

## Recommendations

Expand Down
70 changes: 61 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ Backtesting requires accurate simulation of order execution, position tracking,
- Configurable execution modes (same-bar or next-bar fills)
- Position-level risk rules (stop-loss, take-profit, trailing stops)
- Portfolio-level constraints (max positions, drawdown limits)
- Cash and margin account policies
- Cash, margin, and crypto account policies
- 40+ behavioral knobs for framework-specific parity

The same Strategy class used in backtesting works unchanged in ml4t-live for production deployment.

Expand Down Expand Up @@ -93,6 +94,31 @@ Portfolio-level controls:
from ml4t.backtest.risk import MaxPositions, MaxDrawdown, DailyLossLimit
```

## Framework Profiles

Built-in profiles replicate the behavioral semantics of major backtesting frameworks:

```python
from ml4t.backtest import BacktestConfig

# Match VectorBT behavior (same-bar close fills, fractional shares)
config = BacktestConfig.from_preset("vectorbt")

# Match Backtrader behavior (next-bar open fills, integer shares)
config = BacktestConfig.from_preset("backtrader")

# Match Zipline behavior (next-bar open fills, integer shares, per-share commission)
config = BacktestConfig.from_preset("zipline")

# Match QuantConnect LEAN behavior (same-bar close fills, integer shares)
config = BacktestConfig.from_preset("lean")

# Conservative production settings (higher costs, cash buffer)
config = BacktestConfig.from_preset("realistic")
```

Each profile sets 40+ behavioral knobs (fill timing, execution price, share type, commission model, order processing, etc.) to match the target framework exactly.

## Execution Modes

```python
Expand Down Expand Up @@ -136,15 +162,27 @@ class RankingStrategy(Strategy):
broker.submit_order(asset, quantity=100, side="BUY")
```

## Validation
## Cross-Framework Validation

ml4t-backtest is validated by configuring profiles to match each framework's behavior exactly:

The library is validated against VectorBT Pro, Backtrader, and Zipline:
| Framework | Scenarios | Trade Match | Notes |
|-----------|-----------|-------------|-------|
| VectorBT Pro | 16/16 | 100% | Full feature coverage |
| VectorBT OSS | 16/16 | 100% | Open-source subset |
| Backtrader | 16/16 | 100% | Next-bar execution |
| Zipline | 15/15 | 100% | NYSE calendar alignment |

- 119,000+ trades verified trade-by-trade across frameworks
- 500 assets x 10 years (2,520 bars) stress testing
- 100% PnL match on common execution scenarios
Large-scale validation (250 assets x 20 years, real data):

See [validation/README.md](validation/README.md) for test methodology.
| Profile | Trades | Value | Gap |
|---------|--------|-------|-----|
| zipline_strict | 225,583 | match | 0 trades, $19 (0.0001%) |
| backtrader_strict | 216,980 | match | 1 trade (0.0005%) |
| vectorbt_strict | 210,352 | match | 91 trades (0.04%) |
| lean_strict | 226,172 | match | 589 trades (0.26%) |

See [validation/README.md](validation/README.md) for methodology and detailed results.

Release-gate commands:

Expand All @@ -158,12 +196,26 @@ python validation/run_all_correctness.py --framework backtrader --scenarios 01,0
python validation/run_all_correctness.py --framework zipline --scenarios 01,03,05,09
```

## Performance

Benchmark on 250 assets x 20 years daily data (1.26M bars):

| Metric | Value |
|--------|-------|
| Runtime | ~30s |
| Speed | ~40,000 bars/sec |
| Memory | ~290 MB |
| vs Backtrader | 19x faster |
| vs Zipline | 8x faster |
| vs LEAN | 5x faster |

## Technical Characteristics

- **Event-driven**: Each bar processes sequentially with exit-first logic
- **Point-in-time**: No access to future data within strategy callbacks
- **Configurable fills**: Match behavior of different backtesting frameworks
- **Parquet export**: Results serializable for analysis with ml4t-diagnostic
- **Type-safe**: 0 type diagnostics (ty/Astral), full type annotations

## Related Libraries

Expand All @@ -175,7 +227,7 @@ python validation/run_all_correctness.py --framework zipline --scenarios 01,03,0
## Development

```bash
git clone https://github.com/applied-ai/ml4t-backtest.git
git clone https://github.com/ml4t/ml4t-backtest.git
cd ml4t-backtest
uv sync
uv run pytest tests/ -q
Expand All @@ -186,9 +238,9 @@ uv run ty check

See [LIMITATIONS.md](LIMITATIONS.md) for documented assumptions:

- Partial fills not supported (all-or-nothing)
- No intrabar stop simulation (uses bar OHLC)
- Calendar overnight sessions require configuration
- See LIMITATIONS.md for full list

## License

Expand Down
Loading