Market Making and Liquidity Provision › Optimal Market Making: The Avellaneda-Stoikov Model
Quantitative FinanceAll chapters Market Making and Liquidity Provision › Optimal Market Making: The Avellaneda-Stoikov ModelOne section · Sections in this chapter6 / 13 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
The foundational quantitative model for market making was developed by Marco Avellaneda and Sasha Stoikov in 2008. This model provides optimal bid and ask quotes that balance the trade-off between earning spread and managing inventory risk. The model produces closed-form solutions that capture the essential economics of market making while remaining tractable enough for real-time implementation.
Model Setup
The Avellaneda-Stoikov model builds on several key assumptions that simplify the market making problem while preserving its essential features:
- The asset price follows arithmetic Brownian motion:
- You have a terminal time horizon and seek to maximize expected utility of terminal wealth
- Order arrivals follow Poisson processes with intensity depending on quote distance from mid price
- You have exponential utility with risk aversion parameter
Each assumption serves a specific purpose. Arithmetic Brownian motion simplifies the analysis while capturing the essential feature that prices fluctuate unpredictably. The terminal horizon reflects the fact that you cannot hold positions indefinitely and must eventually flatten your books. The Poisson arrival process captures the random nature of order flow, while exponential utility provides a tractable way to model risk aversion.
Let and be the distances of the ask and bid from the mid price. The order arrival intensity (rate) at a distance from the mid price is modeled as:
where:
- : arrival intensity
- : distance from mid price
- : baseline arrival rate
- : order flow sensitivity to price
This exponential decay function captures the fundamental trade-off in limit order trading: increasing the spread (larger ) yields more profit per trade but exponentially reduces the probability of execution. The parameter controls how sensitive order flow is to price. A high value of means that traders are very price-sensitive, so moving quotes even slightly away from the mid price dramatically reduces execution probability. A low value of indicates price-insensitive traders who will trade across a wider range of prices. Calibrating the model therefore requires estimating for the market.

Optimal Quotes
The model yields closed-form optimal quotes through a dynamic programming argument. In this model, you maximize expected utility of terminal wealth, accounting for the uncertain arrival of orders and the risk of holding inventory. The solution introduces a fundamental concept called the reservation price, which represents your private valuation accounting for inventory:
where:
- : reservation price
- : current mid price
- : current inventory
- : risk aversion parameter
- : absolute volatility of the asset (in price units, distinct from percentage volatility)
- : time remaining until the terminal horizon
The key insight is that the reservation price shifts away from the mid price based on inventory. This shift quantifies how your risk changes your perception of fair value:
- When long (), the reservation price is below the mid price, encouraging selling
- When short (), the reservation price is above the mid price, encouraging buying
To understand why this makes sense, consider being long 50 shares. You face the risk that prices will fall, causing you to lose money on your position. From your perspective, a "fair" price for buying more shares is lower than the market mid price because additional purchases increase your risk. Conversely, a fair price for selling is also lower than the mid price because you are eager to reduce your risk exposure. The reservation price formula captures this adjustment precisely.
The optimal spread around this reservation price is:
where:
- : optimal spread
- : risk aversion parameter
- : absolute volatility
- : time remaining
- : order flow sensitivity
The spread consists of two terms, each with a distinct economic interpretation:
-
: a risk premium that widens with volatility and time horizon. This term reflects the inventory risk component. Higher volatility means greater potential for adverse price moves while holding inventory, so you demand a wider spread as compensation. Similarly, a longer time horizon means more time during which prices could move adversely.
-
: a liquidity premium driven by the elasticity of order flow (). This term captures the trade-off between profit per trade and execution probability. When order flow is very price-sensitive (high ), this term is small because you cannot afford to quote wide spreads without losing all your order flow. When order flow is price-insensitive (low ), you can extract larger spreads.


This leads to optimal bid and ask prices:
where:
- : optimal bid and ask quotes
- : reservation price
- : optimal spread
- : current mid price
- : current inventory
- : risk aversion parameter
- : absolute volatility
- : time remaining until horizon
These formulas show that both the bid and ask prices shift together based on inventory. The spread between them remains constant at , but the center of that spread, the reservation price, moves to encourage inventory-reducing trades.
import numpy as np
import pandas as pd
def avellaneda_stoikov_quotes(S, q, gamma, sigma, T_minus_t, k):
"""
Calculate optimal bid and ask quotes using Avellaneda-Stoikov model.
Parameters:
-----------
S : float - Current mid price
q : float - Current inventory
gamma : float - Risk aversion parameter
sigma : float - Absolute volatility (price units per square-root year)
T_minus_t : float - Time to horizon (in years)
k : float - Order book parameter (sensitivity of fill rate to price)
Returns:
--------
tuple: (bid_price, ask_price, reservation_price, optimal_spread)
"""
# Reservation price - shifts based on inventory
reservation = S - q * gamma * sigma**2 * T_minus_t
# Optimal spread
spread = gamma * sigma**2 * T_minus_t + (2 / gamma) * np.log(1 + gamma / k)
# Optimal bid and ask
bid = reservation - spread / 2
ask = reservation + spread / 2
return bid, ask, reservation, spread
# Example parameters
S = 100.0 # Current price
sigma = 5.0 # Absolute volatility ($ per square-root year)
gamma = 0.1 # Risk aversion
k = 1.5 # Order book parameter
T_minus_t = 1 / 252 # One day to horizon
# Calculate quotes for different inventory levels
inventory_range = np.linspace(-50, 50, 101)
quotes_data = []
for q in inventory_range:
bid, ask, res, spd = avellaneda_stoikov_quotes(
S, q, gamma, sigma, T_minus_t, k
)
quotes_data.append(
{
"inventory": q,
"bid": bid,
"ask": ask,
"reservation": res,
"spread": spd,
}
)
quotes_df = pd.DataFrame(quotes_data)
The figure shows the core intuition of inventory-based quoting. When you are flat (inventory = 0), quotes are centered around the mid price. As inventory grows positive (long position), both quotes shift down to encourage buying from you. The reservation price, what you consider fair value given your position, drops to reflect the risk of holding long inventory. The magnitude of this shift increases with inventory size, which reflects the quadratic growth of inventory risk that we derived earlier.
Parameter Sensitivity
Understanding how model parameters affect optimal quotes helps you calibrate the model to real market conditions. Each parameter captures a different aspect of the market environment, and knowing their effects enables informed adjustment:
import pandas as pd
# Analyze sensitivity to key parameters
param_analysis = []
# Vary risk aversion
for gamma_test in [0.05, 0.1, 0.2, 0.5]:
bid, ask, res, spd = avellaneda_stoikov_quotes(
100, 0, gamma_test, 30.0, 1 / 252, 1.5
)
param_analysis.append(
{
"parameter": "gamma",
"value": gamma_test,
"spread": ask - bid,
"spread_bps": (ask - bid) / 100 * 10000,
}
)
# Vary volatility
for sigma_test in [0.1, 0.2, 0.3, 0.5]:
bid, ask, res, spd = avellaneda_stoikov_quotes(
100, 0, 0.1, 100 * sigma_test, 1 / 252, 1.5
)
param_analysis.append(
{
"parameter": "sigma",
"value": sigma_test,
"spread": ask - bid,
"spread_bps": (ask - bid) / 100 * 10000,
}
)
# Vary time horizon
for T_test in [1 / 252, 5 / 252, 21 / 252, 63 / 252]:
bid, ask, res, spd = avellaneda_stoikov_quotes(
100, 0, 0.1, 5.0, T_test, 1.5
)
param_analysis.append(
{
"parameter": "T",
"value": T_test * 252, # Convert to days
"spread": ask - bid,
"spread_bps": (ask - bid) / 100 * 10000,
}
)
sensitivity_df = pd.DataFrame(param_analysis)Optimal Spread Sensitivity Analysis ============================================================ Risk Aversion (gamma): gamma = 0.05: spread = $1.4902 (149.0 bps) gamma = 0.10: spread = $1.6479 (164.8 bps) gamma = 0.20: spread = $1.9659 (196.6 bps) gamma = 0.50: spread = $2.9364 (293.6 bps) Volatility (sigma): sigma = 0.10: spread = $1.3305 (133.0 bps) sigma = 0.20: spread = $1.4495 (145.0 bps) sigma = 0.30: spread = $1.6479 (164.8 bps) sigma = 0.50: spread = $2.2828 (228.3 bps) Time Horizon (T in days): T = 1 days: spread = $1.3007 (130.1 bps) T = 5 days: spread = $1.3404 (134.0 bps) T = 21 days: spread = $1.4991 (149.9 bps) T = 63 days: spread = $1.9158 (191.6 bps)



The sensitivity analysis reveals several important relationships that guide practical implementation:
- Higher risk aversion leads to wider spreads; more cautious traders demand greater compensation for bearing inventory risk. If you have limited capital or strict risk limits, you would appropriately use a higher gamma value.
- Higher volatility requires wider spreads to compensate for greater inventory risk. This explains why spreads widen during periods of market stress and narrow during calm periods. You dynamically adjust estimates based on recent realized volatility.
- Longer horizons allow wider spreads because there's more time for adverse price moves. This is counterintuitive at first: one might think that more time allows prices to revert. However, the model captures the fact that more time means more uncertainty, and you must be compensated for bearing this uncertainty.