nanobook
n*10⁻⁹ — A deterministic, nanosecond-precision limit order book and matching engine for testing trading algorithms.
What Is This?
A simulated stock exchange that processes orders exactly like a real exchange — with proper price-time priority, partial fills, and cancellations.
nanobook combines three layers: a LOB matching engine (v0.1–v0.2) for deterministic order matching, a portfolio simulator (v0.3–v0.4) for position tracking and strategy backtesting, and Python bindings (v0.4) for seamless integration with data science workflows. Each layer works independently or together.
Who Should Use This?
| If you're a... | Use this for... |
|---|---|
| Quant developer | Backtesting trading strategies with realistic market microstructure |
| Algo trader | Testing order execution logic (slippage, queue position, fill rates) |
| Student | Learning how exchanges actually work under the hood |
| Rust developer | Reference implementation of a financial data structure |
Why This Library?
- Deterministic — Same inputs always produce same outputs (essential for reproducible backtests)
- Fast — 8M+ orders/sec single-threaded, sub-microsecond latency
- Complete — GTC/IOC/FOK, partial fills, modify, cancel, L1/L2/L3 snapshots
- Multi-symbol —
MultiExchangefor independent per-symbol order books - Portfolio engine — Position tracking, cost modeling, rebalancing, financial metrics (Sharpe, Sortino, max drawdown)
- Trailing stops — Fixed, percentage, and ATR-based adaptive trailing
- Python bindings — PyO3 wrappers for Exchange, Portfolio, sweep (
pip install nanobook) - Simple — Single-threaded, in-process, minimal dependencies
Why Not Something Else?
| Framework | Problem |
|---|---|
| VectorBT | Per-asset signals, no cross-sectional ranking |
| Zipline | Abandoned, bundle pain |
| Backtrader | Slow (pure Python), verbose |
| HFTBacktest | Crypto tick-level, wrong abstraction |
| Nautilus Trader | Enterprise complexity |
nanobook's niche: lightweight, factor-strategy-optimized portfolio simulator with an optional LOB execution layer. Lean (3 deps), fast (8M+ orders/sec), Python-friendly.
Architecture
┌─────────────────────────────────────────────────────────┐
│ Python (PyO3) │
│ Factor ranking, strategy logic, Polars DataFrames │
├──────────────────────────┬──────────────────────────────┤
│ │ │
│ ┌───────────────────┐ │ ┌───────────────────────┐ │
│ │ Portfolio Engine │ │ │ LOB Exchange │ │
│ │ │ │ │ │ │
│ │ Position (VWAP) │ │ │ OrderBook │ │
│ │ CostModel (bps) │◄──┼──►│ Matching Engine │ │
│ │ Strategy Trait │ │ │ Stop / Trailing │ │
│ │ Parallel Sweep │ │ │ Event Replay │ │
│ └───────────────────┘ │ └───────────────────────┘ │
│ │ │
│ ┌───────────────────────┴──────────────────────────┐ │
│ │ Shared: Symbol, MultiExchange, BookSnapshot │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
See DOC.md for the full API reference.
See It In Action
Building order book...
SELL 100 @ $50.25 (Alice) ASK $50.50 150 shares
SELL 150 @ $50.50 (Bob) ASK $50.25 100 shares
BUY 100 @ $50.00 (Carol) ---- spread: $0.25 ----
BUY 200 @ $49.75 (Dan) BID $50.00 100 shares
BID $49.75 200 shares
Incoming: BUY 120 @ $50.25 (Eve) - CROSSES SPREAD!
Trades: 100 shares @ $50.25 (Alice filled completely)
Filled: 100, Resting: 20 (Eve's remainder rests on book)
Incoming: MARKET BUY 200 (Frank) - SWEEPS THE BOOK!
Trades: 150 shares @ $50.50 (Bob filled completely)
Unfilled: 50 (no more liquidity!)
The interactive demo explains price-time priority, partial fills, IOC/FOK, and order cancellation.
Features
- Order types: Limit, Market, Cancel, Modify
- Time-in-force: GTC, IOC, FOK
- Stop orders: Stop-market, stop-limit, trailing stops (fixed/percentage/ATR)
- Price-time priority: FIFO matching at each price level
- Nanosecond timestamps: Monotonic counter (not system clock)
- Deterministic: Same inputs → same outputs (essential for backtesting)
- Fast: 8M+ orders/second single-threaded (see Performance)
- Book snapshots: L1 (BBO), L2 (depth), L3 (full book), imbalance, weighted mid
- Event replay: Complete audit trail for deterministic replay
- Portfolio: Position tracking, VWAP entry, cost model, Sharpe/Sortino/drawdown metrics
- Strategy trait: Define
compute_weights(), run backtests withrun_backtest() - Multi-symbol: Independent order books per symbol via
MultiExchange - Parallel sweeps: Rayon-based parameter grid search (optional feature)
- Python bindings: Exchange, Portfolio, sweep via PyO3 with GIL release for parallel sections
Quick Example
use ;
Installation
Rust
Add to Cargo.toml:
[]
= "0.4"
Python
Or build from source:
# Python bindings
&&
Python Quick Start
# Create an exchange
=
# Submit orders (strings for enums, ints for prices in cents)
# sell 100 @ $100.50
=
assert ==
assert . == 10050
# Market orders, stops, trailing stops
# Book queries
, =
=
# Portfolio management
=
=
# Parallel parameter sweep (GIL released for Rayon)
=
API Overview
Core Types
/// Price in smallest units (e.g., cents). Price(10050) = $100.50
;
/// Order side
/// Time-in-force: how long an order stays active
/// Order status
Exchange Operations
let mut exchange = new;
// Submit limit order (returns order ID + any immediate trades)
let result = exchange.submit_limit;
println!;
// Submit market order (always IOC semantics)
let result = exchange.submit_market;
// Cancel an order
let cancel_result = exchange.cancel;
// Modify an order (cancel + replace, loses time priority)
let modify_result = exchange.modify;
// Get order status
if let Some = exchange.get_order
// Book snapshots
let = exchange.best_bid_ask; // L1
let depth = exchange.depth; // L2: top 10 levels
let full = exchange.full_book; // L3: everything
Result Types
How It Works
Order Book Structure
BIDS (sorted high→low) ASKS (sorted low→high)
$100.00: [O1]→[O2]→[O3] $100.50: [O7]→[O8]
$99.50: [O4]→[O5] $101.00: [O9]
$99.00: [O6] $102.00: [O10]→[O11]
↑ Best Bid ↑ Best Ask
- BTreeMap<Price, Level> for sorted price levels
- VecDeque for FIFO queue at each level
- HashMap<OrderId, OrderRef> for O(1) lookup/cancel
- Cached best_price for O(1) BBO access
Matching Algorithm
- Incoming order checks opposite side of book
- If prices cross (buy ≥ best ask, or sell ≤ best bid), match
- Fill at resting order's price (price improvement for aggressor)
- Continue until no more crosses or order fully filled
- Remaining quantity: rests (GTC), cancels (IOC/Market), or entire order cancels (FOK)
Time-in-Force Behavior
| TIF | Partial Fill OK? | Rests on Book? |
|---|---|---|
| GTC | Yes | Yes (remainder) |
| IOC | Yes | No (remainder cancelled) |
| FOK | No | No (all-or-nothing) |
Determinism
- No randomness anywhere
- Timestamps from monotonic counter, not system clock
- Same order sequence always produces same trades
- Event log enables exact replay
Performance
Benchmarks
Measured on AMD Ryzen / Intel Core (single-threaded):
| Operation | Time | Throughput | Complexity |
|---|---|---|---|
| Submit (no match) | 120 ns | 8.3M ops/sec | O(log P) |
| Submit (with match) | ~200 ns | 5M ops/sec | O(log P + M) |
| BBO query | 1 ns | 1B ops/sec | O(1) |
| Cancel | 660 ns† | 1.5M ops/sec | O(N) |
| L2 snapshot (10 levels) | ~500 ns | 2M ops/sec | O(D) |
Where P = price levels, M = orders matched, N = orders at price level, D = depth.
†Cancel is O(N) in orders at that price level. See "Future Optimizations" below.
Optimizations Applied
- FxHash — Non-cryptographic hash for OrderId lookups (+25% vs std HashMap)
- Cached BBO — Best bid/ask cached for O(1) access
- Optional event logging — Disable for max throughput:
# With event logging (default) - enables replay
# Without event logging - maximum performance
Where Time Goes (Submit, No Match)
submit_limit() ~120 ns breakdown:
├── FxHashMap insert ~30 ns order storage
├── BTreeMap insert ~30 ns price level (O(log P))
├── VecDeque push ~5 ns FIFO queue
├── Event recording ~10 ns (optional, for replay)
└── Overhead ~45 ns struct creation, etc.
Future Optimizations
| Optimization | Potential Gain | Complexity | Tradeoff |
|---|---|---|---|
| O(1) cancel | 10x for deep levels | High | Intrusive linked list or tombstones |
| Array-indexed levels | -30 ns | Medium | Requires bounded price range |
| Slab allocator | -10 ns | Medium | More complex memory management |
O(1) Cancel: Currently cancel scans the VecDeque to find the order. For true O(1):
- Tombstone approach: mark cancelled, skip during matching
- Intrusive doubly-linked list with HashMap<OrderId, NodePtr>
These add complexity. Current O(N) cancel is fine unless you have thousands of orders at a single price level (rare in practice).
Rust vs Numba
Single-threaded throughput is roughly equivalent (both compile to LLVM IR). Where Rust wins: zero cold-start (vs Numba's ~300 ms JIT), true parallelism via Rayon with no GIL contention, and deterministic memory without GC pauses.
Use Cases
Strategy Backtesting
for event in historical_events
Market Impact Analysis
let = exchange.best_bid_ask;
let result = exchange.submit_market;
let = exchange.best_bid_ask;
let slippage = bid_after.unwrap.0 - bid_before.unwrap.0;
Queue Position Testing
// Who's first in line at $100?
let competitor = exchange.submit_limit;
let mine = exchange.submit_limit;
// Sell comes in — who gets filled?
exchange.submit_limit;
// Competitor was first, gets filled first
let comp_order = exchange.get_order.unwrap;
let my_order = exchange.get_order.unwrap;
assert_eq!;
assert_eq!;
IOC for Aggressive Execution
// Take liquidity without resting an order
let result = exchange.submit_limit;
// Fills what's available at ≤$100.50, remainder cancelled
println!;
Comparison with Other Rust LOBs
| Library | Throughput | Threading | Order Types | Deterministic | Use Case |
|---|---|---|---|---|---|
| nanobook (this) | 8M ops/sec | Single | Limit, Market, GTC/IOC/FOK | Yes | Backtesting, education |
| limitbook | 3-5M ops/sec | Single | Limit, Market | No | General purpose |
| lobster | ~300K ops/sec | Single | Limit, Market | No | Simple matching |
| OrderBook-rs | 200K ops/sec | Multi | Many (iceberg, peg, etc.) | No | Production HFT |
When to use what:
- This library: You need deterministic replay for backtesting, or you're learning how exchanges work
- limitbook: General-purpose LOB without replay requirements
- OrderBook-rs: Production systems needing thread-safety and complex order types
Feature Flags
| Feature | Default | Description |
|---|---|---|
event-log |
Yes | Event recording for deterministic replay |
serde |
No | Serialize/deserialize all public types |
persistence |
No | File-based event sourcing (JSON Lines) |
portfolio |
No | Portfolio engine, position tracking, metrics |
parallel |
No | Rayon-based parallel parameter sweeps |
Non-Goals
| Temptation | Why Not |
|---|---|
| Spearman/IC/t-stat in Rust | Use scipy/Polars — proven, mature |
| arrow-rs dependency | 2 → 20+ deps. PyO3 handles data exchange |
| Event-driven Strategy trait | Wrong pattern for factor strategies. Batch vectorized |
| Replace VectorBT | VectorBT is fine for ad-hoc research. nanobook = production |
| Networking/WebSocket | In-process only. Wrap externally if needed |
| GUI/dashboard | Use Python (Streamlit/Jupyter) for visualization |
Limitations
This is an educational/testing tool, not a production exchange:
- No networking: In-process only
- No compliance: Self-trade prevention, circuit breakers
- No complex orders: Iceberg, pegged orders
See SPECS.md for the complete specification.
License
MIT
Contributing
Issues and PRs welcome. See SPECS.md for the technical specification.
Recording a Demo GIF
To create an animated GIF of the demo (for docs, presentations, etc.):
# Using vhs (recommended): https://github.com/charmbracelet/vhs
# Using asciinema + agg: