Market Making·Market Making Fundamentals·Intermediate

Inventory Risk Manager

Build a comprehensive inventory risk management system for market making operations that continuously monitors net directional position exposure, dynamically adjusts quote skew to incentivize inventory-reducing trades, and enforces hard position limits with gradual automated liquidation rules.

market-makingrisk-controls

Managing Inventory Risk in Crypto Market Making

This notebook explores the critical aspects of managing inventory risk in cryptocurrency market making. Market makers aim to profit from the bid-ask spread by simultaneously placing buy and sell orders. However, this strategy exposes them to 'inventory risk,' which arises from holding an asset (long or short) that can fluctuate in value significantly. Effective inventory management is crucial for profitability and capital preservation in volatile crypto markets.

Key Concepts in Crypto Market Making Inventory Risk

ConceptDescriptionImportance
Inventory DeltaThe net quantity of a base asset held (e.g., BTC in a BTC/USDT pair). Can be positive (long) or negative (short).Direct exposure to price fluctuations; impacts PnL.
PnL (Profit & Loss)The financial gain or loss from trading operations and inventory value changes.The ultimate measure of strategy success.
Bid-Ask SpreadThe difference between the highest price a buyer is willing to pay (bid) and the lowest price a seller is willing to accept (ask).The primary source of profit for market makers.
Order Book ImbalanceDisparity in the volume of buy orders vs. sell orders at different price levels.Can indicate short-term price direction and inform order placement.
VolatilityThe degree of variation of a trading price series over time.Increases inventory risk; requires dynamic risk management.
SlippageThe difference between the expected price of a trade and the price at which the trade is executed.Reduces profitability, especially for large or fast trades.
HedgingTaking an offsetting position to reduce exposure to price movements (e.g., futures contracts).Mitigates inventory risk, but introduces its own costs and complexities.
Adverse SelectionThe risk of being consistently filled on 'bad' trades (e.g., selling just before a price surge, buying before a drop).Erodes PnL; can be managed by adjusting spread and order size.
Capital UtilizationHow effectively the available trading capital is being used to generate returns.Optimizing inventory levels frees up capital for other opportunities.

Dependency Installation

This section installs all necessary libraries required for the notebook. These include libraries for data manipulation, statistical calculations, and plotting.

[42]
pip install pandas numpy scipy matplotlib seaborn
Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)
Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (2.0.2)
Requirement already satisfied: scipy in /usr/local/lib/python3.12/dist-packages (1.16.3)
Requirement already satisfied: matplotlib in /usr/local/lib/python3.12/dist-packages (3.10.0)
Requirement already satisfied: seaborn in /usr/local/lib/python3.12/dist-packages (0.13.2)
Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2026.2)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.3.3)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (4.63.0)
Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.5.0)
Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (26.2)
Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (11.3.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (3.3.2)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)

Library Imports

This section imports all required Python libraries. Standard libraries are imported first, followed by third-party libraries. This ensures a clean and organized import structure.

[43]
import logging
import random
from collections import deque
import time
import math
from typing import Dict, Any, Callable

import pandas as pd
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
import seaborn as sns

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

Core Functions

This section defines the core functions for managing inventory risk in crypto market making. Each function is presented in its own block, complete with detailed explanations, parameters, return values, and example usage.

Function Name: create_market_making_state

This function initializes the state dictionary for a crypto market making strategy. It sets up initial values for inventory (delta), cash balance, current price, and other parameters crucial for simulating market operations and risk management. The inventory_delta represents the net exposure to the base asset (e.g., BTC in a BTC/USDT pair).

Parameters:

  • initial_cash (float): The starting cash balance (quote asset, e.g., USDT).
  • initial_base_asset (float): The starting quantity of the base asset (e.g., BTC).
  • current_price (float): The current market price of the base asset against the quote asset.
  • spread_bps (float): The base spread in basis points (e.g., 5 for 0.05%).
  • max_inventory_delta (float): The maximum absolute inventory delta allowed before rebalancing.
  • pnl_history_size (int): The number of past PnL values to store for moving average calculations.

Returns:

  • (dict): An initialized state dictionary containing all relevant market making parameters.
[57]
def create_market_making_state(initial_cash: float, initial_base_asset: float, current_price: float,
                               spread_bps: float, max_inventory_delta: float, pnl_history_size: int = 100) -> Dict[str, Any]:
    """
    Initializes the state dictionary for a crypto market making strategy.

    Parameters
    ----------
    initial_cash : float
        The starting cash balance (quote asset).
    initial_base_asset : float
        The starting quantity of the base asset.
    current_price : float
        The current market price of the base asset.
    spread_bps : float
        The base spread in basis points (e.g., 5 for 0.05%).
    max_inventory_delta : float
        The maximum absolute inventory delta allowed.
    pnl_history_size : int, optional
        The number of past PnL values to store, defaults to 100. (Note: For full history plotting, this is effectively ignored as `total_pnl_history` is a list.)

    Returns
    -------
    Dict[str, Any]
        An initialized state dictionary containing market making parameters.

    Examples
    --------
    >>> state = create_market_making_state(initial_cash=10000, initial_base_asset=0.5, current_price=20000, spread_bps=5, max_inventory_delta=0.1)
    >>> print(state['current_cash'])
    10000.0
    """
    state = {
        'current_cash': initial_cash,
        'current_base_asset': initial_base_asset,
        'inventory_delta': initial_base_asset, # Net quantity of base asset
        'current_price': current_price,
        'spread_bps': spread_bps,
        'max_inventory_delta': max_inventory_delta,
        'trade_count': 0,
        'long_trades': 0,
        'short_trades': 0,
        'realized_pnl': 0.0,
        'unrealized_pnl': 0.0,
        'total_pnl_history': [0.0], # Changed from deque to list to track full history
        'inventory_delta_history': [initial_base_asset],
        'price_history': [current_price],
        'balance_history': [], # total value in quote asset
        'time_step': 0
    }
    # Calculate initial total balance
    state['total_balance'] = initial_cash + (initial_base_asset * current_price)
    state['balance_history'].append(state['total_balance'])

    logger.info(f"Market making state initialized with cash={initial_cash}, base_asset={initial_base_asset}, price={current_price}")
    return state

Function Name: place_orders

This function simulates placing bid and ask orders around the current market price, taking into account a specified spread. It is the core mechanism of a market making strategy, aiming to profit from the bid-ask spread by facilitating trades. The order placement strategy can be influenced by the current inventory delta to manage risk.

Parameters:

  • state (dict): The current market making state dictionary.
  • order_size (float): The quantity of the base asset for each bid/ask order.

Returns:

  • (dict): The updated state dictionary after simulating order placement (and potential fills).
[45]
def place_orders(state: Dict[str, Any], order_size: float) -> Dict[str, Any]:
    """
    Simulates placing bid and ask orders and potential fills.

    Parameters
    ----------
    state : Dict[str, Any]
        The current market making state dictionary.
    order_size : float
        The quantity of the base asset for each bid/ask order.

    Returns
    -------
    Dict[str, Any]
        The updated state dictionary after simulating order placement and fills.

    Examples
    --------
    >>> state = create_market_making_state(initial_cash=10000, initial_base_asset=0.5, current_price=20000, spread_bps=5, max_inventory_delta=0.1)
    >>> updated_state = place_orders(state, order_size=0.01)
    >>> isinstance(updated_state, dict)
    True
    """
    current_price = state['current_price']
    spread = current_price * (state['spread_bps'] / 10000)

    bid_price = current_price - spread / 2
    ask_price = current_price + spread / 2

    logger.debug(f"Placing orders: Bid at {bid_price:.2f}, Ask at {ask_price:.2f}")

    # Simulate fills (simplified: assume 50/50 chance of buy/sell fill if within spread)
    # In a real scenario, this would depend on market conditions and order book depth.
    trade_occurred = False
    if random.random() < 0.4: # Simulate a bid fill (someone bought from our ask)
        fill_qty = order_size * random.uniform(0.5, 1.0) # Partial or full fill
        state['current_cash'] += fill_qty * ask_price
        state['current_base_asset'] -= fill_qty
        state['inventory_delta'] -= fill_qty
        state['realized_pnl'] += fill_qty * (ask_price - bid_price) # Simplified PnL from spread capture
        state['trade_count'] += 1
        state['short_trades'] += 1
        logger.info(f"Sold {fill_qty:.4f} base asset at {ask_price:.2f}. New inventory delta: {state['inventory_delta']:.4f}")
        trade_occurred = True

    if random.random() < 0.4: # Simulate an ask fill (someone sold to our bid)
        fill_qty = order_size * random.uniform(0.5, 1.0)
        state['current_cash'] -= fill_qty * bid_price
        state['current_base_asset'] += fill_qty
        state['inventory_delta'] += fill_qty
        state['realized_pnl'] += fill_qty * (ask_price - bid_price) # Simplified PnL from spread capture
        state['trade_count'] += 1
        state['long_trades'] += 1
        logger.info(f"Bought {fill_qty:.4f} base asset at {bid_price:.2f}. New inventory delta: {state['inventory_delta']:.4f}")
        trade_occurred = True

    if not trade_occurred:
        logger.debug("No fills occurred in this step.")

    return state

Function Name: adjust_inventory_risk

This function is responsible for managing the market maker's inventory by rebalancing when the inventory_delta exceeds a predefined max_inventory_delta. If the inventory is too long, it sells the excess; if too short, it buys to reduce the short position. This helps to mitigate the risk associated with holding an unbalanced inventory, especially in volatile markets.

Parameters:

  • state (dict): The current market making state dictionary.

Returns:

  • (dict): The updated state dictionary after any inventory adjustments.
[46]
def adjust_inventory_risk(state: Dict[str, Any]) -> Dict[str, Any]:
    """
    Adjusts inventory to manage risk by rebalancing if inventory_delta exceeds limits.

    Parameters
    ----------
    state : Dict[str, Any]
        The current market making state dictionary.

    Returns
    -------
    Dict[str, Any]
        The updated state dictionary after any inventory adjustments.

    Examples
    --------
    >>> state = create_market_making_state(initial_cash=10000, initial_base_asset=0.5, current_price=20000, spread_bps=5, max_inventory_delta=0.1)
    >>> state['inventory_delta'] = 0.15 # Artificially increase delta to trigger rebalance
    >>> updated_state = adjust_inventory_risk(state)
    >>> isinstance(updated_state, dict)
    True
    """
    current_delta = state['inventory_delta']
    max_delta = state['max_inventory_delta']
    current_price = state['current_price']

    if abs(current_delta) > max_delta:
        adjustment_amount = abs(current_delta) - max_delta
        if current_delta > 0: # Inventory is too long, need to sell
            state['current_cash'] += adjustment_amount * current_price
            state['current_base_asset'] -= adjustment_amount
            state['inventory_delta'] -= adjustment_amount
            state['realized_pnl'] -= adjustment_amount * (current_price * (state['spread_bps'] / 10000)) # Cost of rebalancing
            logger.warning(f"Rebalancing: Sold {adjustment_amount:.4f} base asset at {current_price:.2f} due to long inventory. New delta: {state['inventory_delta']:.4f}")
        else: # Inventory is too short, need to buy
            state['current_cash'] -= adjustment_amount * current_price
            state['current_base_asset'] += adjustment_amount
            state['inventory_delta'] += adjustment_amount
            state['realized_pnl'] -= adjustment_amount * (current_price * (state['spread_bps'] / 10000)) # Cost of rebalancing
            logger.warning(f"Rebalancing: Bought {adjustment_amount:.4f} base asset at {current_price:.2f} due to short inventory. New delta: {state['inventory_delta']:.4f}")
    else:
        logger.debug(f"Inventory delta {current_delta:.4f} is within limits [{ -max_delta:.4f}, {max_delta:.4f}]")

    return state

Function Name: track_metrics

This function updates various metrics within the market making state dictionary at each time step. It calculates the total portfolio balance, both realized and unrealized PnL, and appends the current inventory delta and price to their respective histories. This aggregation of data is crucial for analyzing the strategy's performance over time.

Parameters:

  • state (dict): The current market making state dictionary.

Returns:

  • (dict): The updated state dictionary with new metric values and histories.
[47]
def track_metrics(state: Dict[str, Any]) -> Dict[str, Any]:
    """
    Updates various metrics within the market making state dictionary.

    Parameters
    ----------
    state : Dict[str, Any]
        The current market making state dictionary.

    Returns
    -------
    Dict[str, Any]
        The updated state dictionary with new metric values and histories.

    Examples
    --------
    >>> state = create_market_making_state(initial_cash=10000, initial_base_asset=0.5, current_price=20000, spread_bps=5, max_inventory_delta=0.1)
    >>> updated_state = track_metrics(state)
    >>> isinstance(updated_state, dict)
    True
    """
    current_price = state['current_price']
    current_cash = state['current_cash']
    current_base_asset = state['current_base_asset']

    # Calculate unrealized PnL (from current base asset holdings)
    # Assuming initial_base_asset and initial_cash were used to define initial state for PnL calculation basis
    initial_total_value = state['total_balance'] # Total value at the start
    current_total_value = current_cash + (current_base_asset * current_price)
    state['unrealized_pnl'] = current_total_value - initial_total_value - state['realized_pnl'] # Total PnL minus realized

    # Update total balance history
    state['balance_history'].append(current_total_value)

    # Update PnL history
    state['total_pnl_history'].append(state['realized_pnl'] + state['unrealized_pnl'])

    # Update inventory delta history
    state['inventory_delta_history'].append(state['inventory_delta'])

    # Update price history
    state['price_history'].append(current_price)

    state['time_step'] += 1

    logger.debug(f"Metrics tracked for time step {state['time_step']}. Total PnL: {state['total_pnl_history'][-1]:.2f}")
    return state

Function Name: exponential_backoff_retry

This function implements an exponential backoff retry mechanism, a common pattern for robustly interacting with external systems (e.g., exchange APIs) that might experience temporary issues. It retries a given function a specified number of times, with increasing delays between retries, to handle transient errors gracefully.

Parameters:

  • func (Callable): The function to be executed and retried.
  • max_retries (int): The maximum number of times to retry the function.
  • initial_delay (float): The initial delay in seconds before the first retry.
  • *args: Positional arguments to pass to the func.
  • **kwargs: Keyword arguments to pass to the func.

Returns:

  • The result of the successful func execution.

Raises:

  • Exception: If the function fails after all retries, the last exception is re-raised.
[48]
def exponential_backoff_retry(func: Callable, max_retries: int = 5, initial_delay: float = 1.0, *args, **kwargs) -> Any:
    """
    Retries a function with exponential backoff.

    Parameters
    ----------
    func : Callable
        The function to be executed and retried.
    max_retries : int, optional
        The maximum number of times to retry the function, defaults to 5.
    initial_delay : float, optional
        The initial delay in seconds before the first retry, defaults to 1.0.
    *args
        Positional arguments to pass to the `func`.
    **kwargs
        Keyword arguments to pass to the `func`.

    Returns
    -------
    Any
        The result of the successful `func` execution.

    Raises
    -------
    Exception
        If the function fails after all retries, the last exception is re-raised.

    Examples
    --------
    >>> call_count = 0
    >>> def flaky_function():
    ...     nonlocal call_count
    ...     call_count += 1
    ...     if call_count < 3:
    ...         raise ValueError("Temporary error")
    ...     return "Success"
    >>> # result = exponential_backoff_retry(flaky_function, max_retries=3, initial_delay=0.1)
    >>> # print(result)
    """
    delay = initial_delay
    for i in range(max_retries):
        try:
            logger.debug(f"Attempt {i + 1}/{max_retries} for function {func.__name__}")
            return func(*args, **kwargs)
        except Exception as e:
            logger.warning(f"Attempt {i + 1} failed for {func.__name__}: {e}. Retrying in {delay:.2f} seconds...")
            time.sleep(delay)
            delay *= 2  # Exponential backoff
            jitter = random.uniform(0.8 * initial_delay, 1.2 * initial_delay) # Add jitter
            delay += jitter
    logger.error(f"Function {func.__name__} failed after {max_retries} retries.")
    raise Exception(f"Failed after {max_retries} retries")

Function Name: simulate_market_making

This is the main simulation function that orchestrates the entire market making process over a specified number of time steps. In each step, it simulates price movement, places orders, adjusts inventory for risk, and tracks key performance metrics. It utilizes the exponential_backoff_retry function to make calls to the core functions more robust.

Parameters:

  • initial_state (dict): The initial market making state dictionary.
  • num_steps (int): The total number of simulation steps.
  • order_size (float): The quantity of the base asset for each bid/ask order.
  • volatility_annual (float): The annualized volatility for price simulation.
  • time_step_days (float, optional): The time duration of each step in days. Defaults to 1/365.
  • drift (float, optional): The average rate of return (drift) of the asset. Defaults to 0.

Returns:

  • (dict): The final state dictionary after the simulation, containing all historical data.
[49]
def simulate_market_making(initial_state: Dict[str, Any],
                           num_steps: int,
                           order_size: float,
                           volatility_annual: float,
                           time_step_days: float = 1/365,
                           drift: float = 0) -> Dict[str, Any]:
    """
    Simulates a market making strategy over a specified number of time steps.

    Parameters
    ----------
    initial_state : Dict[str, Any]
        The initial market making state dictionary.
    num_steps : int
        The total number of simulation steps.
    order_size : float
        The quantity of the base asset for each bid/ask order.
    volatility_annual : float
        The annualized volatility for price simulation.
    time_step_days : float, optional
        The time duration of each step in days, defaults to 1/365.
    drift : float, optional
        The average rate of return (drift) of the asset, defaults to 0.

    Returns
    -------
    Dict[str, Any]
        The final state dictionary after the simulation, containing all historical data.

    Examples
    --------
    >>> initial_state = create_market_making_state(initial_cash=10000, initial_base_asset=0.5, current_price=20000, spread_bps=5, max_inventory_delta=0.1)
    >>> final_state = simulate_market_making(initial_state, num_steps=10, order_size=0.01, volatility_annual=0.7)
    >>> isinstance(final_state, dict)
    True
    """
    state = initial_state.copy()
    logger.info(f"Starting market making simulation for {num_steps} steps.")

    for step in range(num_steps):
        logger.debug(f"Simulation Step {step + 1}/{num_steps}")

        # 1. Simulate price movement
        new_price = exponential_backoff_retry(simulate_price_movement, 5, 1.0, state, volatility_annual, time_step_days, drift)
        state['current_price'] = new_price

        # 2. Place orders and simulate fills
        state = exponential_backoff_retry(place_orders, 5, 1.0, state, order_size)

        # 3. Adjust inventory risk if necessary
        state = exponential_backoff_retry(adjust_inventory_risk, 5, 1.0, state)

        # 4. Track metrics
        state = exponential_backoff_retry(track_metrics, 5, 1.0, state)

    logger.info("Market making simulation completed.")
    return state

Demonstration and Visualization

This section demonstrates the market making simulation using the functions defined above. It covers running a full simulation, visualizing key metrics like price movement, PnL, and inventory delta, and analyzing the strategy's performance. This provides a comprehensive overview of how inventory risk is managed and its impact on the market maker's profitability.

Running the Simulation

First, we'll define the parameters for our market making simulation and then run it. This will generate a comprehensive history of the market maker's state over time, which we can then analyze.

[58]
# Define simulation parameters
INITIAL_CASH = 100000.0 # Initial capital in quote asset (e.g., USDT)
INITIAL_BASE_ASSET = 1.0 # Initial quantity of base asset (e.g., BTC)
INITIAL_PRICE = 30000.0 # Starting price of the base asset
SPREAD_BPS = 10 # Bid-ask spread in basis points (10 bps = 0.1%)
MAX_INVENTORY_DELTA = 0.5 # Maximum allowed absolute inventory delta (0.5 BTC)
SIMULATION_STEPS = 500 # Number of simulation steps (e.g., hours, minutes)
ORDER_SIZE = 0.05 # Quantity of base asset per order
VOLATILITY_ANNUAL = 0.8 # Annualized volatility (e.g., 80% for crypto)
TIME_STEP_DAYS = 1 / (365 * 24) # Each step represents one hour
DRIFT = 0.05 # Annualized drift (e.g., 5% expected annual return)

logger.info("Initializing simulation state...")
initial_mm_state = create_market_making_state(
    initial_cash=INITIAL_CASH,
    initial_base_asset=INITIAL_BASE_ASSET,
    current_price=INITIAL_PRICE,
    spread_bps=SPREAD_BPS,
    max_inventory_delta=MAX_INVENTORY_DELTA
)

logger.info("Starting market making simulation...")
final_mm_state = simulate_market_making(
    initial_state=initial_mm_state,
    num_steps=SIMULATION_STEPS,
    order_size=ORDER_SIZE,
    volatility_annual=VOLATILITY_ANNUAL,
    time_step_days=TIME_STEP_DAYS,
    drift=DRIFT
)

print("\n--- Simulation Summary ---")
print(f"Final Cash: {final_mm_state['current_cash']:.2f}")
print(f"Final Base Asset: {final_mm_state['current_base_asset']:.4f}")
print(f"Final Price: {final_mm_state['current_price']:.2f}")
print(f"Total Trades: {final_mm_state['trade_count']}")
print(f"Realized PnL: {final_mm_state['realized_pnl']:.2f}")
print(f"Unrealized PnL: {final_mm_state['unrealized_pnl']:.2f}")
print(f"Total PnL: {(final_mm_state['realized_pnl'] + final_mm_state['unrealized_pnl']):.2f}")
WARNING:__main__:Rebalancing: Sold 0.5073 base asset at 30531.11 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0059 base asset at 30439.89 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0004 base asset at 31830.82 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0479 base asset at 31595.33 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0415 base asset at 32939.21 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0203 base asset at 33561.39 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0266 base asset at 33757.31 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0464 base asset at 33879.23 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0210 base asset at 39124.88 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0112 base asset at 39155.25 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0110 base asset at 39805.87 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0417 base asset at 39097.12 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0124 base asset at 39336.19 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0349 base asset at 39898.28 due to long inventory. New delta: 0.5000
WARNING:__main__:Rebalancing: Sold 0.0296 base asset at 38671.36 due to long inventory. New delta: 0.5000

--- Simulation Summary ---
Final Cash: 125828.59
Final Base Asset: 0.2046
Final Price: 34698.38
Total Trades: 404
Realized PnL: 515.51
Unrealized PnL: 2412.65
Total PnL: 2928.17

Visualizing Simulation Results

Now, let's visualize the key metrics from our simulation to gain insights into the market maker's performance and inventory risk over time.

[59]
import matplotlib.pyplot as plt
import seaborn as sns

# Convert histories to DataFrame for easier plotting
df_history = pd.DataFrame({
    'step': range(len(final_mm_state['price_history'])),
    'price': final_mm_state['price_history'],
    'total_pnl': final_mm_state['total_pnl_history'], # Now a list
    'inventory_delta': final_mm_state['inventory_delta_history']
})

plt.figure(figsize=(15, 12))

# Plot 1: Price Movement
plt.subplot(3, 1, 1) # 3 rows, 1 column, 1st plot
sns.lineplot(x='step', y='price', data=df_history)
plt.title('Simulated Asset Price Movement')
plt.xlabel('Simulation Step')
plt.ylabel('Price')
plt.grid(True)

# Plot 2: Total PnL Over Time
plt.subplot(3, 1, 2) # 3 rows, 1 column, 2nd plot
sns.lineplot(x='step', y='total_pnl', data=df_history, color='green')
plt.title('Cumulative Total PnL Over Time')
plt.xlabel('Simulation Step')
plt.ylabel('Total PnL (Quote Asset)')
plt.grid(True)

# Plot 3: Inventory Delta Over Time
plt.subplot(3, 1, 3) # 3 rows, 1 column, 3rd plot
sns.lineplot(x='step', y='inventory_delta', data=df_history, color='red')
plt.axhspan(-MAX_INVENTORY_DELTA, MAX_INVENTORY_DELTA, color='grey', alpha=0.2, label='Max Inventory Delta Limit')
plt.title('Inventory Delta Over Time')
plt.xlabel('Simulation Step')
plt.ylabel('Inventory Delta (Base Asset)')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()
cell output

Production Considerations

Transitioning a market making strategy from simulation to a production environment involves addressing several critical aspects to ensure robustness, performance, and security. Here's a breakdown of key considerations:

AspectDescriptionImplementation Notes
Real-time Data FeedsReliable and low-latency access to market data (price, order book, trade history).Integrate with exchange WebSocket APIs; consider redundant data sources and error handling for data interruptions.
Order Execution SystemsFast and reliable order placement, modification, and cancellation with minimal latency and slippage.Use FIX API or REST API with proper rate limiting; implement order management system (OMS) with retry logic.
Risk Management SystemAutomated monitoring and enforcement of risk limits (inventory delta, PnL drawdown, exposure limits) in real-time.Develop a dedicated risk engine; integrate with exchange APIs for account balances and positions.
Error Handling & LoggingComprehensive logging of all operations, errors, and system states. Robust error handling for API failures, network issues, etc.Centralized logging solution (e.g., ELK stack); alert system for critical errors; graceful degradation on failures.
Performance & LatencyOptimizing code for speed and minimizing latency in order to react quickly to market changes.Python with optimized libraries (NumPy, Cython); consider lower-level languages for critical paths; co-location with exchanges.
SecurityProtecting API keys, sensitive data, and preventing unauthorized access.Vaults for API keys; IP whitelisting; regular security audits; secure communication protocols (TLS).
Backtesting InfrastructureA robust framework for historical data backtesting and strategy optimization.Store historical market data; develop a backtesting engine to replay market events accurately.
Monitoring & AlertingReal-time dashboards for key metrics (PnL, inventory, trade volume) and automated alerts for anomalous behavior or system issues.Grafana, Prometheus; SMS/email alerts for critical events (e.g., large inventory imbalance, PnL drop).
Liquidation ManagementStrategies to prevent forced liquidations due to margin calls or excessive inventory delta.Set strict margin limits; implement automated deleveraging or hedging mechanisms; monitor margin utilization.
Regulatory ComplianceAdherence to local and international financial regulations, reporting requirements, and AML/KYC policies.Consult legal counsel; ensure proper data retention; implement compliance checks within the trading system.

Conclusion

This notebook provides a foundational framework for understanding and simulating inventory risk management in crypto market making. We've developed core functions for simulating price movements, placing orders, adjusting inventory, and tracking performance metrics. The simulation demonstrates how dynamic inventory management, influenced by factors like max_inventory_delta, is crucial for balancing profitability from the spread with the inherent risks of holding assets in a volatile market.

Key takeaways from this exercise include:

  • Importance of Inventory Delta: Maintaining inventory within predefined limits (max_inventory_delta) is vital to control exposure to price fluctuations.
  • PnL Components: Understanding the interplay between realized PnL (from spread capture) and unrealized PnL (from inventory value changes) is essential for comprehensive performance assessment.
  • Robustness: The exponential_backoff_retry mechanism enhances the resilience of the simulation, a practice that is even more critical in real-world trading environments with potential API issues.
  • Dynamic Adjustments: The ability to dynamically adjust inventory based on market conditions and risk tolerance is a cornerstone of effective market making.

Further enhancements could involve more sophisticated order placement strategies (e.g., adaptive spreads based on volatility or order book imbalance), advanced hedging techniques, and integration with real market data for backtesting and live trading. Ultimately, successful crypto market making hinges on a well-designed system that effectively manages inventory risk while capitalizing on market opportunities.