The Problem
Understanding market microstructure — how orders interact, how liquidity forms, and how prices move — requires more than reading about it. This project builds a working limit order book engine from scratch as a hands-on way to learn the core mechanics before layering any analysis on top.
The current scope is intentionally narrow: get the matching engine right. Agents, simulations, and performance analysis are planned next steps.
Technical Approach
The core data structure is a std::map<Price, std::vector<Order>> — a sorted map of price levels, each holding a FIFO queue of resting orders. This gives $O(\log n)$ insertion and $O(1)$ best-bid/ask lookups while enforcing price-time priority automatically.
// Incoming buy limit order — match against resting sells (price-time priority)
while (working_order.quantity > 0 && !sell_orders.empty() &&
working_order.price >= sell_orders.begin()->first) {
Order& resting = sell_orders.begin()->second.front(); // earliest order at best ask
Quantity trade_qty = std::min(working_order.quantity, resting.quantity);
// Execution always at the resting (maker) price, not the aggressor's
trade_logs.push_back({ next_trade_id++, working_order.order_id, resting.order_id,
resting.price, trade_qty });
working_order.quantity -= trade_qty;
resting.quantity -= trade_qty;
if (resting.quantity == 0) {
sell_orders.begin()->second.erase(sell_orders.begin()->second.begin());
if (sell_orders.begin()->second.empty())
sell_orders.erase(sell_orders.begin()); // remove empty price level
}
}
// Any unfilled remainder rests in the book
if (working_order.quantity > 0)
buy_orders[working_order.price].push_back(working_order);
The pybind11 layer wires each C++ struct into a proper Python class — constructor, readonly fields, and a to_dict() helper for analysis:
py::class_<PendingOrder>(m, "PendingOrder")
.def(py::init<OrderID, TraderID, Price, Quantity, OrderSide>(),
py::arg("order_id"), py::arg("trader_id"),
py::arg("price"), py::arg("quantity"), py::arg("side"))
.def_readonly("order_id", &PendingOrder::order_id)
.def_readonly("trader_id", &PendingOrder::trader_id)
.def_readonly("price", &PendingOrder::price)
.def_readonly("quantity", &PendingOrder::quantity)
.def_readonly("side", &PendingOrder::side)
.def("to_dict", [](const PendingOrder& x) {
py::dict d;
d["order_id"] = x.order_id;
d["trader_id"] = x.trader_id;
d["price"] = x.price;
d["quantity"] = x.quantity;
d["side"] = x.side;
return d;
});
Every other type in the module — Order, TradeLog, Level1Data, OrderBookSnapshot — is bound the same way. The same engine is then accessible from Python:
import market_simulator as ms
sim = ms.Simulator(start_time=0)
# Stage orders (agents will do this programmatically)
sim.place_limit_order(ms.PendingOrder(
order_id=1, trader_id=101, price=99.0, quantity=10, side=ms.OrderSide.BUY))
sim.place_limit_order(ms.PendingOrder(
order_id=2, trader_id=102, price=101.0, quantity=5, side=ms.OrderSide.SELL))
sim.submit_pending_orders()
sim.advance_time(1000) # advance simulation clock by 1 second
l1 = sim.get_current_level1_data()
print(l1.bid_price, l1.ask_price, l1.spread) # 99.0 101.0 2.0
A separate Simulator class wraps the order book and manages a simulation clock, staging orders from multiple callers before submitting them together — the hook for eventually plugging in agents.
The engine supports:
- Limit orders — placed at a specific price, rest in the book if not immediately matched
- Market orders — consume available liquidity at the best available prices
- Cancel / modify — O(1) lookup via a dedicated
order_indexmap - Market data — Level 1 (best bid/ask), Level 2 (full depth snapshot), and full order/trade logs
Current Status
The C++ engine and Python bindings are complete. No agents or simulation scenarios have been written yet — that is the next phase of the project.
| Component | Status |
|---|---|
| C++ matching engine | Done |
| Python bindings (pybind11) | Done |
| Trading agents | Planned |
| Simulation scenarios | Planned |
| Performance benchmarks | Planned |
Methodology
- Motivation — I want to understand quant trading and market microstructure from the inside, not just the outside. Building the engine myself forces precision about details that are easy to gloss over when just reading.
- Architecture — C++ for the matching hot-path; pybind11 to expose it to Python for future research/analysis. The two layers stay cleanly separated.
- Implementation — Built incrementally: limit order matching first, then market orders, then cancel/modify, then the simulator wrapper and Python bindings. Each step was validated against hand-traced order sequences.
- Next steps — Write agent classes that call into the
SimulatorAPI, run scenarios, and start producing data worth analysing.