{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Avellaneda-Stoikov Market Maker Simulator\n",
        "\n",
        "Build a guided market making simulator around inventory risk, reservation prices, and optimal bid/ask quotes.\n",
        "\n",
        "**How to use this notebook:** We give you the structure, the key ideas, starter code, and places where you need to make the important research decisions yourself."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## What you should have by the end\n",
        "\n",
        "- A working prototype\n",
        "- A few graphs/tables that explain what your model is doing\n",
        "- A short write-up of your assumptions\n",
        "- At least one extension that makes the project feel like yours"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Concept\n",
        "\n",
        "Market makers quote both bid and ask prices. They earn spread, but they also get inventory risk.\n",
        "\n",
        "The Avellaneda-Stoikov framework gives a clean way to think about this: your quotes should depend on your inventory, risk aversion, volatility, and the time left in the trading session."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Starter Code\n",
        "\n",
        "1. Simulate a mid-price path.\n",
        "2. Track inventory and cash.\n",
        "3. Quote bid/ask around a reservation price.\n",
        "4. Simulate fills probabilistically.\n",
        "5. Compare PnL and inventory under different parameters."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import pandas as pd\n",
        "\n",
        "rng = np.random.default_rng(7)\n",
        "\n",
        "def simulate_mid_price(n_steps=500, start=100.0, sigma=0.02):\n",
        "    returns = sigma * rng.normal(size=n_steps)\n",
        "    return start + np.cumsum(returns)\n",
        "\n",
        "mid = simulate_mid_price()\n",
        "pd.Series(mid).head()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "def avellaneda_stoikov_quotes(mid_price, inventory, time_left, sigma, gamma, k):\n",
        "    # Starter quote function.\n",
        "    # This is intentionally simplified.\n",
        "    # TODO:\n",
        "    # - Derive the reservation price term carefully.\n",
        "    # - Experiment with different fill-intensity models.\n",
        "    # - Decide whether your spread formula is realistic.\n",
        "    reservation_price = mid_price - inventory * gamma * sigma**2 * time_left\n",
        "    optimal_spread = gamma * sigma**2 * time_left + (2 / gamma) * np.log(1 + gamma / k)\n",
        "\n",
        "    bid = reservation_price - optimal_spread / 2\n",
        "    ask = reservation_price + optimal_spread / 2\n",
        "    return bid, ask\n",
        "\n",
        "avellaneda_stoikov_quotes(\n",
        "    mid_price=100,\n",
        "    inventory=5,\n",
        "    time_left=1.0,\n",
        "    sigma=0.02,\n",
        "    gamma=0.1,\n",
        "    k=1.5\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "def run_market_maker_simulation(mid_prices, gamma=0.1, k=1.5):\n",
        "    # Skeleton simulator.\n",
        "    # TODO:\n",
        "    # - Complete the fill logic.\n",
        "    # - Track cash, inventory, and marked-to-market PnL.\n",
        "    # - Test multiple gamma values.\n",
        "    inventory = 0\n",
        "    cash = 0.0\n",
        "    records = []\n",
        "\n",
        "    for t, mid_price in enumerate(mid_prices):\n",
        "        time_left = 1 - t / len(mid_prices)\n",
        "        bid, ask = avellaneda_stoikov_quotes(mid_price, inventory, time_left, 0.02, gamma, k)\n",
        "\n",
        "        # Placeholder fill probabilities.\n",
        "        # TODO: make probability depend on distance from mid.\n",
        "        bid_filled = rng.random() < 0.05\n",
        "        ask_filled = rng.random() < 0.05\n",
        "\n",
        "        if bid_filled:\n",
        "            inventory += 1\n",
        "            cash -= bid\n",
        "        if ask_filled:\n",
        "            inventory -= 1\n",
        "            cash += ask\n",
        "\n",
        "        pnl = cash + inventory * mid_price\n",
        "        records.append((t, mid_price, bid, ask, inventory, cash, pnl))\n",
        "\n",
        "    return pd.DataFrame(records, columns=[\"t\", \"mid\", \"bid\", \"ask\", \"inventory\", \"cash\", \"pnl\"])\n",
        "\n",
        "sim = run_market_maker_simulation(mid)\n",
        "sim.tail()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Open-ended extensions\n",
        "\n",
        "- Compare low vs high risk aversion\n",
        "- Add adverse selection: fills are more likely right before price moves against you\n",
        "- Add transaction costs\n",
        "- Add inventory limits\n",
        "- Write a short reflection on why naive market making gets cooked"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Final write-up prompts\n",
        "\n",
        "Answer these before putting the project on your resume:\n",
        "\n",
        "1. What problem were you trying to solve?\n",
        "2. What assumptions did your model make?\n",
        "3. Where did the model work well?\n",
        "4. Where did it fail?\n",
        "5. What would you improve with more time?\n",
        "6. What is one interesting thing you learned that was not obvious before starting?"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.x"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
