Skip to content

jayhere1/quotient

Repository files navigation

quotient

A C++ limit-order-book engine and market-making simulator, driven from a Python research layer. See docs/specs/2026-07-03-quotient-design.md.

Honest framing: quotient is a deterministic, single-threaded event-replay simulator. Reported performance numbers are engine microbenchmarks (ns/op) and replay throughput (events/s) — not production/colocated HFT wire latency.

Build (C++)

cmake -B build -DQUOTIENT_BUILD_TESTS=ON -DQUOTIENT_BUILD_APPS=ON -DQUOTIENT_BUILD_BENCHMARKS=ON
cmake --build build -j
ctest --test-dir build --output-on-failure

Build (Python)

uv venv
uv pip install -e ".[dev]"
uv run pytest -v

Market data (M2)

The recorder/ package captures Kraken's live L2 order-book feed (WS v2 book channel) to Parquet, scaling every price/qty to integers and validating each message against Kraken's CRC-32 checksum live, via the same L2Book C++ reconstructor used by the engine (verified against the real feed — 0 mismatches). recorder/replay.py reads a capture back and re-validates every checksum offline — deterministic, no network, CI-testable. A small real capture is checked in at data/samples/btcusd_book_sample.parquet and replayed by tests/test_replay.py. See recorder/ for the schema, normalization, precision, capture (record.py), and replay code.

Matching engine benchmarks

Two implementations of the same price-time-priority book, built with -DCMAKE_BUILD_TYPE=Release on Apple M4 Max / macOS (Apple Silicon). OrderBook is the correctness-first baseline (std::map levels + std::list FIFO); FlatOrderBook is the cache-optimized version (flat tick-indexed level arrays + a pooled, index-linked intrusive FIFO). Both pass the identical golden + differential-fuzzer suite. These are single-threaded operation microbenchmarks, not production wire latency (see the honesty note above).

Operation OrderBook (baseline) FlatOrderBook (optimized) Speedup
rest one limit order 53.1 ns/op 28.2 ns/op 1.88×
cancel one order 34.2 ns/op 15.2 ns/op 2.25×
clear a 10k-order book 37.4 M orders/s 60.6 M orders/s 1.62×

FlatOrderBook trades generality for speed: it requires a bounded price range known at construction (realistic per instrument). Level access is O(1) and cache-friendly; the cursor rescan on best-level depletion is amortized O(1) for liquid books but degrades on pathologically sparse ones — a hierarchical-bitmap price index would remove that, and is possible future work.

Market-data reconstruction

L2Book reconstructs a Kraken depth-10 book from the WS book feed and validates every message against Kraken's CRC-32 checksum (verified against Kraken's published example). Release build, Apple M4 Max / macOS (Apple Silicon):

Operation Result
apply one L2 update (depth-10 book) 21.0 ns/op
compute Kraken CRC-32 (depth-10 book) 962.5 ns/op

Simulation (M3)

An event-driven market-making simulator (Simulator::run in engine/include/quotient/simulator.hpp / engine/src/simulator.cpp) replays a captured Kraken L2 feed (MarketEvents) through a priority-queue event loop that merges market updates with our own order actions under a configurable latency budget (SimConfig::latency_ns — the delay between a strategy's decision and its action landing on the book). Ordering is deterministic by (time, seq) — no RNG.

A Strategy (on_book_update / on_fill → place/cancel Actions) drives the sim; M3 ships one trivial reference strategy, FixedSpreadStrategy, which quotes a fixed half-spread around the mid. The Python driver (recorder/sim_driver.py) wires a recorded Parquet capture straight into the C++ Simulator — running FixedSpreadStrategy over the real 991-message Kraken sample (data/samples/btcusd_book_sample.parquet) produces 2 fills, inventory = 2, mtm_pnl = 235, deterministically, with inventory conserved across the run (enforced by tests, not just asserted here).

Honest framing — the fill model is a documented approximation, not an exchange match:

  • Passive fills consume our resting order's queue position as the displayed L2 depth at that price level decreases. Aggregated L2 diffs can't distinguish a trade from a cancel, so this is optimistic — it assumes all depth reduction at our level was a trade against us.
  • Aggressive fills (a crossing Place) walk the displayed book depth with slippage, but are non-impacting shadow fills — they don't remove liquidity from the replayed book, since the book only evolves from the recorded feed, never from our own orders.
  • Depth-window edge: the book is truncated to a fixed depth (top 10 by default), so a resting order at a price that scrolls outside the visible window reads as zero depth and would be treated as consumed. The shipped FixedSpreadStrategy quotes near the mid, inside the window, so this doesn't arise here; a strategy resting deeper (or at a smaller depth) would need the passive model to guard the visible-band boundary.

This is an honestly-documented L2-only proxy for fills, not a claim of exact exchange-match fidelity (that would require L3, order-level data).

Simulator throughput, Release build (-DCMAKE_BUILD_TYPE=Release), Apple M4 Max / macOS (Apple Silicon), single-threaded microbenchmark (benchmarks/bench_simulator.cpp) — not wire latency, see the honesty note at the top of this README:

Benchmark Result
Simulator::run (FixedSpreadStrategy, synthetic 10k-event market) ~14.9M events/s

Avellaneda–Stoikov maker (M4)

AvellanedaStoikovStrategy (engine/include/quotient/avellaneda_stoikov.hpp / engine/src/avellaneda_stoikov.cpp) is the first real Strategy implementation, replacing M3's trivial FixedSpreadStrategy with the classic Avellaneda–Stoikov (2008) inventory-skewed quoting model. Given the current mid s, inventory q, and params (σ, γ, τ, k), it computes a reservation price that shifts the quote away from the mid in the direction that reduces inventory:

r = s − q·γ·σ²·τ

and an optimal half-spread (independent of inventory) trading off inventory risk against order-arrival intensity:

δ = ½·γ·σ²·τ + (1/γ)·ln(1 + γ/k)

with the final quotes bid = round(r − δ), ask = round(r + δ). The strategy re-quotes (cancel + replace) whenever the target tick moves, and tracks its own inventory via on_fill.

Honest framing:

  • Only σ is calibrated from data. recorder/calibrate.py estimates σ as the population std-dev of successive integer-mid changes (ticks/message-step) over a reconstructed book from a real capture — σ = 1.3628 over the 991-message sample. γ, τ, k are model parameters, not calibrated — a full order-flow-intensity (k/A) fit needs trade-level data (Kraken's trade channel, arrival rates by price offset), which the recorder does not yet capture. That's explicit M5 work, not silently assumed.
  • Float math, integer book. r and δ are computed in double precision (the closed-form A-S formulas aren't naturally integer), then the final bid/ask are rounded to the nearest tick (std::llround) before touching the book — the order book, fills, and PnL all stay integer-only, per the M1 design.
  • Real sample vs. synthetic scenario. Run end-to-end (recorder/sim_driver.py's run_as_over_capture) over the real, checksum-validated 991-message Kraken sample with calibrated σ, the A-S maker produces 3 fills, inventory −1, cash 631791, mtm_pnl −135 — deterministic, but too short a sample (991 messages, one symbol) to see the inventory skew mechanism do meaningful work. The inventory-control behavior (quotes skewing away from the mid as inventory grows) is instead demonstrated with a synthetic scenario in engine/tests/test_avellaneda_stoikov.cpp. A rich PnL/adverse-selection study across longer captures and a latency sweep is M5, not this task.
  • Throughput is a microbenchmark, not wire latency. Same caveat as M3's simulator number above — single-threaded, no network/exchange round-trip modeled.

A-S maker throughput, Release build, Apple M4 Max, single-threaded microbenchmark (benchmarks/bench_avellaneda_stoikov.cpp) — Simulator::run with AvellanedaStoikovStrategy over the same synthetic 10k-event market as the M3 benchmark above:

Benchmark Result
Simulator::run (AvellanedaStoikovStrategy, synthetic 10k-event market) ~3.5M events/s

The A-S maker is slower than FixedSpreadStrategy (~14.9M events/s) because every book update now does floating-point reservation-price/spread math (including a log) instead of a fixed offset, plus more frequent cancel/replace churn from re-quoting on every tick move — still a single-threaded microbenchmark, not a claim about production or colocated-HFT latency.

Releases

Packages

Contributors

Languages