IronCondor
IronCondor is a high-performance backtester for options-trading strategies with order-book-level fill simulation, written in Rust.
It is the deterministic replay engine around an upstream options stack,
not a re-implementation of it. Pricing, Greeks, multi-leg strategies, and
exit policies come from
optionstratlib; order matching
comes from
option-chain-orderbook
and orderbook-rs;
synthetic option chains come from
OptionChain-Simulator.
This crate contributes the replay loop, the dual fill models, P&L
attribution by Greek, the result bundle, and the Python bindings.
Why order-book-level fills
Most backtesters fill option orders at mid or bid/ask with a fixed slippage assumption. IronCondor adds a realistic mode that routes every order through a real options matching engine — queue position, partial fills, multi-level book walks, and a resting GTC lifecycle — so fill risk is an emergent property of the book rather than a guess. A fast naive mode (mid/spread plus configurable slippage) stays available for quick iteration, and both modes emit the identical fill-report shape, so downstream analytics is mode-agnostic.
Key properties
- Deterministic replay. For a fixed
(seed, config, data, crate version, Rust toolchain, lockfile)the four Parquet tables are byte-identical and the manifest is identical minus its one wall-clock provenance field. Across environments the guarantee is logical equivalence under a documented normalization (canonical row ordering, canonical JSON). No wall clock, no unseeded RNG, and no map-iteration order reaches a result. - Money as integer cents. Every execution and result boundary carries
money as integer cents; order-book prices are
u128ticks.f64is confined to the upstream pricing/Greeks kernel and the documented derived-analytics columns. - Dual execution modes.
naive(mid/spread plus slippage) andrealistic(a real order book: queue position, partial fills, multi-level walks, resting GTC orders), selected once from config with no per-step dynamic dispatch. - P&L attribution by Greek. Each step's mark-to-market change is
decomposed into theta, delta, vega, and spread capture minus fees, closed
by an exact integer-cents residual: for every step,
theta + delta + vega + spread − fees + residualequals the equity change by construction. A large residual is an advisory model-quality signal, never a run failure. - A frozen result bundle. Every run publishes an
ironcondor.bundle.v1directory (a manifest plus four Parquet tables) consumed by ChainView. - Hardened against untrusted input. The engine parses untrusted files
and runs inside CI, so every external input pairs a validation with a
resource ceiling and a typed error — no panic, hang, or OOM on malformed
input. Parsers are fuzzed, the crate is
#![forbid(unsafe_code)], errors are the typedBacktestError, and none cross the Python boundary as a panic. - Performance as an acceptance criterion. The replay loop holds zero
steady-state allocation, and the hot paths (loop, fill models, conversion,
bundle writer, PyO3 boundary) carry CI-gated budgets measured with
criterionandhdrhistogram. As a recorded baseline (seeBENCH.md), a fullrun_backtestover a 2048-step, four-leg iron-condor Parquet chain runs at a p50 of about 2.35 µs/step (about 425k steps/sec/core) in naive mode on an Apple M4 Max — a measurement, not a guarantee.
Feature flags
| Feature | Default | What it adds |
|---|---|---|
| (none) | yes | Replay engine, naive execution, Parquet/CSV historical feeds, and the result bundle. |
orderbook |
Realistic, liquidity-aware fills routed through option-chain-orderbook. |
|
simulator |
Synthetic chain sessions from OptionChain-Simulator over HTTP. | |
python |
PyO3 bindings, built as a wheel with maturin (PyPI publication planned). |
Quick start (Rust)
Drive one backtest end to end — a Parquet chain in, an equity curve out:
use ;
use ExpirationDate;
use ExitPolicy;
use Decimal;
Quick start (Python)
The python feature builds a PyO3 extension module. Wheels are not yet on
PyPI; build one locally with maturin:
=
= # publishes an ironcondor.bundle.v1 directory
# summary metrics as a dict
= # a pandas DataFrame with integer-cents columns
The result bundle
Every run publishes an ironcondor.bundle.v1 directory: a manifest.json
(run metadata, strategy, config, data source, code version) plus four Parquet
tables — fills.parquet, equity_curve.parquet, positions.parquet, and
greeks_attribution.parquet. Writes are atomic (temp file plus rename). The
schema tag is frozen and its lineage is coordinated with
ChainView, which consumes the
bundle in replay mode — so a schema change is a deliberate SemVer event, not
an accident.
Status and versioning
0.5.0 completes the v0.1–v0.5 roadmap — the engine, both fill models, the
full analytics and result bundle, and the Python bindings — with the v1.0
stability gates wired: the Rust public surface, the configuration surface,
and the bundle schema are each pinned by a committed snapshot that fails CI
on drift. Under SemVer 0.x, breaking changes may still land in minor
releases; the 1.0 cut follows the documented one-quarter stability window.
Documentation states present-tense claims only for behaviour that exists,
and no benchmark number is written before it is measured.
Ecosystem
Part of a family of Rust crates for options-trading infrastructure: OptionStratLib · Option-Chain-OrderBook · OrderBook-rs · OptionChain-Simulator · ChainView
Contact
Joaquin Bejar — jb@taunais.com
License
MIT — see LICENSE.