State-of-the-art event-driven backtesting engine for quantitative trading
Project description
ml4t-backtest
Event-driven backtesting engine for quantitative trading strategies with realistic execution modeling.
Part of the ML4T Library Ecosystem
This library is one of five interconnected libraries supporting the machine learning for trading workflow described in Machine Learning for Trading:
Each library addresses a distinct stage: data infrastructure, feature engineering, signal evaluation, strategy backtesting, and live deployment.
What This Library Does
Backtesting requires accurate simulation of order execution, position tracking, and risk management. ml4t-backtest provides:
- Event-driven architecture with point-in-time correctness (no look-ahead bias)
- Exit-first order processing matching real broker behavior
- 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
The same Strategy class used in backtesting works unchanged in ml4t-live for production deployment.
Installation
pip install ml4t-backtest
Quick Start
from ml4t.backtest import Engine, Strategy, BacktestConfig, DataFeed
from ml4t.backtest.risk import StopLoss, TakeProfit, RuleChain
class TrendFollowing(Strategy):
def __init__(self, fast=10, slow=30):
self.fast = fast
self.slow = slow
def on_data(self, timestamp, data, context, broker):
close = data["close"]
fast_ma = close.rolling(self.fast).mean().iloc[-1]
slow_ma = close.rolling(self.slow).mean().iloc[-1]
position = broker.get_position("SPY")
if fast_ma > slow_ma and position is None:
broker.submit_order("SPY", quantity=100, side="BUY")
elif fast_ma < slow_ma and position is not None:
broker.close_position("SPY")
config = BacktestConfig(
initial_cash=100_000,
commission_rate=0.001,
)
feed = DataFeed(price_data)
engine = Engine(feed, TrendFollowing(), config)
result = engine.run()
print(f"Total Return: {result.total_return:.2%}")
print(f"Sharpe Ratio: {result.metrics['sharpe_ratio']:.2f}")
Risk Management
Position-level exit rules:
from ml4t.backtest.risk import StopLoss, TakeProfit, TrailingStop, RuleChain
class MyStrategy(Strategy):
def on_start(self, broker):
broker.set_position_rules(RuleChain([
StopLoss(pct=0.05),
TakeProfit(pct=0.15),
TrailingStop(pct=0.03),
]))
Portfolio-level controls:
from ml4t.backtest.risk import MaxPositions, MaxDrawdown, DailyLossLimit
Execution Modes
from ml4t.backtest import ExecutionMode, StopFillMode
# Same-bar fills (VectorBT style)
config = BacktestConfig(
execution_mode=ExecutionMode.SAME_BAR,
stop_fill_mode=StopFillMode.STOP_PRICE,
)
# Next-bar fills (Backtrader style)
config = BacktestConfig(
execution_mode=ExecutionMode.NEXT_BAR,
stop_fill_mode=StopFillMode.STOP_PRICE,
)
Commission and Slippage
from ml4t.backtest import PercentCommission, PercentSlippage
config = BacktestConfig(
commission_model=PercentCommission(rate=0.001),
slippage_model=PercentSlippage(rate=0.0005),
)
Multi-Asset Support
class RankingStrategy(Strategy):
def on_data(self, timestamp, data, context, broker):
returns = data["close"].pct_change(20)
ranked = returns.iloc[-1].sort_values(ascending=False)
# Long top 10
for asset in ranked.head(10).index:
if broker.get_position(asset) is None:
broker.submit_order(asset, quantity=100, side="BUY")
Validation
The library is validated against VectorBT Pro, Backtrader, and Zipline:
- 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
See validation/README.md for test methodology.
Release-gate commands:
# Fast parity contract gate (scenario 01 across vectorbt/backtrader/zipline)
ML4T_COMPARISON_INPROC=1 uv run pytest tests/contracts/test_cross_engine_contracts.py -q
# Full correctness runner (selected scenarios)
python validation/run_all_correctness.py --framework vectorbt_oss --scenarios 01,03,05,09
python validation/run_all_correctness.py --framework backtrader --scenarios 01,03,05,09
python validation/run_all_correctness.py --framework zipline --scenarios 01,03,05,09
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
Related Libraries
- ml4t-data: Market data acquisition and storage
- ml4t-engineer: Feature engineering and technical indicators
- ml4t-diagnostic: Signal evaluation and statistical validation
- ml4t-live: Live trading with broker integration
Development
git clone https://github.com/applied-ai/ml4t-backtest.git
cd ml4t-backtest
uv sync
uv run pytest tests/ -q
uv run ty check
Known Limitations
See LIMITATIONS.md for documented assumptions:
- Partial fills not supported (all-or-nothing)
- No intrabar stop simulation (uses bar OHLC)
- Calendar overnight sessions require configuration
License
MIT License - see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ml4t_backtest-0.1.0a9.tar.gz.
File metadata
- Download URL: ml4t_backtest-0.1.0a9.tar.gz
- Upload date:
- Size: 221.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08ef0c4ec873d37b4665afc8c7f195782103178250743d7f33ed0e67f71f996a
|
|
| MD5 |
5f4537223a832fcd16a65fa40a7c73b8
|
|
| BLAKE2b-256 |
8a144820c95fb85acfc71c7dc55a7eb50c818f1b3f114f0f8138fc095cc2b488
|
Provenance
The following attestation bundles were made for ml4t_backtest-0.1.0a9.tar.gz:
Publisher:
release.yml on ml4t/backtest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ml4t_backtest-0.1.0a9.tar.gz -
Subject digest:
08ef0c4ec873d37b4665afc8c7f195782103178250743d7f33ed0e67f71f996a - Sigstore transparency entry: 1005572348
- Sigstore integration time:
-
Permalink:
ml4t/backtest@b18a64a8ac763b0c38fdbd07623c7d9f64ae0652 -
Branch / Tag:
refs/tags/v0.1.0a9 - Owner: https://github.com/ml4t
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b18a64a8ac763b0c38fdbd07623c7d9f64ae0652 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ml4t_backtest-0.1.0a9-py3-none-any.whl.
File metadata
- Download URL: ml4t_backtest-0.1.0a9-py3-none-any.whl
- Upload date:
- Size: 135.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
575ee936cd36741482b27a4c80ca898086b979e3d7362399201a686d8253d6e0
|
|
| MD5 |
e09ac9d5ecc960e685733191f988d580
|
|
| BLAKE2b-256 |
287da2fb5b94afd93d6b725e0b69648ed4357a113ddf5d14789ec2e0dafed0b3
|
Provenance
The following attestation bundles were made for ml4t_backtest-0.1.0a9-py3-none-any.whl:
Publisher:
release.yml on ml4t/backtest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ml4t_backtest-0.1.0a9-py3-none-any.whl -
Subject digest:
575ee936cd36741482b27a4c80ca898086b979e3d7362399201a686d8253d6e0 - Sigstore transparency entry: 1005572350
- Sigstore integration time:
-
Permalink:
ml4t/backtest@b18a64a8ac763b0c38fdbd07623c7d9f64ae0652 -
Branch / Tag:
refs/tags/v0.1.0a9 - Owner: https://github.com/ml4t
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b18a64a8ac763b0c38fdbd07623c7d9f64ae0652 -
Trigger Event:
push
-
Statement type: