getcracked
Blog / Engineering

Optimizing a Limit Order Book for HFT

Engineering

The Order Book is the heart of an exchange or trading system. It must support Insert, Cancel, and Execute operations with predictable, constant-time latency.


The Goal: O(1) Everything

OperationFrequencyTarget Latency
Add OrderHigh< 100ns
Cancel OrderVery High (90% of traffic)< 50ns
Execute (Match)Low< 200ns

Why std::map Fails

A std::map<Price, Level> is usually implemented as a Red-Black Tree.
Cons: $O(\log N)$ inserts/deletes. Worst of all: Pointer Chasing. Each node is allocated separately on the heap, destroying CPU cache locality.

The Standard HFT Pattern: Array + Linked List

1. Price Levels as Array: If ticks are dense (e.g. Eurodollar Futures), use a flat array indexed by price. $O(1)$ access.
2. Orders as Doubly Linked List: Each Price Level contains a linked list of orders.
3. Order Map: A hash map (std::unordered_map or custom open-addressing map) mapping OrderID → (Price, OrderNode*).

1
// Conceptual C++ Structure for O(1) Add/Cancel/Execute
2
struct Order {
3
    uint64_t id;
4
    uint32_t quantity;
5
    Order* next;
6
    Order* prev;
7
};
8

9
struct PriceLevel {
10
    uint64_t price;
11
    Order* head; // Front of the queue (priority execution)
12
    Order* tail; // Back of the queue (new orders here)
13
};
14

15
// If using flat array for prices (e.g. tick size 1 cent)
16
// indices correspond to price * 100
17
std::vector<PriceLevel> book(MAX_PRICE);
18

19
// For O(1) Cancellations:
20
// We need to find the Order* instantly given an ID.
21
// std::unordered_map is O(1) avg, but O(N) worst case.
22
// Use a custom open-addressing hash table with linear probing.
23
FlatHashMap<uint64_t, Order*> order_map;
24

25
void cancel_order(uint64_t order_id) {
26
    Order* ord = order_map.get(order_id);
27
    if (!ord) return;
28

29
    // Unlink from doubly linked list in O(1)
30
    if (ord->prev) ord->prev->next = ord->next;
31
    if (ord->next) ord->next->prev = ord->prev;
32
    
33
    // Update head/tail of PriceLevel if needed...
34
    
35
    // Free nodes to a custom memory pool (Object Pool)
36
    // NEVER call 'delete' in the hot path!
37
    order_pool.free(ord);
38
}

Memory Pools (Arena Allocation)

new and delete are system calls (expensive!). They also fragment memory.
Pre-allocate a giant array of Order objects at startup. When you need a new order, just increment an index. When you free it, add it to a "free list". This keeps all orders contiguous in memory, maximizing cache hits.