Project 1: Limit Order Book Engine

Build the fundamental data structure of every exchange and trading system - a fully functional limit order book that maintains bid/ask levels, handles order matching, and reports trades with nanosecond-level performance.


1. Quick Reference

Attribute Value
Difficulty Intermediate
Time Estimate 1-2 weeks
Languages C++ or Rust
Prerequisites Basic C++/Rust, understanding of maps/queues/linked lists
Key Topics Cache optimization, data structures, memory layout, order matching, custom allocators
Coolness Level Level 4: Hardcore Tech Flex
Portfolio Value Resume Gold - Every HFT firm asks about this

2. Learning Objectives

By completing this project, you will be able to:

  1. Understand limit order book mechanics: Explain how bids, asks, price levels, and order queues work in real exchanges
  2. Master cache-friendly data structure design: Implement data layouts that minimize cache misses and maximize throughput
  3. Learn price-time priority matching: Build a matching algorithm that correctly executes orders following exchange rules
  4. Gain experience with memory pooling: Eliminate malloc/new from your hot path using custom allocation strategies
  5. Benchmark and optimize for nanosecond latency: Use profiling tools to identify bottlenecks and measure improvements
  6. Compare data structure tradeoffs: Analyze red-black trees vs hash maps vs arrays for price level organization
  7. Build the foundation for HFT systems: This project is the core component that all subsequent projects build upon

3. Theoretical Foundation

3.1 Core Concepts

What is a Limit Order Book?

A limit order book (LOB) is the central data structure used by all electronic exchanges to match buyers with sellers. Every stock exchange (NYSE, NASDAQ), cryptocurrency exchange (Coinbase, Binance), and futures exchange (CME) maintains an order book for each tradable instrument.

                    LIMIT ORDER BOOK: AAPL

    +-----------------------------------------------------+
    |                    ASKS (SELLS)                      |
    |                                                      |
    |  Price    | Quantity | Orders                        |
    |  ---------+----------+-----------------------        |
    |  $150.30  |   2,500  | [O5:500, O6:2000]      <-- Best Ask
    |  $150.29  |   1,200  | [O4:1200]                     |
    |  $150.28  |   3,800  | [O3:800, O7:3000]             |
    |  ---------+----------+-----------------------        |
    |                                                      |
    |              === SPREAD: $0.02 ===                   |
    |                                                      |
    |  ---------+----------+-----------------------        |
    |  $150.26  |   4,100  | [O1:1000, O2:100, ...]  <-- Best Bid
    |  $150.25  |   2,300  | [O8:2300]                     |
    |  $150.24  |   5,600  | [O9:1000, O10:4600]           |
    |                                                      |
    |                    BIDS (BUYS)                       |
    +-----------------------------------------------------+

Key Terminology:

Term Definition
Bid An offer to buy at a specific price
Ask (Offer) An offer to sell at a specific price
Best Bid Highest price any buyer is willing to pay
Best Ask Lowest price any seller is willing to accept
Spread Difference between best ask and best bid
Price Level All orders at the same price
Depth Total quantity available at each price level
Liquidity How easily you can trade without moving the price

Price-Time Priority (FIFO)

When multiple orders exist at the same price level, they are executed in the order they arrived. This is called price-time priority or FIFO (First In, First Out).

Price Level $150.26:

  [Order A: 9:00:00.001] -> [Order B: 9:00:00.003] -> [Order C: 9:00:00.007]
          ^                          ^
    Arrived first              Arrived second
    (Matched first)            (Matched second)

Why This Matters: Speed is everything in HFT. If you can submit your order 1 microsecond faster than a competitor, you get priority at that price level. This is why HFT firms spend billions on infrastructure.

Order Types and Lifecycle

Order Types You Must Support:

  1. Limit Order: Buy/sell at a specific price or better
    • BUY 100 @ $150.25 - Will only execute at $150.25 or lower
    • SELL 100 @ $150.30 - Will only execute at $150.30 or higher
  2. Cancel Order: Remove a previously submitted order
    • Must be O(1) or O(log n) to avoid exploitation
  3. Market Order (optional for this project): Buy/sell immediately at best available price

Order Lifecycle State Machine:

                   Order Lifecycle

    +-------+     +----------+     +-------------+
    |  NEW  |---->|  PENDING |---->|   FILLED    |
    +-------+     +----------+     +-------------+
        |              |                  ^
        |              |                  |
        |              v                  |
        |         +----------+            |
        |         | PARTIAL  |------------+
        |         +----------+
        |              |
        |              v
        v         +----------+
    +--------+    | CANCELED |
    |REJECTED|    +----------+
    +--------+

    NEW:       Order received but not yet processed
    PENDING:   Order resting in book, waiting for match
    PARTIAL:   Some quantity filled, remainder still in book
    FILLED:    All quantity matched, order complete
    CANCELED:  Order removed before full execution
    REJECTED:  Order failed validation (invalid price, etc.)

The Matching Algorithm

When a new order arrives, the matching engine attempts to match it against existing orders on the opposite side:

MATCHING ALGORITHM: New Buy Order @ $150.28 for 1,500 shares

Step 1: Check if order crosses the spread
        Best Ask: $150.26 (1,000 shares)
        New Buy:  $150.28 (1,500 shares)
        $150.28 >= $150.26? YES (Order crosses!)

Step 2: Match against best ask
        Match: 1,000 shares @ $150.26
        Trade generated: BUY 1,000 @ $150.26
        Remaining: 500 shares to fill

Step 3: Move to next price level
        Next Ask: $150.27 (800 shares)
        Match: 500 shares @ $150.27
        Trade generated: BUY 500 @ $150.27
        Order fully filled!

Step 4: Update order book
        - Remove filled orders from ask side
        - Update price level quantities
        - Broadcast market data update

Pseudocode:

function match_order(incoming_order):
    while incoming_order.remaining_qty > 0:
        # Get best opposite price level
        opposite_level = get_best_level(opposite_side(incoming_order.side))

        if opposite_level is None:
            break  # No more liquidity

        if not prices_cross(incoming_order.price, opposite_level.price, incoming_order.side):
            break  # Order doesn't cross spread

        # Match against orders at this level (FIFO order)
        for resting_order in opposite_level.orders:
            match_qty = min(incoming_order.remaining_qty, resting_order.remaining_qty)

            generate_trade(incoming_order, resting_order, match_qty, opposite_level.price)

            incoming_order.remaining_qty -= match_qty
            resting_order.remaining_qty -= match_qty

            if resting_order.remaining_qty == 0:
                remove_order(resting_order)

            if incoming_order.remaining_qty == 0:
                break

        # If level is empty, remove it
        if opposite_level.total_qty == 0:
            remove_level(opposite_level)

    # If order has remaining quantity, add to book
    if incoming_order.remaining_qty > 0:
        add_to_book(incoming_order)

3.2 Why This Matters

The order book is the most performance-critical data structure in a trading system:

Operation Frequency Latency Requirement Why It Matters
Add Order 10,000-100,000/sec < 1 microsecond Every strategy starts with placing orders
Cancel Order 50,000-500,000/sec < 500 nanoseconds Rapid requoting requires instant cancellation
Match/Execute Variable < 1 microsecond Slow matching = missed opportunities
Best Bid/Ask 100,000+/sec < 100 nanoseconds Price check happens before every decision

Industry Performance Standards:

  • NASDAQ: Processes 1+ million messages per second per symbol
  • CME: Sub-microsecond matching latency
  • NYSE: Order book updates every 64 microseconds (consolidated feed)
  • HFT Firms: Internal matching in 10-100 nanoseconds

Real Financial Impact:

Scenario: You're quoting both sides of AAPL
Your Quote: BID $150.00 x 1000 | ASK $150.02 x 1000

The market moves against you (AAPL drops)
You need to cancel your $150.00 bid BEFORE someone sells to you

If your cancel takes 10 microseconds and a competitor's takes 1 microsecond:
  - They cancel first, you get filled on stale quote
  - Loss: potentially $0.10/share x 1000 shares = $100 per incident
  - This happens thousands of times per day

3.3 Historical Context

Evolution of Electronic Trading:

Timeline of Electronic Trading

1960s-1970s:    Physical trading floors, open outcry
                Order books maintained on paper by specialists

1971:           NASDAQ launched - first electronic exchange
                Order books stored in centralized computers

1980s-1990s:    Electronic Communication Networks (ECNs)
                Competition drives innovation

1998:           SEC Regulation ATS enables more ECNs
                Market fragmentation begins

2000s:          Decimalization ($0.01 increments)
                Algorithmic trading emerges
                Latency becomes competitive advantage

2005:           Reg NMS - National Best Bid Offer requirement
                Order routing becomes complex

2010s:          Microsecond wars
                Co-location, FPGA-based trading
                Flash crashes reveal fragility

2020s:          Nanosecond precision
                AI-driven market making
                Rust gaining adoption for safety

3.4 Common Misconceptions

Misconception 1: “std::map or a red-black tree is good enough for production”

Reality: std::map operations involve pointer chasing and cache misses. Production order books use flat arrays with intrusive lists, achieving 10-50x better performance. A tree traversal that looks O(log n) can take microseconds due to cache misses.

Misconception 2: “The matching algorithm is the hard part”

Reality: The matching algorithm is straightforward (just FIFO). The hard part is the data structure organization that makes matching fast. Algorithm complexity is O(m) where m is matched orders; the challenge is constant factors.

Misconception 3: “Memory allocation is fine if it’s fast”

Reality: Even “fast” allocators like jemalloc add 50-200ns variance. Production HFT systems use pre-allocated pools with O(1) allocation. Any syscall or heap operation in the hot path is unacceptable.

Misconception 4: “Optimizations don’t matter until you have a working version”

Reality: Partially true for correctness, but architecture decisions made early (data layout, allocation strategy) are very hard to change later. Design for performance from the start.

Misconception 5: “Exchanges use complex AI for matching”

Reality: Matching is deliberately simple and deterministic. Price-time priority is a well-defined, auditable algorithm. Complexity lives in the data structure, not the algorithm.


4. Project Specification

4.1 What You Will Build

A limit order book engine that:

  1. Maintains sorted price levels for both bids (descending) and asks (ascending)
  2. Supports core order operations: Add, Cancel, Modify
  3. Implements price-time priority matching correctly
  4. Generates trade events when orders cross
  5. Reports market data: Best Bid/Offer, depth at each level
  6. Achieves sub-microsecond operation times (target: < 500ns for common operations)

4.2 Functional Requirements

ID Requirement Priority
FR1 Accept limit orders with: symbol, side (buy/sell), price, quantity, order_id Must Have
FR2 Match incoming orders against existing book using price-time priority Must Have
FR3 Cancel orders by order_id in O(1) or O(log n) time Must Have
FR4 Report best bid and best ask prices and quantities Must Have
FR5 Generate trade events with: buyer_id, seller_id, price, quantity, timestamp Must Have
FR6 Support multiple price levels (at least 1000 levels per side) Must Have
FR7 Maintain FIFO order within each price level Must Have
FR8 Handle partial fills correctly, updating remaining quantity Must Have
FR9 Provide order acknowledgments (accepted, rejected, filled, canceled) Must Have
FR10 Support order modification (price change = cancel + new, quantity change) Should Have
FR11 Support multiple symbols (order book per symbol) Should Have
FR12 Provide depth snapshot (top N levels with quantities and order counts) Should Have
FR13 Track and report statistics (orders processed, trades executed, latency) Should Have
FR14 Support IOC (Immediate-Or-Cancel) order type Nice to Have
FR15 Support FOK (Fill-Or-Kill) order type Nice to Have
FR16 Implement market orders (execute at best available price) Nice to Have

4.3 Non-Functional Requirements

Requirement Target Measurement Method
Add Order Latency < 500ns (p50), < 2us (p99) Measure with rdtsc or std::chrono::high_resolution_clock
Cancel Order Latency < 200ns (p50), < 1us (p99) Same measurement approach
Best Bid/Ask Query < 50ns Direct struct access, no computation
Memory Usage < 100 bytes per order Count struct sizes, verify with sizeof
Throughput > 1 million orders/second Benchmark with sustained load
Determinism No dynamic allocation in hot path Profile heap usage during operation
Correctness Price-time priority always maintained Property-based testing
Memory Efficiency < 500MB for 1M orders Monitor RSS during stress tests

4.4 Example Usage / Output

$ ./orderbook

=== LIMIT ORDER BOOK ENGINE ===

> ADD BUY 100 50.25 order_001
ACK order_001 ADDED @ 50.25 x 100
BOOK: BID 50.25 x 100 | ASK -- x --

> ADD BUY 200 50.24 order_002
ACK order_002 ADDED @ 50.24 x 200
BOOK: BID 50.25 x 100 | ASK -- x --

> ADD SELL 150 50.30 order_003
ACK order_003 ADDED @ 50.30 x 150
BOOK: BID 50.25 x 100 | ASK 50.30 x 150

> ADD SELL 50 50.25 order_004
ACK order_004 ADDED @ 50.25 x 50
TRADE order_001 order_004 50@50.25
ACK order_004 FILLED
BOOK: BID 50.25 x 50 | ASK 50.30 x 150

> CANCEL order_001
ACK order_001 CANCELED (remaining 50)
BOOK: BID 50.24 x 200 | ASK 50.30 x 150

> ADD SELL 300 50.20 order_005
ACK order_005 ADDED @ 50.20 x 300
TRADE order_002 order_005 200@50.24
PARTIAL order_005 200 filled, 100 remaining @ 50.20
BOOK: BID -- x -- | ASK 50.20 x 100

> DEPTH
=== ORDER BOOK DEPTH ===
        ASKS
        ----
50.30 | 150 (1 orders)
50.20 | 100 (1 orders)
        ----
      SPREAD: $50.20 (no bids)
        ----
        BIDS
        ----
      (empty)

> STATS
Operations: 5 adds, 1 cancel
Trades: 2 (250 shares total)
Avg add latency: 342ns (p50), 891ns (p99)
Avg cancel latency: 156ns (p50), 412ns (p99)
Memory: 5 orders x 64 bytes = 320 bytes

4.5 Real World Outcome

When complete, you will have:

  1. A functional matching engine that can process orders, generate trades, and maintain consistent book state

  2. Benchmark results demonstrating performance progression:
    === BENCHMARK RESULTS ===
    
    Phase 1 (std::map baseline):
    Add Order:    2,340ns (p50), 5,120ns (p99)
    Cancel Order: 1,890ns (p50), 4,230ns (p99)
    Throughput:   180K orders/second
    
    Phase 2 (Custom allocators):
    Add Order:    456ns (p50), 1,230ns (p99)
    Cancel Order: 234ns (p50), 678ns (p99)
    Throughput:   850K orders/second
    
    Phase 3 (Cache-optimized):
    Add Order:    312ns (p50), 891ns (p99)
    Cancel Order: 89ns (p50), 234ns (p99)
    Best Bid/Ask: 12ns (p50), 31ns (p99)
    Throughput:   2.4M orders/second
    
  3. Understanding of why your design choices affect performance

  4. Foundation for Project 3 (Matching Engine) which adds networking

5. Solution Architecture

5.1 High-Level Design

                     ORDER BOOK ENGINE ARCHITECTURE

+------------------------------------------------------------------------+
|                          ORDER BOOK ENGINE                              |
+------------------------------------------------------------------------+
|                                                                         |
|  +------------------------------------------------------------------+  |
|  |                         ORDER INPUT                               |  |
|  |                                                                   |  |
|  |   +-----------+    +-----------+    +-----------+                |  |
|  |   | Add Order |    |Cancel Ord |    |Modify Ord |                |  |
|  |   +-----+-----+    +-----+-----+    +-----+-----+                |  |
|  |         |                |                |                       |  |
|  +---------+----------------+----------------+-----------------------+  |
|            |                |                |                          |
|            v                v                v                          |
|  +------------------------------------------------------------------+  |
|  |                      ORDER VALIDATOR                              |  |
|  |   - Price > 0, Quantity > 0                                       |  |
|  |   - Order ID unique (for adds), exists (for cancels)             |  |
|  |   - Side valid (BUY/SELL)                                        |  |
|  +------------------------------------------------------------------+  |
|            |                                                            |
|            v                                                            |
|  +------------------------------------------------------------------+  |
|  |                      MATCHING ENGINE                              |  |
|  |                                                                   |  |
|  |   +----------------------------------------------------------+   |  |
|  |   |              ORDER BOOK (per symbol)                      |   |  |
|  |   |                                                           |   |  |
|  |   |   +------------------+      +------------------+          |   |  |
|  |   |   |    BIDS (Buy)    |      |   ASKS (Sell)    |          |   |  |
|  |   |   |  (desc by price) |      | (asc by price)   |          |   |  |
|  |   |   |                  |      |                  |          |   |  |
|  |   |   | PriceLevel 50.26 |      | PriceLevel 50.28 |          |   |  |
|  |   |   |   +- Order Queue |      |   +- Order Queue |          |   |  |
|  |   |   | PriceLevel 50.25 |      | PriceLevel 50.29 |          |   |  |
|  |   |   |   +- Order Queue |      |   +- Order Queue |          |   |  |
|  |   |   |        ...       |      |        ...       |          |   |  |
|  |   |   +------------------+      +------------------+          |   |  |
|  |   |                                                           |   |  |
|  |   |   +--------------------------------------------------+    |   |  |
|  |   |   |         ORDER ID -> ORDER* MAP                    |    |   |  |
|  |   |   |   (for O(1) cancel/modify by order_id)           |    |   |  |
|  |   |   +--------------------------------------------------+    |   |  |
|  |   |                                                           |   |  |
|  |   +----------------------------------------------------------+   |  |
|  |                                                                   |  |
|  +------------------------------------------------------------------+  |
|            |                                                            |
|            v                                                            |
|  +------------------------------------------------------------------+  |
|  |                       EVENT OUTPUT                                |  |
|  |                                                                   |  |
|  |   +----------+    +----------+    +----------+    +----------+   |  |
|  |   |Order ACK |    |  Trade   |    |  Cancel  |    | Market   |   |  |
|  |   |          |    |  Event   |    |   ACK    |    | Data     |   |  |
|  |   +----------+    +----------+    +----------+    +----------+   |  |
|  |                                                                   |  |
|  +------------------------------------------------------------------+  |
|                                                                         |
|  +------------------------------------------------------------------+  |
|  |                      MEMORY POOL                                  |  |
|  |   Pre-allocated Order objects for zero-allocation hot path       |  |
|  +------------------------------------------------------------------+  |
|                                                                         |
+------------------------------------------------------------------------+

5.2 Key Components

Component Responsibility Performance Requirement
Order Validator Validate order parameters before processing O(1), < 50ns
Order Book Maintain sorted price levels and order queues O(log n) add, O(1) best price
Price Level FIFO queue of orders at same price O(1) add/remove from ends
Order Map OrderID -> Order* for fast lookup O(1) lookup and delete
Matching Logic Execute crossing orders, generate trades O(m) where m = matched orders
Memory Pool Pre-allocated order storage O(1) allocate/deallocate
Event Publisher Output trades, acks, market data Non-blocking, append-only

5.3 Data Structures

Price Level Organization Options:

Data Structure Add Level Remove Level Find Best Memory Overhead Cache Behavior
std::map<Price, Level> O(log n) O(log n) O(1) amortized High (nodes) Poor (pointer chase)
Sorted vector O(n) O(n) O(1) Low Excellent
Skip list O(log n) O(log n) O(1) Medium Medium
Array + hash map O(1) O(1) O(1) Medium Excellent

Recommended: Price Level Array

Price Level Array Design

Price Range: $100.00 - $200.00 at $0.01 increments = 10,000 possible levels
Array Size: 10,000 x sizeof(PriceLevel*) = 80KB (fits in L2 cache!)

Price Index = (price - base_price) / tick_size

+----------------------------------------------------------------+
| Index:  0      1      2      3      ...   9999                  |
| Price: 100.00 100.01 100.02 100.03  ...  199.99                |
|        |      |      |      |       ...   |                    |
|        v      v      v      v             v                    |
|       NULL  Level* NULL  Level*    ...  NULL                   |
|              |            |                                     |
|              v            v                                     |
|            +-----+     +-----+                                 |
|            |Queue|     |Queue|                                 |
|            |O1->O2     |O7->O8                                 |
|            +-----+     +-----+                                 |
+----------------------------------------------------------------+

Best bid/ask tracking:
- best_bid_idx: index of highest non-empty bid level
- best_ask_idx: index of lowest non-empty ask level

After removing best level, scan for next non-empty level
(Typically 1-3 indices away, so O(1) in practice)

Order Queue: Intrusive Doubly-Linked List

Intrusive Linked List Design

Instead of:
  std::list<Order*>  // Allocates ListNode objects separately

Use intrusive:
  struct Order {
      Order* prev;    // Part of Order struct itself
      Order* next;    // No separate allocation!
      uint64_t id;
      uint32_t qty;
      // ...
  };

Price Level Queue:
+-------------------------------------------------------------+
|  head --> Order_A <--> Order_B <--> Order_C <-- tail        |
|           |                                     |            |
|           +------------- FIFO queue ------------+           |
+-------------------------------------------------------------+

Operations:
  Add to back:   O(1) - tail->next = new; new->prev = tail; tail = new;
  Remove any:    O(1) - prev->next = next; next->prev = prev;
  Get front:     O(1) - return head;

Order Struct Memory Layout:

// BAD: Wasted space due to alignment padding (56 bytes with 11 wasted)
struct OrderBad {
    char side;           // 1 byte + 7 padding
    double price;        // 8 bytes
    uint32_t quantity;   // 4 bytes + 4 padding
    uint64_t order_id;   // 8 bytes
    Order* next;         // 8 bytes
    Order* prev;         // 8 bytes
    uint64_t timestamp;  // 8 bytes
};

// GOOD: Ordered by size to minimize padding (48 bytes, no waste)
struct OrderGood {
    uint64_t order_id;   // 8 bytes
    uint64_t timestamp;  // 8 bytes
    double price;        // 8 bytes
    Order* next;         // 8 bytes
    Order* prev;         // 8 bytes
    uint32_t quantity;   // 4 bytes
    char side;           // 1 byte
    char padding[3];     // explicit padding
};

// BEST: Fixed-point price, minimal size (40 bytes)
struct OrderBest {
    Order* next;           // 8 bytes - hot path access first
    Order* prev;           // 8 bytes
    uint64_t order_id;     // 8 bytes
    int64_t price_ticks;   // 8 bytes (price in tick units, not float)
    uint32_t quantity;     // 4 bytes
    uint16_t symbol_id;    // 2 bytes
    uint8_t side;          // 1 byte
    uint8_t flags;         // 1 byte (canceled, partial, etc.)
};

5.4 Algorithm Overview

Add Order Algorithm:

function add_order(order):
    // Step 1: Validate
    if not valid(order):
        return REJECTED

    // Step 2: Check for match
    if order.side == BUY:
        while order.remaining_qty > 0 AND best_ask exists AND order.price >= best_ask.price:
            match_at_level(order, best_ask_level)
    else:  // SELL
        while order.remaining_qty > 0 AND best_bid exists AND order.price <= best_bid.price:
            match_at_level(order, best_bid_level)

    // Step 3: If remaining quantity, add to book
    if order.remaining_qty > 0:
        level = get_or_create_level(order.side, order.price)
        level.add_order(order)
        order_map[order.id] = order
        update_best_price(order.side)

    return order.status

function match_at_level(incoming, level):
    for resting_order in level.orders:  // FIFO order
        match_qty = min(incoming.remaining_qty, resting_order.remaining_qty)

        emit_trade(incoming, resting_order, match_qty, level.price)

        incoming.remaining_qty -= match_qty
        resting_order.remaining_qty -= match_qty

        if resting_order.remaining_qty == 0:
            level.remove_order(resting_order)
            order_map.remove(resting_order.id)
            order_pool.deallocate(resting_order)

        if incoming.remaining_qty == 0:
            break

    if level.is_empty():
        remove_level(level)
        update_best_price(level.side)

Cancel Order Algorithm:

function cancel_order(order_id):
    order = order_map.get(order_id)
    if order is None:
        return NOT_FOUND

    level = get_level(order.side, order.price)
    level.remove_order(order)  // O(1) with doubly-linked list

    if level.is_empty():
        remove_level(level)
        update_best_price(order.side)

    order_map.remove(order_id)
    order_pool.deallocate(order)

    return CANCELED

6. Implementation Guide

6.1 Development Environment Setup

C++ Setup:

# Ubuntu/Debian
sudo apt-get update
sudo apt-get install g++ cmake gdb valgrind linux-tools-generic

# Verify compiler supports C++17/20
g++ --version  # Need 7+ for C++17, 10+ for C++20

# Install perf for profiling (Linux)
sudo apt-get install linux-tools-common linux-tools-$(uname -r)

# macOS
xcode-select --install
brew install cmake

# Create project
mkdir -p orderbook/{src,include,tests,benchmarks}
cd orderbook

Rust Setup:

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env

# Create project
cargo new orderbook
cd orderbook

# Add dependencies to Cargo.toml
cat >> Cargo.toml << 'EOF'
[dev-dependencies]
criterion = "0.5"

[[bench]]
name = "orderbook_bench"
harness = false
EOF

6.2 Project Structure

C++ Structure:

orderbook/
+-- CMakeLists.txt
+-- src/
|   +-- main.cpp              # CLI entry point
|   +-- orderbook.cpp         # Order book implementation
|   +-- matching_engine.cpp   # Matching logic
|   +-- memory_pool.cpp       # Custom allocator (Phase 2)
+-- include/
|   +-- order.hpp             # Order struct
|   +-- orderbook.hpp         # OrderBook class
|   +-- price_level.hpp       # PriceLevel with order queue
|   +-- matching_engine.hpp   # Matching interface
|   +-- memory_pool.hpp       # Pool allocator
+-- tests/
|   +-- test_orderbook.cpp
|   +-- test_matching.cpp
+-- benchmarks/
|   +-- bench_orderbook.cpp
+-- README.md

Rust Structure:

orderbook/
+-- Cargo.toml
+-- src/
|   +-- main.rs               # CLI entry point
|   +-- lib.rs                # Library root
|   +-- order.rs              # Order struct
|   +-- orderbook.rs          # OrderBook implementation
|   +-- price_level.rs        # PriceLevel
|   +-- matching.rs           # Matching logic
+-- tests/
|   +-- integration_tests.rs
+-- benches/
|   +-- orderbook_bench.rs
+-- README.md

6.3 The Core Question You’re Answering

“How do you organize price levels and orders such that the most common operations (add order, match, cancel) are O(1) or O(log n) while maintaining cache efficiency?”

This question forces you to confront the reality that big-O notation alone is insufficient for HFT. An O(log n) tree operation with poor cache behavior can be slower than an O(n) scan of a cache-resident array.

The answer involves:

  1. Choosing data structures that keep hot data together in memory
  2. Minimizing pointer indirection (each pointer chase is a potential cache miss)
  3. Pre-allocating memory to avoid allocation in the hot path
  4. Using integer arithmetic (fixed-point prices) instead of floating-point

6.4 Concepts You Must Understand First

Before starting implementation, verify your understanding with these self-assessment questions:

Concept Self-Assessment Question Where to Learn
Hash maps What is the expected time complexity of insert/lookup? What happens during a resize? CS:APP Ch. 6, Language docs
Linked lists How do you remove a node from a doubly-linked list in O(1) when you have a pointer to that node? Any data structures textbook
CPU cache hierarchy What is a cache line (typically 64 bytes)? Why does data locality matter? CS:APP Ch. 6
Struct alignment Why does the order of struct members affect sizeof? What is padding? CS:APP Ch. 3.9
Memory allocation Why is malloc potentially slow? What is a memory pool? CS:APP Ch. 9
Fixed-point arithmetic How do you represent $150.25 as an integer (15025 ticks)? Why prefer this over float? Any numerics reference

6.5 Questions to Guide Your Design

Data Structure Questions:

  1. How will you represent prices internally? (float, double, int64 ticks?)
  2. What data structure will hold all price levels? (map, vector, array?)
  3. How will you maintain FIFO order within a price level?
  4. How will you enable O(1) cancel-by-order-id?
  5. How will you track the best bid and best ask efficiently?
  6. What happens when a price level becomes empty?

Memory Questions:

  1. Will you allocate Order objects on demand or pre-allocate a pool?
  2. How much memory will your order book use with 100,000 orders?
  3. What is the size of your Order struct after considering alignment?
  4. Will your hot data structures fit in L1/L2/L3 cache?

Algorithm Questions:

  1. When a new buy order matches multiple sell orders, in what order are they filled?
  2. After filling the best ask level completely, how do you find the new best ask?
  3. What happens if a cancel arrives for an order that’s currently being matched?
  4. How do you handle partial fills (order partially matched, rest rests in book)?

Performance Questions:

  1. What operations happen most frequently? (Hint: typically cancel > add > match)
  2. Where will cache misses likely occur in your design?
  3. How will you benchmark your implementation?
  4. What is your target latency for each operation type?

6.6 Thinking Exercise

Before writing any code, work through this scenario by hand on paper:

Scenario:

Start with an empty order book. Process these orders in sequence:

1. ADD BUY  100 @ 50.25  order_A
2. ADD BUY  200 @ 50.24  order_B
3. ADD BUY  150 @ 50.25  order_C
4. ADD SELL  50 @ 50.30  order_D
5. ADD SELL 300 @ 50.25  order_E
6. CANCEL order_B
7. ADD SELL 100 @ 50.20  order_F

Questions to answer on paper:

  1. After step 3, draw the bid side of the book. Which orders are at price 50.25, and in what FIFO order?

  2. After step 4, what does the book look like? What trades (if any) were generated?

  3. After step 5, what trades were generated? List each trade with: price, quantity, buyer order, seller order.

  4. After step 5, what is the state of order_A? Order_C? Order_E?

  5. After step 6, draw the full book (both bids and asks).

  6. After step 7, what trades were generated? What is the final book state?

Verification:

  • Total shares bought should equal total shares sold across all trades
  • Every trade should have happened at the price of the resting (older) order
  • FIFO should have been maintained within each price level

6.7 Hints in Layers

If you’re stuck, reveal hints one at a time:

Hint 1 - Starting Direction Start with the simplest possible implementation using standard library containers: **C++:** ```cpp std::map<int64_t, std::list<Order*>> bids; // price -> orders (descending) std::map<int64_t, std::list<Order*>> asks; // price -> orders (ascending) std::unordered_map<uint64_t, Order*> order_lookup; // id -> order ``` **Rust:** ```rust use std::collections::{BTreeMap, VecDeque, HashMap}; bids: BTreeMap<i64, VecDeque> // Need Reverse wrapper for descending asks: BTreeMap<i64, VecDeque> order_lookup: HashMap<u64, OrderRef> ``` Get correctness first. Benchmark. Then optimize. </details>
Hint 2 - Matching Logic Structure For the matching logic, process orders in this sequence: 1. **Check if incoming order crosses the spread** - Buy order crosses if: `buy_price >= best_ask_price` - Sell order crosses if: `sell_price <= best_bid_price` 2. **If it crosses, iterate through opposing levels** ```cpp while (remaining_qty > 0 && crosses_spread(order)) { auto& best_level = get_best_opposing_level(order.side); match_against_level(order, best_level); } ``` 3. **Match against individual orders in FIFO order** ```cpp void match_against_level(Order& incoming, Level& level) { while (!level.orders.empty() && incoming.remaining > 0) { Order& resting = level.orders.front(); uint32_t match_qty = std::min(incoming.remaining, resting.remaining); execute_trade(incoming, resting, match_qty, level.price); // Update quantities, remove filled orders } } ``` 4. **After matching, add remainder to book if any**
Hint 3 - Technical Implementation Details **Price Level Array Implementation (for optimization phase):** ```cpp class OrderBook { static constexpr int64_t MIN_PRICE = 1; // $0.01 static constexpr int64_t MAX_PRICE = 1000000; // $10,000.00 static constexpr size_t NUM_LEVELS = MAX_PRICE - MIN_PRICE + 1; std::array<PriceLevel*, NUM_LEVELS> bid_levels{}; std::array<PriceLevel*, NUM_LEVELS> ask_levels{}; int64_t best_bid_price = 0; // 0 = no bids int64_t best_ask_price = MAX_PRICE + 1; // MAX+1 = no asks size_t price_to_index(int64_t price) { return static_cast(price - MIN_PRICE); } void update_best_bid_after_remove(int64_t removed_price) { if (removed_price != best_bid_price) return; // Scan downward for next non-empty level for (int64_t p = removed_price - 1; p >= MIN_PRICE; --p) { if (bid_levels[price_to_index(p)] != nullptr) { best_bid_price = p; return; } } best_bid_price = 0; // No bids remaining } }; ``` **Intrusive List for O(1) Order Removal:** ```cpp struct Order { Order* prev = nullptr; Order* next = nullptr; // ... other fields }; class OrderQueue { Order* head = nullptr; Order* tail = nullptr; uint32_t total_qty = 0; public: void push_back(Order* order) { if (tail) { tail->next = order; order->prev = tail; tail = order; } else { head = tail = order; } total_qty += order->quantity; } void remove(Order* order) { if (order->prev) order->prev->next = order->next; else head = order->next; if (order->next) order->next->prev = order->prev; else tail = order->prev; total_qty -= order->remaining_qty; order->prev = order->next = nullptr; } Order* front() { return head; } bool empty() { return head == nullptr; } }; ``` </details>
Hint 4 - Tools and Debugging **Benchmarking with high precision:** ```cpp #include auto start = std::chrono::high_resolution_clock::now(); // ... operation auto end = std::chrono::high_resolution_clock::now(); auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count(); ``` **For even more accurate timing on x86:** ```cpp #include uint64_t start = __rdtsc(); // ... operation uint64_t end = __rdtsc(); uint64_t cycles = end - start; // Convert to ns: cycles / cpu_freq_ghz ``` **Profiling with perf (Linux):** ```bash # Record performance counters perf stat -e cycles,instructions,cache-misses,cache-references ./orderbook_bench # Record call graph for flamegraph perf record -g ./orderbook_bench perf report ``` **Checking struct sizes and alignment:** ```cpp static_assert(sizeof(Order) == 40, "Order size unexpected - check padding"); std::cout << "Order size: " << sizeof(Order) << " bytes\n"; std::cout << "Order alignment: " << alignof(Order) << "\n"; // Check layout std::cout << "Offset of prev: " << offsetof(Order, prev) << "\n"; std::cout << "Offset of next: " << offsetof(Order, next) << "\n"; ``` **Memory allocation tracking:** ```bash # Use valgrind massif for heap profiling valgrind --tool=massif ./orderbook_bench ms_print massif.out.* # Check for allocations in hot path valgrind --tool=callgrind ./orderbook_bench ``` </details> ### 6.8 The Interview Questions They'll Ask These are real questions asked at HFT firms and exchanges: 1. **"Walk me through what happens when a limit order arrives at your book."** - Describe validation, spread crossing check, matching loop, remainder handling 2. **"Why did you choose [data structure X] for price levels? What are the tradeoffs?"** - Be prepared to discuss map vs array vs skip list, with cache behavior analysis 3. **"How do you handle the cancel-add race condition?"** - What if a cancel arrives while the order is mid-match? - What if the same order_id is reused before cancel completes? 4. **"What is the memory layout of your Order struct? Why did you order the fields that way?"** - Demonstrate understanding of alignment, padding, cache lines 5. **"Your cancel is O(1). Show me exactly how that works."** - Walk through the hash map lookup + intrusive list removal 6. **"What happens when the best bid or ask price level is depleted?"** - How do you find the new best price? What's the complexity? 7. **"How would you handle multiple symbols? What changes?"** - One book per symbol, symbol_id field, book lookup structure 8. **"Your benchmark shows 500ns. Where is the time going? How would you make it faster?"** - Discuss profiling results, cache analysis, remaining optimizations 9. **"How do you ensure correctness? How do you know FIFO is maintained?"** - Property-based testing, invariant checking, trace logging 10. **"What's the difference between your implementation and how NYSE/NASDAQ does it?"** - FPGA acceleration, kernel bypass, co-location, distributed matching ### 6.9 Books That Will Help | Topic | Book | Specific Chapters | |-------|------|-------------------| | Cache optimization, memory hierarchy | "Computer Systems: A Programmer's Perspective" (CS:APP) by Bryant & O'Hallaron | Ch. 6 (Memory Hierarchy) | | Struct layout and alignment | CS:APP | Ch. 3.9 (Heterogeneous Data Structures) | | Memory allocators | CS:APP | Ch. 9 (Virtual Memory), especially 9.9 | | C++ data structures for trading | "Building Low Latency Applications with C++" by Sourav Ghosh | Ch. 4-6 | | Lock-free programming (for future optimization) | "Rust Atomics and Locks" by Mara Bos | Ch. 1-3, 5-6 | | Rust ownership and performance | "Programming Rust" by Blandy & Orendorff | Ch. 4-5, 16, 21 | | Linux performance tools | "Systems Performance" by Brendan Gregg | Ch. 5-7 | | Data structures deep dive | "Introduction to Algorithms" (CLRS) | Ch. 12-13 (Trees) | ### 6.10 Implementation Phases **Phase 1: Correctness First (3-5 days)** Goal: Working order book with std::map/BTreeMap Deliverables: - Order struct with all required fields - OrderBook with add/cancel/match operations - Correct price-time priority matching - CLI for interactive testing - Basic unit tests Expected latency: 2-5 microseconds per operation (that's okay!) **Phase 2: Custom Allocation (2-3 days)** Goal: Eliminate heap allocation from hot path Deliverables: - Memory pool for Order objects - Pre-allocated order ID map (if sequential IDs) - Benchmark showing allocation impact - Stress test with millions of orders Expected improvement: 3-5x latency reduction **Phase 3: Cache Optimization (2-3 days)** Goal: Maximize cache efficiency Deliverables: - Price level array replacing tree - Optimized Order struct layout - Best price tracking optimization - Cache miss analysis with perf Expected improvement: 2-5x additional latency reduction **Final Targets:** - Add Order: < 500ns (p50) - Cancel Order: < 200ns (p50) - Best Bid/Ask: < 50ns - Throughput: > 1M orders/second ### 6.11 Key Implementation Decisions | Decision | Option A | Option B | Recommendation | |----------|----------|----------|----------------| | Price representation | float/double | int64 ticks | **int64 ticks** - Exact comparisons, faster | | Price levels | std::map (tree) | Array indexed by price | **Array** for phase 3, map for phase 1 | | Order queue | std::list/VecDeque | Intrusive linked list | **Intrusive** - No allocation, O(1) remove | | Order ID lookup | std::unordered_map | Pre-allocated array | Map first, array if IDs sequential | | Memory allocation | malloc/new per order | Memory pool | **Pool** for phase 2+ | --- ## 7. Testing Strategy ### Unit Tests ```cpp TEST(OrderBook, BasicAddOrder) { OrderBook book; OrderId id = book.add_order(Side::BUY, 5025, 100); // 50.25 price ASSERT_NE(id, INVALID_ORDER_ID); EXPECT_EQ(book.best_bid_price(), 5025); EXPECT_EQ(book.best_bid_qty(), 100); EXPECT_TRUE(book.best_ask_price() == NO_PRICE); } TEST(OrderBook, MatchingCrossesSpread) { OrderBook book; book.add_order(Side::BUY, 5025, 100); // BID 50.25 x 100 auto trades = book.add_order(Side::SELL, 5020, 50); // SELL 50.20 x 50 ASSERT_EQ(trades.size(), 1); EXPECT_EQ(trades[0].price, 5025); // Trades at resting order price EXPECT_EQ(trades[0].quantity, 50); EXPECT_EQ(book.best_bid_qty(), 50); // Remaining } TEST(OrderBook, FIFOWithinPriceLevel) { OrderBook book; auto id1 = book.add_order(Side::BUY, 5025, 100); auto id2 = book.add_order(Side::BUY, 5025, 100); // Same price, later auto trades = book.add_order(Side::SELL, 5025, 150); // First trade should be against id1 (arrived first) EXPECT_EQ(trades[0].buyer_id, id1); EXPECT_EQ(trades[0].quantity, 100); // Second trade against id2 EXPECT_EQ(trades[1].buyer_id, id2); EXPECT_EQ(trades[1].quantity, 50); } TEST(OrderBook, CancelOrder) { OrderBook book; auto id = book.add_order(Side::BUY, 5025, 100); EXPECT_TRUE(book.cancel_order(id)); EXPECT_TRUE(book.best_bid_price() == NO_PRICE); EXPECT_FALSE(book.cancel_order(id)); // Already canceled } TEST(OrderBook, PartialFill) { OrderBook book; book.add_order(Side::BUY, 5025, 100); auto trades = book.add_order(Side::SELL, 5025, 30); EXPECT_EQ(trades[0].quantity, 30); EXPECT_EQ(book.best_bid_qty(), 70); // 100 - 30 remaining } ``` ### Integration Tests ```cpp TEST(OrderBook, FullTradingScenario) { OrderBook book; // Build up book book.add_order(Side::BUY, 5024, 200); book.add_order(Side::BUY, 5025, 100); book.add_order(Side::BUY, 5025, 150); book.add_order(Side::SELL, 5030, 150); book.add_order(Side::SELL, 5028, 300); // Aggressive sell sweeps through bids auto trades = book.add_order(Side::SELL, 5020, 400); // Should have matched: 100@50.25, 150@50.25, 150@50.24 uint32_t total_matched = 0; for (auto& t : trades) total_matched += t.quantity; EXPECT_EQ(total_matched, 400); // Remaining bid at 50.24 should be 50 EXPECT_EQ(book.best_bid_price(), 5024); EXPECT_EQ(book.best_bid_qty(), 50); } ``` ### Benchmark Tests ```cpp void benchmark_add_cancel(int iterations) { OrderBook book; std::vector ids; ids.reserve(iterations); // Measure add latency auto start = high_resolution_clock::now(); for (int i = 0; i < iterations; ++i) { int64_t price = 5000 + (i % 100); ids.push_back(book.add_order(Side::BUY, price, 100)); } auto add_time = high_resolution_clock::now() - start; // Measure cancel latency start = high_resolution_clock::now(); for (auto id : ids) { book.cancel_order(id); } auto cancel_time = high_resolution_clock::now() - start; std::cout << "Add: " << (add_time.count() / iterations) << "ns avg\n"; std::cout << "Cancel: " << (cancel_time.count() / iterations) << "ns avg\n"; } ``` --- ## 8. Common Pitfalls & Debugging ### Pitfall 1: Using Floating Point for Prices **Problem:** ```cpp // BAD: Floating point comparison issues if (order.price == best_ask.price) // May fail due to precision! ``` **Root Cause:** Floating point arithmetic can introduce small errors. `150.25` might actually be `150.24999999...` **Fix:** ```cpp // GOOD: Use fixed-point integer representation // Price 150.25 with tick size 0.01 = 15025 ticks int64_t price_ticks = static_cast(price / tick_size + 0.5); ``` **Verification:** Print prices in hex before and after calculations. ### Pitfall 2: Memory Allocation in Hot Path **Problem:** ```cpp // BAD: Allocating during every add void add_order(...) { Order* order = new Order(...); // Heap allocation! } ``` **Root Cause:** `new`/`malloc` can take 100-500ns and cause variance. **Fix:** ```cpp // GOOD: Pre-allocated pool Order* order = order_pool.allocate(); // O(1), ~5-10ns ``` **Verification:** Run under valgrind --tool=massif to see heap activity. ### Pitfall 3: Iterator Invalidation During Matching **Problem:** ```cpp // BAD: Modifying container while iterating for (auto it = level.orders.begin(); it != level.orders.end(); ++it) { if (should_remove(*it)) { level.orders.erase(it); // Invalidates iterator! } } ``` **Root Cause:** Erasing from std::list/vector invalidates iterators. **Fix:** ```cpp // GOOD: Use intrusive list with careful pointer handling // Or: Collect to-remove items, then erase after loop for (auto it = level.orders.begin(); it != level.orders.end(); ) { if (should_remove(*it)) { it = level.orders.erase(it); // erase returns next valid iterator } else { ++it; } } ``` ### Pitfall 4: Forgetting to Update Best Price After Level Removal **Problem:** ```cpp void remove_order(Order* order) { level.remove(order); if (level.empty()) { levels.erase(level.price); // Forgot to update best_bid_price or best_ask_price! } } ``` **Root Cause:** Best price becomes stale, causing incorrect matching. **Fix:** ```cpp if (level.empty()) { levels.erase(level.price); if (side == Side::BUY && level.price == best_bid_price) { update_best_bid(); // Scan for new best } } ``` ### Pitfall 5: Incorrect FIFO Order **Problem:** Using `std::set` or `std::map` for orders at a price level. **Root Cause:** These containers sort by key, not insertion order. **Fix:** Use a queue-like structure (std::list, VecDeque, or intrusive list). **Verification:** Add three orders at same price, match two, verify the first two were matched. --- ## 9. Extensions & Challenges ### Extension 1: Market Orders (Medium) Add support for market orders that execute immediately at the best available price: - No price limit specified - Take whatever liquidity is available - Handle case where not enough liquidity exists ### Extension 2: IOC and FOK Order Types (Medium) - **IOC (Immediate-Or-Cancel)**: Match what you can, cancel the rest - **FOK (Fill-Or-Kill)**: Match entire quantity or cancel entirely ### Extension 3: Order Modification (Medium) Allow changing order price/quantity: - Price change: Cancel old, add new (loses time priority) - Quantity decrease: Modify in place (keeps time priority) - Quantity increase: Cancel old, add new (loses time priority) ### Extension 4: Multiple Symbols (Hard) Maintain separate order books for different symbols: - Symbol ID in Order struct - Book lookup by symbol (hash map or array) - Consider memory layout for multiple books ### Extension 5: SIMD Price Level Scanning (Advanced) Use SIMD instructions to scan for non-empty price levels: ```cpp // Using AVX2 to scan 4 price levels at once __m256i levels = _mm256_load_si256(...); __m256i mask = _mm256_cmpeq_epi64(levels, _mm256_setzero_si256()); int empty_mask = _mm256_movemask_pd((__m256d)mask); // Process non-empty levels ``` --- ## 10. Real-World Connections ### How Exchanges Actually Do This **NASDAQ INET:** - Written in C++, processes 1M+ messages/second - Uses FPGA for lowest-latency matching - Price-time priority with some pro-rata allocation - Co-location available for ~$10K/month **CME Globex:** - Sub-microsecond matching engine - Specialized network hardware (Arista switches) - Uses central limit order book (CLOB) model - Handles 500M+ messages per day **NYSE Pillar:** - Cloud-native architecture (unusual for exchanges) - Still achieves microsecond latency - Complex order types and auctions ### What HFT Firms Add Beyond This 1. **Kernel bypass networking** (DPDK, RDMA) 2. **FPGA-based matching** for nanosecond latency 3. **Lock-free everything** (no mutexes in hot path) 4. **Custom memory allocators** for determinism 5. **CPU affinity and isolation** (dedicated cores) 6. **Timestamping at the NIC** (PTP, GPS) --- ## 11. Resources ### Books | Book | Relevance | |------|-----------| | "Computer Systems: A Programmer's Perspective" | Cache optimization, memory layout | | "Building Low Latency Applications with C++" | Full trading system design | | "Programming Rust" | Rust implementation patterns | | "Rust Atomics and Locks" | Future lock-free optimization | | "The Linux Programming Interface" | System calls, memory management | ### Online Resources - [aspone/OrderBook (GitHub)](https://github.com/aspone/OrderBook) - Reference C++ implementation - [Building-Low-Latency-Applications-with-CPP (GitHub)](https://github.com/PacktPublishing/Building-Low-Latency-Applications-with-CPP) - Full code examples - [Alex Abosi's LOB Implementation Guide](https://alexabosi.wordpress.com/2014/08/28/limit-order-book-implementation-for-low-latency-trading-in-c/) - Practical walkthrough - [Databento: Rust vs C++ for Trading](https://databento.com/blog/rust-vs-cpp) - Language comparison ### Papers - "A Design Patterns for Low-Latency Applications" (arXiv) - Design patterns for trading systems - "The High-Frequency Trading Arms Race" - Market structure analysis --- ## 12. Self-Assessment Checklist ### Understanding (Can You Explain?) - [ ] What is a limit order book and why is it the core of an exchange? - [ ] How does price-time priority (FIFO) work within a price level? - [ ] Why does the order book use fixed-point prices instead of floating point? - [ ] What makes intrusive linked lists faster than std::list? - [ ] Why is memory allocation in the hot path problematic? - [ ] How does CPU cache hierarchy affect data structure choice? - [ ] What determines whether an incoming order will match or rest in the book? ### Implementation (Did You Build?) - [ ] Order struct with proper memory layout (no wasted padding) - [ ] Price level storage (map in phase 1, array in phase 3) - [ ] FIFO order queue within each price level - [ ] Order ID to Order* lookup for O(1) cancel - [ ] Matching algorithm with correct price-time priority - [ ] Best bid/ask tracking with update on level removal - [ ] Memory pool for order allocation (phase 2) ### Performance (Did You Measure?) - [ ] Add order latency < 500ns (p50)? - [ ] Cancel order latency < 200ns (p50)? - [ ] Best bid/ask query < 50ns? - [ ] Throughput > 1M orders/second? - [ ] No heap allocations in hot path (verified with profiler)? ### Quality (Did You Verify?) - [ ] FIFO order is maintained (tested with multiple orders at same price) - [ ] Partial fills work correctly - [ ] Canceling non-existent order returns error - [ ] Book state is consistent after any sequence of operations - [ ] Memory is properly cleaned up (no leaks) --- ## 13. Submission / Completion Criteria Your project is complete when: 1. **Functionality** - [ ] Add, cancel, and match operations work correctly - [ ] Price-time priority is maintained - [ ] Trades are generated with correct price and quantity - [ ] Book state is queryable (best bid/ask, depth) 2. **Performance** - [ ] Benchmark results documented - [ ] Phase 1 baseline measured - [ ] At least one optimization phase completed - [ ] Improvement quantified 3. **Code Quality** - [ ] Clean, readable code with comments - [ ] Unit tests with good coverage - [ ] README with build instructions - [ ] Example usage documented 4. **Understanding** - [ ] Can explain design decisions - [ ] Can answer interview questions from section 6.8 - [ ] Can describe future optimization opportunities --- **Next Project**: [P02 - Lock-Free Market Data Handler](P02-lock-free-market-data-handler.md) - Now that you have an order book, learn to feed it market data using lock-free queues for zero-mutex, zero-blocking data distribution.