Skip to content

Avellaneda-Stoikov Market Making: The Reservation Price and Inventory Risk

Market making looks simple: quote both sides, capture the spread. The hard part is that inventory accumulates, prices move, and tight quotes get run over. Avellaneda-Stoikov (2008) solves the trade-off with two formulas: the reservation price and the optimal spread.

4 of 12 ~11 min

Market making is the cleanest strategy in finance in theory and the hardest in practice. The idea is simple: post bids and asks, capture the spread, manage inventory. The reality: your fills are random, the price moves, and inventory piles up in exactly the direction you don’t want. Every second, a market maker is answering two questions — where to center the quotes and how wide to open the spread — and the two answers are tangled together.

The modern mathematical treatment is Avellaneda-Stoikov (2008). Its elegance is that it cleanly decouples those two questions: the quote center comes from one formula — the reservation price — and the spread from another, each with its own economics. It’s the standard reference for academic market making and the skeleton of many quantitative market-making strategies. Harris covers market making in Ch. 13, with extensive discussion of dealer behavior. Below we derive the model and both formulas, then get it to the trading floor.

The Market Maker’s Problem

You make markets in asset SS. You post a bid SbS^b and an ask SaS^a, with midprice Sm=(Sa+Sb)/2S^m = (S^a + S^b) / 2 and half-spread δ=(Sa−Sb)/2\delta = (S^a - S^b) / 2.

Flow is price-sensitive: buyers arrive at rate λb(Sb)\lambda^b(S^b) (the less aggressive your bid, the longer you wait) and sellers at rate λa(Sa)\lambda^a(S^a) (likewise). When someone hits your bid, you buy and inventory rises; when someone lifts your ask, you sell and inventory falls. You hold inventory qq (positive = long, negative = short). In the simplest version the midprice is a driftless Brownian motion:

dS=σdWdS = \sigma dW

The objective is to maximize expected utility of terminal wealth. Avellaneda-Stoikov use exponential utility:

max⁡  E[−e−γ(XT+qTST)]\max \; E\left[ -e^{-\gamma (X_T + q_T S_T)} \right]

where XTX_T is terminal cash, qTSTq_T S_T is the terminal inventory marked at mid, and γ\gamma is risk aversion. Exponential utility isn’t a casual choice: it makes the optimal policy independent of the wealth level, which is what makes the problem tractable. Variants add a running inventory penalty; the structure survives.

Note what’s not in this setup: informed traders. The price is exogenous, and flow responds only to your quote distance. That absence will matter when we compare with Glosten-Milgrom below.

The Avellaneda-Stoikov Solution

Solving the HJB equation for the value function v(q,S,t)v(q, S, t) — the optimal continuation value at inventory qq, midprice SS, time tt — gives quotes with the structure:

Sa=Sr+δa,Sb=Sr−δbS^a = S^r + \delta_a, \quad S^b = S^r - \delta_b

The quotes no longer straddle the midprice; they straddle a new center, SrS^r — the reservation price. That is the decoupling promised above: first choose the center, then the width.

The reservation price is the price at which the market maker is indifferent between buying and selling:

Sr=Sm−q⋅γσ2(T−t)S^r = S^m - q \cdot \gamma \sigma^2 (T - t)

where qq is current inventory, γ\gamma risk aversion, σ\sigma midprice volatility, and T−tT - t time remaining. Read its direction: when you’re long (q>0q > 0), the reservation price sits below the mid — you’ll sell a little cheap (you want the inventory gone) and demand an even lower price to buy more. Short, the reverse. The shift scales with volatility and time remaining: the harder the position can hurt you and the longer you’d have to carry it, the further you drag the whole quote structure.

The width half of the answer is the optimal half-spread:

δa=δb=δ=γσ2(T−t)2+1γlog⁡(1+γκ)\delta_a = \delta_b = \delta = \frac{\gamma \sigma^2 (T - t)}{2} + \frac{1}{\gamma} \log\left(1 + \frac{\gamma}{\kappa}\right)

where κ\kappa is the elasticity of arrival rates to quote distance (the κ\kappa in the flow model λ(δ)=Ae−κδ\lambda(\delta) = A e^{-\kappa \delta}). In the limit κ→∞\kappa \to \infty (a very liquid market with highly price-sensitive flow):

δ≈γσ2(T−t)2+1κ\delta \approx \frac{\gamma \sigma^2 (T-t)}{2} + \frac{1}{\kappa}

The two terms carry different economics and are worth reading separately.

The first, γσ2(T−t)/2\gamma \sigma^2 (T - t)/2, is the price of inventory risk. It widens with risk aversion, with volatility, and with time remaining. The logic: holding inventory in a volatile market is risky, and the spread is the compensation you charge for it. Widen the spread and your fill probability drops — less flow — but each fill earns more. The formula sits at the optimum of exactly that trade-off.

The second, (1/γ)log⁡(1+γ/κ)≈1/κ(1/\gamma) \log(1 + \gamma/\kappa) \approx 1/\kappa, is the base spread: the width you’d quote even at zero inventory. It’s priced by flow elasticity — large κ\kappa (flow highly price-sensitive, liquid markets) means you can’t extract much and the base spread is tight; small κ\kappa (inelastic flow) lets you charge more. Empirically κ\kappa is larger in liquid markets; crypto majors have higher κ\kappa than small-caps. One thing worth being precise about: inside the AS model this term comes from flow elasticity, not adverse selection — there are no informed traders in the model at all. Adverse selection is real, but it’s an account you settle outside the model; we come back to it below.

Putting It Together

Assembling reservation price and half-spread, the optimal quotes are:

Sa=Sm−qγσ2(T−t)+γσ2(T−t)2+1γlog⁡(1+γ/κ)S^a = S^m - q \gamma \sigma^2 (T-t) + \frac{\gamma \sigma^2 (T-t)}{2} + \frac{1}{\gamma} \log(1 + \gamma/\kappa) Sb=Sm−qγσ2(T−t)−γσ2(T−t)2−1γlog⁡(1+γ/κ)S^b = S^m - q \gamma \sigma^2 (T-t) - \frac{\gamma \sigma^2 (T-t)}{2} - \frac{1}{\gamma} \log(1 + \gamma/\kappa)

With q=0q = 0 the quotes are symmetric around the mid. With q>0q > 0 (long), the whole structure shifts down: both bid and ask sit lower, and you’re coaxing the market to buy from you and discouraging it from selling to you. With q<0q < 0 it shifts up. Pile on enough inventory and your ask drops below the midprice — not a bug, but the model forcing you out of the position.

A Worked Example (with a Units Trap)

Step on the trap first. Take parameters that merely “look reasonable”: γ=0.1\gamma = 0.1, κ=1.5\kappa = 1.5. Base spread = (1/γ)log⁡(1+γ/κ)=10⋅log⁡(1+0.0667)=10⋅0.0646=0.646(1/\gamma)\log(1 + \gamma/\kappa) = 10 \cdot \log(1 + 0.0667) = 10 \cdot 0.0646 = 0.646 — sixty-five cents per side? On a $100 stock that's a 130 bp full spread, absurdly wide. The arithmetic is fine; the mistake is treating $\gammaandand\kappa$as dimensionless. Both carry units (1/price) and must be consistent with$\sigma^2$(price²/time) and$T-t$ (time). With mismatched units this model’s quotes can be off by orders of magnitude — that’s check number one whenever you use AS.

Now with units squared away. Stock at Sm=100.00S^m = 100.00, daily vol 2% (so σ=2\sigma = 2 dollars/√day, σ2=4\sigma^2 = 4), remaining horizon T−t=0.1T - t = 0.1 days (about 40 minutes), γ=0.001\gamma = 0.001 (per dollar), κ=10\kappa = 10 (per dollar), inventory q=+500q = +500 shares long.

Reservation shift: qγσ2(T−t)=500⋅0.001⋅4⋅0.1=0.20q \gamma \sigma^2 (T-t) = 500 \cdot 0.001 \cdot 4 \cdot 0.1 = 0.20 dollars, so Sr=99.80S^r = 99.80.

Half-spread: the inventory component γσ2(T−t)/2=0.0002\gamma \sigma^2 (T-t)/2 = 0.0002 is negligible; the base component (1/γ)log⁡(1+γ/κ)=1000⋅log⁡(1.0001)≈0.10(1/\gamma)\log(1 + \gamma/\kappa) = 1000 \cdot \log(1.0001) \approx 0.10 dollars. Half-spread ≈0.10\approx 0.10.

Quotes: bid 99.80−0.10=99.7099.80 - 0.10 = 99.70, ask 99.80+0.10=99.9099.80 + 0.10 = 99.90.

Look at what happened: your ask of $99.90 sits below the $100.00 midprice. Five hundred shares of long inventory make the model prefer “losing” 10 cents to the mid on the way out over holding the position and betting on direction. That is the entire point of the reservation-price mechanism — the quote center follows inventory, the spread only sets width. For calibration feel: once γσ2(T−t)\gamma \sigma^2 (T-t) (the per-share quote-shift slope) is set sensibly, active market makers’ total spreads typically land between 1 bp and 100 bps, depending on the asset’s volatility, the desk’s risk tolerance, and the horizon.

Where Glosten-Milgrom and Ho-Stoll Fit

The modern view splits market making into two orthogonal problems: adverse selection (does my counterparty know more than I do?) and inventory management (how do I control position risk?). Glosten-Milgrom (article 4) is the canonical model of the first: a dealer quotes static bid and ask, the spread purely compensates for being picked off by informed traders, and inventory doesn’t exist. Ho-Stoll (1981) and Avellaneda-Stoikov are models of the second: spread and quote skew manage inventory risk, and informed traders don’t exist.

Set γ→0\gamma \to 0 in AS and every inventory term vanishes, leaving only the 1/κ1/\kappa base spread — structurally echoing Glosten-Milgrom’s central insight that a spread survives even at zero inventory, though the economics differ: GM’s spread is an information rent, AS’s base spread is a flow-elasticity rent. A real market maker collects both rents: AS hands you the formula for the inventory half, and the adverse-selection half has to be patched in from GM’s logic outside the model. The two models aren’t competitors; they’re two lines of the same bill.

Add Almgren-Chriss (article 9) and the picture completes: optimal execution is to a one-sided parent order what Avellaneda-Stoikov is to two-sided quoting — down to the parallel role of the risk-aversion parameter. Understand all three and you have the skeleton of modern microstructure.

From Model to Production: The Missing Pieces

The AS quotes are a starting point, not a destination. A real high-frequency market maker has to settle at least these additional accounts on top of it.

Queue position. The model assumes posting means filling; in reality you’re at the back of the queue, so the good fills don’t reach you and the bad ones (price about to trade through you) can’t be escaped. Harris Ch. 13 and article 1 cover this. Quote sniping. HFTs detect your stale quotes and trade against them — not “informed” in any fundamental sense, just statistical arbitrage on quote changes, but to your ledger it looks exactly like adverse selection. Latency. The model is continuous-time; you have millisecond-scale latency and your cancels are always half a beat late. The tick grid. Model prices are continuous; real quotes must land on discrete ticks (one cent in US equities; in crypto it varies by venue and pair), and the rounding itself changes your queue fate. Borrow costs. Short inventory pays borrow fees, which belongs in the quotes on the negative-inventory side. Fees and rebates. Maker-taker schedules add to or subtract from every fill.

A production market-making system stacks hundreds of such adjustments on the AS skeleton. The core formulas give you the structure; the rest is engineering.

Calibration and Implementation

Three parameters need estimating to run the model. σ\sigma: realized volatility of the midprice, on a 1–5 minute rolling window. κ\kappa: from the relationship between quote distance and fill intensity (regressions on quote-update data). γ\gamma cannot be estimated from data — it is your risk preference, set by the trading mandate, commonly ranging from 1 to 10410^4 in model units. Set it too small and your quotes are tight enough to get run over; too large and you’re too wide to see flow. In volatile regimes, either let the σ\sigma estimate catch up fast or raise γ\gamma outright — don’t fight a crash with calm-market parameters, because every input to this model is a function of the regime.

The core logic fits in a few lines:

import numpy as np

def avellaneda_stoikov_quotes(mid, inventory, sigma, T_remaining,
                              gamma=1.0, kappa=1.0):
    """
    Compute optimal bid/ask quotes per Avellaneda-Stoikov.
    mid: current midprice
    inventory: current inventory (positive = long)
    sigma: per-period volatility of midprice
    T_remaining: time remaining in horizon (in same units as sigma)
    gamma: risk aversion
    kappa: arrival rate elasticity
    """
    reservation = mid - inventory * gamma * sigma**2 * T_remaining
    half_spread = (gamma * sigma**2 * T_remaining) / 2 + \
                  (1 / gamma) * np.log(1 + gamma / kappa)
    return reservation - half_spread, reservation + half_spread

Real implementations add tick rounding, queue-position adjustments, and inventory limits — that last one deserves emphasis: the model does not constrain inventory, it only nudges you gently back toward q=0q = 0. A real desk’s hard limits must be bolted on outside the model; don’t expect the skew term to stop a one-sided pile-up in an extreme tape.

Arithmion packages this pipeline. arithmion.marketmaking.AvellanedaStoikov provides real-time quote computation with calibrated parameters, inventory tracking and skew, per-fill P&L attribution, backtesting on real L2 data, and extensions for multi-asset books, jump-diffusion midprices, and borrow costs; the MarketMaker module handles calibration — you supply historical L2 data, it estimates σ\sigma and κ\kappa and helps you choose γ\gamma. The whole chain plugs into arithmion.execution and arithmion.tca for full performance analysis.

What to Actually Watch When Trading It

A few field notes worth taping to the monitor when the model becomes a daily tool. The reservation price is your anchor: when inventory is heavy, the quote center should move, and resisting the skew is betting against the model on direction. The half-spread is your risk budget: a tick tighter means more fills and more risk. If you follow the model honestly, inventory mean-reverts toward zero — and conversely, inventory that won’t return to zero means something outside the model (latency, toxic flow, parameter drift) is leaking. The most important note: AS does not detect adverse selection. Even with the right spread posted, you will still lose to informed traders; monitor your realized spread at multiple horizons, and if it’s negative you have a toxicity problem that needs a signal from outside the model (and the discipline to stop quoting when flow turns toxic).

Harris Ch. 13 has the full practitioner view, the Avellaneda-Stoikov paper is the formal reference, and Cartea-Jaimungal-Penalva (2015) organizes the model and its many extensions into textbook form.

References

  • Harris, L. Trading and Exchanges: Market Microstructure for Practitioners. Oxford University Press, 2003. Ch. 13 (dealers and market makers). ISBN: 978-0-19-514470-3.
  • Avellaneda, M., Stoikov, S. “High-Frequency Trading in a Limit Order Book.” Quantitative Finance, 2008.
  • Glosten, L., Milgrom, P. “Bid, Ask and Transaction Prices in a Specialist Market with Heterogeneously Informed Traders.” Journal of Financial Economics, 1985.
  • Ho, T., Stoll, H. “Optimal Dealer Pricing Under Transactions and Return Uncertainty.” Journal of Financial Economics, 1981.
  • Cartea, A., Jaimungal, S., Penalva, J. Algorithmic and High-Frequency Trading. Cambridge University Press, 2015.