Documentation
orderbook is a production-grade Go library for running a central
limit order book (CLOB) and matching engine — the core of an exchange, a trading
simulator, or any system where buyers and sellers meet at a price. It is embeddable,
uses integer-exact pricing (int64 ticks and lots, never floats), has a
zero-allocation hot path and a lock-free single-writer core, is deterministic and
replayable, and ships both the order types real venues use and an opt-in layer of
pre-trade risk & anti-manipulation controls.
go get github.com/intrepidkarthi/orderbook/pkg/matching
New to order books? Start with the from-scratch guide. For the full API, see pkg.go.dev; for release history, the changelog (latest: v0.6.0, the market-integrity release).
Architecture
The project follows one rule: strict downward layering. The matching core
depends on nothing above it; persistence, surveillance, the gateway, auctions,
research, and the web demo build on top. This keeps the core lean and importable on
its own — most consumers use only types, orderbook, and
matching.
apps / web / examples (consume everything below)
gateway (enforcing edge: rate gate, speed bump, audit)
surveillance · backtest · strategy · sim · signals
wal · auction · marketdata (persistence, call auctions, feeds)
──────────────── CORE ────────────────
matching (engine · Runner · events · snapshots · pre-trade risk)
orderbook (book · price ladder · depth · L2/L3 · snapshot)
types (Order, Trade, iceberg, errors)
Dependencies flow one direction only — downward. Credit, identity, fees, and wire protocols stay outside the core, the same boundary production venues draw (see the core-vs-layer boundary).
Order book internals
Data structure
Each side of the book is a map[price]→level for O(1) level lookup,
plus a price-sorted slice maintained by binary-search insertion. Each level is a FIFO
queue of orders (time priority). Cancels are O(1): an orderID→node index
into intrusive doubly-linked lists unlinks a resting order without a scan. Book nodes
and price levels are pooled, so steady-state add/cancel/match allocate nothing.
Price–time priority
An incoming order matches against the best price first; among orders at the same price, first-come-first-served. Trades always print at the resting (maker) order's price.
Integer-exact pricing
Prices are int64 ticks and quantities int64 lots — no
floating point on the money path, so there is no silent rounding drift. A per-symbol
Instrument converts decimals to ticks/lots only at the API boundary. This
is both a correctness decision and a performance one: the hot path is pure integer
arithmetic and allocation-free.
Determinism
Given the same ordered input stream, the engine produces byte-identical trades and
book state. There is no wall clock or RNG in the matching path; anything that must be
reproducible (timestamps, ids) is injected through Config.Clock. This is
what makes record→replay, WAL recovery, and golden-file tests possible — a recorded
session replays to an identical trade digest.
Concurrency — single writer, lock-free hot path
One matching goroutine owns the book with no lock on the hot path (the LMAX model).
A Runner fronts it with an MPSC command queue so many producers submit
concurrently while the matcher stays sequential and deterministic. A single book is
inherently sequential at the match point, so you scale out across symbols
(one book per symbol, via a shard router), not by parallelizing one book. Overload is
handled by bounded backpressure: new orders are shed with ErrQueueFull
while cancels keep flowing.
Order types
| Type | Behavior |
|---|---|
| Market | Takes liquidity immediately; may sweep several levels. |
| Limit | Rests at a price or better; can be a maker. |
| Stop / Stop-limit | Dormant until the market reaches a trigger price, then fires as a market/limit order. |
| Iceberg | Shows only a display slice; the hidden reserve auto-refills as it fills (with optional randomized peaks to defeat sniffing). |
| Post-only | Maker-only — rejected if it would cross the spread and take. |
| Pegged | Price derived from a reference (bid / ask / mid) plus a signed offset. |
| OCO / bracket | Two linked orders; one completing cancels the other. |
| Trailing stop | A stop that ratchets with the market by a fixed trail and never retreats. |
Time-in-force: GTC (rest until cancelled), IOC (fill now, cancel the rest),
FOK (fill entirely now or cancel all). Any advanced family can be feature-flagged
off (Config.DisabledClasses) so one buggy exotic type is disabled
without downing the venue — the ASX / Binance lesson.
Matching
An incoming order crosses the resting book by price–time priority until it is filled or the book stops crossing. The resting side is the maker; the crossing order is the taker. Trades print at the maker's price, so a large taker "walks the book" and pays a worse average price (slippage).
Self-trade prevention
When an order would match the same user's (or trade group's) resting order, one of five modes applies: cancel-newest (default), cancel-oldest, cancel-both, decrement (reduce both by the overlap, no trade — the modern default), or allow. A privileged liquidation order can be exempted so it is not self-blocked.
Pro-rata
An optional per-engine mode. At each price level, an incoming order's quantity is allocated in proportion to resting size instead of strictly FIFO, with the rounding remainder distributed so allocations sum exactly.
Auctions
A call-auction uncross computes the single clearing price that maximizes matched
volume across resting bids and asks. auction.AuctionSession runs one for
the open, the close, or halt recovery; its RandomizedClose jitters the
uncross time deterministically (replay-safe) so nobody can aim aggression at the print
— the structural defense against marking the close.
Market data
Three granularities of book view:
- L1 — top of book (best bid/ask and sizes).
- L2 — aggregated size per price level.
- L3 / MBO — every resting order individually, price-then-time ordered.
Snapshots carry a monotonic sequence number for gap detection, and executions are
available as a trade tape (time & sales). A typed, sequence-numbered
EventSink stream (accepted / trade / canceled / rejected / halted) is the
seam every market-data, drop-copy, and audit adapter sits on.
Security & market integrity
A first-class concern, not an afterthought. Every control here is grounded in a real regulatory enforcement action or market incident — spoofing convictions, the Knight Capital blow-up, oracle-manipulation hacks, integer-overflow exploits. The full catalogue (27 attacks → detection signal → defense → whether it belongs in the core or a layer above) is in docs/THREAT-MODEL.md; every knob below is documented in docs/CONFIG.md.
In-core pre-trade risk controls
Opt-in (zero value = off), enforced on the cold reject path so the zero-alloc hot path is untouched; liquidation/ADL orders are exempt from the caps, and the time-based checks are bypassed on deterministic replay.
| Control | Defends against | Grounded in |
|---|---|---|
| MaxOrderQty / MaxOrderNotional | Fat-finger / fat-order blowups | Knight Capital ($440M) |
| MinOrderQty / MinOrderNotional | Dust that bloats the book | Resource exhaustion |
| MaxOrdersPerAccount | Per-account order-flooding | Quote stuffing |
| MinRestingTime | Spoofing (post size, pull before it fills) | JPMorgan ($920M), Coscia |
| DedupClientOrderIDs | Replayed / duplicated orders double-booking | FIX PossDup abuse |
| MaxMarkStep + MinMarkDepth | Oracle / mark-price manipulation (jump & patient drag) | Mango ($110M), Hyperliquid JELLY |
| MaxForceTradeQty | Book-clearing liquidation cascades | BitMEX, Hyperliquid HLP |
| BandBreachPause | Volatility spikes (timed pause + auto-resume) | LULD / the 2010 Flash Crash |
| Guardrail (self-output) | Runaway / repurposed-path order spraying | Knight Capital |
| IcebergPeakJitter | Hidden-reserve sniffing / order anticipation | Iceberg detection research |
| int64 + notional overflow guard | Integer overflow / precision leaks | Bitcoin CVE-2010-5139 (184B BTC) |
Surveillance detectors (pkg/surveillance)
Fed off the event stream, these detect and alert (they never touch the match path): spoofing / layering, order-to-trade ratio (the strongest standing spoofing signal), marking-the-close aggressor dominance, ramping / momentum ignition, pinging (tiny rapid-cancel bursts probing hidden liquidity), and a cross-book correlator that flags a user manipulating several correlated products at once.
Circuit breakers & degraded states
- Price band — a collar around a risk-clamped mark/index price (not just the last trade) rejects limit orders priced beyond ±band; a breach can escalate to a timed pause.
- Degraded states —
Open/CancelOnly(accept cancels, reject new liquidity — the venue wind-down path) /Halted, withHALTED/RESUMEDemitted on the event stream so operators can page. - Stop-cascade protection — triggered stops chain in a bounded cascade, never an unbounded loop.
Enforcing gateway (pkg/gateway)
At the edge, above the core: a per-account token-bucket rate gate that rejects an over-quota order (cancels are never gated — keep-cancels-flowing under load) and an asymmetric speed bump on liquidity-taking orders (the IEX latency-arbitrage defense), plus a CAT-style audit export off the sequenced event stream.
The core-vs-layer boundary
A matching core earns trust by being small, deterministic, and neutral. It defends market-microstructure mechanics and its own arithmetic; it deliberately does not own beneficial-ownership clustering, credit / margin, oracle price sourcing, ingress ordering / MEV, or authentication — those are identity / clearing / oracle / gateway concerns by design. The core refuses to act on an unbacked mark; it cannot manufacture a good one. See THREAT-MODEL §6 for why that line is drawn where it is.
Recovery & persistence
Because matching is deterministic, durability is just a log of commands.
pkg/wal is an append-only, length-prefixed write-ahead log: submits and
cancels are recorded write-ahead (before the engine applies them) and
Sync is the group-commit durability point. A crash mid-write leaves a torn
tail that the reader stops at cleanly. Recovery replays the log into a fresh engine to
reach byte-identical book state — the same journal-plus-snapshot model LMAX and Binance
use.
Snapshots (TakeSnapshot / RestoreEngine) are keyed to the
engine sequence, so recovery is bounded to O(recent): load a snapshot, replay
only the WAL tail after it. Operational guidance is in
docs/INTEGRATION.md.
Performance
Core microbenchmarks (Apple M-series, Go 1.23, single-threaded). CI runs them on every push; full methodology in docs/BENCHMARKS.md.
| Operation | ns/op | allocs/op |
|---|---|---|
| Best bid/ask read | 6.3 | 0 |
| Cancel (drain) | 253 | 0 |
| Cancel / replace (MM churn) | 180 | 0 |
Match round-trip (Match) | 352 | 0 |
Match round-trip (Process wrapper) | 491 | 4 |
Tail latency — a ~90%-cancel / 10%-new mix against a warm book:
p50 83 ns · p99 167 ns · p999 292 ns, 0 allocs/op. The zero-alloc
Match(order, buf) path appends value trades into a caller buffer;
Process is the ergonomic wrapper (4 allocs) for when convenience beats the
last nanosecond.
Full documentation
The deep documentation lives as versioned markdown in the repository (and renders on GitHub); the API reference is on pkg.go.dev.
| Document | What's inside |
|---|---|
| THREAT-MODEL.md | Order-book attacks & defenses: 27-attack catalogue with real enforcement cases, top-ten deep dives, what the engine defends, and the core-vs-layer boundary. |
| CONFIG.md | Every configuration knob — instrument grid, engine policy, and all pre-trade risk controls — with defaults and validation. |
| INTEGRATION.md | Embedding & operating the engine: reference architecture, WAL/recovery, market integrity, backpressure, scaling, and a production checklist. |
| SPEC.md | Architecture, the order model, and the core design decisions with their rationale. |
| EXCHANGE-ARCHITECTURE.md | How real venues (MetaTrader, Binance, Coinbase, Nasdaq/LMAX/CME/IEX, dYdX/Hyperliquid) implement matching, and the incidents that shaped this design. |
| BENCHMARKS.md | Performance results, methodology, and how to reproduce. |
| LEARN.md | Order books and market making from first principles. |
| CHANGELOG.md | Release history (v0.1.0 → v0.6.0) with breaking-change notes. |
| pkg.go.dev | Generated API reference for every package, with runnable examples. |
Getting started
go get github.com/intrepidkarthi/orderbook/pkg/matching
eng := matching.NewEngine(matching.DefaultConfig("BTC-USD"))
// A resting sell (maker) — integer ticks & lots
ask, _ := types.NewOrder("alice", "BTC-USD", types.SideSell,
types.OrderTypeLimit, 30000, 5, types.TIFGoodTillCancel)
eng.Process(ask)
// A crossing buy (taker) — trades against it at the maker price
bid, _ := types.NewOrder("bob", "BTC-USD", types.SideBuy,
types.OrderTypeLimit, 30000, 3, types.TIFGoodTillCancel)
res := eng.Process(bid)
for _, t := range res.Trades {
fmt.Printf("%d @ %d\n", t.Quantity, t.Price) // 3 @ 30000
}
Turn on the market-integrity controls that fit your venue via matching.Config
(see Security), wrap the engine in a Runner for
concurrency, and enable pkg/wal for durability. Runnable examples:
go run ./examples/basic, ./examples/eventfeed,
./examples/gateway. Full API on
pkg.go.dev.