{ "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 }