๐ฌ Saturday Deep Dive: ่ก็ฅจไบคๆๆๆฎๅๅผๆ / Stock Exchange Order Matching Engine (18 min read)
๐ NeetCode: 54/150 ยท SysDesign: 53/40 ยท Behavioral: 53/40 ยท Frontend: 37/50 ยท AI: 26/30
๐ฅ 65-day streak!
Overview / ๆฆ่ฟฐ
ไธญๆ๏ผ
ๆฎๅๅผๆ๏ผOrder Matching Engine๏ผOME๏ผๆฏๆดไธชไบคๆๆ็ๅฟ่ใ็บฝ็บฆ่ฏๅธไบคๆๆๆฏ็งๅค็ๆฐ็พไธ็ฌ่ฎขๅ๏ผๅปถ่ฟ่ฆๆฑๅจๅพฎ็ง็บงๅซใๅฎ่งฃๅณ็ๆ ธๅฟ้ฎ้ขๆฏ๏ผๅจไปทๆ ผ/ๆถ้ดไผๅ
็ๅๅไธ๏ผๅฐไนฐๅ๏ผBid๏ผๅๅๅ๏ผAsk๏ผ้ซๆ้
ๅฏน๏ผๅนถไปฅๆๅ
ฌๅนณ็ไปทๆ ผๆไบคใ
่ฟไธช็ณป็ป็้พ็นไธๅจไบ้ป่พๅคๆ๏ผ่ๅจไบ๏ผ
ๆ็ซฏๅๅ้ ๏ผๆฏ็ง็พไธ็บง TPS็กฎๅฎๆง ๏ผ็ธๅ็่ฎขๅๅบๅ๏ผๆฐธ่ฟไบง็็ธๅ็ๆไบค็ปๆๅ
ฌๅนณๆง ๏ผFIFO๏ผๅ
่ฟๅ
ๅบ๏ผไฟ่ฏ๏ผๆ "ๆ้"ๆไน
ๆง ๏ผๅดฉๆบๅ่ฝ็ฒพ็กฎ้ๆพ
English:
An order matching engine is the core of any exchange โ NYSE handles millions of orders/second with microsecond latency. The problem: given buy (bid) and sell (ask) orders, match them at the best fair price following price-time priority. Hard part isn't the algorithm โ it's building it at extreme throughput with deterministic, crash-safe execution.
Why this matters in interviews: it combines data structures (priority queues), concurrency, event sourcing, and system design into one question. Meta, Jane Street, Citadel, and Stripe all ask variants of this.
Part 1: Theory / ็่ฎบๅบ็ก (5 min)
ๆ ธๅฟๆฆๅฟต / Core Concepts
่ฎขๅ็ฑปๅ / Order Types
Copy
้ไปทๅ (Limit Order): ไปฅๆๅฎไปทๆ ผๆๆดไผไปทๆ ผไนฐ/ๅ
ๅธไปทๅ (Market Order): ็ซๅณไปฅๆไผๅธไปทๆไบค
ๆคๅ (Cancel Order): ๅๆถๅทฒๆๆๅ
่ฎขๅ็ฐฟ็ปๆ / The Order Book
ASKS (ๅๆน) โ ๆไปทๆ ผๅๅบๆๅ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Price | Qty | Orders โ
โ 102.50 | 500 | [A1, A2] โ โ best ask (lowest sell)
โ 103.00 | 300 | [A3] โ
โ 103.50 | 1000 | [A4, A5] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SPREAD (ๅทฎไปท)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 101.50 | 400 | [B3, B4] โ
โ 101.00 | 600 | [B2] โ
โ 100.50 | 800 | [B1] โ โ best bid (highest buy)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
BIDS (ไนฐๆน) โ ๆไปทๆ ผ้ๅบๆๅ
ๆฎๅ่งๅ / Matching Rules
Price Priority : Best price first (highest bid, lowest ask)Time Priority : Same price โ earliest order first (FIFO)ๆไบคๆกไปถ : bid_price >= ask_price ๆถ่งฆๅๆไบค
ไธไธชไธๅ้ / Three Invariants
ไปปๆๆถๅป๏ผbest_bid < best_ask๏ผๅฆๅ็ซๅณๆฎๅ๏ผ ๅไปทๆ ผ็บงๅซๅ
๏ผ่ฎขๅไธฅๆ ผ FIFO ๆๆ็ถๆๅๆด้่ฟ event log ๆไน
ๅ๏ผๅฏ้ๆพ
ๅ
ณ้ฎๆฐๆฎ็ปๆ / Key Data Structures
Copy
# Price level: all orders at the same price
# Use deque for O(1) FIFO add/remove at head
from collections import deque
class PriceLevel:
def __init__(self, price: float):
self.price = price
self.orders: deque[Order] = deque() # FIFO queue
self.total_qty: int = 0
# Order book side: sorted price levels
# Bids: max-heap (negate price for Python's min-heap)
# Asks: min-heap
import heapq
class OrderBookSide:
def __init__(self, is_bid: bool):
self.is_bid = is_bid
self.levels: dict[float, PriceLevel] = {}
self.heap: list[float] = [] # price heap for O(log n) best price
Part 2: Step-by-Step Implementation / ไธๆญฅไธๆญฅๅฎ็ฐ (8 min)
ๅฎๆดๆฎๅๅผๆๅฎ็ฐ / Full Matching Engine
Copy
import heapq
import time
import uuid
from collections import deque
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class Side(Enum):
BID = "bid" # Buy order
ASK = "ask" # Sell order
class OrderType(Enum):
LIMIT = "limit"
MARKET = "market"
@dataclass
class Order:
order_id: str
symbol: str
side: Side
order_type: OrderType
price: Optional[float] # None for market orders
quantity: int
timestamp: int = field(default_factory=lambda : time.time_ns())
filled_qty: int = 0
@property
def remaining_qty(self) -> int:
return self.quantity - self.filled_qty
@dataclass
class Trade:
trade_id: str
symbol: str
price: float
quantity: int
buyer_order_id: str
seller_order_id: str
timestamp: int = field(default_factory=lambda : time.time_ns())
class OrderBook:
"""
Maintains bids and asks for a single symbol.
Internal representation:
- bids: max-heap (negate price โ Python min-heap trick)
- asks: min-heap
- levels: dict mapping price โ PriceLevel (deque of orders)
- order_map: dict mapping order_id โ Order (for O(1) cancel)
"""
def __init__(self, symbol: str):
self.symbol = symbol
# Bids: store (-price, timestamp) for max-heap behavior
self._bid_heap: list[tuple[float, int, float]] = [] # (-price, ts, price)
# Asks: store (price, timestamp)
self._ask_heap: list[tuple[float, int]] = []
self._bid_levels: dict[float, deque] = {}
self._ask_levels: dict[float, deque] = {}
self._order_map: dict[str, Order] = {} # for cancellation
self._cancelled: set[str] = set() # lazy deletion set
def best_bid(self) -> Optional[float]:
"""O(log n) โ skip cancelled/empty levels"""
while self._bid_heap:
neg_price, _, price = self._bid_heap[0 ]
level = self._bid_levels.get(price)
# Clean up: if level is empty or all cancelled, pop and continue
if level and len(level) > 0 :
return price
heapq.heappop(self._bid_heap)
if price in self._bid_levels:
del self._bid_levels[price]
return None
def best_ask(self) -> Optional[float]:
"""O(log n) โ skip empty levels"""
while self._ask_heap:
price, _ = self._ask_heap[0 ]
level = self._ask_levels.get(price)
if level and len(level) > 0 :
return price
heapq.heappop(self._ask_heap)
if price in self._ask_levels:
del self._ask_levels[price]
return None
def add_limit_order(self, order: Order):
"""Add limit order to the book (no immediate matching here)."""
self._order_map[order.order_id] = order
if order.side == Side.BID:
if order.price not in self._bid_levels:
self._bid_levels[order.price] = deque()
# Push to heap: negate for max-heap, include timestamp for tie-breaking
heapq.heappush(self._bid_heap, (-order.price, order.timestamp, order.price))
self._bid_levels[order.price].append(order)
else :
if order.price not in self._ask_levels:
self._ask_levels[order.price] = deque()
heapq.heappush(self._ask_heap, (order.price, order.timestamp))
self._ask_levels[order.price].append(order)
def cancel_order(self, order_id: str) -> bool:
"""
Lazy deletion: mark as cancelled, skip during matching.
O(1) โ no heap restructuring needed.
"""
if order_id not in self._order_map:
return False
self._cancelled.add(order_id)
return True
def pop_best_ask(self) -> Optional[Order]:
"""Get and remove the best ask order (FIFO within price level)."""
while True :
best = self.best_ask()
if best is None :
return None
level = self._ask_levels[best]
while level:
order = level[0 ]
if order.order_id in self._cancelled or order.remaining_qty <= 0 :
level.popleft() # Remove cancelled/filled orders
continue
return level[0 ] # Peek โ don't pop yet (partial fills)
# Level empty after cleanup
del self._ask_levels[best]
heapq.heappop(self._ask_heap)
def pop_best_bid(self) -> Optional[Order]:
"""Get and remove the best bid order (FIFO within price level)."""
while True :
best = self.best_bid()
if best is None :
return None
level = self._bid_levels[best]
while level:
order = level[0 ]
if order.order_id in self._cancelled or order.remaining_qty <= 0 :
level.popleft()
continue
return level[0 ]
del self._bid_levels[best]
heapq.heappop(self._bid_heap)
class MatchingEngine:
"""
Core matching engine. Single-threaded per symbol (no locks needed).
Architecture:
- One OrderBook per symbol (isolated state)
- process_order() = the single entry point
- Returns list of Trade objects (for downstream processing)
Matching algorithm: continuous auction (price-time priority)
"""
def __init__(self):
self._books: dict[str, OrderBook] = {}
def _get_book(self, symbol: str) -> OrderBook:
if symbol not in self._books:
self._books[symbol] = OrderBook(symbol)
return self._books[symbol]
def process_order(self, order: Order) -> list[Trade]:
"""
Main entry point. Handles limit and market orders.
Returns list of trades generated.
"""
book = self._get_book(order.symbol)
trades = []
if order.order_type == OrderType.MARKET:
# Market orders: match immediately, no resting in book
trades = self._match_market(book, order)
else :
# Limit orders: try to match first, rest remainder in book
trades = self._match_limit(book, order)
return trades
def _match_limit(self, book: OrderBook, incoming: Order) -> list[Trade]:
"""
Limit order matching:
- BUY limit: match against asks where ask_price <= buy_price
- SELL limit: match against bids where bid_price >= sell_price
"""
trades = []
if incoming.side == Side.BID:
# Incoming buy: sweep asks from best (lowest) upward
while incoming.remaining_qty > 0 :
best_ask = book.best_ask()
# Stop if no asks, or best ask is above our limit price
if best_ask is None or best_ask > incoming.price:
break
trade = self._execute_match(book, incoming, book, is_incoming_bid=True )
if trade:
trades.append(trade)
else :
# Incoming sell: sweep bids from best (highest) downward
while incoming.remaining_qty > 0 :
best_bid = book.best_bid()
# Stop if no bids, or best bid is below our limit price
if best_bid is None or best_bid < incoming.price:
break
trade = self._execute_match(book, incoming, book, is_incoming_bid=False )
if trade:
trades.append(trade)
# Rest unfilled portion in the book
if incoming.remaining_qty > 0 :
# Create a new order for remaining quantity
resting = Order(
order_id=incoming.order_id,
symbol=incoming.symbol,
side=incoming.side,
order_type=incoming.order_type,
price=incoming.price,
quantity=incoming.remaining_qty, # Only rest what's left
timestamp=incoming.timestamp,
)
book.add_limit_order(resting)
return trades
def _match_market(self, book: OrderBook, incoming: Order) -> list[Trade]:
"""Market orders match against all available liquidity at any price."""
trades = []
while incoming.remaining_qty > 0 :
if incoming.side == Side.BID:
if book.best_ask() is None :
break # No liquidity โ partial fill
trade = self._execute_match(book, incoming, book, is_incoming_bid=True )
else :
if book.best_bid() is None :
break
trade = self._execute_match(book, incoming, book, is_incoming_bid=False )
if trade:
trades.append(trade)
return trades
def _execute_match(
self,
book: OrderBook,
incoming: Order,
_book: OrderBook,
is_incoming_bid: bool,
) -> Optional[Trade]:
"""
Execute a single match between incoming order and best resting order.
Price determination: resting order's price (passive price priority).
"""
if is_incoming_bid:
resting = book.pop_best_ask()
else :
resting = book.pop_best_bid()
if resting is None :
return None
# Trade quantity = minimum of both remaining quantities
fill_qty = min(incoming.remaining_qty, resting.remaining_qty)
# Trade price = resting order's price (maker price wins)
fill_price = resting.price
# Update filled quantities
incoming.filled_qty += fill_qty
resting.filled_qty += fill_qty
# If resting order is fully filled, remove from level deque
if resting.remaining_qty == 0 :
if is_incoming_bid:
book._ask_levels[resting.price].popleft()
else :
book._bid_levels[resting.price].popleft()
trade = Trade(
trade_id=str(uuid.uuid4()),
symbol=incoming.symbol,
price=fill_price,
quantity=fill_qty,
buyer_order_id=incoming.order_id if is_incoming_bid else resting.order_id,
seller_order_id=resting.order_id if is_incoming_bid else incoming.order_id,
)
return trade
def cancel_order(self, symbol: str, order_id: str) -> bool:
book = self._get_book(symbol)
return book.cancel_order(order_id)
# โโโ Example Usage โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if __name__ == "__main__" :
engine = MatchingEngine()
# Resting sell orders in the book
ask1 = Order("A1" , "AAPL" , Side.ASK, OrderType.LIMIT, price=102.00 , quantity=100 )
ask2 = Order("A2" , "AAPL" , Side.ASK, OrderType.LIMIT, price=101.50 , quantity=50 )
ask3 = Order("A3" , "AAPL" , Side.ASK, OrderType.LIMIT, price=101.50 , quantity=75 )
engine.process_order(ask1) # No trades (no bids yet)
engine.process_order(ask2)
engine.process_order(ask3)
# Incoming buy limit order at 102.00 โ should sweep asks at 101.50 first
buy = Order("B1" , "AAPL" , Side.BID, OrderType.LIMIT, price=102.00 , quantity=200 )
trades = engine.process_order(buy)
for t in trades:
print(f"TRADE: {t.quantity} @ {t.price} | buyer={t.buyer_order_id} seller={t.seller_order_id}" )
# Expected:
# TRADE: 50 @ 101.50 | buyer=B1 seller=A2 (FIFO: A2 first at 101.50)
# TRADE: 75 @ 101.50 | buyer=B1 seller=A3
# TRADE: 75 @ 102.00 | buyer=B1 seller=A1 (75 remaining of B1 matches A1)
# Remaining 0 qty from B1 โ fully filled!
็ณป็ปๆถๆๅพ / System Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Exchange Platform โ
โ โ
[Broker APIs] โโโโโโโบ โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ
[Web Portal] โโโโโโโบ โ โ Order โ โ Risk Engine โ โ
[FIX Protocol]โโโโโโโบ โ โ Gateway โโโโโบโ (pre-trade โ โ
โ โ (TCP/TLS) โ โ checks) โ โ
โ โโโโโโโโฌโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโโ โ
โ โ โ โ
โ โผ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Sequencer / Order Bus โ โ
โ โ (assigns monotonic sequence IDs) โ โ
โ โ Kafka / LMAX Disruptor โ โ
โ โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโดโโโโโโโโโโโ โ
โ โผ โผ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ Matching โ โ Matching โ โ
โ โ Engine โ โ Engine โ โ
โ โ (AAPL,GOOG)โ โ (TSLA,AMZN) โ โ
โ โ [in-memory]โ โ [in-memory] โ โ
โ โโโโโโโโฌโโโโโโโ โโโโโโโโโโฌโโโโโโโโโ โ
โ โ โ โ
โ โผ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Trade Reporter โ โ
โ โ (publishes fills to market data) โ โ
โ โโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โผ โผ โผ
[Event Log] [Market Data Feed] [Clearing House]
(Kafka/WAL) (Level 1/2 quotes) (settlement)
่ฏทๆฑๆตๅ่ฟฝ่ธช / Request Flow Trace (Cross-Check)
Let's trace buy = Order("B1", "AAPL", BID, LIMIT, price=102.00, qty=200) through the diagram and code:
Order Gateway receives order over FIX/TCP, validates formatRisk Engine checks: does client have enough buying power? Position limits?Sequencer assigns sequence_id=5042 and writes to Kafka topic orders-AAPLMatching Engine (AAPL) consumes from Kafka, calls engine.process_order(buy)Code: _match_limit() โ checks best_ask() = 101.50 โค 102.00 โ calls _execute_match() Fills 50 @ 101.50 (A2), 75 @ 101.50 (A3), 75 @ 102.00 (A1) Trade objects emitted โ Trade Reporter publishes to market data feedEvent Log persists each trade with sequence_id for crash recovery
โ
Diagram boxes match code path exactly.
Part 3: Edge Cases & Gotchas / ่พน็ๆ
ๅต (2 min)
ไธญๆ๏ผ
ๅบๆฏ ้ฎ้ข ่งฃๅณๆนๆก ๅธไปทๅๆ ๆตๅจๆง ้จๅๆไบคๆๅฎๅ
จๅคฑ่ดฅ ่ฟๅๆไบค้๏ผๆชๆไบค้ๆฅๅ็ปๅฎขๆท๏ผๆไบไบคๆๆ็ดๆฅๆ็ป ๆฎๅๅผๆๅดฉๆบ ๅ
ๅญไธญ่ฎขๅ็ฐฟไธขๅคฑ ้่ฟ Kafka offset ้ๆพๅ
จ้่ฎขๅๆฅๅฟ้ๅปบ ไปทๆ ผ็ฒพๅบฆ ๆตฎ็น่ฏฏๅทฎๅฏผ่ด 101.4999... โ 101.5 ็จๆดๆฐ่กจ็คบไปทๆ ผ ๏ผ$101.50 โ 10150 (cents)้ๅค่ฎขๅ ็ฝ็ป้่ฏๅฏผ่ดๅไธ่ฎขๅ่ขซๅค็ไธคๆฌก ๅน็ญ้ฎ๏ผorder_id ๅฏไธ๏ผsequencer ๅป้ ๅไปทๆ ผๅค่ฎขๅๆฎๅ้กบๅบ FIFO ่ขซ็ ดๅ deque ไฟ่ฏๆๅ
ฅ้กบๅบ๏ผtimestamp ไฝไธบ็ฌฌไบๆๅบ้ฎ
English:
Copy
# CRITICAL: Never use float for prices in production!
# Wrong โ
price = 101.50 # float precision issues
# Right โ
โ store as integer cents or fixed-point
price_cents = 10150 # $101.50 in cents
# Or use Python's Decimal
from decimal import Decimal
price = Decimal("101.50" ) # Exact representation
The "ghost order" problem : After partial fills, you must update remaining_qty, not create new orders. Otherwise you double-count in the book.
Clock skew : Two matching engines on different machines may see different timestamps. Use the Sequencer's sequence number as the canonical ordering โ not wall clock time.
Part 4: Real-World Application / ๅฎ้
ๅบ็จ (2 min)
ไธญๆ โ ็ๅฎ็ณป็ปๅฆไฝๅฎ็ฐ๏ผ
LMAX Disruptor Pattern๏ผๆๅ
ทๅฝฑๅๅ็ๆถๆ๏ผ
ๅ็บฟ็จๆฎๅๅผๆ + ๆ ้็ฏๅฝข็ผๅฒๅบ๏ผRing Buffer๏ผ ๆฏ็งๅค็ 600 ไธ่ฎขๅ๏ผๅปถ่ฟ < 1ฮผs ๆ ธๅฟๆด่ง๏ผๅ็บฟ็จ + ้ซๆ้ๅ ๆฏๅค็บฟ็จ + ้ๆดๅฟซ
Nasdaq / NYSE ็ๅๆณ๏ผ
ๆฏไธช่ก็ฅจไธไธช็ฌ็ซ็ๆฎๅๅผๆ๏ผ็ฉ็้็ฆป๏ผ ็จ FPGA ็กฌไปถๅ ้ๅ
ณ้ฎ่ทฏๅพ๏ผ็บณ็ง็บงๅปถ่ฟ๏ผ ไบไปถๆบฏๆบ๏ผEvent Sourcing๏ผ๏ผ่ฎขๅ็ฐฟ็ถๆ = ๅ
จ้ไบไปถๅๆพ
้้่ๅบๆฏไธญ็ๅบ็จ๏ผ
Airbnb/Uber ๅฎไปท ๏ผไพ้ๆฎๅ๏ผๅจๆไปทๆ ผ่ฐๆดๅนฟๅ็ซไปท๏ผRTB๏ผ ๏ผๅฎๆถ็ซไปท๏ผ100ms ๅ
ๅฎๆๆธธๆไบคๆๆ ๏ผSteam ๅธๅบใEve Online ็ปๆต็ณป็ป
English โ Where this appears in production:
Exchange | Engine | Latency | Notes
-------------|-----------------|------------|---------------------------
LMAX | Disruptor | < 1ฮผs | Single-threaded ring buffer
Nasdaq | INET | < 100ฮผs | FPGA for hot path
NYSE | Pillar | < 100ฮผs | Co-location for HFT firms
Binance | Custom C++ | ~1ms | Crypto, simpler rules
Interactive | Custom Java | ~1ms | Retail, less HFT pressure
Brokers | | |
Key insight for interviews: the matching algorithm is simple (2 priority queues). The hard parts are persistence, fairness, and latency at scale.
Part 5: Interview Simulation / ้ข่ฏๆจกๆ (3 min)
ๅ
ธๅ้ข่ฏ่ฟ็จ / Typical Interview Flow
Q1: ไฝ ไผ็จไปไนๆฐๆฎ็ปๆๆฅๅฎ็ฐ่ฎขๅ็ฐฟ๏ผ
A: ๆฏไธชไปทๆ ผๆกฃไฝ็จ deque๏ผFIFO ไฟ่ฏ๏ผ๏ผไปทๆ ผ็ดขๅผ็จๅ ๏ผheapq๏ผๅฟซ้ๆฅๆพๆไผไปทใไนฐๅ็จๆๅคงๅ ๏ผๅๅ๏ผ๏ผๅๅ็จๆๅฐๅ ใๆถ้ดๅคๆๅบฆ๏ผๆๅ
ฅ O(log n)๏ผๆฎๅ O(k log n)๏ผk = ๆไบค็ฌๆฐ๏ผใ
Q2: How do you handle the engine crashing mid-trade?
A: Event sourcing + write-ahead log. Every order gets a monotonic sequence ID from the sequencer before entering the engine. On restart, replay all events with seq_id > last_checkpoint. This gives exactly-once semantics. The matching engine is a pure function: same input sequence โ same output.
Q3: ๅฆไฝๆฏๆๅคไธชไบคๆๆไน้ด็ๆไผไปทๆ ผ่ทฏ็ฑ๏ผSmart Order Routing๏ผ๏ผ
A: SOR ๅฑๅจๆฎๅๅผๆไนไธใๅฎ่ฎข้
ๅคไธชไบคๆๆ็ Level 2 ๅธๅบๆทฑๅบฆๆฐๆฎ๏ผ่ฎขๅ็ฐฟๅฟซ็
ง๏ผ๏ผ่ฎก็ฎๆไผๆง่ก่ทฏๅพ๏ผๅฐๅคง่ฎขๅๆๅ๏ผOrder Splitting๏ผๅ้ๅฐไธๅไบคๆๆใๅ
ณ้ฎๆๆ๏ผๅไบคๆๆๆฐๆฎๆๅปถ่ฟ๏ผ้่ฆๅค็่ฟๆๆฅไปทใ
Q4: What's the difference between a limit order book and an auction?
A: Limit order book = continuous matching (every new order tries to match immediately). Auction = collect orders for a time period, then batch-match at a single clearing price (used at market open/close). Auctions reduce the advantage of being microseconds faster; continuous books reward speed.
Q5: How would you scale this to handle 10 million orders/second?
A: Three strategies:
1. Partition by symbol โ each symbol is its own stateful engine on a dedicated thread/process. No cross-symbol state needed.
2. LMAX Disruptor โ replace Kafka with a single-producer/multi-consumer lock-free ring buffer for ~10x lower latency.
3. FPGA offload โ the inner matching loop (heap operations, price comparison) maps well to hardware; Nasdaq uses this for <100ns latency.
4. No distributed transactions โ each symbol's book is authoritative and independent. Cross-symbol atomicity (e.g., pairs trading) happens at a higher layer.
ๆป็ป / Summary
Copy
ๆ ธๅฟ่ฆ็น / Key Takeaways:
1. ๆฐๆฎ็ปๆ: heap (ไปทๆ ผๆๅบ) + deque (FIFO within level) + hashmap (O(1) cancel)
2. ๆฎๅ่งๅ: price-time priority, resting price wins, lazy deletion for cancel
3. ๆไน
ๅ: event sourcing โ ่ฎขๅ็ฐฟๆฏไบไปถๆฅๅฟ็็ฉๅ่งๅพ
4. ๆง่ฝ: ๅ็บฟ็จ + ๆ ้ > ๅค็บฟ็จ + ้ (LMAX ่ฏๆ)
5. ่ธฉๅ: ๆฐธ่ฟ็จๆดๆฐ/Decimal่กจ็คบไปทๆ ผ๏ผไธ่ฆ็จfloat
Algorithm complexity:
- Add limit order: O(log P) where P = distinct price levels
- Match: O(k log P) where k = number of fills
- Cancel: O(1) lazy deletion
้ข่ฏไธๅฅ่ฏ: "The algorithm is two priority queues. The engineering is making it fast, fair, and crash-safe."
๐ References: