Market Making in Python
A liquidity-providing strategy that simultaneously quotes bid and ask prices, profiting from the bid-ask spread while managing directional inventory risk.
Definition
Market Making is the institutional practice of continuously quoting both a bid (buy) price and an ask (sell) price for a financial instrument, providing liquidity to the market in exchange for earning the bid-ask spread. Market makers profit from the spread between what buyers pay and what sellers receive, but are exposed to adverse selection risk — the risk that informed traders (who know something the market maker does not) consistently trade against them. The optimal market-making problem, formalized by Avellaneda and Stoikov (2008), involves dynamically adjusting quote placement based on current inventory level, volatility, and remaining time horizon to balance spread capture against inventory risk.
Quantitative Formula
From the Avellaneda-Stoikov model: is the reservation price (the market maker's indifference price), is the mid-price, is the current inventory, is the risk aversion coefficient, is variance, and is the remaining horizon. The optimal spread is symmetric around and widens with volatility and inventory imbalance, ensuring the market maker skews quotes to reduce unwanted inventory.
Why It Matters in Backtesting
Backtesting market-making strategies requires tick-level or order-book-level data — daily OHLC bars are completely useless. The critical metric is not Sharpe Ratio but rather the P&L per lot traded versus the adverse selection cost. A naive backtest that assumes fills at the quoted price without modeling adverse selection will dramatically overstate profitability. In practice, approximately 30–50% of a market maker's quote volume is filled by informed traders moving against the position, and this cost must be explicitly modeled in any realistic backtest.
Python Implementation
import numpy as np
import pandas as pd
def avellaneda_stoikov_quotes(mid_prices: pd.Series, inventory: pd.Series,
sigma: float = 0.02, gamma: float = 0.1,
kappa: float = 1.5, T: float = 1.0) -> pd.DataFrame:
"""
Computes optimal bid/ask quotes using the Avellaneda-Stoikov (2008) model.
mid_prices: Series of mid-market prices.
inventory: Series of current inventory (positive = long, negative = short).
sigma: Asset volatility (daily).
gamma: Risk aversion parameter.
kappa: Order arrival intensity parameter.
T: Time horizon in days.
"""
t = np.linspace(0, T, len(mid_prices))
time_remaining = T - t
# Reservation price: skewed from mid based on inventory and time
reservation_price = mid_prices - inventory * gamma * (sigma ** 2) * time_remaining
# Optimal spread around reservation price
base_spread = (1 / gamma) * np.log(1 + gamma / kappa)
inventory_adjustment = 0.5 * inventory * gamma * (sigma ** 2) * time_remaining
bid_price = reservation_price - base_spread / 2 - inventory_adjustment
ask_price = reservation_price + base_spread / 2 - inventory_adjustment
return pd.DataFrame({
"mid": mid_prices.values,
"reservation_price": reservation_price.values,
"bid": bid_price.values,
"ask": ask_price.values,
"spread": (ask_price - bid_price).values
}, index=mid_prices.index)Test this in a live environment
Stop running Jupyter notebooks locally. Paste this Market Making code directly into Valetha's Strategy Lab and run a full historical backtest in seconds.
Open the Python Strategy Lab