Adverse Selection and Inventory Models

Hard·28 min read
Market MicrostructureMarket MakingAdverse SelectionStochastic ControlAvellaneda-Stoikov

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:

  1. 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.

  2. 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: StS_t, modelled as a Brownian motion: dSt=σdWtdS_t = \sigma\, dW_t (zero drift under the market maker's view).
  • Inventory: qtZq_t \in \mathbb{Z}, the signed position in shares (positive = long, negative = short).
  • Bid and ask quotes: Ptb=StδbP_t^b = S_t - \delta^b and Pta=St+δaP_t^a = S_t + \delta^a, where δb,δa>0\delta^b, \delta^a > 0 are the half-spreads on each side.
  • Order arrivals: Poisson processes with intensity depending on the posted spread: λb(δb)\lambda^b(\delta^b) for buy orders hitting the bid, λa(δa)\lambda^a(\delta^a) for sell orders lifting the ask.
  • Finite horizon: the market maker operates over [0,T][0, T] and liquidates at t=Tt = T at the mid-price with a terminal inventory penalty.
  • Volatility σ\sigma and time horizon TT: annualised; σ=0.010.30\sigma = 0.01\text{–}0.30 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: V{VL,VH}V \in \{V_L, V_H\} with prior probability Pr[V=VH]=μ0\Pr[V = V_H] = \mu_0.
  • Fraction π(0,1)\pi \in (0,1) of traders are informed: they know VV and trade to profit.
  • Fraction 1π1 - \pi are uninformed (liquidity) traders: they buy or sell with equal probability 1/21/2, independently of VV.
  • 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 μt=Pr[V=VHhistory]\mu_t = \Pr[V = V_H \mid \text{history}]:

μt+=Pr[V=VHbuy]=μtPr[buyVH]Pr[buy],\mu_t^+ = \Pr[V = V_H \mid \text{buy}] = \frac{\mu_t \cdot \Pr[\text{buy} \mid V_H]}{\Pr[\text{buy}]},

where:

  • Pr[buyVH]=π1+(1π)12=1+π2\Pr[\text{buy} \mid V_H] = \pi \cdot 1 + (1-\pi) \cdot \frac{1}{2} = \frac{1 + \pi}{2} (informed always buys when V=VHV = V_H; uninformed buys with prob 1/2).
  • Pr[buy]=μt1+π2+(1μt)1π2\Pr[\text{buy}] = \mu_t \cdot \frac{1+\pi}{2} + (1-\mu_t) \cdot \frac{1-\pi}{2}.

The posterior after a buy is μt+>μt\mu_t^+ > \mu_t — a buy order provides positive evidence that V=VHV = V_H. After a sell, μt<μt\mu_t^- < \mu_t.

Equilibrium Quotes

The dealer sets quotes to break even against the mixture of trader types. At each step, the competitive equilibrium requires:

Pta=E[Vbuy at t]=μt+VH+(1μt+)VL,P_t^a = \mathbb{E}[V \mid \text{buy at } t] = \mu_t^+ V_H + (1 - \mu_t^+) V_L,

Ptb=E[Vsell at t]=μtVH+(1μt)VL.P_t^b = \mathbb{E}[V \mid \text{sell at } t] = \mu_t^- V_H + (1 - \mu_t^-) V_L.

The bid-ask spread at time tt:

st=PtaPtb=(μt+μt)(VHVL).s_t = P_t^a - P_t^b = (\mu_t^+ - \mu_t^-)(V_H - V_L).

Key result: The spread is proportional to π(VHVL)\pi(V_H - V_L) — 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 μt\mu_t converges to the true state, reducing uncertainty.

Price impact: Each trade causes the mid-quote to move. After a buy: the mid moves from μtVH+(1μt)VL\mu_t V_H + (1-\mu_t)V_L to μt+VH+(1μt+)VL\mu_t^+ V_H + (1-\mu_t^+)V_L. 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 (t,St,qt)(t, S_t, q_t) and the controls are the bid and ask half-spreads (δtb,δta)(\delta^b_t, \delta^a_t).

Mid-price dynamics: dSt=σdWt.dS_t = \sigma\, dW_t.

Inventory dynamics: the market maker's inventory changes when orders arrive. With Poisson order flow, the intensity of bid fills at spread δb\delta^b is λb(δb)=Aekδb\lambda^b(\delta^b) = A e^{-k\delta^b} and similarly for ask fills. The exponential demand curve is the simplest decreasing function in δ\delta: large spread → low fill rate. Parameter k>0k > 0 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: dXt=PtadNtaPtbdNtb,dX_t = P_t^a\, dN_t^a - P_t^b\, dN_t^b, where NtaN_t^a and NtbN_t^b are counting processes of ask fills and bid fills, with intensities λa\lambda^a and λb\lambda^b.

Objective: maximise expected terminal wealth adjusted for inventory risk at t=Tt = T:

maxδb,δaE ⁣[XT+qTSTγ2qT2σ2(Tt)],\max_{\delta^b, \delta^a} \mathbb{E}\!\left[X_T + q_T S_T - \frac{\gamma}{2} q_T^2 \sigma^2 (T-t)\right],

where the terminal penalty γ2qT2σ2(Tt)\frac{\gamma}{2}q_T^2\sigma^2(T-t) is an approximation to the expected variance of the terminal inventory, with γ>0\gamma > 0 the risk aversion parameter. Equivalently, this is the CARA expected utility E[eγWT]-\mathbb{E}[e^{-\gamma W_T}] linearised near zero.

HJB Equation

The value function u(t,s,q)=maxδb,δaE[WTSt=s,qt=q]u(t, s, q) = \max_{\delta^b,\delta^a} \mathbb{E}[W_T \mid S_t = s, q_t = q] satisfies the Hamilton-Jacobi-Bellman equation:

tu+12σ2ssu+maxδaλa(δa)[δa+u(t,s,q1)u(t,s,q)]+maxδbλb(δb)[δb+u(t,s,q+1)u(t,s,q)]=0,\partial_t u + \frac{1}{2}\sigma^2 \partial_{ss} u + \max_{\delta^a} \lambda^a(\delta^a)[\delta^a + u(t,s,q-1) - u(t,s,q)] + \max_{\delta^b} \lambda^b(\delta^b)[\delta^b + u(t,s,q+1) - u(t,s,q)] = 0,

with terminal condition u(T,s,q)=qsγ2q2σ20=qsu(T,s,q) = qs - \frac{\gamma}{2}q^2\sigma^2 \cdot 0 = qs.

Change of Variables and Closed-Form Solution

Avellaneda and Stoikov reduce this to a tractable form via the substitution:

u(t,s,q)=sqγ2q2σ2(Tt)+1γlnw(t,q),u(t, s, q) = s \cdot q - \frac{\gamma}{2} q^2 \sigma^2 (T - t) + \frac{1}{\gamma}\ln w(t, q),

where w(t,q)w(t,q) satisfies a linear ODE system. For the exponential demand curve λ(δ)=Aekδ\lambda(\delta) = A e^{-k\delta}, the optimal spreads take the closed form:

δta=1γln ⁣(1+γk)+γσ22(Tt)(2q+1),\delta^{a*}_t = \frac{1}{\gamma}\ln\!\left(1 + \frac{\gamma}{k}\right) + \frac{\gamma\sigma^2}{2}(T-t)\bigl(2q + 1\bigr),

δtb=1γln ⁣(1+γk)+γσ22(Tt)(12q),\delta^{b*}_t = \frac{1}{\gamma}\ln\!\left(1 + \frac{\gamma}{k}\right) + \frac{\gamma\sigma^2}{2}(T-t)\bigl(1 - 2q\bigr),

or more elegantly, the reservation price (the market maker's fair value of the asset given their inventory):

r(t,q)=Stqγσ2(Tt),r(t, q) = S_t - q \cdot \gamma \sigma^2 (T - t),

and the optimal spread around this reservation price:

δa+δb=2γln ⁣(1+γk)+γσ2(Tt).\delta^{a*} + \delta^{b*} = \frac{2}{\gamma}\ln\!\left(1 + \frac{\gamma}{k}\right) + \gamma\sigma^2(T-t).

Interpretation

Reservation price: the market maker with long inventory q>0q > 0 sets a reservation price r<Sr < S — 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 (q<0q < 0) shifts quotes upward.

Optimal spread: the spread has two components:

  • 2γln(1+γ/k)\frac{2}{\gamma}\ln(1 + \gamma/k): the adverse selection / profit margin component, increasing in risk aversion γ\gamma and decreasing in demand elasticity kk.
  • γσ2(Tt)\gamma\sigma^2(T-t): the inventory risk component, proportional to remaining time and variance. As tTt \to T, 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

  1. Zero inventory: at q=0q = 0, the reservation price equals the mid r=Sr = S and the bid/ask spreads are symmetric. The optimal spread is 2γln(1+γ/k)+γσ2(Tt)\frac{2}{\gamma}\ln(1 + \gamma/k) + \gamma\sigma^2(T-t).

  2. Near expiry (tTt \to T): the inventory risk component vanishes, the spread collapses to 2γln(1+γ/k)\frac{2}{\gamma}\ln(1 + \gamma/k) (the pure adverse selection component). The market maker quotes tightly because there is no future inventory risk.

  3. High γ\gamma: more risk-averse market maker quotes a wider spread (collects more per trade) but adjusts inventory more aggressively by skewing quotes.

  4. Reservation price skew: with q=10q = 10 long, γ=0.01\gamma = 0.01, σ=0.20\sigma = 0.20, Tt=1T - t = 1: r=S10×0.01×0.04=S0.004r = S - 10 \times 0.01 \times 0.04 = S - 0.004. 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 λ(δ)=Aekδ\lambda(\delta) = Ae^{-k\delta} 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 σ\sigma. 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 u(t,s,q)sqγ2q2σ2(Tt)+terms in spread incomeu(t,s,q) \approx sq - \frac{\gamma}{2}q^2\sigma^2(T-t) + \text{terms in spread income}. The marginal value of holding one more unit of inventory is qusqγσ2(Tt)\partial_q u \approx s - q\gamma\sigma^2(T-t). Setting this equal to rr (the reservation price — the indifference price for accepting one more unit) gives r=Sqγσ2(Tt)r = S - q\gamma\sigma^2(T-t). Long inventory (q>0q>0): the market maker already bears variance q2σ2\propto q^2\sigma^2; adding another unit increases the variance by (2q+1)γσ2/2(2q+1)\gamma\sigma^2/2, which is penalised at rate γ\gamma. The reservation price is therefore below mid — the market maker is a reluctant buyer. Optimal quotes: b=rδbb = r - \delta^b, a=r+δaa = r + \delta^a, where δb+δa=2γln(1+γ/k)+γσ2(Tt)\delta^b + \delta^a = \frac{2}{\gamma}\ln(1+\gamma/k) + \gamma\sigma^2(T-t).

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 πt\pi_t the arriving order is from an informed trader (causing permanent price impact), with probability 1πt1-\pi_t from an uninformed trader. The market maker updates μt=Pr[V=VH]\mu_t = \Pr[V = V_H] after each trade (GM Bayesian updating), which shifts the mid-price estimate StS_t. Simultaneously, the market maker solves a stochastic control problem (AS) with the updated mid. The optimal spread has three components:

δa+δb=2γln ⁣(1+γk)profit margin+γσ2(Tt)inventory risk+πt(VHVL)adverse selection.\delta^{a*} + \delta^{b*} = \underbrace{\frac{2}{\gamma}\ln\!\left(1 + \frac{\gamma}{k}\right)}_{\text{profit margin}} + \underbrace{\gamma\sigma^2(T-t)}_{\text{inventory risk}} + \underbrace{\pi_t(V_H - V_L)}_{\text{adverse selection}}.

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 πt0\pi_t \to 0 after many trades resolve the information asymmetry).

Read the theory? Run the code.

View Notebook