File: stock-exchange/solution.py

Date: 2026-06-05

Time: 13:20

stock-exchange/solution.py — Stock Exchange Matching Engine

Purpose

This file implements a limit order book (LOB) matching engine — the core component of a stock exchange that accepts buy/sell orders and matches them into trades. It's a system design interview implementation demonstrating how exchanges achieve price-time priority matching, the fundamental algorithm behind every modern stock exchange.

The file owns three responsibilities:

1. Order lifecycle management — tracking orders from NEW through PARTIALLY_FILLED to FILLED or CANCELLED

2. Price-time priority matching — executing trades at the best available price, with earlier orders at the same price level filled first

3. Multi-symbol routing — the Exchange class dispatches orders to per-symbol OrderBook instances

Key Components

Order

A mutable order record. Notable contract details:

Trade

An immutable record of a fill. Uses a class-level _counter for sequential trade IDs (t1, t2, ...). The create classmethod is the only intended constructor — it auto-generates the ID and timestamp.

Warning for tests: _counter is class-level and never resets. Tests that assert on specific trade IDs (e.g., t1) will break if test ordering changes or fixtures don't reset the counter.

OrderBook

The core data structure — one per symbol. Internal state:

| Field | Type | Purpose |

|-------|------|---------|

| _bids | dict[float, deque[Order]] | Buy orders grouped by price |

| _asks | dict[float, deque[Order]] | Sell orders grouped by price |

| bidprices | list[float] | Sorted descending — [0] is best bid |

| askprices | list[float] | Sorted ascending — [0] is best ask |

| _orders | dict[str, Order] | All orders by ID (including filled/cancelled) |

| _trades | list[Trade] | Full trade history |

Key methods:

Exchange

A thin routing layer. Lazily creates OrderBook instances on first access to a symbol. This is the public API surface — callers interact with Exchange, not OrderBook directly (though the example code does grab the book for assertions).

Patterns

Price-time priority (FIFO). Within each price level, orders are stored in a deque and consumed from the left (popleft). New orders append to the right. This gives strict time priority without needing to sort by timestamp.

Aggressive-then-rest. placeorder first tries to match the incoming order against the opposite side (match_order), then adds any unfilled remainder to the book. This is the standard exchange pattern — incoming orders are "aggressors" and existing orders are "resting."

Sort-on-insert for price levels. addtobook appends a price then re-sorts the entire price list. This is O(n log n) per insertion — fine for an interview implementation but a real exchange would use bisect.insort or a sorted container. The sorted lists keep bidprices[0] as best bid and ask_prices[0] as best ask, making BBO lookups O(1).

Passive matching price. Trades always execute at the resting order's price, not the aggressor's. This is correct exchange behavior — if you place a buy at $151 and the best ask is $150, you get filled at $150.

Dependencies

Imports: Only stdlib — time for timestamps and deque for FIFO queues at each price level. No external dependencies.

Imported by:

Flow

A typical order lifecycle:

1. Caller creates an Order (status: NEW)

2. Calls exchange.placeorder(order) → routes to OrderBook.placeorder

3. placeorder registers the order in orders, then calls matchorder

4. matchorder walks the opposite side:

5. Back in place_order: if the order has remaining quantity:

6. All generated trades are appended to _trades and returned to the caller.

Invariants

Error Handling

Minimal — consistent with an interview implementation:

Topics to Explore

Beliefs