Skip to main content

Rust-based tick-level backtester (WIP)

Project description

crypto-rs-backtester

Open In Colab

Video

Your Crypto Backtest Is Lying to You - Fix It with Rust | crypto-rs-backtester

Your Crypto Backtest Is Lying to You - Fix It with Rust | crypto-rs-backtester

Japanese version: see README.ja.md.

A tick-level, high-precision backtester powered by Rust × Python (Polars), designed for researchers. WIP.

  • Goal: Combine Python's agility with Rust's deterministic, high-performance simulation to eliminate performance bottlenecks, look-ahead bias, and poor reproducibility.
  • Scope: Mainly crypto spot/futures (CEX). Validates microstructure such as multi-exchange, latency, and queue position.

Key Highlights

  • Performant by design: Rust event-driven core with fixed-point arithmetic; Python focuses on research interface.
  • Zero-copy pipeline: Use Polars (Arrow) in Python and hand off to Rust with minimal copying.
  • Microstructure fidelity: Latency modeling, L2/L3 queue logic, and realistic race conditions (e.g., PendingCancel).
  • Determinism first: Seeded RNGs, stable tie-breaking for identical timestamps.

See docs/SPEC.md for details.

Features (current state)

  • Hybrid architecture: Rust core (event-driven, fixed-point i64) + Python strategy interface.
  • Separated timelines: ts_exchange (ground truth) / ts_local (what the strategy observes) / ts_sim (total order of all events).
  • Look-ahead prevention: Strategies only see MarketView after feed latency. Order arrival and ACK are strictly ordered on ts_sim.
  • Execution modes: Tick mode (on_tick) and Batch mode (on_ticks / on_order_updates).
  • Data ingestion: via Polars LazyFrame (minimal copying), or zero-copy via Arrow C Stream (run_arrow).
  • Determinism: Fixed RNG seed, stable tie-breakers for simultaneous events, lexicographic assignment of symbol IDs.

See docs/SPEC.md for details.


Repository Structure

  • backtester-core/: Rust simulation core (src/*.rs, tests/, benches/)
  • backtester-py/: PyO3 wrapper exposing the core to Python
  • python/: Python package rust_backtester/ and tests in python/tests/
  • docs/: Specs and plans (SPEC.md, PLAN.md, etc.)
  • Root Cargo.toml: Rust workspace, pyproject.toml: maturin build

Installation

pip install crypto-rs-backtester

Install & Build (dev)

Prerequisites: Python 3.9+ / Rust toolchain / maturin

# Virtualenv
python -m venv .venv && source .venv/bin/activate

# Dev install (builds Rust extension)
pip install -e .[dev]

# Alternative: direct build
maturin develop

Quickstart (Python)

Required columns: ts_exchange:Int64, price:Int64, qty:Int64, side:Int8 Recommended: seq:Int64 (stable order for same-timestamp), ts_local:Int64 (if missing, applies ts_exchange + feed_latency_ns) Aliases: ts_eventts_exchange, sizeqty

import polars as pl
from rust_backtester import Backtester

# Tiny deterministic dataset (1e-8 fixed point: 100.0 => 100_00000000)
lf = pl.DataFrame({
    "ts_exchange": [1_000, 2_000, 3_000, 4_000],
    "price": [100_00000000, 101_00000000, 99_00000000, 100_00000000],
    "qty":   [  1_00000000,   1_00000000,  1_00000000,   1_00000000],
    "side":  [            1,           -1,           1,           -1],
    "seq": list(range(4)),
}).lazy()

class MyStrategy:
    def on_tick(self, tick: dict, ctx):
        # Example: place a passive order using the received tick
        ctx.submit_order(
            symbol_id=int(tick["symbol_id"]),
            side=1,  # 1=Buy, -1=Sell
            price=int(tick["price"]),
            qty=1_00000000,
        )

bt = Backtester(
    data={"binance:BTC/USDT": lf},
    seed=42,
    python_mode="tick",    # or "batch"
    batch_ms=100,
    feed_latency_ns=1_000,  # applies ts_local = ts_exchange + 1_000 (ns)
)

result = bt.run(MyStrategy())
print(result.stats())
print(result.trades())

Batch mode (higher throughput)

class MyBatch:
    def on_ticks(self, ticks: list[dict], ctx):
        for t in ticks:
            ctx.submit_order(symbol_id=t["symbol_id"], side=1, price=t["price"], qty=1_00000000)

bt = Backtester(data={"binance:BTC/USDT": lf}, seed=42, python_mode="batch", batch_ms=50)
res = bt.run(MyBatch())

Arrow zero-copy path (for large datasets)

# Pass a PyArrow RecordBatchReader implementing __arrow_c_stream__
res = bt.run_arrow(stream=rb_reader, strategy=MyBatch())

Build, Test, Benchmarks

  • Python tests: pytest -q
    • Benchmarks only: pytest -m bench -q
  • Rust build: cargo build -p backtester-core
  • Rust tests: cargo test -p backtester-core
  • Rust benches: cargo bench -p backtester-core

Note: python/tests/conftest.py auto-runs maturin develop if the extension isn't installed.


Benchmark Configuration (Practical Conditions)

Criterion benches in Rust can be tuned via environment variables (defaults: 4 symbols × 250k ticks each):

  • BACKTEST_BENCH_NSYMBOLS (default: 4)
  • BACKTEST_BENCH_TICKS_PER_SYMBOL (default: 250000)
  • BACKTEST_BENCH_DT_NS tick spacing in ns (default: 1000)
  • BACKTEST_BENCH_SYMBOL_STAGGER_NS per-symbol start offset in ns (default: 10000)
  • BACKTEST_BENCH_FEED_LATENCY_NS (default: 2000000)
  • BACKTEST_BENCH_ORDER_UPDATE_LATENCY_NS (default: 1000000)
  • BACKTEST_BENCH_ORDER_LATENCY_NS (default: 500000)
  • BACKTEST_BENCH_SUBMIT_EVERY_N order submission interval in ticks (default: 256)
  • BACKTEST_BENCH_MAX_BATCH_NS batch-mode window in ns (default: 10000000)

Example:

# 8 symbols × 500k ticks per symbol, 5ms batch window
BACKTEST_BENCH_NSYMBOLS=8 \
BACKTEST_BENCH_TICKS_PER_SYMBOL=500000 \
BACKTEST_BENCH_MAX_BATCH_NS=5000000 \
cargo bench -p backtester-core --bench bench_core

The E2E benches (bench_engine_e2e_*) measure both Tick and Batch modes under identical conditions. Synthetic data is deterministic and includes realistic order flow (opposite-side, same-price passive limit orders at a fixed interval).

Profile-Guided Optimization (PGO)

To build with PGO for maximum performance (Linux/macOS):

# Requires llvm-profdata (part of LLVM tools)
make pgo

This runs a 4-step pipeline:

  1. Instrumentation build
  2. Profile generation (runs benchmarks)
  3. Profile merge
  4. Optimized build using profiles

Expected improvement: 5-15% throughput.


Examples (example/)

  • example/colab_backtester_demo.ipynb
    • Minimal E2E demo notebook. Click the badge above to run in Colab.
    • For local use, start Jupyter from the repo root and open files under example/.
  • example/crypto_researcher_adoption_guide.md
    • Practical guide for researchers: onboarding, data schema, strategy modes (tick/batch), performance tuning.

Note: Large datasets are not bundled. Start with the minimal data generated by tests in python/tests/ or the Colab demo.


Coding Style & Core Principles

  • Rust: edition 2024, cargo fmt / cargo clippy. Naming: functions/modules snake_case, types CamelCase.
  • Python: PEP 8, 4-space indent. Type hints required for new/changed code.
  • Determinism first: fixed RNG seeds, stable ordering. Avoid f64 for monetary logic (only at I/O boundaries).

Contribution & Commit Guidelines

  • Start with docs/SPEC.md and docs/PLAN.md.
  • Conventional Commits: e.g., feat(core): add queue model, chore(fmt): rustfmt.
  • Branches: feature/..., fix/..., chore/....
  • PRs should include What/Why, linked issues, test plan (commands + results), and performance notes if core paths changed. Update docs/ when APIs or architecture shift.

Status & Roadmap

This project is under active development (WIP). APIs and internals may change.

  • Technical spec: docs/SPEC.md
  • Plan/tests/benches: docs/PLAN.md
  • Researcher adoption guide: example/crypto_researcher_adoption_guide.md
  • Colab demo: example/colab_backtester_demo.ipynb

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

crypto_rs_backtester-0.1.2.tar.gz (101.4 kB view details)

Uploaded Source

Built Distributions

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

crypto_rs_backtester-0.1.2-cp39-abi3-win_amd64.whl (2.1 MB view details)

Uploaded CPython 3.9+Windows x86-64

crypto_rs_backtester-0.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.8 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

crypto_rs_backtester-0.1.2-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (3.6 MB view details)

Uploaded CPython 3.9+macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

File details

Details for the file crypto_rs_backtester-0.1.2.tar.gz.

File metadata

  • Download URL: crypto_rs_backtester-0.1.2.tar.gz
  • Upload date:
  • Size: 101.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for crypto_rs_backtester-0.1.2.tar.gz
Algorithm Hash digest
SHA256 00310463db89d33895cb3d4269a0b23f92088aedb1e3b12d89a33d437ab56eda
MD5 154383b0e633761fab2d5aacbfbc1158
BLAKE2b-256 373426f51befd2ddd5107b05166b2480339f38aa9880990bc1a7c6b0d84f2240

See more details on using hashes here.

Provenance

The following attestation bundles were made for crypto_rs_backtester-0.1.2.tar.gz:

Publisher: release.yml on takurot/crypto-rs-backtester

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

File details

Details for the file crypto_rs_backtester-0.1.2-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for crypto_rs_backtester-0.1.2-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 44f7862ef9040c15490b2b551c91d855fe503fbd9c0b86e5d9be77755a6476a9
MD5 f493af99d24b70b0f993aca06f567b5c
BLAKE2b-256 bb7ac11f71c5af6ed66346faf7efecb5c949a3a2d20aa04fb800f6907a2dce11

See more details on using hashes here.

Provenance

The following attestation bundles were made for crypto_rs_backtester-0.1.2-cp39-abi3-win_amd64.whl:

Publisher: release.yml on takurot/crypto-rs-backtester

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

File details

Details for the file crypto_rs_backtester-0.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for crypto_rs_backtester-0.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 98d75da7bd1f5a7dbe0364f707e22ed135bbf5ac4cf70f4445e10243bb4077be
MD5 8c439c7b7fc90701750e44980d30ebf2
BLAKE2b-256 42dda812c08c07ebcb4623804321ed15f259d0d990617b5717451d1ebf80310c

See more details on using hashes here.

Provenance

The following attestation bundles were made for crypto_rs_backtester-0.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on takurot/crypto-rs-backtester

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

File details

Details for the file crypto_rs_backtester-0.1.2-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for crypto_rs_backtester-0.1.2-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 4e23fd9db51d3733929463051c5369aaf9bf2803de3fc9d3caa53c3bc01bc766
MD5 f056e50bb434a583cba962f8dca3d0ea
BLAKE2b-256 930e8974891ad6d38f8dd9e08726b5e78bf3c74afe39f45a9574a6c92fdae9f5

See more details on using hashes here.

Provenance

The following attestation bundles were made for crypto_rs_backtester-0.1.2-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: release.yml on takurot/crypto-rs-backtester

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