Ondřej Kutil
CV

Projects / Order Book Engine

Order Book Engine

High-throughput system modelling market liquidity and minimising execution slippage.

C++Pythonpybind11
github ↗

Overview

A working limit order book engine built from scratch — a hands-on way to learn market microstructure (how orders interact, how liquidity forms, how prices move) 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_index map
  • 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.

ComponentStatus
C++ matching engineDone
Python bindings (pybind11)Done
Trading agentsPlanned
Simulation scenariosPlanned
Performance benchmarksPlanned

Build Notes

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.

Code & Artifacts