Files
estudio_mercados/notebooks/.ipynb_checkpoints/02_simulacion_mercado-checkpoint.ipynb
T

667 lines
26 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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
}