Deterministic two-stage backtest pipeline for crypto L2 order book data (incremental_book_L2) exported by Tardis.dev.
- Compile stage: strict CSV → validate → quantize (fixed-point ints) → binary event log (
.evlog+.idx+ manifest). - Replay stage: deterministic event replay → book reconstruction → strategy loop → fills + equity curve + Sharpe/PSR/DSR.
- Deterministic artifacts: compiler emits input/output hashes and fails if inputs change mid-compile.
- Fail-loud ingestion: strict schema, monotone
local_timestamp, exact divisibility to instrument increments. - Fixed-point economics in core (
Ticks,Lots,QuoteAtoms);Decimalonly at ingestion boundaries. - Configurable failure policy: hard-fail or quarantine (skip-row / skip-batch / halt).
- Reference Python L2 book implementation (crossing detection; no silent “repairs”).
- Minimal sim loop (v1): top-of-book market orders, fee model, optional JSONL tape output.
All commands below assume you are running from the repository root (so mm_bt/ is importable).
- Python 3.12+
- Optional:
tardis-dev(dataset download + instrument metadata API) - Optional:
pytest(tests)
python -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements.txtRuntime is stdlib-only; tardis-dev is only needed for downloading data and instrument metadata.
The engine expects Tardis “downloadable CSV” datasets on disk in this canonical layout:
{root}/{exchange}/{data_type}/{date}/{symbol}.csv.gz
Example:
./data/binance/incremental_book_L2/2024-01-01/BTCUSDT.csv.gz
You can download into that layout (requires tardis-dev + network):
python -m mm_bt.cli.tardis_download \
--out-root ./data \
--exchange binance \
--symbol BTCUSDT \
--date 2024-01-01 \
--data-type incremental_book_L2python -m mm_bt.cli.bt_compile \
--tardis-root ./data \
--exchange binance \
--symbol BTCUSDT \
--date 2024-01-01 \
--out ./evlog_outIf you already have a single *.csv/*.csv.gz file, you can point the compiler at it directly with --l2 /path/to/file.csv.gz.
Quantization configuration (pick one):
- Explicit increments:
--price-increment ... --amount-increment ... - Static instrument meta JSON:
--instrument-meta ./instrument_meta.json(plus--exchange/--symbol/--date) - Tardis instrument metadata API (requires
tardis-dev):--exchange/--symbol/--date(usesTARDIS_API_KEYor--tardis-api-key) - Fallback: infer increments from the L2 CSV
Outputs:
*.evlog: binary event log of L2 batches*.idx: time→offset index (optional at run-time, but recommended)*.manifest.json: hashes, format versions, quantizer params*.quarantine.jsonl: only when--failure-policy quarantineis used (default output path)
python -m mm_bt.cli.bt_run \
--evlog ./evlog_out/binance-BTCUSDT-2024-01-01-incremental_book_L2.evlog \
--index ./evlog_out/binance-BTCUSDT-2024-01-01-incremental_book_L2.idx \
--initial-cash 1000000000 \
--strategy dummy \
--qty-lots 1 \
--fee-bps 0 \
--tape ./evlog_out/tape.jsonlUnits:
Ticks = price / price_incrementLots = amount / amount_incrementQuoteAtoms = Ticks * Lots(quote notional divided byprice_increment * amount_increment)
Built-in strategies (CLI):
--strategy dummy: alternating buy/sell market orders--strategy random: seeded per-batch RNG market orders (requires--seed)
pytest -q- Primary ordering time is Tardis
local_timestamp(receive time), converted asts_recv_ns = local_timestamp_us * 1_000(exchange timestamps are not assumed monotone). - Rows with identical
local_timestampare one message; apply the full batch before reading book state. local_timestampmust be non-decreasing within a file; there is no reorder buffer.incremental_book_L2rows are level updates (not deltas);amount=0deletes that price level.is_snapshotfalse→true resets the local book state.- Economic quantities are fixed-point ints; parsing/quantization is exact, once, at ingestion (no hidden rounding).
- L2 only (no trades stream, no queue/depletion model).
- Market orders only; fill model is top-of-book and rejects if size exceeds available.
- Single-symbol/day per compiled evlog.