667 lines
26 KiB
Plaintext
667 lines
26 KiB
Plaintext
{
|
||
"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
|
||
}
|