{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Simulación de Mercado con Agentes\n",
    "\n",
    "Simulador agent-based donde **makers** colocan bids/asks y **takers** lanzan market orders.\n",
    "El precio emerge de la interacción entre ellos.\n",
    "\n",
    "## Parámetros ajustables\n",
    "\n",
    "| Parámetro | Qué controla |\n",
    "|---|---|\n",
    "| `sigma` | Volatilidad del precio (cuánto se mueve) |\n",
    "| `mu` | Drift/tendencia (positivo = sube, negativo = baja) |\n",
    "| `n_makers` | Cuántos market makers hay poniendo liquidez |\n",
    "| `n_takers_lambda` | Ritmo de llegada de takers (órdenes/tick) |\n",
    "| `maker_spread` | Spread base que los makers quieren capturar |\n",
    "| `gamma` | Aversión al riesgo del maker (alto = ajusta más por inventario) |\n",
    "| `taker_size_alpha` | Exponente power-law para tamaño de órdenes (bajo = más ballenas) |\n",
    "| `hawkes_alpha` | Contagio entre trades (alto = más ráfagas) |\n",
    "| `hawkes_beta` | Decaimiento del contagio (alto = ráfagas más cortas) |\n",
    "| `jump_intensity` | Frecuencia de saltos bruscos de precio |\n",
    "| `jump_size_std` | Tamaño promedio de los saltos |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "ename": "Exception",
     "evalue": "File `'01_matching_engine_fifo.ipynb'` not found.",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mOSError\u001b[39m                                   Traceback (most recent call last)",
      "\u001b[31mOSError\u001b[39m: File `'01_matching_engine_fifo.ipynb'` not found.",
      "\nThe above exception was the direct cause of the following exception:\n",
      "\u001b[31mException\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m      1\u001b[39m \u001b[38;5;66;03m# Importar todo del notebook 01\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m get_ipython().run_line_magic(\u001b[33m'run'\u001b[39m, \u001b[33m'01_matching_engine_fifo.ipynb'\u001b[39m)\n",
      "\u001b[31mException\u001b[39m: File `'01_matching_engine_fifo.ipynb'` not found."
     ]
    }
   ],
   "source": [
    "# Importar todo del notebook 01\n",
    "%run 01_matching_engine_fifo.ipynb"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Parámetros de simulación"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from dataclasses import dataclass\n",
    "\n",
    "@dataclass\n",
    "class SimParams:\n",
    "    \"\"\"Todos los parámetros ajustables de la simulación.\"\"\"\n",
    "\n",
    "    # --- Precio fundamental ---\n",
    "    initial_price: float = 100.0       # precio inicial\n",
    "    mu: float = 0.0                    # drift (tendencia): 0 = sin tendencia\n",
    "    sigma: float = 0.02                # volatilidad por tick (2%)\n",
    "\n",
    "    # --- Saltos (jump-diffusion) ---\n",
    "    jump_intensity: float = 0.02       # prob de salto por tick (2%)\n",
    "    jump_size_std: float = 0.05        # std del tamaño del salto (5%)\n",
    "\n",
    "    # --- Makers ---\n",
    "    n_makers: int = 5                  # número de market makers\n",
    "    maker_spread: float = 0.5          # spread base (en unidades de precio)\n",
    "    maker_qty: float = 10.0            # qty base por orden de maker\n",
    "    gamma: float = 0.1                 # aversión al riesgo (Avellaneda-Stoikov)\n",
    "    maker_levels: int = 3              # niveles de profundidad que pone cada maker\n",
    "\n",
    "    # --- Takers ---\n",
    "    n_takers_lambda: float = 2.0       # media de takers por tick (Poisson)\n",
    "    taker_size_alpha: float = 2.0      # exponente power-law para tamaño (mayor = menos ballenas)\n",
    "    taker_size_min: float = 1.0        # tamaño mínimo de orden taker\n",
    "    taker_size_max: float = 100.0      # tamaño máximo de orden taker\n",
    "\n",
    "    # --- Hawkes (clustering de takers) ---\n",
    "    hawkes_alpha: float = 0.5          # excitación por trade (0 = Poisson puro)\n",
    "    hawkes_beta: float = 1.0           # decaimiento de excitación\n",
    "\n",
    "    # --- Simulación ---\n",
    "    n_ticks: int = 500                 # duración de la simulación\n",
    "    seed: int = 42                     # semilla para reproducibilidad\n",
    "\n",
    "\n",
    "params = SimParams()\n",
    "print(\"Parámetros cargados\")\n",
    "print(f\"  Precio inicial: {params.initial_price}\")\n",
    "print(f\"  Volatilidad: {params.sigma}\")\n",
    "print(f\"  Makers: {params.n_makers} (spread={params.maker_spread}, γ={params.gamma})\")\n",
    "print(f\"  Takers λ: {params.n_takers_lambda} (Hawkes α={params.hawkes_alpha})\")\n",
    "print(f\"  Ticks: {params.n_ticks}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Generador de precio fundamental\n",
    "\n",
    "El \"precio verdadero\" que los agentes intentan seguir. Usa **jump-diffusion**:\n",
    "- La mayor parte del tiempo se mueve suavemente (GBM)\n",
    "- De vez en cuando da un salto brusco (jump)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_fundamental_prices(p: SimParams) -> np.ndarray:\n",
    "    \"\"\"Genera serie de precios fundamentales con jump-diffusion.\n",
    "\n",
    "    S(t+1) = S(t) * exp((mu - sigma²/2)*dt + sigma*sqrt(dt)*Z + J*N)\n",
    "    donde Z ~ N(0,1), N ~ Bernoulli(jump_intensity), J ~ N(0, jump_size_std)\n",
    "    \"\"\"\n",
    "    rng = np.random.default_rng(p.seed)\n",
    "    prices = np.zeros(p.n_ticks)\n",
    "    prices[0] = p.initial_price\n",
    "\n",
    "    dt = 1.0  # cada tick es una unidad de tiempo\n",
    "\n",
    "    for t in range(1, p.n_ticks):\n",
    "        # GBM component\n",
    "        z = rng.standard_normal()\n",
    "        gbm = (p.mu - 0.5 * p.sigma**2) * dt + p.sigma * np.sqrt(dt) * z\n",
    "\n",
    "        # Jump component\n",
    "        jump = 0.0\n",
    "        if rng.random() < p.jump_intensity:\n",
    "            jump = rng.normal(0, p.jump_size_std)\n",
    "\n",
    "        prices[t] = prices[t-1] * np.exp(gbm + jump)\n",
    "\n",
    "    return prices\n",
    "\n",
    "\n",
    "# Preview\n",
    "fund_prices = generate_fundamental_prices(params)\n",
    "plt.figure(figsize=(12, 3))\n",
    "plt.plot(fund_prices, linewidth=0.8)\n",
    "plt.title(f'Precio fundamental (σ={params.sigma}, jumps={params.jump_intensity})')\n",
    "plt.xlabel('Tick')\n",
    "plt.ylabel('Precio')\n",
    "plt.grid(True, alpha=0.3)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Hawkes process para arrival de takers\n",
    "\n",
    "Genera cuántos takers llegan en cada tick. Con Hawkes, un trade excita más trades:\n",
    "- `hawkes_alpha = 0` → Poisson puro (sin contagio)\n",
    "- `hawkes_alpha > 0` → trades generan más trades (ráfagas)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_hawkes_arrivals(p: SimParams, n_trades_per_tick: list[int]) -> list[int]:\n",
    "    \"\"\"Genera número de takers por tick usando Hawkes process.\n",
    "\n",
    "    λ(t) = λ_base + Σ α * exp(-β * (t - tᵢ))\n",
    "    donde tᵢ son los ticks donde hubo trades.\n",
    "    \"\"\"\n",
    "    rng = np.random.default_rng(p.seed + 1)\n",
    "    arrivals = []\n",
    "    excitation = 0.0  # acumulador de excitación\n",
    "\n",
    "    for t in range(p.n_ticks):\n",
    "        # Intensidad actual\n",
    "        lam = p.n_takers_lambda + excitation\n",
    "        lam = max(0.1, lam)  # piso para evitar λ negativo\n",
    "\n",
    "        # Número de takers este tick\n",
    "        n = rng.poisson(lam)\n",
    "        arrivals.append(n)\n",
    "\n",
    "        # Actualizar excitación: decae + se excita por trades\n",
    "        excitation *= np.exp(-p.hawkes_beta)\n",
    "        if t < len(n_trades_per_tick):\n",
    "            excitation += p.hawkes_alpha * n_trades_per_tick[t]\n",
    "        else:\n",
    "            excitation += p.hawkes_alpha * n\n",
    "\n",
    "    return arrivals\n",
    "\n",
    "\n",
    "# Preview con Poisson puro\n",
    "arrivals_preview = generate_hawkes_arrivals(params, [0] * params.n_ticks)\n",
    "print(f\"Takers por tick: min={min(arrivals_preview)}, max={max(arrivals_preview)}, mean={np.mean(arrivals_preview):.1f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Agentes\n",
    "\n",
    "### Market Maker (Avellaneda-Stoikov)\n",
    "Calcula su **precio de reserva** según inventario:\n",
    "- Si compró mucho → baja sus precios para vender\n",
    "- Si vendió mucho → sube sus precios para comprar\n",
    "\n",
    "### Taker\n",
    "Lanza market orders con tamaño power-law (muchas chicas, pocas grandes)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class MakerState:\n",
    "    \"\"\"Estado interno de un market maker.\"\"\"\n",
    "    maker_id: str\n",
    "    inventory: float = 0.0        # positivo = largo, negativo = corto\n",
    "    pnl: float = 0.0              # profit & loss acumulado\n",
    "    active_order_ids: list = field(default_factory=list)\n",
    "\n",
    "\n",
    "def maker_quotes(state: MakerState, mid: float, p: SimParams, t: int, rng) -> list[Order]:\n",
    "    \"\"\"Genera las órdenes de un maker usando Avellaneda-Stoikov.\n",
    "\n",
    "    Precio de reserva: r = mid - inventory * gamma * sigma²\n",
    "    Spread óptimo: delta = gamma * sigma² + spread_base\n",
    "    \"\"\"\n",
    "    # Precio de reserva: ajustado por inventario\n",
    "    # Si inventory > 0 (compré mucho), r baja → mis asks bajan para vender\n",
    "    # Si inventory < 0 (vendí mucho), r sube → mis bids suben para comprar\n",
    "    reservation = mid - state.inventory * p.gamma * p.sigma**2\n",
    "\n",
    "    # Spread: base + ajuste por volatilidad\n",
    "    half_spread = p.maker_spread / 2 + p.gamma * p.sigma**2 / 2\n",
    "\n",
    "    orders = []\n",
    "    for level in range(p.maker_levels):\n",
    "        offset = level * half_spread * 0.5  # niveles más profundos\n",
    "        qty = p.maker_qty * (1 + level * 0.5)  # más qty en niveles profundos\n",
    "\n",
    "        # Pequeña variación para que los makers no sean idénticos\n",
    "        noise = rng.uniform(-0.05, 0.05)\n",
    "\n",
    "        bid_price = round(reservation - half_spread - offset + noise, 2)\n",
    "        ask_price = round(reservation + half_spread + offset + noise, 2)\n",
    "\n",
    "        if bid_price > 0:\n",
    "            orders.append(Order(side=Side.BUY, price=bid_price, qty=qty))\n",
    "        if ask_price > 0:\n",
    "            orders.append(Order(side=Side.SELL, price=ask_price, qty=qty))\n",
    "\n",
    "    return orders\n",
    "\n",
    "\n",
    "def taker_order(mid: float, p: SimParams, rng) -> Order:\n",
    "    \"\"\"Genera una market order de taker.\n",
    "\n",
    "    Lado: 50/50 compra/venta\n",
    "    Tamaño: power-law (Pareto) truncada\n",
    "    \"\"\"\n",
    "    side = Side.BUY if rng.random() < 0.5 else Side.SELL\n",
    "\n",
    "    # Power-law: P(size > x) ~ x^(-alpha)\n",
    "    # Pareto genera valores >= 1, escalamos al rango deseado\n",
    "    raw_size = (rng.pareto(p.taker_size_alpha) + 1) * p.taker_size_min\n",
    "    size = min(raw_size, p.taker_size_max)\n",
    "    size = round(size, 1)\n",
    "\n",
    "    return Order(side=side, price=0, qty=size, order_type=OrderType.MARKET)\n",
    "\n",
    "\n",
    "print(\"Agentes definidos: MakerState, maker_quotes (Avellaneda-Stoikov), taker_order (power-law)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Loop de simulación\n",
    "\n",
    "En cada tick:\n",
    "1. El precio fundamental se mueve (GBM + jumps)\n",
    "2. Cada maker cancela sus órdenes anteriores y coloca nuevas\n",
    "3. Llegan N takers (Hawkes) y lanzan market orders\n",
    "4. El engine matchea todo\n",
    "5. Se actualizan inventarios y PnL de los makers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class SimResult:\n",
    "    \"\"\"Resultados de la simulación para análisis.\"\"\"\n",
    "    fundamental_prices: np.ndarray   # precio \"verdadero\"\n",
    "    trade_prices: list[float]        # precio de cada trade\n",
    "    trade_times: list[int]           # tick de cada trade\n",
    "    trade_sizes: list[float]         # tamaño de cada trade\n",
    "    spreads: list[float]             # spread en cada tick\n",
    "    midprices: list[float]           # midprice del book en cada tick\n",
    "    taker_arrivals: list[int]        # takers por tick\n",
    "    maker_states: list[MakerState]   # estado final de los makers\n",
    "    n_trades_per_tick: list[int]     # trades por tick\n",
    "\n",
    "\n",
    "def run_simulation(p: SimParams) -> SimResult:\n",
    "    \"\"\"Ejecuta la simulación completa.\"\"\"\n",
    "    rng = np.random.default_rng(p.seed)\n",
    "\n",
    "    # Generar precios fundamentales\n",
    "    fund_prices = generate_fundamental_prices(p)\n",
    "\n",
    "    # Inicializar engine y makers\n",
    "    engine = MatchingEngineFIFO()\n",
    "    makers = [MakerState(maker_id=f\"maker_{i}\") for i in range(p.n_makers)]\n",
    "\n",
    "    # Resultados\n",
    "    trade_prices, trade_times, trade_sizes = [], [], []\n",
    "    spreads, midprices = [], []\n",
    "    n_trades_per_tick = []\n",
    "\n",
    "    # Hawkes state\n",
    "    hawkes_excitation = 0.0\n",
    "\n",
    "    for t in range(p.n_ticks):\n",
    "        mid = fund_prices[t]\n",
    "\n",
    "        # --- MAKERS: cancelar y recolocar ---\n",
    "        for maker in makers:\n",
    "            # Cancelar órdenes anteriores\n",
    "            for oid in maker.active_order_ids:\n",
    "                engine.cancel(oid)\n",
    "            maker.active_order_ids = []\n",
    "\n",
    "            # Colocar nuevas\n",
    "            quotes = maker_quotes(maker, mid, p, t, rng)\n",
    "            for q in quotes:\n",
    "                engine.submit(q)\n",
    "                maker.active_order_ids.append(q.order_id)\n",
    "\n",
    "        # --- TAKERS: generar con Hawkes ---\n",
    "        lam = p.n_takers_lambda + hawkes_excitation\n",
    "        lam = max(0.1, lam)\n",
    "        n_takers = rng.poisson(lam)\n",
    "\n",
    "        tick_trades = 0\n",
    "        for _ in range(n_takers):\n",
    "            order = taker_order(mid, p, rng)\n",
    "            trades = engine.submit(order)\n",
    "            tick_trades += len(trades)\n",
    "\n",
    "            for tr in trades:\n",
    "                trade_prices.append(tr.price)\n",
    "                trade_times.append(t)\n",
    "                trade_sizes.append(tr.qty)\n",
    "\n",
    "                # Actualizar inventario de makers\n",
    "                for maker in makers:\n",
    "                    if tr.buyer_order_id in maker.active_order_ids:\n",
    "                        maker.inventory += tr.qty\n",
    "                        maker.pnl -= tr.price * tr.qty\n",
    "                    elif tr.seller_order_id in maker.active_order_ids:\n",
    "                        maker.inventory -= tr.qty\n",
    "                        maker.pnl += tr.price * tr.qty\n",
    "\n",
    "        # Hawkes: actualizar excitación\n",
    "        hawkes_excitation *= np.exp(-p.hawkes_beta)\n",
    "        hawkes_excitation += p.hawkes_alpha * tick_trades\n",
    "\n",
    "        n_trades_per_tick.append(tick_trades)\n",
    "\n",
    "        # Registrar estado del book\n",
    "        sp = engine.book.spread\n",
    "        spreads.append(sp if sp is not None else 0.0)\n",
    "        mp = engine.book.midprice\n",
    "        midprices.append(mp if mp is not None else mid)\n",
    "\n",
    "    # PnL final: mark-to-market\n",
    "    final_price = fund_prices[-1]\n",
    "    for maker in makers:\n",
    "        maker.pnl += maker.inventory * final_price\n",
    "\n",
    "    return SimResult(\n",
    "        fundamental_prices=fund_prices,\n",
    "        trade_prices=trade_prices,\n",
    "        trade_times=trade_times,\n",
    "        trade_sizes=trade_sizes,\n",
    "        spreads=spreads,\n",
    "        midprices=midprices,\n",
    "        taker_arrivals=n_trades_per_tick,\n",
    "        maker_states=makers,\n",
    "        n_trades_per_tick=n_trades_per_tick,\n",
    "    )\n",
    "\n",
    "\n",
    "print(\"run_simulation() definida\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Ejecutar simulación base"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "result = run_simulation(params)\n",
    "\n",
    "print(f\"Total trades: {len(result.trade_prices)}\")\n",
    "print(f\"Spread promedio: {np.mean(result.spreads):.4f}\")\n",
    "print(f\"Trades/tick promedio: {np.mean(result.n_trades_per_tick):.1f}\")\n",
    "print(f\"\\nEstado final de makers:\")\n",
    "for m in result.maker_states:\n",
    "    print(f\"  {m.maker_id}: inventario={m.inventory:.1f}, PnL={m.pnl:.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. Dashboard de resultados"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_simulation(result: SimResult, params: SimParams):\n",
    "    \"\"\"Dashboard de la simulación.\"\"\"\n",
    "    fig, axes = plt.subplots(4, 1, figsize=(14, 12), gridspec_kw={'height_ratios': [3, 1, 1, 1]})\n",
    "\n",
    "    # --- Panel 1: Precio ---\n",
    "    ax = axes[0]\n",
    "    ax.plot(result.fundamental_prices, color='gray', linewidth=0.8, alpha=0.5, label='Fundamental')\n",
    "    ax.plot(result.midprices, color='#3498db', linewidth=0.8, label='Midprice (book)')\n",
    "    if result.trade_prices:\n",
    "        ax.scatter(result.trade_times, result.trade_prices, s=1, alpha=0.3, color='orange', label='Trades')\n",
    "    ax.set_ylabel('Precio')\n",
    "    ax.set_title(f'Simulación: {params.n_makers} makers, λ_takers={params.n_takers_lambda}, '\n",
    "                 f'σ={params.sigma}, γ={params.gamma}, Hawkes α={params.hawkes_alpha}')\n",
    "    ax.legend(loc='upper left', fontsize=8)\n",
    "    ax.grid(True, alpha=0.3)\n",
    "\n",
    "    # --- Panel 2: Spread ---\n",
    "    ax = axes[1]\n",
    "    ax.fill_between(range(len(result.spreads)), result.spreads, color='#9b59b6', alpha=0.5)\n",
    "    ax.set_ylabel('Spread')\n",
    "    ax.set_ylim(0, np.percentile(result.spreads, 99) * 1.5 if result.spreads else 1)\n",
    "    ax.grid(True, alpha=0.3)\n",
    "\n",
    "    # --- Panel 3: Trades por tick ---\n",
    "    ax = axes[2]\n",
    "    ax.bar(range(len(result.n_trades_per_tick)), result.n_trades_per_tick,\n",
    "           color='#e67e22', alpha=0.6, width=1.0)\n",
    "    ax.set_ylabel('Trades/tick')\n",
    "    ax.grid(True, alpha=0.3)\n",
    "\n",
    "    # --- Panel 4: Volumen por trade ---\n",
    "    ax = axes[3]\n",
    "    if result.trade_sizes:\n",
    "        ax.scatter(result.trade_times, result.trade_sizes, s=2, alpha=0.4, color='#2ecc71')\n",
    "    ax.set_ylabel('Tamaño orden')\n",
    "    ax.set_xlabel('Tick')\n",
    "    ax.set_yscale('log')\n",
    "    ax.grid(True, alpha=0.3)\n",
    "\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_simulation(result, params)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Experimentos: comparar escenarios\n",
    "\n",
    "Ajusta los parámetros y observa cómo cambia el mercado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# --- Experimento 1: Mercado tranquilo vs volátil ---\n",
    "\n",
    "calm = SimParams(sigma=0.005, jump_intensity=0.0, n_ticks=300, seed=42)\n",
    "volatile = SimParams(sigma=0.05, jump_intensity=0.1, jump_size_std=0.08, n_ticks=300, seed=42)\n",
    "\n",
    "r_calm = run_simulation(calm)\n",
    "r_volatile = run_simulation(volatile)\n",
    "\n",
    "fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n",
    "\n",
    "axes[0].plot(r_calm.midprices, color='#3498db')\n",
    "axes[0].set_title(f'Tranquilo (σ={calm.sigma}, jumps=0)')\n",
    "axes[0].set_ylabel('Precio')\n",
    "axes[0].grid(True, alpha=0.3)\n",
    "\n",
    "axes[1].plot(r_volatile.midprices, color='#e74c3c')\n",
    "axes[1].set_title(f'Volátil (σ={volatile.sigma}, jumps={volatile.jump_intensity})')\n",
    "axes[1].grid(True, alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(f\"Spread promedio → Tranquilo: {np.mean(r_calm.spreads):.4f}, Volátil: {np.mean(r_volatile.spreads):.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# --- Experimento 2: Pocos makers vs muchos makers ---\n",
    "\n",
    "few_makers = SimParams(n_makers=1, n_ticks=300, seed=42)\n",
    "many_makers = SimParams(n_makers=10, n_ticks=300, seed=42)\n",
    "\n",
    "r_few = run_simulation(few_makers)\n",
    "r_many = run_simulation(many_makers)\n",
    "\n",
    "fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n",
    "\n",
    "axes[0].fill_between(range(len(r_few.spreads)), r_few.spreads, color='#e74c3c', alpha=0.5)\n",
    "axes[0].set_title(f'1 maker → spread promedio: {np.mean(r_few.spreads):.4f}')\n",
    "axes[0].set_ylabel('Spread')\n",
    "axes[0].grid(True, alpha=0.3)\n",
    "\n",
    "axes[1].fill_between(range(len(r_many.spreads)), r_many.spreads, color='#2ecc71', alpha=0.5)\n",
    "axes[1].set_title(f'10 makers → spread promedio: {np.mean(r_many.spreads):.4f}')\n",
    "axes[1].grid(True, alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# --- Experimento 3: Sin Hawkes vs con Hawkes fuerte ---\n",
    "\n",
    "no_hawkes = SimParams(hawkes_alpha=0.0, n_ticks=300, seed=42)\n",
    "strong_hawkes = SimParams(hawkes_alpha=1.5, hawkes_beta=0.5, n_ticks=300, seed=42)\n",
    "\n",
    "r_no_h = run_simulation(no_hawkes)\n",
    "r_strong_h = run_simulation(strong_hawkes)\n",
    "\n",
    "fig, axes = plt.subplots(2, 2, figsize=(14, 6))\n",
    "\n",
    "axes[0][0].bar(range(len(r_no_h.n_trades_per_tick)), r_no_h.n_trades_per_tick,\n",
    "               color='#3498db', alpha=0.6, width=1.0)\n",
    "axes[0][0].set_title('Poisson puro (hawkes_alpha=0)')\n",
    "axes[0][0].set_ylabel('Trades/tick')\n",
    "\n",
    "axes[0][1].bar(range(len(r_strong_h.n_trades_per_tick)), r_strong_h.n_trades_per_tick,\n",
    "               color='#e74c3c', alpha=0.6, width=1.0)\n",
    "axes[0][1].set_title('Hawkes fuerte (alpha=1.5, beta=0.5)')\n",
    "\n",
    "axes[1][0].plot(r_no_h.midprices, color='#3498db', linewidth=0.8)\n",
    "axes[1][0].set_ylabel('Midprice')\n",
    "\n",
    "axes[1][1].plot(r_strong_h.midprices, color='#e74c3c', linewidth=0.8)\n",
    "\n",
    "for ax in axes.flat:\n",
    "    ax.grid(True, alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(f\"Max trades/tick → Poisson: {max(r_no_h.n_trades_per_tick)}, Hawkes: {max(r_strong_h.n_trades_per_tick)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# --- Experimento 4: Gamma bajo vs alto (aversión al riesgo del maker) ---\n",
    "\n",
    "low_gamma = SimParams(gamma=0.01, n_ticks=300, seed=42)\n",
    "high_gamma = SimParams(gamma=1.0, n_ticks=300, seed=42)\n",
    "\n",
    "r_low_g = run_simulation(low_gamma)\n",
    "r_high_g = run_simulation(high_gamma)\n",
    "\n",
    "fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n",
    "\n",
    "axes[0].fill_between(range(len(r_low_g.spreads)), r_low_g.spreads, color='#2ecc71', alpha=0.5)\n",
    "axes[0].set_title(f'γ={low_gamma.gamma} (maker agresivo)\\nspread prom: {np.mean(r_low_g.spreads):.4f}')\n",
    "axes[0].set_ylabel('Spread')\n",
    "axes[0].grid(True, alpha=0.3)\n",
    "\n",
    "axes[1].fill_between(range(len(r_high_g.spreads)), r_high_g.spreads, color='#e74c3c', alpha=0.5)\n",
    "axes[1].set_title(f'γ={high_gamma.gamma} (maker conservador)\\nspread prom: {np.mean(r_high_g.spreads):.4f}')\n",
    "axes[1].grid(True, alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(f\"\\nPnL makers γ={low_gamma.gamma}: {[f'{m.pnl:.0f}' for m in r_low_g.maker_states]}\")\n",
    "print(f\"PnL makers γ={high_gamma.gamma}: {[f'{m.pnl:.0f}' for m in r_high_g.maker_states]}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.13.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
