trading4 Min Read

Market Making Basics: Spread Capture vs Inventory Risk (in Python)

Gorav Singal

October 30, 2026

TL;DR

A market maker posts a BID and an ASK at the same time and profits from the SPREAD between them: buy at the bid from a seller, sell at the ask to a buyer, pocket the difference. The catch is INVENTORY RISK — orders don't arrive evenly, so you accumulate a position you didn't choose, and if the price then moves against that inventory, the loss can dwarf every spread you captured. The core control is to SKEW your quotes against inventory: when you get long, shift both quotes down (lower bid so you buy less, lower ask so you sell more) to offload. A toy market maker in Python shows it works: with no control, inventory random-walked to ±90; with skewing, it stayed within ±3 (std 25.5 → 0.8) — at the cost of some gross P&L. Market making is a risk-management game: the spread is the easy part, controlling inventory is the job.

Market Making Basics: Spread Capture vs Inventory Risk (in Python)

So far you’ve been a taker — crossing the spread to get filled. A market maker is the other side: a maker who provides liquidity by resting orders on both sides of the book, and gets paid the spread for the service. It sounds like free money (“just earn the spread forever!”) and it absolutely is not, for one reason that this article is really about: inventory risk.

A note up front: educational material, not financial advice. Nothing here is a recommendation to trade.

The edge: capture the spread

A market maker continuously quotes a bid (willing to buy) below the mid and an ask (willing to sell) above it, and waits to get hit on either side.

Market making: quote a bid below the mid and an ask above it; a seller hits your bid (you buy), a buyer lifts your ask (you sell), and the round trip earns you the spread. But inventory you accumulate while waiting for the other side is the risk that can wipe out the spread.

  • A seller comes along and hits your bid → you buy at 99.95.
  • A buyer comes along and lifts your ask → you sell at 100.05.
  • Round trip: you bought at 99.95 and sold at 100.05, keeping the 0.10 spread — and you took almost no directional view. Do this thousands of times a day and the spreads add up.

That’s the edge, and it’s real: market makers are paid to provide the liquidity everyone else consumes. In exchange they often get maker rebates too. On a liquid instrument with steady two-way flow, capturing the spread is a genuine, repeatable business.

The catch: inventory risk

Here’s the problem. Orders don’t arrive evenly. Sometimes sellers keep hitting your bid and no buyers show up — so you keep buying, accumulating a long inventory you never chose. Now you’re not market-neutral at all; you’re sitting on a big long position. And if the price then falls, the mark-to-market loss on that inventory can dwarf every spread you ever captured. The spread is the edge; the inventory you pile up while waiting for the other side is the risk that can wipe it out. That asymmetry — small steady gains from the spread, occasional large losses from inventory — is the central tension of market making.

A toy market maker in Python

Let’s simulate it. A mid-price random walk, orders arriving on each side, and a market maker quoting around a reservation price that leans against its inventory.

import numpy as np

def run_mm(gamma, seed=1, n=40_000):
    rng = np.random.default_rng(seed)
    mid, half, tick = 100.0, 0.05, 0.01
    inv, cash, invs = 0, 0.0, []
    alpha = 6.0                              # how much aggressive quotes get filled
    for _ in range(n):
        mid += rng.normal(0, 0.02)           # mid random-walks
        r = mid - gamma * inv * tick         # reservation price leans against inventory
        bid, ask = r - half, r + half
        if rng.random() < 0.5:               # a seller may hit our BID -> we buy
            p = min(max(0.5 + alpha * (bid - mid), 0.02), 0.98)
            if rng.random() < p: cash -= bid; inv += 1
        else:                                # a buyer may lift our ASK -> we sell
            p = min(max(0.5 - alpha * (ask - mid), 0.02), 0.98)
            if rng.random() < p: cash += ask; inv -= 1
        invs.append(inv)
    invs = np.array(invs)
    return cash + inv * mid, invs.std(), int(np.abs(invs).max())

for gamma, label in [(0.0, "no inventory control"), (3.0, "skew against inventory")]:
    pnl, istd, imax = run_mm(gamma)
    print(f"{label:24}: P&L {pnl:7.1f}  inv std {istd:5.1f}  |max inv| {imax}")

The key variable is gamma, the inventory skew. With gamma = 0, we quote symmetrically around the mid and ignore our inventory. With gamma > 0, when we get long (inv > 0) the reservation price r drops, which lowers both our bid (so we buy less) and our ask (so we sell more, offloading the position). The quotes lean to pull inventory back toward zero.

The measured result: skew tames inventory

configuration P&L inventory std |max inventory|
no inventory control 477.8 25.5 90
skew against inventory 278.8 0.8 3

Without control, inventory random-walked out to ±90 — a huge, unwanted directional position that one adverse price move could turn into a catastrophic loss. With skewing, inventory stayed within ±3 (standard deviation collapsed from 25.5 to 0.8). The cost was some gross P&L (477 → 279), because leaning your quotes means you capture slightly less spread. That trade — give up some gross profit to crush inventory risk — is the entire craft of market making. A market maker who maximises spread capture and ignores inventory will have a great month and then one terrible day that ends the business.

What real market making adds

The toy captures the core tension; real market making layers on:

  • Adverse selection — the trader hitting your quote often knows something (there’s news, a big order coming). You systematically buy just before prices fall. Makers manage this by widening quotes in fast markets and pulling quotes around news.
  • Optimal quoting models — the Avellaneda–Stoikov framework formalises exactly how to set the spread and skew given your inventory, risk aversion, and time horizon. Our gamma is a crude version of its inventory term.
  • Speed — on lit exchanges, market making is a latency game: you must update quotes faster than others can pick you off. This is why serious market making is co-located and written in C++ — a world the trading course covers in depth.

Takeaways

  • A market maker quotes both sides and earns the spread — buy at the bid, sell at the ask, pocket the difference, thousands of times.
  • The real risk is inventory: uneven order flow leaves you holding a position you didn’t choose, and an adverse move on it can wipe out all your captured spread.
  • The core control is to skew quotes against inventory. Measured: skewing cut |max inventory| from 90 to 3 (std 25.5 → 0.8), trading some gross P&L for far lower risk.
  • Market making is a risk-management game — the spread is easy; controlling inventory (and adverse selection, and speed) is the job.

Next: the discipline that keeps any of these strategies from blowing up — position sizing and the Kelly criterion: how much to bet.

Share

Related Posts

Order-Book Imbalance: A Microstructure Signal (in Python)

Order-Book Imbalance: A Microstructure Signal (in Python)

The strategies so far used daily or minute bars. This last one goes down to the…

Position Sizing & the Kelly Criterion in Python

Position Sizing & the Kelly Criterion in Python

Every strategy article so far asked ”what should I trade?” This one asks the…

Momentum & Trend Following in Python

Momentum & Trend Following in Python

Momentum is, along with value, one of the most robust and widely-documented…

Mean Reversion & Pairs Trading in Python

Mean Reversion & Pairs Trading in Python

The moving-average crossover bet on direction and lost. Here’s a strategy with a…

Algo Trading Vocabulary — The Cheat-Sheet That Unlocks Everything

Algo Trading Vocabulary — The Cheat-Sheet That Unlocks Everything

Every field has a vocabulary you must own before anything else makes sense, and…

Execution Algorithms: VWAP & TWAP in Python

Execution Algorithms: VWAP & TWAP in Python

Every strategy so far produced signals — when to be long, short, or flat. But a…

Latest Posts

Execution Algorithms: VWAP & TWAP in Python

Execution Algorithms: VWAP & TWAP in Python

Every strategy so far produced signals — when to be long, short, or flat. But a…

Position Sizing & the Kelly Criterion in Python

Position Sizing & the Kelly Criterion in Python

Every strategy article so far asked ”what should I trade?” This one asks the…

Momentum & Trend Following in Python

Momentum & Trend Following in Python

Momentum is, along with value, one of the most robust and widely-documented…

Mean Reversion & Pairs Trading in Python

Mean Reversion & Pairs Trading in Python

The moving-average crossover bet on direction and lost. Here’s a strategy with a…

The Moving-Average Crossover, Backtested Honestly in Python

The Moving-Average Crossover, Backtested Honestly in Python

If algo trading has a “hello world,” it’s the moving-average crossover. Every…

Order-Book Imbalance: A Microstructure Signal (in Python)

Order-Book Imbalance: A Microstructure Signal (in Python)

The strategies so far used daily or minute bars. This last one goes down to the…