Limit order book matching engine
A matching engine written from the mechanics of an exchange rather than from a library: price-time priority, trades at the maker's price, and a noise generator that keeps the book populated so the engine has something to match against.
What I set out to check
Whether a market simulation could rest on an engine I actually understood. Agent-based work on market microstructure is only as trustworthy as the venue the agents trade in, and a simulator taken off the shelf hides exactly the mechanics that determine what the agents learn — who gets filled first, at whose price, and what happens to the remainder.
Writing the matching engine is how those rules stop being assumptions. It is also the piece that has to exist before anything interesting can be built on top: without a believable book there is nothing for a learning agent to trade against.
What I built
A single-threaded matching engine for one symbol per book, with a `MatchingEngine` over the top that registers symbols and takes an `OrderRequest` carrying side, order type, price, quantity and timestamp.
Each side of the book is a `BTreeMap` from price to a `VecDeque` of order ids: bids iterated descending so the highest price is the best bid, asks ascending so the lowest is the best ask, and within a price level orders served first-in-first-out. That is price-time priority, chosen deliberately over pro-rata, and the queue at each level is what makes time an actual input rather than a tiebreak.
The matching rules are the part worth stating plainly. A buy limit matches asks priced at or below it, a sell limit matches bids at or above it, and a market order takes whatever rests on the contra side. Every match executes at the *maker's* price, not the taker's. An order that fills completely returns fully filled; a limit order with a remainder rests on the book; a market order with a remainder is cancelled, because a resting market order is not a thing.
Prices are `u64` integers in whatever unit the caller picks — the crate documentation is explicit that there is no built-in notion of dollars or cents, and that consistency is the caller's job. That is the decision that keeps the book exact, since floating-point prices accumulate error precisely where you least want it.
The noise generator is what makes the engine testable in isolation. It submits random orders around a configurable mid price, drawn from a uniform band, with quantities from a range and a configurable share arriving as market orders rather than limits — ten per cent by default. That populates the book without needing recorded market data.
Correctness is pinned by eleven unit tests covering the cases that matter — resting, exact match, sweeping several levels, price-time priority, cancellation, and a market order against an empty book — plus two documentation tests, so the usage examples in the crate docs are compiled and run rather than left to rot.
Where it diverged
It diverges from a real venue by omission, and the omissions are the point of this section.
There are no fees, so there is no maker-taker economics and no reason for a strategy to prefer providing liquidity over taking it. There is no latency and no notion of message ordering beyond arrival, so the race conditions that dominate real execution do not exist. There are no opening or closing auctions, no hidden or iceberg liquidity, and no self-match prevention. Cancellation and modification exist only as far as the order lifecycle needs them, not as a realistic model of how order flow actually behaves.
Queue position is modelled, which matters, but only as pure FIFO — real venues complicate it in ways this does not attempt.
One subtlety is worth stating because it constrains what the engine can be used for. Orders carry a timestamp, and trades are stamped with the taker's, but the timestamp takes no part in priority: position in a level is arrival order at the book. For a simulation that submits orders as it generates them those are the same thing. For replaying recorded flow, where events can reach the engine out of timestamp order, they are not — the book would need to sort by timestamp before that use is sound.
None of that makes the engine wrong. It makes it a book with correct matching mechanics and an idealised environment around it, which is a reasonable place to start and a bad place to stop without saying so.
What I would do next
Two things would make this publishable rather than merely built. The first is a number: nothing in the repository records throughput or match latency, and for a matching engine that is the natural headline. Driving it with the noise generator and reporting orders matched per second, with a latency distribution rather than a mean, is a short job that turns a description into evidence.
The second is closing the gap between the noise generator and anything resembling real flow. Uniform random orders around a mid price produce a book, but not one with the queue dynamics or arrival clustering that make execution hard, so results measured against it flatter any strategy tested there.
After that, the thread the icebox already names: replacing noise traders with agents that learn, which is what the engine was built to make possible in the first place.
- Stack
- Rust