mansoor-mamnoon
limit-order-book
C++

High-performance limit order book engine with C++ core and Python SDK. Processes 20M+ msgs/sec with µs latency. Supports real crypto/equity data replay, spread/imbalance/impact analytics, and backtesting of VWAP, TWAP, POV, and market-making strategies with reproducible PnL and risk metrics.

Last updated Jul 8, 2026
70
Stars
22
Forks
3
Issues
+1
Stars/day
Attention Score
49
Language breakdown
C++ 67.1%
Python 30.9%
Dockerfile 1.4%
CMake 0.6%
Files click to expand
README

Limit Order Book (LOB) Engine — C++20

License: MIT Contributions Welcome Release Release Docker Benchmarks

A high-performance C++ matching engine that processes buy/sell orders with exchange-style semantics. Demonstrates low-latency hot-path design, cache-friendly data structures, reproducible benchmarking, and clean build/test tooling.


🧰 Tech Stack

🔤 Languages & Compilers

C++20 Python CMake GCC Clang

✅ Testing & CI/CD

Catch2 PyBind11 GitHub Actions cmocka

⚡ Performance & Profiling

perf Valgrind gprof AddressSanitizer

📂 Data & Processing

Parquet pandas NumPy

🐳 Containers & Release

Docker GHCR

📊 Visualization & Reporting

Matplotlib Streamlit Jupyter

🖥️ Systems & Infra

Linux Ubuntu macOS

⚡ Throughput: 20.7M msgs/sec 📊 Latency: p50=0.04µs, p99≈1µs ✅ Verified on real BTCUSDT BinanceUS data

🔎 Quick Highlights

  • Core engine (BookCore): limit & market orders, cancels, modifies, FIFO per price level.
  • Order flags: IOC, FOK, POST_ONLY, STP (self-trade prevention).
  • Persistence: binary snapshots (write/load) + replay tool.
  • Performance: slab memory pool, side-specialized matching (branch elimination), cache-hot best-level pointers, -fno-exceptions -fno-rtti.
  • Tooling: benchmark tool (percentiles + histogram CSVs), Catch2 unit tests, profiling toggle (-fno-omit-frame-pointer -g).

🧭 Architecture

Engine flow

+-------------------+          +----------------+ |  Incoming Orders  |  ----->  |    BookCore    | +-------------------+          |  (match/rest)  |                                +--------+-------+                                         |                                         v +--------------------+          +-------------------+ | PriceLevels (B/A)  |<-------->|   LevelFIFO(s)    | | best_bid/ask +     |          |  intrusive queues | | bestlevelptr     |          +-------------------+ +-------------------+                   |                                         v                                +-------------------+                                | Logger / Snapshot |                                |  (events, trades) |                                +-------------------+

Data layout

Bids ladder (higher is better)        Asks ladder (lower is better) bestbid --> [px=100][FIFO] -> ...    bestask --> [px=101][FIFO] -> ...

LevelFIFO (intrusive): head <-> node <-> node <-> ... <-> tail (FIFO fairness, O(1) ops)

Memory pool (slab allocator)

+------------------------- 1 MiB slab -------------------------+ | [OrderNode][OrderNode][OrderNode] ... [OrderNode]            | +--------------------------------------------------------------+                       ^ free list (O(1) alloc/free)


🗂️ Repository Layout

cpp/
  include/lob/
    book_core.hpp        # engine API & hot-path helpers
    price_levels.hpp     # ladders: contiguous & sparse implementations
    types.hpp            # Tick, Quantity, IDs, flags, enums
    logging.hpp          # snapshot writer/loader, event logger interface
    mempool.hpp          # slab allocator for OrderNode
  src/
    book_core.cpp        # engine implementation (side-specialized matching)
    price_levels.cpp     # TU for headers (keeps targets happy)
    logging.cpp          # snapshot I/O + logger implementation
    util.cpp             # placeholder TU for lob_util
  tools/
    bench.cpp            # synthetic benchmark -> CSV + histogram
    replay.cpp           # snapshot replay tool
  CMakeLists.txt         # inner build (library + tools + tests)
docs/
  bench.md               # benchmark method + sample results
  bench.csv              # percentiles output (generated by bench_tool)
  hist.csv               # latency histogram 0–100µs (generated)
python/
  olob/bindings.cpp     # pybind11 module (target: lobcpp -> _lob)
CMakeLists.txt           # outer build (FetchContent Catch2; drives inner)

🛠️ Build & Run

Configure & build (Release)

rm -rf build cmake -S . -B build -DCMAKEBUILDTYPE=Release \       -DLOBBUILDTESTS=ON -DLOB_PROFILING=ON cmake --build build -j

CMake options

  • LOBBUILDTESTS (ON/OFF): build Catch2 tests.
  • LOB_PROFILING (ON/OFF): add -fno-omit-frame-pointer -g for clean profiler stacks.
  • LOBENABLEASAN (Debug only): AddressSanitizer for tests/tools.
  • LOB_LTO (Release only): optional -flto.
Unit tests
ctest --test-dir build --output-on-failure

Benchmark (CSV + histogram)

./build/cpp/bench_tool --msgs 2000000 --warmup 50000 \   --out-csv docs/bench.csv --hist docs/hist.csv
Example output:
msgs=2000000, time=0.156s, rate=12854458.3 msgs/s latency_us: p50=0.04 p90=0.08 p99=0.08 p99.9=0.12
See docs/bench.md, docs/bench.csv, and docs/hist.csv for reproducible results.

Replay from snapshot

./build/cpp/replay_tool <snapshot.bin>


📚 Engine API (Essentials)

Types (include/lob/types.hpp)

  • Side { Bid, Ask }, Tick (price), Quantity, OrderId, UserId, Timestamp, SeqNo.
  • Flags: IOC, FOK, POST_ONLY, STP.
Orders / results
  • NewOrder { seq, ts, id, user, side, price, qty, flags }.
  • ModifyOrder { seq, ts, id, newprice, newqty, flags }.
  • ExecResult { filled, remaining }.
BookCore (include/lob/book_core.hpp)
  • ExecResult submit_limit(const NewOrder&).
  • ExecResult submit_market(const NewOrder&).
  • bool cancel(OrderId id).
  • ExecResult modify(const ModifyOrder&).
Ladders (include/lob/price_levels.hpp)
  • PriceLevelsContig(PriceBand) — contiguous array for bounded tick ranges.
  • PriceLevelsSparseunordered_map<Tick, LevelFIFO> for unbounded ranges.
  • Both expose bestbid()/bestask() and cache-hot bestlevelptr(Side).
Snapshots & logging (include/lob/logging.hpp, src/logging.cpp)
  • SnapshotWriter::write_snapshot(...).
  • loadsnapshotfile(...).
  • IEventLogger + JsonlBinLogger (jsonl + binary events/trades; optional snapshots).

⚙️ Design & Performance Choices

  • Slab allocator (arena)
O(1) alloc/free for hot-path order nodes. Snapshot-loaded nodes tagged for safe deletion.
  • Branch elimination
Side-specialized templates eliminate per-iteration if (side).
  • Cache-hot top-of-book
Direct pointer to best level reduces cache misses.
  • Lean binary
Compiled with -fno-exceptions -fno-rtti -O3 -march=native.
  • Deterministic FIFO
Intrusive list ensures strict arrival order.
  • Reproducibility
Benchmarks emit percentiles + histograms into CSVs.

🧪 Minimal Integration (C++)

using namespace lob;
PriceLevelsSparse bids, asks;
BookCore book(bids, asks, /logger/nullptr);

NewOrder o{1, 0, 42, 7, Side::Bid, 1000, 10, 0}; auto r1 = book.submit_limit(o); // may trade or rest at 1000 auto ok = book.cancel(42); // cancel by ID


🔬 Profiling

Linux (perf)

perf stat -d ./build/cpp/bench_tool --msgs 2000000 perf record -g -- ./build/cpp/bench_tool --msgs 2000000 perf report

macOS (Instruments) Use Time Profiler with frame pointers (-DLOB_PROFILING=ON).


🌐 Crypto Data Connector

A Python CLI ships alongside the C++ engine to capture and normalize live exchange data.

Capture raw Binance US data

lob crypto-capture --exchange binanceus --symbol BTCUSDT \   --minutes 2 --raw-dir raw --snapshot-every-sec 60
  • Connects to Binance US WebSocket streams (diffDepth, trade).
  • Pulls a REST snapshot every N seconds (--snapshot-every-sec).
  • Persists gzipped JSONL to:
raw/YYYY-MM-DD/<exchange>/<symbol>/…

Normalize into Parquet

lob normalize --exchange binanceus --date $(date -u +%F) \   --symbol BTCUSDT --raw-dir raw --out-dir parquet
Produces:
parquet/YYYY-MM-DD/binanceus/BTCUSDT/events.parquet

Schema

  • ts → event timestamp (ns, UTC)
  • side"B" (bid) or "A" (ask)
  • price → price level
  • qty → size traded or resting
  • type"book" (order book update) or "trade"
Inspect with pandas
import pandas as pd df = pd.read_parquet("parquet/2025-08-24/binanceus/BTCUSDT/events.parquet") print(df.head()) print(df.dtypes) print(len(df))


✅ Real Capture Example (BTCUSDT, Binance US)

I ran a full 1-hour capture of BTCUSDT from Binance US and normalized it:

lob crypto-capture --exchange binanceus --symbol BTCUSDT \
  --minutes 60 --raw-dir raw --snapshot-every-sec 60 && \
lob normalize --exchange binanceus --date $(date -u +%F) \
  --symbol BTCUSDT --raw-dir raw --out-dir parquet

This produced a normalized Parquet dataset at:

parquet/YYYY-MM-DD/binanceus/BTCUSDT/events.parquet

First rows (pandas)

import pandas as pd df = pd.read_parquet("parquet/2025-08-24/binanceus/BTCUSDT/events.parquet") print(df.head()) print(df.dtypes) print("Total rows:", len(df))

Sample output:

ts side     price     qty   type 0 2025-08-24 10:00:00.123   B  63821.45   0.002   book 1 2025-08-24 10:00:00.456   A  63822.10   0.004   book 2 2025-08-24 10:00:00.789   B  63820.50   0.010   trade ... Total rows: 3,512,947

Quick visualization (best bid/ask over time) I generated a simple chart from the Parquet file:

python docs/makedepthchart.py \   --parquet parquet/2025-08-24/binanceus/BTCUSDT/events.parquet \   --out docs/depth_chart.png
Depth chart

Note: This chart approximates best bid/ask by forward-filling incremental book updates. It demonstrates that live capture & normalization worked end-to-end.


📖 Order Book Reconstruction & Validation

What it does

  • Starts from a full exchange REST snapshot.
  • Applies WebSocket depth updates (diffs) in strict sequence to rebuild the live Level-2 book.
  • Periodically resyncs from later snapshots when gaps are detected.
  • Computes a top-N checksum and records best bid/ask per step.

Why it matters

  • Produces a deterministic, gap-aware view of the L2 book.
  • Handles out-of-order updates, drops, and partial feeds.
  • Enables objective quality checks via tick-level drift vs. the exchange’s own snapshot.

🔧 Usage

Capture (example, Binance US)

lob crypto-capture --exchange binanceus --symbol BTCUSDT \   --minutes 35 --raw-dir raw --snapshot-every-sec 60

Reconstruct

python -m orderbook_tools.reconstruct \   --raw-dir raw --date YYYY-MM-DD --exchange binanceus --symbol BTCUSDT \   --out-dir recon --tick-size 0.01 \   --snap-glob "depth/snapshot-.json.gz" \   --diff-glob "depth/diffs-.jsonl.gz"

Validate (30-minute check)

python -m orderbook_tools.validate \   --raw-dir raw --recon-dir recon \   --date YYYY-MM-DD --exchange binanceus --symbol BTCUSDT \   --tick-size 0.01 --window-min 30 \   --snap-glob "depth/snapshot-.json.gz"

▶️ Replay Engine (Real-Time & N× Accelerated)

What it does

  • Replays normalized market events into the C++ BookCore, preserving inter‑arrival gaps with a speed control (e.g., 1x, 10x, 50x).
  • Samples TAQ‑like quotes on a fixed time grid (e.g., every 50 ms): best bid/ask, mid, spread, microprice.
  • Records trades as event‑driven prints.
  • Writes outputs to CSV (and optionally Parquet via a tiny Python helper).
Why it matters
  • Produces deterministic, monotonic time series for research & backtests.
  • Enables fast‑forward processing for large captures.
  • Exercises the actual C++ book under realistic feeds.
Usage
# Convert your normalized Parquet to CSV with required columns python - <<'EOF' import pandas as pd df = pd.read_parquet("parquet/YYYY-MM-DD/<exchange>/<symbol>/events.parquet") if 'ts_ns' not in df.columns:   df['tsns'] = pd.todatetime(df['ts'], utc=True).view('int64') df['type'] = df['type'].astype(str).str.lower() df['side'] = df['side'].astype(str).str.lower().map({'b':'B','bid':'B','buy':'B','a':'A','ask':'A','sell':'A','s':'A'}).fillna('A') df[['tsns','type','side','price','qty']].tocsv("parquet_export.csv", index=False) EOF

Replay at 50×, sampling quotes every 50 ms

./build/cpp/replay_tool \ --file parquet_export.csv \ --speed 50x \ --cadence-ms 50 \ --quotes-out taq_quotes.csv \ --trades-out taq_trades.csv

Optional: convert TAQ CSVs to Parquet

python python/csvtoparquet.py --in taqquotes.csv --out taqquotes.parquet python python/csvtoparquet.py --in taqtrades.csv --out taqtrades.parquet

📊 Market Analytics

This module computes microstructure metrics from replayed TAQ quotes and reconstructed depth:

  • Spread: ask − bid
  • Microprice: imbalance-aware mid = (bidpx·asksz + askpx·bidsz) / (bidsz + asksz)
  • Imbalance: bid volume ÷ (bid volume + ask volume)
  • Depth (L1–L10): cumulative quantities at the top 10 bid/ask levels

Usage

python -m olob.metrics \
  --quotes taq_quotes.csv \
  --depth-top10 recon/2025-08-25/binanceus/BTCUSDT/top10_depth.parquet \
  --out-json   analytics/summary.json \
  --plots-out  analytics/plots
  • analytics/summary.json: time-weighted averages and percentiles.
  • analytics/plots/: saved PNG charts (examples below).

📈 Example Outputs

Spread over time Spread over time

Mid vs Microprice Mid vs Microprice

Best-level imbalance (L1) Best-level imbalance

Top-10 Bid Depth Bid depth L1–L10

Top-10 Ask Depth Ask depth L1–L10


📊 Microstructure Analytics (volatility, impact, flow, imbalance, clustering)

This repository includes a Python module that computes microstructure metrics from TAQ-style quotes/trades (and optional depth), and produces reproducible figures + a JSON summary.


🔍 What it computes

  • Realized volatility (Parkinson & Garman–Klass) on mid-price (best bid/ask).
  • Impact curves: average future mid move (bp) vs trade size buckets (notional deciles & size percentiles), across horizons (e.g., 0.5s, 1s).
  • Order-flow autocorrelation from signed trades.
  • Short-horizon drift vs L1 imbalance (decile bins).
  • Clustering of impact-curve shapes (k-means) to reveal execution regimes.

⚙️ Key design choices

  • Robust time joins via merge_asof + uniform drift grid (shift(-k) makes future-mid trivial).
  • Schema flexibility: supports both wide and tidy depth; falls back to quote sizes when depth doesn’t overlap.
  • Timestamp normalization (s/ms/µs → ns) and coverage extension to avoid empty joins.
  • Outputs: PNGs and a JSON summary for downstream analysis or reporting.

▶️ Usage (example)

python -m olob.microstructure \
  --quotes taq_quotes.csv \
  --trades taq_trades.csv \
  --depth-top10 recon/YYYY-MM-DD/<exchange>/<symbol>/top10_depth.parquet \
  --plots-out analytics/plots \
  --out-json analytics/microstructure_summary.json \
  --bar-sec 60 --rv-window 30 \
  --impact-horizons-ms 500,1000 \
  --autocorr-max-lag 50 \
  --drift-grid-ms 1000 \
  --debug-out analytics/debug

📦 Generated artifacts

Figures (PNG)

  • analytics/plots/vol.png — Annualized Parkinson & Garman–Klass on mid.
  • analytics/plots/impact.png — Impact curves: future mid (bp) vs size buckets.
  • analytics/plots/oflow_autocorr.png — Signed trade autocorrelation by lag.
  • analytics/plots/driftvsimbalance.png — Future drift (bp) by L1 imbalance decile.
  • analytics/plots/impact_clusters.png — Cluster centroids of impact-curve shapes.
Summary (JSON)
  • analytics/microstructure_summary.json

📈 Diagrams

Realized Volatility Impact Curves Order-Flow Autocorr Drift vs Imbalance Impact Clusters


📑 HTML Report Generator

A single command produces a self-contained HTML report with plots and stats from captured market data.


🔧 Usage

lob analyze --exchange binanceus --symbol BTCUSDT \
  --date 2025-08-25 --hour-start 03:00

📋 What it does

  • Slices a 1-hour window from normalized Parquet data.
  • Replays events through the native replay_tool to generate TAQ-style quotes & trades.
  • Runs analytics: spread, microprice, imbalance, depth.
  • Runs microstructure metrics: realized volatility, impact curves, order-flow autocorr, imbalance drift, clustering.
  • Emits a single HTML file with embedded PNGs + JSON stats:
out/reports/2025-08-25_BTCUSDT.html

📈 Sample Output (report sections)

  • Spread over time
  • Mid vs Microprice
  • Best-level imbalance (L1)
  • Depth (bid/ask top-10)
  • Realized Volatility
  • Impact Curves
  • Order-Flow Autocorrelation
  • Drift vs Imbalance
  • Impact Clusters

🌐 Portability

The HTML report is fully self-contained — just open it in any browser, no external files needed.

✅ Performance & Analytics

I validated the engine on both synthetic and real exchange data.


🧪 Synthetic Benchmark

./build/cpp/bench_tool \
  --msgs 8000000 --warmup 200000 \
  --band 1000000:1000064:1 \
  --latency-sample 0

Throughput

msgs=8000000, time=0.386s, rate=20714662.3 msgs/s

./build/cpp/bench_tool \
  --msgs 8000000 --warmup 200000 \
  --band 1000000:1000064:1 \
  --latency-sample 1024

Latency (sample_every=1024)

p50=0.042 µs, p90=0.083 µs, p99≈1.0 µs, p99.9≈96 µs, p99.99≈176 µs


📊 Real-Data Replay & Analysis

lob analyze --exchange binanceus --symbol BTCUSDT \
  --date 2025-08-25 --hour-start 03:00

Output (excerpt)

[analytics] wrote out/tmpreport/analytics/summary.json and plots -> out/tmpreport/analytics/plots [cadence] quotes median Δ=50.0 ms, p90 Δ=50.0 ms, rows=44258 ... [ok] wrote clustering figure impact_clusters.png with k=3 [ok] Wrote: out/tmpreport/analytics/plots/vol.png, impact.png, oflowautocorr.png, driftvsimbalance.png [ok] Summary JSON: out/tmpreport/analytics/microstructuresummary.json [report] wrote out/reports/2025-08-25_BTCUSDT.html

Artifacts include

  • out/reports/2025-08-25_BTCUSDT.html — self-contained HTML report.
  • Plots: vol.png, impact.png, oflowautocorr.png, driftvsimbalance.png, impactclusters.png.
  • JSON summaries under out/tmp_report/analytics/.

🏁 Result

  • Synthetic throughput: >20M msgs/sec (≥3M Gate passed).
  • Real-data replay: Clean report with volatility, impact curves, autocorr, drift, and clustering.

📈 Strategy Backtesting (VWAP/TWAP)

The repository includes a lightweight strategy API and backtester for parent-order execution.


🔧 What it provides

  • Strategy interface with callbacks:
- on_tick — per-quote updates. - on_bar — bar-close scheduling. - on_fill — feedback on executed clips.
  • Schedulers:
- TWAP — evenly slices parent order across bars. - VWAP — weights slices by traded volume per bar (falls back to TWAP if trades missing).
  • Execution controls:
- min_clip — smallest child order size. - cooldown_ms — minimum delay between clips. - force_taker — flag to choose market vs passive execution.
  • Cost model:
- Tick/lot rounding. - Fixed latency from decision to arrival. - Fees/rebates (bps).

📤 Outputs

The CLI produces:
  • *_fills.csv — detailed child fills (ts, px, qty, bar).
  • *summary.json — aggregate stats (filledqty, avgpx, notional, fees, signedcost, params).

▶️ Usage

lob backtest \
  --strategy docs/strategy/vwap.yaml \
  --quotes taq_quotes.csv \
  --trades taq_trades.csv \
  --out out/backtests/vwap_run

Example summary

{   "filled_qty": 1.67,   "avg_px": 113060.98,   "notional": 188427.42,   "fees": 37.69,   "signed_cost": 188465.11 }

📊 PnL & Risk Metrics + Reproducibility

The backtester now produces PnL and risk statistics alongside fills:

  • Realized / Unrealized PnL
  • Mark-to-mid equity curve
  • Max drawdown
  • Turnover ratio
  • Sharpe-like metric (from 1s equity returns)

📦 Outputs per run

  • *_fills.csv — executed child orders.
  • *_summary.json — execution summary.
  • pnl_timeseries.csv — time series of cash, inventory, equity.
  • risk_summary.json — aggregated PnL & risk stats.
  • checksums.sha256.json — deterministic hash over all artifacts.

▶️ Example run

lob backtest \
  --strategy docs/strategy/vwap.yaml \
  --quotes taq_quotes.csv \
  --trades taq_trades.csv \
  --out out/backtests/vwap_run \
  --seed 123

Produces

[fills]    out/backtests/vwaprun/vwapbtcusdt1hfills.csv [summary]  out/backtests/vwaprun/vwapbtcusdt1hsummary.json [risk]     out/backtests/vwaprun/pnltimeseries.csv, risk_summary.json [checksum] out/backtests/vwap_run/checksums.sha256.json


📑 Results

out/backtests/vwaprun/risksummary.json
{
  "final_inventory": 0.0,
  "avg_cost": 113061.0,
  "last_mid": 113062.5,
  "pnl_realized": 12.34,
  "pnl_unrealized": -1.56,
  "pnl_total": 10.78,
  "max_drawdown": -5.42,
  "turnover": 1.98,
  "sharpe_like": 1.12,
  "fee_bps": 2.0,
  "rows_equity": 22,
  "rows_fills": 5
}

🔄 Reproducibility

Two identical runs with the same seed produce identical checksums:
jq -S . out/backtests/vwap_run/checksums.sha256.json > /tmp/a.json
jq -S . out/backtests/vwap_run2/checksums.sha256.json > /tmp/b.json
diff /tmp/a.json /tmp/b.json  # no output -> identical ✅

✅ Strategy Comparison (VWAP / TWAP / POV / Iceberg)

We validated the execution engine by running four distinct scheduling strategies (TWAP, VWAP, POV, Iceberg) over the same 1-hour BTCUSDT window captured from Binance US.

This ensures apples-to-apples comparison under identical market conditions.

📊 Results

| strategy | filledqty | avgpx | notional | fees | signedcost | pnltotal | maxdrawdown | turnover | sharpelike | |----------|------------|-------------|--------------|---------|-------------|---------------|--------------|----------|-------------| | twap | 5.1000 | 110506.59 | 563583.62 | 112.72 | 563696.34 | -555095.27 | 546299.92 | 1.0 | 350.93 | | vwap | 5.1000 | 110499.67 | 563548.30 | 112.71 | 563661.01 | -555089.00 | 554420.84 | 1.0 | 498.22 | | pov | 0.9925 | 110444.31 | 109615.98 | 21.92 | 109637.91 | -57830.99 | 56724.40 | 1.0 | 334.48 | | iceberg | 5.0999 | 110493.91 | 563507.91 | 112.70 | 563620.61 | -553977.94 | 478685.39 | 1.0 | 202.18 |

  • TWAP / VWAP: fully filled target 5 BTC parent order.
  • POV: under-filled (~0.99 BTC) due to limited observed market volume vs target %.
  • Iceberg: successfully replenished hidden slices to achieve ~5 BTC filled.
Artifacts per run include:
  • *_fills.csv — all child orders executed
  • *_summary.json — structured execution report
  • pnltimeseries.csv, risksummary.json — equity + risk stats
  • checkums.sha256.json — deterministic reproducibility
This proves end-to-end functionality of the strategy API, cost model, and queue-aware execution loop.

🔄 Parameter Sweeps & Parallel Backtests

I implemented a parallel sweep that runs a grid of backtests, aggregates results, ranks by a risk-adjusted metric, and saves charts + CSVs for reproducibility.

Acceptance window (UTC): 2025-08-26 08:07:03 → 09:07:01 Grid: parentqty ∈ {2,5}, minclip ∈ {0.05,0.1}, cooldown_ms ∈ {0,250}, side=buy, seed=1 Artifacts: per-run JSON/CSVs + aggregate.csv, best.json, ranking.png, and the plots below.


✅ Results (summary)

[ok] aggregate -> out/sweeps/acceptance/aggregate.csv
[ok] ranking   -> out/sweeps/acceptance/ranking.png
[ok] best      -> out/sweeps/acceptance/best.json

📈 Evidence (charts)

1) Sweep ranking (top-K) Sweep ranking

2) Risk vs Return (all configs) Risk scatter

3) Equity curve (best run) Equity curve

4) PnL timeseries (best run) PnL timeseries

💡 If I want to embed a specific run’s figures, I can use its exact slugged folder name (from best.json or ls out/sweeps/acceptance/*/) and point to any PNGs generated inside that folder.


⚡ Optional: Auto-generate plots at sweep end

If I want the sweep itself to emit the plots above automatically, I can add this to the end of run_sweep() in python/olob/sweep.py (right after writing aggregate.csv / best.json):
# Auto-plots for README evidence
try:
    from . import makereadmefigs as _figs
    figs.riskscatter(aggcsv, outroot / "plots" / "riskscatter.png")
    if (out_root / "best.json").exists():
        best = json.loads((outroot / "best.json").readtext())
        bestdir = Path(best["rundir"])
        (outroot / "plots").mkdir(parents=True, existok=True)
        figs.equitycurve(bestdir, outroot / "plots" / "equitycurve.png")
        figs.pnltimeseries(bestdir, outroot / "plots" / "pnltimeseries.png")
except Exception as e:
    print(f"[warn] could not auto-generate README plots: {e}")

🔍 One-liners to verify README file references

# These are the files my README links to
ls -l out/sweeps/acceptance/ranking.png
ls -l out/sweeps/acceptance/plots/risk_scatter.png
ls -l out/sweeps/acceptance/plots/equity_curve.png
ls -l out/sweeps/acceptance/plots/pnl_timeseries.png

If any of these don’t exist, I can regenerate them with:

python -m olob.makereadmefigs --sweep-dir out/sweeps/acceptance

🎥 Minimal Live Visualization

A major upgrade to this project is the live visualization layer. Instead of inspecting only static CSVs, I can now see the market and strategy evolve in real time. This bridges raw data with intuition — a key requirement for understanding trading algorithms.


▶️ Running the Streamlit App

I can launch the interactive dashboard locally with:

streamlit run app.py

The app provides:

  • Price panels: midprice, microprice, and spread replayed tick by tick.
  • PnL & Inventory panel: equity, cash, and position side-by-side.
  • Optional depth heatmap: liquidity across the top-10 bid/ask levels.
  • Playback controls:
- ⏯ Play / Pause - 🔄 Speed multipliers (1× / 10× / 100×) - 🪟 Visible window (10–300 sec) - 📊 Forward-fill & resample smoothing

📺 Example Interface

Here is a screenshot of the Streamlit app layout and controls: Streamlit UI


🎬 Demo Playback (GIF Evidence)

For readers who can’t run Streamlit, here’s a 60-second replay GIF generated directly from captured quotes. It shows how the midprice and spread move over time: Replay Demo


🌟 Why This Matters

  • Visual validation: makes it easy to spot how strategies interact with the order book.
  • Engaging for reviewers: GIF evidence lives in the repo; app can be launched in one command.
  • Bridges quant + intuition: more compelling than raw CSVs or tables alone.

📸 Snapshot + Mid-File Replay Proof (LOB)

This section documents how to reproduce and prove that taking a snapshot at a cut timestamp + resuming replay from the mid-file tail produces the same fills and economics as a single-pass replay.

It also explains the artifacts in out/snapshot_proof/ so contributors know what each file means.


🔨 Build

cmake -S cpp -B build/cpp -DCMAKEBUILDTYPE=Release
cmake --build build/cpp -j

This produces build/cpp/tools/replay_tool with snapshot options:

  • --snapshot-at-ns <CUTNS> – dump a snapshot once replay time ≥ CUTNS
  • --snapshot-out <FILE> – write snapshot to this file
  • --snapshot-in <FILE> – resume replay from a snapshot
  • --quotes-out <CSV> – emit L1 TAQ quotes (tsns,bidpx,bidqty,askpx,ask_qty)
  • --trades-out <BIN> – emit trades binary (optional debug)
Note: bidpx/askpx are ticks. Multiply by tick size if downstream code expects prices.

▶️ Run the Proof

If you have Parquet, convert once to CSV:

python - <<'PY'
import pandas as pd, pathlib
p = pathlib.Path("parquet/2025-08-25/binanceus/BTCUSDT/events.parquet")
df = pd.read_parquet(p)
df.tocsv(p.withsuffix(".csv"), index=False)
print("Wrote", p.with_suffix(".csv"))
PY

Then run the CLI:

lob snapshot-proof \
  --events parquet/2025-08-25/binanceus/BTCUSDT/events.csv \
  --cut-ns 1724544000000000000 \
  --out out/snapshot_proof \
  --strategy docs/strategy/twap.yaml

🔍 What Happens

  • Pass A: Replay from start → when tsns >= CUTNS, dump snapshot.bin.
Continue emitting L1 quotes to quotes_A.csv.
  • Tail: Extract all events where tsns >= CUTNSevents_tail.csv.
  • Pass B: Replay the tail starting from snapshot.bin → emit quotes_B.csv.
  • Backtest (optional): If --strategy is provided, run backtests on A and B and compare.

📂 Artifacts (out/snapshot_proof/)

  • snapshot.bin — the saved snapshot at the cut; consumed by --snapshot-in.
  • .snap_tmp/ — internal temp snapshot files (inspectable, can be deleted).
  • eventstail.csv — tail slice (tsns >= CUT_NS).
  • quotes_A.csv — L1 TAQ from single-pass replay.
  • quotes_B.csv — L1 TAQ from resume replay.
  • tradesA.bin, tradesB.bin — raw trade binaries (debug / analysis).
  • equivalence.json — when backtesting, compares A vs B fills:
{
    "ok": true,
    "message": "strict equality on shared columns",
    "fillsA": "out/snapshotproof/bt/A/twap_fills.csv",
    "fillsB": "out/snapshotproof/bt/B/twap_fills.csv"
  }

📊 Backtests (out/snapshot_proof/bt/)

  • A/ — results on single-pass quotes.
- twap_fills.csv — order-level fills used in equality checks. - twap_summary.json — slippage, avg cost, etc. - risk_summary.json — risk metrics (if enabled). - pnl_timeseries.csv — equity/PnL time series. - checksums.sha256.json — integrity hashes.
  • B/ — results on snapshot+resume quotes (same schema as A).
Pass criteria: A vs B fills and econ are identical (equivalence.json: ok=true). If not, CLI exits non-zero with pointers to A/B artifacts.

⚙️ Direct replay_tool Usage (Optional)

Pass A:

build/cpp/tools/replay_tool \   --file parquet/2025-08-25/binanceus/BTCUSDT/events.csv \   --snapshot-at-ns 1724544000000000000 \   --snapshot-out out/snapshot_proof/snapshot.bin \   --quotes-out  out/snapshotproof/quotesA.csv \   --trades-out out/snapshotproof/tradesA.bin

Create tail (pandas filter): eventstail.csv with tsns >= CUT_NS.

Pass B:

build/cpp/tools/replay_tool \   --file out/snapshotproof/eventstail.csv \   --snapshot-in out/snapshot_proof/snapshot.bin \   --quotes-out  out/snapshotproof/quotesB.csv \   --trades-out out/snapshotproof/tradesB.bin


📊 Snapshot Proof Diagrams

After running:

lob snapshot-proof \
  --events parquet/2025-08-25/binanceus/BTCUSDT/events.csv \
  --cut-ns 1724544000000000000 \
  --out out/snapshot_proof \
  --strategy docs/strategy/twap.yaml

generate diagrams (use equity as PnL)

python makeday20charts.py --root out/snapshot_proof --pnl-val equity

the following three diagrams are produced in out/snapshot_proof/:

  • Top-of-book A vs B
Compares bid/ask evolution from single-pass (A) vs snapshot+resume (B). Top-of-book A vs B
  • Cumulative fills A vs B
Tracks cumulative filled quantity over time; both lines should overlap exactly. Cumulative fills A vs B
  • PnL timeseries A vs B (equity)
Uses the equity column (cash + inventory value) as the PnL measure. PnL timeseries A vs B
✅ When all three charts overlap between A and B, this demonstrates that “snapshot at cut + mid-file replay” reproduces the single-pass replay.

🧱 Containerized Analytics + GHCR Release + Report Generation

I now ship a reproducible, containerized analytics pipeline with GitHub Actions publishing to GHCR on version tags. This section explains how to build, release, run, and verify the outputs.


1) 🐳 Docker Image (local build)

A Dockerfile lives at repo root and builds a two-stage image:

  • Builder: compiles C++ tools (replaytool) and pybind11 lob.so against Python 3.11 (ABI aligned).
  • Runtime: ships lob CLI, scientific Python stack, and C++ artifacts.
Build locally:
docker build -t lob:v1.0 .

Sanity check:

docker run --rm lob:v1.0 --help | head -n 5

Expect:

Usage: python -m olob.cli [OPTIONS] COMMAND [ARGS]...

LOB utilities


2) 🚀 GitHub Actions (automatic release to GHCR)

A workflow at .github/workflows/release.yml:

  • Triggers on tags matching v* (e.g., v1.0, v1.1.0).
  • Builds the Docker image using Buildx.
  • Pushes to GHCR as:
- ghcr.io/<OWNER>/<REPO>:<tag> - ghcr.io/<OWNER>/<REPO>:latest
  • Creates a GitHub Release for the same tag (with auto notes).
Tag & push to trigger:
git tag v1.0
git push origin v1.0

Verify in GitHub:

  • Actions tab → release workflow runs green.
  • Packages → image appears as ghcr.io/<OWNER>/<REPO>.
  • Releases → new v1.0 release with notes.
(Optional): Make the GHCR package Public via GitHub → Packages → Settings.


3) 📑 One-Command HTML Report (Evidence)

I can generate a self-contained HTML report for any date/hour window using normalized parquet.

Expected host layout:

parquet/2025-08-25/binanceus/BTCUSDT/events.parquet recon/2025-08-25/binanceus/BTCUSDT/top10_depth.parquet   # optional (enables depth charts) out/                                                     # will hold results

Run (local Docker build):

docker run --rm \   -v "$PWD/parquet:/data/parquet:ro" \   -v "$PWD/recon:/data/recon:ro" \   -v "$PWD/out:/out" \   lob:v1.0 analyze \   --exchange binanceus --symbol BTCUSDT \   --date 2025-08-25 --hour-start 03:00 \   --parquet-dir /data/parquet \   --out-reports /out \   --depth-top10 /data/recon/2025-08-25/binanceus/BTCUSDT/top10_depth.parquet \   --tmp /out/tmp_report

Run (GHCR image after tagging):

docker run --rm \   -v "$PWD/parquet:/data/parquet:ro" \   -v "$PWD/recon:/data/recon:ro" \   -v "$PWD/out:/out" \   ghcr.io/<OWNER>/<REPO>:v1.0 analyze \   --exchange binanceus --symbol BTCUSDT \   --date 2025-08-25 --hour-start 03:00 \   --parquet-dir /data/parquet \   --out-reports /out \   --depth-top10 /data/recon/2025-08-25/binanceus/BTCUSDT/top10_depth.parquet \   --tmp /out/tmp_report


📦 What Gets Produced (Proof Artifacts)

Final Report HTML

out/2025-08-25_BTCUSDT.html 

or out/reports/2025-08-25_BTCUSDT.html depending on CLI version

Contains:

  • Spread over time.
  • Mid vs Microprice.
  • Best-level imbalance (L1).
  • Top-10 Bid/Ask Depth (if --depth-top10 provided).
  • Microstructure (if enabled): realized volatility, impact curves, order-flow autocorr, drift vs imbalance, impact clusters.
  • Embedded JSON summary for reproducibility.
Intermediates (kept via --tmp):
out/tmp_report/   taq_quotes.csv   taq_trades.csv   analytics/     plots/       spread.png       mid_microprice.png       imbalance_L1.png       depth_bid.png           # present if --depth-top10 provided       depth_ask.png           # present if --depth-top10 provided       vol.png                 # microstructure enabled       impact.png              # microstructure enabled       oflow_autocorr.png      # microstructure enabled       driftvsimbalance.png  # microstructure enabled       impact_clusters.png     # microstructure enabled     summary.json


✅ Verify (Copy/Paste)

# Confirm HTML report exists
ls -l out | grep -E 'BTCUSDT.*\.html' || true

Confirm quotes/trades were produced

head -n 3 out/tmpreport/taqquotes.csv head -n 3 out/tmpreport/taqtrades.csv

Confirm analytics plots exist

ls -l out/tmp_report/analytics/plots

Confirm JSON summary

cat out/tmp_report/analytics/summary.json | head -n 50

Expected columns (quotes CSV):

tsns,bidpx,bidqty,askpx,ask_qty 

(metrics layer computes spread/mid/microprice and sanitizes invalid rows)


🛠️ Troubleshooting (Fast)

  • “Parquet not found” → ensure you run docker run from repo root and mount the correct host path into /data/parquet.
docker run --rm -v "$PWD/parquet:/data/parquet:ro" lob:v1.0 ls -R /data/parquet | head -50
  • Missing depth charts → provide --depth-top10 parquet (from reconstruction step) and mount /data/recon.
  • Microstructure plots missing → check logs in out/tmp_report/analytics/; ensure scikit-learn is present (it is, in the image).

🎯 Summary

  • Low-latency hot path: arenas, branch minimization, cache locality.
  • Exchange semantics: FIFO fairness, flags (IOC/FOK/POST_ONLY/STP), cancel/modify.
  • Measurement discipline: benchmarks with CSV artifacts and reproducible docs.
  • Practical integration: replayable snapshots and a Python data connector with real exchange capture.
🔗 More in this category

© 2026 GitRepoTrend · mansoor-mamnoon/limit-order-book · Updated daily from GitHub