Market Making and Liquidity Provision › Implementing a Market Making Simulation

Michael BrenndoerferDecember 31, 20256 min read
Quantitative FinanceAll chapters
Part 12 of 15Market Making and High-Frequency Trading
Chapter 65Market Making and Liquidity Provision › Implementing a Market Making Simulation
Reading section 7 of 13 · Implementing a Market Making Simulation · 6 minSection 7 of 13 · 6 min

Book contents

Section 7 of 13 · 6 min read

Market Making and Liquidity Provision › Implementing a Market Making SimulationOne section · Sections in this chapter7 / 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

Let's build a complete simulation to see how these concepts work together in practice. We'll simulate your strategy using the Avellaneda-Stoikov model over a trading day. This simulation will illustrate how the theoretical framework translates into actual trading behavior and outcomes.

In[21]:
Code
import numpy as np
import pandas as pd


class MarketMakingSimulator:
    """
    Simulates a market making strategy using Avellaneda-Stoikov quotes.
    """

    def __init__(
        self,
        initial_price=100,
        volatility=0.3,
        gamma=0.1,
        k=1.5,
        lambda_base=5.0,
        dt=1 / (252 * 6.5 * 60),
    ):
        """
        Parameters:
        -----------
        initial_price : float - Starting mid price
        volatility : float - Annualized volatility
        gamma : float - Risk aversion
        k : float - Order book parameter
        lambda_base : float - Base arrival rate (orders per minute)
        dt : float - Time step (fraction of year)
        """
        self.S0 = initial_price
        self.sigma = volatility
        self.gamma = gamma
        self.k = k
        self.lambda_base = lambda_base
        self.dt = dt

    def simulate(self, n_steps, T_total):
        """
        Run simulation for n_steps.

        Parameters:
        -----------
        n_steps : int - Number of time steps
        T_total : float - Total time horizon in years

        Returns:
        --------
        dict with simulation results
        """
        np.random.seed(789)

        # Initialize tracking arrays
        prices = np.zeros(n_steps + 1)
        inventories = np.zeros(n_steps + 1)
        cash = np.zeros(n_steps + 1)
        bids = np.zeros(n_steps)
        asks = np.zeros(n_steps)
        trades = []

        prices[0] = self.S0

        for t in range(n_steps):
            S = prices[t]
            q = inventories[t]
            T_remaining = T_total - t * self.dt

            # Calculate optimal quotes
            bid, ask, _, _ = avellaneda_stoikov_quotes(
                S, q, self.gamma, self.sigma, max(T_remaining, 1e-6), self.k
            )

            bids[t] = bid
            asks[t] = ask

            # Calculate fill probabilities
            delta_bid = S - bid
            delta_ask = ask - S

            # Poisson arrival probabilities for this time step
            lambda_bid = (
                self.lambda_base
                * np.exp(-self.k * delta_bid)
                * self.dt
                * 252
                * 6.5
                * 60
            )
            lambda_ask = (
                self.lambda_base
                * np.exp(-self.k * delta_ask)
                * self.dt
                * 252
                * 6.5
                * 60
            )

            # Simulate arrivals (simplify to at most one order per time step)
            bid_fill = np.random.random() < lambda_bid
            ask_fill = np.random.random() < lambda_ask

            # Update inventory and cash
            inventories[t + 1] = inventories[t]
            cash[t + 1] = cash[t]

            if bid_fill:  # Market maker buys
                inventories[t + 1] += 1
                cash[t + 1] -= bid
                trades.append(
                    {"time": t, "side": "buy", "price": bid, "qty": 1}
                )

            if ask_fill:  # Market maker sells
                inventories[t + 1] -= 1
                cash[t + 1] += ask
                trades.append(
                    {"time": t, "side": "sell", "price": ask, "qty": 1}
                )

            # Simulate price movement (arithmetic Brownian motion)
            prices[t + 1] = (
                prices[t] + self.sigma * np.sqrt(self.dt) * np.random.randn()
            )

        # Calculate final P&L
        final_value = cash[-1] + inventories[-1] * prices[-1]
        initial_value = 0

        return {
            "prices": prices,
            "inventories": inventories,
            "cash": cash,
            "bids": bids,
            "asks": asks,
            "trades": pd.DataFrame(trades),
            "final_pnl": final_value - initial_value,
            "final_inventory": inventories[-1],
        }
In[22]:
Code
# Run simulation for a full trading day (6.5 hours, ~23,400 seconds)
# Using minute-level granularity
n_minutes = 390  # 6.5 hours
T_day = 1 / 252  # One trading day

simulator = MarketMakingSimulator(
    initial_price=100,
    volatility=0.3,
    gamma=0.1,
    k=1.5,
    lambda_base=2.0,  # Average 2 orders per minute at fair price
)

results = simulator.simulate(n_minutes, T_day)
Out[24]:
Console
Market Making Simulation Results - Full Trading Day
============================================================

Trading Activity:
  Total trades: 591
  Buys: 290
  Sells: 301

Position:
  Final inventory: -11 shares
  Max long: 4 shares
  Max short: -15 shares

P&L:
  Final P&L: $381.37
  Final inventory value: $-1100.14
  Cash position: $1481.51
Out[25]:
Visualization
Line chart showing mid-price in black with bid and ask quote lines and buy and sell fill markers over a simulated trading day.
Simulation of Avellaneda-Stoikov market making dynamics showing mid-price and quotes. The optimal bid and ask quotes fluctuate around the mid-price (black) to manage inventory risk.
Area chart showing inventory fluctuating around zero with long positions shaded green and short positions shaded red.
Inventory evolution during the simulation. Quote adjustments successfully constrain inventory within a mean-reverting range.

The simulation illustrates how the market making strategy works in practice. The top panel shows the mid price evolution with your bid and ask quotes, while trade executions are marked with triangles. The bottom panel shows how inventory evolves over time. Notice how it tends to fluctuate around zero because the quotes encourage mean reversion. When inventory drifts positive, the lower quotes attract sellers to buy from you, pulling inventory back down. When inventory drifts negative, the higher quotes attract buyers to sell to you.