A low-latency limit order book and matching engine in C++, built from scratch. It implements the core machinery of an exchange: a price-time priority order book, a binary wire protocol, and a POSIX TCP server that matches order flow from concurrent clients.
Written for correctness first and speed second, with an honest benchmark suite that reports real numbers on named hardware.
- Cache-resident matching path. Price levels live in a flat array indexed by price, not a pointer-based tree, so the hot path stays in cache. Best-price lookup uses a bitmap with a hardware bit-scan (
countr_zero) instead of a scan. - Zero allocation on the hot path. Orders live in a preallocated pool with a free list. Adding and cancelling are O(1) with no
malloc. Handles carry a generation counter, so a stale cancel of a recycled slot fails safely instead of corrupting a live order. - Allocation-free binary protocol. Fixed-layout, little-endian messages parsed by reading fixed offsets, no strings, no JSON. Framing is type-byte-plus-fixed-size, so a raw byte stream is split back into messages cleanly.
- Non-blocking TCP server. A single network thread runs a
poll()event loop over all client sockets; a separate matching thread owns the book. They hand off through mutex-guarded queues and a self-pipe wakeup. The book itself is lock-free because exactly one thread touches it. A slow client can never stall matching. - Measured, not asserted. Benchmarks time the engine in isolation and end-to-end over TCP, using CPU cycle counters, and report percentiles rather than averages.
On Apple Silicon (arm64), single thread:
| Path | Metric | Result |
|---|---|---|
| Engine, in-process | matching throughput | 60M+ ops/sec (~16 ns/op) |
| Engine, in-process | add / cancel | ~77M / ~134M ops/sec |
| End-to-end, over TCP | throughput | 1.5M+ orders/sec |
| End-to-end, over TCP | latency | ~30 us p50 round-trip |
In-process numbers measure the matching engine as direct function calls. End-to-end numbers add sockets, syscalls, and thread handoff, which is why they are microseconds and orders of magnitude lower throughput. Both are reported separately on purpose.
Note on precision: this machine's cycle counter updates in ~42 ns steps, so sub-42 ns per-operation percentiles are not resolvable here and the in-process per-op figure is derived from throughput. For fine-grained in-process p50/p99 in nanoseconds, run benchmark_engine.cpp on an x86 Linux host, where rdtsc resolves to about 0.3 ns.
include/liquidbook/
types.hpp shared vocabulary: Price, Qty, OrderId, Side, Event
pool.hpp preallocated order pool, free list, generation handles
book.hpp flat-array price ladder, bitmap best-price, matcher, cancel
protocol.hpp struct <-> byte translation, framing, little-endian helpers
src/
server.cpp POSIX TCP server: poll() network thread + matching thread
client.cpp test driver that sends orders and prints events
tests/
test_book.cpp 13 scenario tests + full-book invariant checker
test_protocol.cpp encode/decode round-trip tests
benchmarks/
benchmark_engine.cpp in-process add/cancel/match latency + throughput
benchmark_net_to_net.cpp end-to-end latency + throughput over TCP
docs/
notes.md full decision log and concept reference
The core library is header-only. Prices are integer ticks and quantities integer lots; floating point never touches the book.
Requires a C++20 compiler. No external dependencies.
# tests
g++ -std=c++20 -O2 -Wall -Wextra -Iinclude tests/test_book.cpp -o build/lb_tests && ./build/lb_tests
g++ -std=c++20 -O2 -Wall -Wextra -Iinclude tests/test_protocol.cpp -o build/lb_proto && ./build/lb_proto
# server and client
g++ -std=c++20 -O2 -Wall -Wextra -Iinclude src/server.cpp -o build/lb_server -pthread
g++ -std=c++20 -O2 -Wall -Wextra -Iinclude src/client.cpp -o build/lb_client
./build/lb_server 9001 # terminal A
./build/lb_client 9001 # terminal B: prints Ack, Ack, Fill
# benchmarks
g++ -std=c++20 -O2 -Wall -Wextra -Iinclude benchmarks/benchmark_engine.cpp -o build/lb_bench -pthread
g++ -std=c++20 -O2 -Wall -Wextra -Iinclude benchmarks/benchmark_net_to_net.cpp -o build/lb_bench_net -pthread
./build/lb_bench 200000 # in-process
./build/lb_server 9001 & ./build/lb_bench_net 9001 # end-to-end (fresh server)The matching rules are strict price-time priority. An incoming order matches the best-priced opposite level first, and within a level fills the oldest resting order first. Fills execute at the resting order's price, so price improvement accrues to the aggressor. Market and IOC remainders never rest; limit remainders join the book.
The order lifecycle emits events in a fixed order: an acknowledgement first, then zero or more fills in match order, then a terminal cancel if a remainder dies. Cancels answer either success or too-late, and a cancel that races a fill loses, because a fill that already happened is a fact.
The server keeps the book free of any I/O. The same submit and cancel calls run whether driven by a unit test in-process or by bytes off a socket, which is what lets the engine be benchmarked in isolation and also served over the network without duplicated logic.
Full reasoning for every decision is in docs/notes.md.