Skip to main content

State-of-the-art event-driven backtesting engine for quantitative trading

Project description

ml4t-backtest

Python 3.12+ PyPI License: MIT

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:

ML4T Library Ecosystem

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, 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.

ml4t-backtest Architecture

Installation

pip install ml4t-backtest

Quick Start

import polars as pl
from ml4t.backtest import Engine, Strategy, BacktestConfig, DataFeed

class SignalStrategy(Strategy):
    def on_data(self, timestamp, data, context, broker):
        for asset, bar in data.items():
            signal = bar.get("signals", {}).get("prediction", 0)
            price = bar.get("close", 0)
            position = broker.get_position(asset)

            if position is None and signal > 0.5:
                shares = (broker.get_account_value() * 0.10) / price
                if shares > 0:
                    broker.submit_order(asset, shares)
            elif position is not None and signal < -0.5:
                broker.close_position(asset)

config = BacktestConfig(
    initial_cash=100_000,
    commission_rate=0.001,
    slippage_rate=0.0005,
)

feed = DataFeed(prices_df=prices, signals_df=signals)
engine = Engine(feed, SignalStrategy(), config)
result = engine.run()

print(f"Total Return: {result.metrics['total_return_pct']:.2f}%")
print(f"Sharpe Ratio: {result.metrics['sharpe']:.2f}")

Risk Management

Position-level exit rules:

from ml4t.backtest import Strategy, 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.portfolio.limits import MaxDrawdownLimit, DailyLossLimit

Framework Profiles

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

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

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 BacktestConfig, CommissionType

config = BacktestConfig(
    commission_rate=0.001,         # 10 bps percentage
    slippage_rate=0.0005,          # 5 bps slippage
    stop_slippage_rate=0.001,      # Additional slippage for stop exits
)

# Or per-share (Interactive Brokers style)
config = BacktestConfig(
    commission_type=CommissionType.PER_SHARE,
    commission_per_share=0.005,
    commission_minimum=1.0,
)

Multi-Asset Rebalancing

from ml4t.backtest import Strategy, TargetWeightExecutor, RebalanceConfig

class WeightStrategy(Strategy):
    def __init__(self):
        self.executor = TargetWeightExecutor(RebalanceConfig(
            min_trade_value=100,
            min_weight_change=0.01,
        ))
        self.bar_count = 0

    def on_data(self, timestamp, data, context, broker):
        self.bar_count += 1
        if self.bar_count % 21 != 1:  # Monthly rebalance
            return

        # ML predictions → portfolio weights
        weights = {}
        for asset, bar in data.items():
            signal = bar.get("signals", {}).get("prediction", 0)
            if signal and signal > 0:
                weights[asset] = signal
        if weights:
            total = sum(weights.values())
            weights = {a: w / total for a, w in weights.items()}
            self.executor.execute(weights, data, broker)

Cross-Framework Validation

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

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

Large-scale validation (250 assets x 20 years, real data):

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 for methodology and detailed results.

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

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

Documentation

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

  • 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/ml4t/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:

  • No intrabar stop simulation (uses bar OHLC)
  • Calendar overnight sessions require configuration
  • See LIMITATIONS.md for full list

License

MIT License - see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ml4t_backtest-0.1.0b6.tar.gz (271.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ml4t_backtest-0.1.0b6-py3-none-any.whl (148.3 kB view details)

Uploaded Python 3

File details

Details for the file ml4t_backtest-0.1.0b6.tar.gz.

File metadata

  • Download URL: ml4t_backtest-0.1.0b6.tar.gz
  • Upload date:
  • Size: 271.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for ml4t_backtest-0.1.0b6.tar.gz
Algorithm Hash digest
SHA256 d5436404f3741a3995c4df7d572608ccce08e895af39129b66ca882921a25ab5
MD5 cb461fc08c649e8af4d868d01361c796
BLAKE2b-256 01900ee5a29dd66a00a21ee7fff50dca930a782bede10dbefc2ce1f229bbbb8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for ml4t_backtest-0.1.0b6.tar.gz:

Publisher: release.yml on ml4t/backtest

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ml4t_backtest-0.1.0b6-py3-none-any.whl.

File metadata

  • Download URL: ml4t_backtest-0.1.0b6-py3-none-any.whl
  • Upload date:
  • Size: 148.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for ml4t_backtest-0.1.0b6-py3-none-any.whl
Algorithm Hash digest
SHA256 3587265ed6142fbe2502725db61c3860516ad9feabcde0410a5119e5e2256f2a
MD5 77a37b37ca7c66ca246fb0ad5ed1969c
BLAKE2b-256 0f304206b1549cb708e99fe411c036b7d6f6442b00eac96d842bbfd1f1661f4e

See more details on using hashes here.

Provenance

The following attestation bundles were made for ml4t_backtest-0.1.0b6-py3-none-any.whl:

Publisher: release.yml on ml4t/backtest

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page