The Limit Order Book
Finance
The LIMIT ORDER BOOK (LOB) is the actual mechanism by which prices are formed in modern electronic markets. Every order placed by every market participant lives in the LOB until it is matched against an incoming order or cancelled. The continuous double auction — orders crossing against each other under PRICE-TIME PRIORITY — produces the bid-ask spread, the apparent depth of the market, and the small-scale price dynamics that algorithmic traders care about. This page covers the mechanics of the LOB, the standard order types, and the first-order quantities a trader needs to understand to interact with one productively.
A working LOB model also underpins more advanced execution theory: Almgren-Chriss is an aggregated, continuous-time abstraction of LOB dynamics; market-making models (Avellaneda-Stoikov 2008, Cartea-Jaimungal-Penalva) take the LOB seriously as the trading interface and optimize quote placement directly; modern execution algorithms (VWAP, IS, POV) submit limit and market orders into the LOB and care about FILL RATES, QUEUE POSITION, and MICROSTRUCTURE noise.
What the LOB actually contains
Two queues for each price level: the BID side and the ASK side. The bid side lists prices someone is willing to BUY at; the ask side lists prices someone is willing to SELL at. At each price, orders are queued in time-of-arrival order. The best bid is the highest price on the bid side; the best ask is the lowest price on the ask side. By construction (in a non-locked market), .
Key derived quantities:
- Spread: best ask − best bid. The minimum price you'd pay to instantly buy and then sell. Tight in liquid markets (1 tick on liquid US equities), wide in illiquid markets (multiple percent).
- Midprice: (best bid + best ask) / 2. Simple but ignores the size imbalance at each side — when the bid is "thick" and ask is "thin," fair value is tilted upward.
- Microprice: size-weighted midpoint, . Tilts toward the side with LESS liquidity. The right "fair value" estimate when bid and ask sizes differ.
- DEPTH: total quantity available at each price level (and at all levels combined within a given price range). The market's CAPACITY to absorb large orders.
- QUEUE: the queue position of an order — how many shares are ahead of you at your price level. Queue priority is set by submission time; better queue = higher likelihood of fill.
Order types
- LIMIT ORDER: specify price and size; rest in the queue at that price until filled or cancelled. Bid orders go on the bid side, asks on the ask side. The DEFAULT order type and the one that PROVIDES liquidity.
- MARKET ORDER: specify size only; matches immediately against the opposite side, walking down the book as needed. CONSUMES liquidity. Pays the bid-ask spread.
- MARKETABLE LIMIT: a limit order with a price aggressive enough to cross the opposite side. The portion that crosses fills immediately as a market order; the residual rests.
- STOP ORDER: a market order triggered when the price crosses a threshold. Common for stop-loss / take-profit positions.
- IOC (Immediate-or-Cancel): market order that cancels any unfilled portion immediately. Useful for sniping liquidity without leaving residual orders.
- FOK (Fill-or-Kill): market order that either fills the FULL size immediately or cancels entirely. Used for orders where partial fills are useless.
- POST-ONLY: limit order that REJECTS if it would cross the book. Common for market makers who get fees for resting liquidity and want to avoid accidentally crossing.
- HIDDEN / ICEBERG: limit order with hidden size — display less than the full quantity in the book. Reduces market impact at the cost of giving up queue priority on the hidden portion.
Matching
The matching engine enforces PRICE-TIME PRIORITY: incoming orders match against resting orders by (1) BEST PRICE first, (2) WITHIN a price level, FIRST-IN-FIRST-OUT. This is the standard for US equities, futures, and most listed derivatives. Some markets use PRO-RATA matching at each price (orders share the inbound flow in proportion to size); the variant matters for liquidity-provision incentives but the core mechanics are the same.
A market BUY of 250 shares against an ask book with 100 @ 100.00, 400 @ 100.02, 800 @ 100.05: matches 100 @ 100.00 (clearing that level), then 150 @ 100.02 (partial fill of the next level). Average execution price 100.012 — 3.7 cents above the original midpoint of 99.975. That 3.7 cents is TEMPORARY IMPACT — the cost of demanding immediate liquidity.
Code: a minimal LOB
# Minimal limit-order-book simulator with price-time priority matching.
# Demonstrates: bid-ask spread, midpoint vs microprice, market-order
# slippage as it walks through book depth.
class LimitOrderBook:
def __init__(self):
self.bids = {} # price -> list of (order_id, size)
self.asks = {}
self.next_id = 1
self.trades = []
def submit_limit(self, side, price, size):
oid = self.next_id; self.next_id += 1
book = self.bids if side == 'buy' else self.asks
# Cross any marketable portion before resting
size = self._match(side, price, size)
if size > 0:
book.setdefault(price, []).append((oid, size))
return oid
def submit_market(self, side, size):
self._match(side, None, size)
def _match(self, side, limit_price, size):
opposite = self.asks if side == 'buy' else self.bids
# Best price first: ascending asks for a buyer, descending bids for a seller
prices = sorted(opposite, reverse=(side == 'sell'))
for px in prices:
if size <= 0: break
if limit_price is not None:
if side == 'buy' and px > limit_price: break
if side == 'sell' and px < limit_price: break
queue = opposite[px]
while queue and size > 0:
oid, qty = queue[0]
if qty <= size:
self.trades.append((side, px, qty))
size -= qty
queue.pop(0)
else:
self.trades.append((side, px, size))
queue[0] = (oid, qty - size)
size = 0
if not queue:
del opposite[px]
return size
def best_bid(self): return max(self.bids) if self.bids else None
def best_ask(self): return min(self.asks) if self.asks else None
def spread(self):
if self.bids and self.asks: return self.best_ask() - self.best_bid()
return None
def microprice(self):
"""Volume-weighted midprice between best bid and ask.
Bid-side liquidity → midprice tilts UP toward the ask, and vice versa."""
if not (self.bids and self.asks): return None
bb, ba = self.best_bid(), self.best_ask()
bsize = sum(q for _, q in self.bids[bb])
asize = sum(q for _, q in self.asks[ba])
return (ba*bsize + bb*asize) / (bsize + asize)
# Build a small book with several levels of depth
lob = LimitOrderBook()
lob.submit_limit('buy', 99.95, 200)
lob.submit_limit('buy', 99.94, 500)
lob.submit_limit('buy', 99.92, 1000)
lob.submit_limit('sell', 100.00, 100)
lob.submit_limit('sell', 100.02, 400)
lob.submit_limit('sell', 100.05, 800)
print(f"Initial book state:")
print(f" Best bid: {lob.best_bid()} Best ask: {lob.best_ask()}")
print(f" Quoted spread: {lob.spread():.4f}")
print(f" Midprice: {(lob.best_bid() + lob.best_ask()) / 2:.4f}")
print(f" Microprice: {lob.microprice():.4f}")
print(f" (Microprice tilts toward the side with LESS liquidity — here the ask")
print(f" has only 100 shares vs 200 on the bid, so price tilts UP.)")
# Submit a market buy of 250 shares — walks through two ask levels
print(f"\nMarket buy of 250 shares:")
lob.submit_market('buy', 250)
for trade in lob.trades:
print(f" Filled {trade[2]:>4} shares @ {trade[1]}")
print(f"\nAfter the trade:")
print(f" Best bid: {lob.best_bid()} Best ask: {lob.best_ask()}")
print(f" Quoted spread: {lob.spread():.4f}")
# Slippage analysis
fills = lob.trades
total_size = sum(q for _, _, q in fills)
total_cost = sum(px*q for _, px, q in fills)
avg_fill = total_cost / total_size
print(f"\nSlippage:")
print(f" Average fill price: {avg_fill:.4f}")
print(f" Original midpoint: 99.9750")
print(f" Total slippage: {avg_fill - 99.9750:.4f}")
print(f" (This is temporary impact — the cost of demanding immediate liquidity.)") Output:
Initial book state:
Best bid: 99.95 Best ask: 100.0
Quoted spread: 0.0500
Midprice: 99.9750
Microprice: 99.9833
(Microprice tilts toward the side with LESS liquidity — here the ask
has only 100 shares vs 200 on the bid, so price tilts UP.)
Market buy of 250 shares:
Filled 100 shares @ 100.0
Filled 150 shares @ 100.02
After the trade:
Best bid: 99.95 Best ask: 100.02
Quoted spread: 0.0700
Slippage:
Average fill price: 100.0120
Original midpoint: 99.9750
Total slippage: 0.0370
(This is temporary impact — the cost of demanding immediate liquidity.) Three things to read off. (1) The microprice (99.983) is above the midprice (99.975) because the BEST ASK has less size (100) than the best bid (200). The microprice "leans" toward the side with less depth, which is closer to the fair-value direction the market is likely to move. (2) The 250-share market buy fills two levels deep, paying 100.00 for the first 100 shares and 100.02 for the next 150. Total slippage of 3.7 cents on a 5-cent spread market — about 70% of the quoted spread, plus a few extra cents from walking through depth. (3) After the trade, the new best ask has moved to 100.02 (with the residual 250 shares from the partially-filled level); the spread WIDENS to 7 cents temporarily. Real markets re-fill quickly as market makers post new bids and asks at the cleared levels.
Spread, queue, and the cost of immediacy
The bid-ask spread is the PRIMARY COST of trading. Cross half of it on entry, half on exit; if the spread is 5 cents on a $100 stock, every round-trip costs 5 cents (5 basis points) just for liquidity. For a strategy with edge of 10 bps per trade, the spread is the difference between profit and loss.
Three ways to reduce the cost of immediacy:
- POST limit orders at the best bid (when buying) or best ask (when selling) instead of crossing. Pay zero spread if you get filled — but you might not, and the market could move away. Quote management is the central problem of market making.
- HIDDEN orders or POST-ONLY to avoid crossing accidentally and to capture rebates (some exchanges pay liquidity providers a small fee for resting orders).
- SLICED execution via Almgren-Chriss or VWAP: spread the impact over many small trades through the day instead of crossing the book once with a big market order.
Queue position dynamics
For a limit-order trader, QUEUE POSITION at a given price level determines fill probability. Posting LATE puts you at the back of the queue; you fill ONLY if everyone ahead of you fills first OR cancels. Posting EARLY (or RECEIVING priority via rebates) is the difference between filling and not filling.
Queue dynamics are surprisingly intricate. The expected fill probability for an order at position in a queue of total size depends on:
- The PROCESS of inbound market orders (Poisson rate, size distribution).
- The CANCELLATION rate of orders ahead of you (orders cancel before filling at substantial frequencies, often 90%+).
- The likelihood that the BEST PRICE moves before you fill (market quotes away from your level, you're left behind).
Cartea-Jaimungal-Penalva (2015) and Cont-Stoikov-Talreja (2010) develop stochastic models for these dynamics; the resulting QUEUE-VALUE functions are key inputs to optimal market-making algorithms.
Microstructure effects
The LOB produces small-scale price dynamics that violate the constant-vol log-normal assumption of derivatives pricing models:
- Bid-ask bounce: each successive trade alternates between hitting the bid and lifting the ask, even if the underlying value is constant. Adds artificial high-frequency volatility.
- Tick size effects: prices snap to a discrete grid (1 cent for US equities, 1/4 cent for futures). Adds rounding noise to all microstructure statistics.
- Microstructure noise: the combination of bid-ask bounce, tick size, and order-arrival jitter. Realized variance computed at sub-second timescales is dominated by noise rather than signal — see the realized volatility page for how to filter it.
- Imbalance signals: large size at the bid (relative to the ask) often signals an UPWARD price move in the next few seconds. The microprice captures this; modern market-makers price queue dynamics in directly.
What this enables
Production execution-algorithm software lives and breathes on LOB data. Standard production capabilities:
- Smart order routing (SOR): decide which exchange / venue to route each child order to based on quoted prices, hidden liquidity expectations, fees, and latency.
- Market-making strategies: continuously quote both sides of the book, adjust quotes based on inventory and short-term price predictions.
- High-frequency arbitrage: detect transient mispricings between correlated venues or instruments faster than other traders can.
- Predictive cancellation: pull resting limit orders just before they get adversely selected by toxic flow.
- Order book reconstruction: from sequential message streams, rebuild the FULL LOB state at any nanosecond in history for research and post-trade analytics.
Related
- Almgren-Chriss optimal execution — the macro-level execution model that abstracts LOB depth into impact coefficients.
- Greeks and delta hedging — the hedging trades that execute through the LOB.
- GARCH and realized volatility — microstructure-noise filtering for high-frequency volatility estimation.
- Sharpe and backtest evaluation — backtest realism requires modelling at least minimal LOB dynamics rather than assuming infinite liquidity at the close price.