A multi-threaded limit-order matching engine in C++20, built as a Unix-socket client/server. Multiple clients connect concurrently and submit buy/sell/cancel orders; the engine matches them against a per-instrument order book using price-time priority.
-
Price-time priority matching — orders are matched at the best available price first, and by arrival time within the same price level.
-
Fine-grained locking — rather than a single global lock on the whole book, the engine uses a layered locking scheme:
- a
shared_mutexper instrument book (readers can match concurrently; writers get exclusive access when resting a new order), - a
shared_mutexper side (buy/sell) of the book, - a
mutexper price level, - a
mutexper individual order.
This lets unrelated instruments, and even unrelated price levels within the same instrument, be touched by different threads at the same time.
- a
-
Lazy cancellation — cancelling an order just flags it (
cancelled = true); it's removed from the book the next time it's encountered during matching, avoiding an expensive in-place list removal on the cancel path. -
One thread per client connection, detached and independent, reading commands off a Unix domain socket.
makeThis produces two binaries:
engine— the matching engine serverclient— a simple CLI client that reads commands from stdin and forwards them over a Unix socket
Requires clang/clang++ with C++20 support (see the Makefile for flags).
Start the engine, giving it a path for the Unix domain socket:
./engine /tmp/exchange.sockIn another terminal, connect a client and feed it commands:
./client /tmp/exchange.sock < commands.txtEach line is one command:
| Command | Format | Example |
|---|---|---|
| Buy | B <order_id> <instrument> <price> <count> |
B 125 GOOG 2705 30 |
| Sell | S <order_id> <instrument> <price> <count> |
S 126 GOOG 2705 30 |
| Cancel | C <order_id> |
C 125 |
Lines starting with # and blank lines are ignored.
The engine prints one line per event to stdout:
| Event | Format |
|---|---|
| Order resting on the book | B/S <order_id> <instrument> <price> <count> <timestamp> |
| Order executed (fill) | E <resting_id> <new_id> <exec_id> <price> <count> <timestamp> |
| Order cancelled/rejected | X <order_id> A/R <timestamp> |
- Locks are always acquired in a consistent order (book → side → price level → order) to avoid deadlock.
- Matching happens under a shared lock on the book, so multiple orders can be matched concurrently; only resting a new order (when it isn't fully filled) requires the exclusive book lock.
- This is a learning/portfolio project, not a production trading system — there's no persistence, no risk checks, and no networked (TCP) transport.