Setup
The Market Making Problem
A market maker simultaneously posts a bid and an ask, collecting the spread on each round-trip trade. The business model is simple in principle: buy low (bid), sell high (ask), pocket the half-spread on each side. In practice, the market maker faces two fundamental risks:
-
Adverse selection: informed counterparties trade against the market maker only when the posted price is wrong. The market maker buys at the bid just before the price falls, or sells at the ask just before the price rises.
-
Inventory risk: continuous order flow is unbalanced. The market maker accumulates net directional positions that must be unwound at cost, and that generate P&L variance proportional to the position squared times the volatility.
This module covers two foundational models: the Glosten-Milgrom (1985) model of the bid-ask spread from adverse selection, and the Avellaneda-Stoikov (2008) stochastic control model of optimal market making under inventory risk.
Conventions and Assumptions
- Mid-price: , modelled as a Brownian motion: (zero drift under the market maker's view).
- Inventory: , the signed position in shares (positive = long, negative = short).
- Bid and ask quotes: and , where are the half-spreads on each side.
- Order arrivals: Poisson processes with intensity depending on the posted spread: for buy orders hitting the bid, for sell orders lifting the ask.
- Finite horizon: the market maker operates over and liquidates at at the mid-price with a terminal inventory penalty.
- Volatility and time horizon : annualised; for typical equities.
Glosten-Milgrom Model
Setup
Glosten and Milgrom (1985) model a competitive dealer who sets bid and ask quotes to break even in expectation against a mixture of informed and uninformed traders.
Assumptions:
- True asset value: with prior probability .
- Fraction of traders are informed: they know and trade to profit.
- Fraction are uninformed (liquidity) traders: they buy or sell with equal probability , independently of .
- Trades arrive one at a time. The dealer observes the trade direction (buy or sell) but not the trader type.
Bayesian Updating
After observing a buy order, the dealer updates their belief :
where:
- (informed always buys when ; uninformed buys with prob 1/2).
- .
The posterior after a buy is — a buy order provides positive evidence that . After a sell, .
Equilibrium Quotes
The dealer sets quotes to break even against the mixture of trader types. At each step, the competitive equilibrium requires:
The bid-ask spread at time :
Key result: The spread is proportional to — the product of the probability of informed trading and the magnitude of the information asymmetry. As trades occur and the dealer updates beliefs, the spread narrows: the posterior converges to the true state, reducing uncertainty.
Price impact: Each trade causes the mid-quote to move. After a buy: the mid moves from to . This is the permanent price impact — the dealer revises their fair value estimate upward.
Avellaneda-Stoikov Model
Problem Formulation
Avellaneda and Stoikov (2008) model the market maker's optimal quoting problem as a stochastic control problem. The state is and the controls are the bid and ask half-spreads .
Mid-price dynamics:
Inventory dynamics: the market maker's inventory changes when orders arrive. With Poisson order flow, the intensity of bid fills at spread is and similarly for ask fills. The exponential demand curve is the simplest decreasing function in : large spread → low fill rate. Parameter controls the elasticity of order arrival to spread.
Wealth dynamics: on each fill, wealth changes by the fill price. The market maker's cash process: where and are counting processes of ask fills and bid fills, with intensities and .
Objective: maximise expected terminal wealth adjusted for inventory risk at :
where the terminal penalty is an approximation to the expected variance of the terminal inventory, with the risk aversion parameter. Equivalently, this is the CARA expected utility linearised near zero.
HJB Equation
The value function satisfies the Hamilton-Jacobi-Bellman equation:
with terminal condition .
Change of Variables and Closed-Form Solution
Avellaneda and Stoikov reduce this to a tractable form via the substitution:
where satisfies a linear ODE system. For the exponential demand curve , the optimal spreads take the closed form:
or more elegantly, the reservation price (the market maker's fair value of the asset given their inventory):
and the optimal spread around this reservation price:
Interpretation
Reservation price: the market maker with long inventory sets a reservation price — they value the asset less than the mid because they already hold too much. This shifts both quotes downward: they quote a lower bid (less eager to buy more) and a lower ask (more eager to sell to reduce inventory). A short position () shifts quotes upward.
Optimal spread: the spread has two components:
- : the adverse selection / profit margin component, increasing in risk aversion and decreasing in demand elasticity .
- : the inventory risk component, proportional to remaining time and variance. As , this term vanishes and the market maker quotes tightly to avoid closing with inventory.
Implementation
import numpy as np
from dataclasses import dataclass
@dataclass
class MarketMakerParams:
sigma: float # mid-price volatility (annualised)
gamma: float # risk aversion (CARA coefficient)
k: float # order arrival elasticity (larger k -> faster decay with spread)
A: float # baseline arrival rate (orders per unit time at zero spread)
T: float # time horizon (years)
def reservation_price(S: float, q: int, t: float, p: MarketMakerParams) -> float:
"""
Avellaneda-Stoikov reservation price.
r(t,q) = S - q * gamma * sigma^2 * (T - t)
"""
return S - q * p.gamma * p.sigma**2 * (p.T - t)
def optimal_spreads(q: int, t: float, p: MarketMakerParams) -> tuple[float, float]:
"""
Compute optimal bid and ask half-spreads around the reservation price.
Returns (delta_bid, delta_ask) such that:
bid quote = reservation_price - delta_bid
ask quote = reservation_price + delta_ask
"""
half_spread = (1.0 / p.gamma) * np.log(1.0 + p.gamma / p.k) \
+ 0.5 * p.gamma * p.sigma**2 * (p.T - t)
# Inventory-adjusted asymmetry
skew = q * p.gamma * p.sigma**2 * (p.T - t)
delta_ask = half_spread + skew / 2.0
delta_bid = half_spread - skew / 2.0
return max(delta_bid, 1e-6), max(delta_ask, 1e-6)
def optimal_quotes(S: float, q: int, t: float, p: MarketMakerParams) -> tuple[float, float]:
"""
Return (bid_price, ask_price) optimal quotes.
"""
r = reservation_price(S, q, t, p)
db, da = optimal_spreads(q, t, p)
return r - db, r + da
def simulate_market_maker(p: MarketMakerParams, n_steps: int = 10000) -> dict:
"""
Simulate market making under Avellaneda-Stoikov optimal control.
Mid-price: GBM with zero drift.
Order arrivals: Poisson with rate A * exp(-k * delta).
Returns dict with time series of S, q, wealth, P&L.
"""
dt = p.T / n_steps
S = 100.0
q = 0
cash = 0.0
rng = np.random.default_rng(seed=42)
S_hist = np.zeros(n_steps + 1)
q_hist = np.zeros(n_steps + 1, dtype=int)
W_hist = np.zeros(n_steps + 1)
S_hist[0] = S; q_hist[0] = q; W_hist[0] = 0.0
for i in range(n_steps):
t = i * dt
bid, ask = optimal_quotes(S, q, t, p)
db = S - bid # bid half-spread
da = ask - S # ask half-spread
# Arrival rates for this step
rate_b = p.A * np.exp(-p.k * db) * dt # expected arrivals in [t, t+dt]
rate_a = p.A * np.exp(-p.k * da) * dt
# Poisson arrivals (Bernoulli approx for small dt)
n_buys = rng.poisson(rate_a) # market buys hit the ask
n_sells = rng.poisson(rate_b) # market sells hit the bid
# Update inventory and cash
q -= n_buys
cash += n_buys * ask
q += n_sells
cash -= n_sells * bid
# Mid-price diffusion
S += p.sigma * np.sqrt(dt) * rng.standard_normal()
S_hist[i+1] = S
q_hist[i+1] = q
W_hist[i+1] = cash + q * S # mark-to-market wealth
return dict(S=S_hist, q=q_hist, wealth=W_hist,
final_pnl=float(W_hist[-1]))
Validation
Analytical Checks
-
Zero inventory: at , the reservation price equals the mid and the bid/ask spreads are symmetric. The optimal spread is .
-
Near expiry (): the inventory risk component vanishes, the spread collapses to (the pure adverse selection component). The market maker quotes tightly because there is no future inventory risk.
-
High : more risk-averse market maker quotes a wider spread (collects more per trade) but adjusts inventory more aggressively by skewing quotes.
-
Reservation price skew: with long, , , : . The market maker values the position 0.4 ticks below mid and quotes accordingly.
Limitations
Poisson order flow with exponential decay. The exponential demand curve is mathematically convenient but may not match empirical order arrival rates, which can be more concave (orders arrive even at wide spreads if the market is one-sided) or have threshold effects (zero arrivals above a maximum spread).
Constant volatility. The model assumes constant . In reality, volatility is stochastic and correlated with order flow — exactly when spreads should widen, the model under-quotes.
Single asset. Multi-asset market making with correlated inventories requires solving a multi-dimensional stochastic control problem. The Avellaneda-Stoikov solution does not extend trivially.
No LOB depth. The model assumes the market maker posts at the best quote. In practice, the market maker must decide whether to post at the best, join a large queue at the best, or post inside the spread. Queue position dynamics (see Module 1) materially affect fill rates.
Adverse selection not modelled separately. The Avellaneda-Stoikov model captures inventory risk but conflates adverse selection with inventory cost. The Glosten-Milgrom framework captures adverse selection but has no inventory dynamics. A complete model integrates both: the market maker learns from order flow (Bayesian updating of fair value) while managing inventory risk (stochastic control).
Interview Angle
L1. A market maker posts a bid and an ask. Under what conditions does the market maker lose money on a trade? Explain the terms "adverse selection" and "inventory risk" with a concrete example for a market maker in a single stock.
Adverse selection: the market maker posts a bid at $99.98 for a stock worth $100. An informed trader knows a negative earnings announcement is imminent and sells to the market maker at $99.98. The stock falls to $99.50 — the market maker bought at $99.98 and now holds a stock worth $99.50. Inventory risk: the market maker has been buying consistently due to more sellers than buyers. They are now long 5000 shares. If the stock falls 0.5%, they lose $250 — their spread income of $50 (at 1 cent per trade, 5000 trades) is wiped out. Adverse selection is a single-trade loss from informed counterparties; inventory risk accumulates from imbalanced flow.
L2. Derive the reservation price in the Avellaneda-Stoikov model. Why does a long inventory position reduce the reservation price? Show the relationship between the reservation price and the optimal bid/ask quotes.
Derivation: The value function is . The marginal value of holding one more unit of inventory is . Setting this equal to (the reservation price — the indifference price for accepting one more unit) gives . Long inventory (): the market maker already bears variance ; adding another unit increases the variance by , which is penalised at rate . The reservation price is therefore below mid — the market maker is a reluctant buyer. Optimal quotes: , , where .
L3. Compare the Glosten-Milgrom and Avellaneda-Stoikov models: what does each explain, and what does each miss? How would you combine them into a unified market maker model, and what is the resulting structure of the optimal bid-ask spread?
GM: explains why the spread is positive in a market with adverse selection. The spread compensates for expected losses to informed traders. GM is a static, single-period model — no dynamics, no inventory. AS: explains how the market maker dynamically manages inventory over a finite horizon and adjusts quotes as inventory accumulates. Captures time-to-close and inventory penalties but treats all order flow as uninformed (no learning).
Unified model: Model order flow as a mixture: with probability the arriving order is from an informed trader (causing permanent price impact), with probability from an uninformed trader. The market maker updates after each trade (GM Bayesian updating), which shifts the mid-price estimate . Simultaneously, the market maker solves a stochastic control problem (AS) with the updated mid. The optimal spread has three components:
The first term is the gross margin per trade (independent of inventory), the second is the inventory risk premium (shrinks as time to close decreases), and the third is the adverse selection premium (shrinks as after many trades resolve the information asymmetry).