Markets: A Rigorous Course

Exercise · m01_order_book.py

Build the exchange

NSE runs a continuous limit order book with price-time priority. You'll build one. This is the single best way to understand bid, ask, spread, depth, market orders, slippage and why "the price" is really two prices.

Rules:

  • A BUY limit at price p matches resting SELL orders priced <= p, best (lowest) price first; at equal prices, earliest order first.
  • A SELL limit at p matches resting BUY orders priced >= p, highest first.
  • Trades execute at the RESTING (maker) order's price.
  • Any unfilled remainder of a limit order rests in the book.
  • A market order matches like a limit with an infinitely aggressive price; any unfilled remainder is discarded (NSE converts/rejects; we discard).
  • Order ids are unique strings.

Data structures are your call. A dict of price -> deque per side, with sorted price lists (bisect) or heaps, is plenty.

Read notes.md 1.4 first. Check: cd exercises && .venv/bin/python -m pytest modules/M01-market-structure/tests

Tests (6)

  • test_top_of_book
  • test_price_time_priority_market_buy
  • test_aggressive_limit_rests_remainder
  • test_cancel
  • test_market_order_on_thin_book_discards_rest
  • test_random_conservation

Theory: M01 notes. Run with Ctrl/⌘+Enter.

not run yet

Not saved: sign up to keep your code
"""Build the exchange.

NSE runs a continuous limit order book with price-time priority. You'll build
one. This is the single best way to understand bid, ask, spread, depth,
market orders, slippage and why "the price" is really two prices.

Rules:
  * A BUY limit at price p matches resting SELL orders priced <= p,
    best (lowest) price first; at equal prices, earliest order first.
  * A SELL limit at p matches resting BUY orders priced >= p, highest first.
  * Trades execute at the RESTING (maker) order's price.
  * Any unfilled remainder of a limit order rests in the book.
  * A market order matches like a limit with an infinitely aggressive price;
    any unfilled remainder is discarded (NSE converts/rejects; we discard).
  * Order ids are unique strings.

Data structures are your call. A dict of price -> deque per side, with
sorted price lists (bisect) or heaps, is plenty.

Read notes.md §1.4 first. Check:
    cd exercises && .venv/bin/python -m pytest modules/M01-market-structure/tests
"""

from dataclasses import dataclass


@dataclass(frozen=True)
class Fill:
    maker_id: str
    taker_id: str
    price: float
    qty: int


class OrderBook:
    def __init__(self) -> None:
        raise NotImplementedError

    def add_limit(self, order_id: str, side: str, price: float, qty: int) -> list[Fill]:
        """side is 'buy' or 'sell'. Returns fills in execution order."""
        raise NotImplementedError

    def add_market(self, order_id: str, side: str, qty: int) -> list[Fill]:
        raise NotImplementedError

    def cancel(self, order_id: str) -> bool:
        """Remove a resting order. True if it was resting, else False."""
        raise NotImplementedError

    def best_bid(self) -> float | None:
        raise NotImplementedError

    def best_ask(self) -> float | None:
        raise NotImplementedError

    def spread(self) -> float | None:
        raise NotImplementedError

    def mid(self) -> float | None:
        raise NotImplementedError

    def depth(self, side: str, levels: int = 5) -> list[tuple[float, int]]:
        """Aggregated [(price, total_qty), ...] best first, for 'buy' (bids) or 'sell' (asks)."""
        raise NotImplementedError

No runs yet

The stub's functions raise NotImplementedError until you write them. Fill in one, press Run tests, and watch its tests turn green.

Reference solution

Available after your first run, with an account approved for code runs.

Create a free account

Reading is open to everyone. An account keeps your progress, answers, notes and code drafts, and gives you the study plan, the journals and the simulator. Running code needs an account approved for it.