Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LiquidBook

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.

Highlights

  • 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.

Measured performance

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.

Layout

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.

Build and run

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)

Design

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.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages