No look-ahead. No float rounding. No guesswork.

Backtest crypto strategies at nanosecond resolution.

Most research backtesters make it too easy to see the future, blur exchange time with local time, or leak float rounding into PnL. crypto-rs-backtester keeps those failure modes explicit with a deterministic Rust core and a Python research interface.

Zero float errors
integer money path
3-way timeline
exchange, local, simulator
Rust-speed core
Python stays ergonomic
simulation timeline
$ pip install crypto-rs-backtester
> deterministic seed=42, python_mode="batch"
tick feed latency applied batch stable tie-breaks

Problem

Most backtests hide the assumptions that matter in live crypto markets.

Queue position, local observation time, order acknowledgement, and integer money accounting should be first-class parts of the simulation model.

Before

Typical Python backtester

  • Future data leaks are hard to detect.
  • Float rounding can drift into PnL logic.
  • Latency often becomes a single loose assumption.
After

crypto-rs-backtester

  • ts_exchange, ts_local, and ts_sim stay separated.
  • Fixed-point i64 keeps the money path integer-only.
  • feed_latency_ns injects nanosecond-scale observation delay.

Current capabilities

Research ergonomics with a simulation core that stays strict.

Use Python for strategy iteration while Rust handles deterministic event ordering, fixed-point accounting, and latency-aware execution.

01

Look-ahead prevention

Strategies only observe market state after feed latency. Order arrivals and acknowledgements are sequenced on the simulator timeline.

02

Microstructure fidelity

Model multi-exchange crypto data, latency, L2/L3 queue logic, and realistic race conditions such as pending cancels.

03

Polars and Arrow ingestion

Feed Polars LazyFrames into the Python API, or use the Arrow C Stream path when large datasets demand minimal copying.

04

Determinism first

Seeded RNGs, stable tie-breakers for identical timestamps, and lexicographic symbol assignment keep research runs reproducible.

Architecture

Python stays expressive. Rust owns the event loop.

The repository is organized as a Rust workspace with a PyO3 wrapper and a Python package named rust_backtester. The core uses event-driven simulation and fixed-point i64 values for monetary logic.

  • Tick mode for direct on_tick strategy callbacks.
  • Batch mode for higher throughput with on_ticks and on_order_updates.
  • Separated timelines for ts_exchange, ts_local, and ts_sim.
Polars LazyFrame Python strategy
PyO3 boundary Rust simulator
Trades + stats Reproducible results

Quickstart

Install the package, pass a LazyFrame, run your strategy.

pip install crypto-rs-backtester

import polars as pl
from rust_backtester import Backtester

lf = pl.DataFrame({
    "ts_exchange": [1000, 2000, 3000, 4000],
    "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, ctx):
        ctx.submit_order(
            symbol_id=int(tick["symbol_id"]),
            side=1,
            price=int(tick["price"]),
            qty=1_00000000,
        )

bt = Backtester(
    data={"binance:BTC/USDT": lf},
    seed=42,
    python_mode="tick",
    feed_latency_ns=1_000,
)

result = bt.run(MyStrategy())
print(result.stats())
class MyBatch:
    def on_ticks(self, ticks, ctx):
        for tick in ticks:
            ctx.submit_order(
                symbol_id=tick["symbol_id"],
                side=1,
                price=tick["price"],
                qty=1_00000000,
            )

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

result = bt.run(MyBatch())
# For large datasets, pass a PyArrow RecordBatchReader
# implementing __arrow_c_stream__.

bt = Backtester(
    data={},
    seed=42,
    python_mode="batch",
    batch_ms=50,
)

result = bt.run_arrow(
    stream=record_batch_reader,
    strategy=MyBatch(),
)

Performance

Measured on the repository's Criterion scenarios.

Representative local benchmark output from benchmark/auto_tuning_review_fix_20260130.txt. Throughput estimates use the median time for 1,000,000 synthetic ticks.

Event loop 134.23 ms

Median for bench_event_loop_1m_ticks, about 7.45M ticks/sec.

E2E tick mode 187.61 ms

Median for 4 symbols x 250k ticks, about 5.33M ticks/sec.

E2E batch mode 182.25 ms

Median for the same 1M-tick scenario, about 5.49M ticks/sec.

Status

Work in progress, designed for serious research workflows.

Specs first

Technical design lives in docs/SPEC.md with implementation planning in docs/PLAN.md.

Benchmarkable

Criterion benches can scale symbols, ticks, latency, order cadence, and batch windows through environment variables.

PGO ready

The repository includes a profile-guided optimization pipeline for maximum Rust core throughput on Linux and macOS.

Start now

Bring deterministic simulation into your crypto research stack.