{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Estimación de precios futuros con Monte Carlo\n",
    "\n",
    "Usamos los parámetros calibrados de BTC/USDT real para generar **miles de caminos de precio posibles** y estimar:\n",
    "- Distribución del precio a distintos horizontes\n",
    "- Intervalos de confianza (fan chart)\n",
    "- Probabilidad de subir/bajar X%\n",
    "- Value at Risk (VaR) y Expected Shortfall\n",
    "\n",
    "**Importante:** Esto NO es una predicción. Es un modelo probabilístico que dice \"dado cómo se ha comportado el mercado, estos son los escenarios posibles\". La distribución real tiene colas más pesadas que nuestro modelo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Listo\n"
     ]
    }
   ],
   "source": [
    "import sys, os\n",
    "sys.path.insert(0, os.path.join(os.environ.get('FN_REGISTRY_ROOT', os.path.expanduser('~/fn_registry')), 'python', 'functions'))\n",
    "sys.path.insert(0, os.path.join(os.environ.get('FN_REGISTRY_ROOT', os.path.expanduser('~/fn_registry')), 'python', 'functions', 'pipelines'))\n",
    "\n",
    "from finance.finance import generate_gbm_prices\n",
    "from run_market_sim import run_market_sim\n",
    "import numpy as np\n",
    "import polars as pl\n",
    "import matplotlib.pyplot as plt\n",
    "from matplotlib.colors import LinearSegmentedColormap\n",
    "\n",
    "print('Listo')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Parámetros calibrados y escenarios"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Precio actual: $66,760\n",
      "σ minuto: 0.000514\n",
      "σ diaria: 0.0195 (1.95%)\n",
      "σ anual:  0.37 (37%)\n",
      "Simulaciones: 5,000\n"
     ]
    }
   ],
   "source": [
    "# Precio actual de BTC\n",
    "CURRENT_PRICE = 66760.0\n",
    "\n",
    "# Parámetros calibrados de notebook 06 (datos reales 1M trades)\n",
    "CALIBRATED = dict(\n",
    "    sigma=0.000514,          # por minuto\n",
    "    mu=0.0,                  # sin drift (conservador)\n",
    "    jump_intensity=0.013,    # 1.3% de velas con jump\n",
    "    jump_size_std=0.000356,  # tamaño de los jumps\n",
    ")\n",
    "\n",
    "# Horizontes de simulación\n",
    "HORIZONS = {\n",
    "    '1 hora':   60,\n",
    "    '4 horas':  240,\n",
    "    '1 día':    1440,\n",
    "    '1 semana': 10080,\n",
    "}\n",
    "\n",
    "N_SIMS = 5000  # simulaciones por escenario\n",
    "\n",
    "print(f'Precio actual: ${CURRENT_PRICE:,.0f}')\n",
    "print(f'σ minuto: {CALIBRATED[\"sigma\"]:.6f}')\n",
    "print(f'σ diaria: {CALIBRATED[\"sigma\"] * np.sqrt(1440):.4f} ({CALIBRATED[\"sigma\"] * np.sqrt(1440) * 100:.2f}%)')\n",
    "print(f'σ anual:  {CALIBRATED[\"sigma\"] * np.sqrt(1440 * 365):.2f} ({CALIBRATED[\"sigma\"] * np.sqrt(1440 * 365) * 100:.0f}%)')\n",
    "print(f'Simulaciones: {N_SIMS:,}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Generar caminos de precio Monte Carlo\n",
    "\n",
    "Para cada simulación generamos un camino completo de precios usando GBM + jumps.\n",
    "El horizonte más largo (1 semana = 10,080 minutos) incluye a todos los demás."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Generados 5,000 caminos de 10,080 ticks (7 días)\n",
      "Shape: (5000, 10080)\n"
     ]
    }
   ],
   "source": [
    "max_ticks = max(HORIZONS.values())\n",
    "\n",
    "# Generar todos los caminos (matrix: N_SIMS x max_ticks)\n",
    "all_paths = np.zeros((N_SIMS, max_ticks))\n",
    "\n",
    "for i in range(N_SIMS):\n",
    "    path = generate_gbm_prices(\n",
    "        initial_price=CURRENT_PRICE,\n",
    "        n_ticks=max_ticks,\n",
    "        seed=i,\n",
    "        **CALIBRATED,\n",
    "    )\n",
    "    all_paths[i] = path\n",
    "\n",
    "print(f'Generados {N_SIMS:,} caminos de {max_ticks:,} ticks ({max_ticks/1440:.0f} días)')\n",
    "print(f'Shape: {all_paths.shape}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Fan chart — todos los caminos posibles\n",
    "\n",
    "El fan chart muestra la distribución del precio en cada momento.\n",
    "Las bandas representan percentiles: cuanto más oscuro, más probable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 391184 characters]",
      "text/plain": [
       "<Figure size 1600x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data",
     "transient": {}
    },
    {
     "data": {
      "image/png": "[encoded data, 527076 characters]",
      "text/plain": [
       "<Figure size 1600x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data",
     "transient": {}
    },
    {
     "data": {
      "image/png": "[encoded data, 670580 characters]",
      "text/plain": [
       "<Figure size 1600x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data",
     "transient": {}
    },
    {
     "data": {
      "image/png": "[encoded data, 826864 characters]",
      "text/plain": [
       "<Figure size 1600x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data",
     "transient": {}
    }
   ],
   "source": [
    "def plot_fan_chart(paths, horizon_ticks, horizon_name, n_sample_paths=50):\n",
    "    \"\"\"Fan chart con bandas de percentiles.\"\"\"\n",
    "    data = paths[:, :horizon_ticks]\n",
    "    ticks = np.arange(horizon_ticks)\n",
    "    \n",
    "    # Percentiles\n",
    "    bands = [\n",
    "        (1, 99, '#3498db', 0.08),\n",
    "        (5, 95, '#3498db', 0.12),\n",
    "        (10, 90, '#3498db', 0.18),\n",
    "        (25, 75, '#3498db', 0.25),\n",
    "        (40, 60, '#3498db', 0.35),\n",
    "    ]\n",
    "    \n",
    "    fig, ax = plt.subplots(figsize=(16, 7))\n",
    "    \n",
    "    for plo, phi, color, alpha in bands:\n",
    "        lo = np.percentile(data, plo, axis=0)\n",
    "        hi = np.percentile(data, phi, axis=0)\n",
    "        ax.fill_between(ticks, lo, hi, color=color, alpha=alpha, label=f'p{plo}-p{phi}')\n",
    "    \n",
    "    # Mediana\n",
    "    median = np.median(data, axis=0)\n",
    "    ax.plot(ticks, median, color='#2c3e50', linewidth=1.5, label='Mediana')\n",
    "    \n",
    "    # Sample paths\n",
    "    rng = np.random.default_rng(0)\n",
    "    idx = rng.choice(N_SIMS, n_sample_paths, replace=False)\n",
    "    for j in idx:\n",
    "        ax.plot(ticks, data[j], linewidth=0.15, alpha=0.3, color='#7f8c8d')\n",
    "    \n",
    "    ax.axhline(y=CURRENT_PRICE, color='red', linestyle='--', linewidth=0.8, alpha=0.5, label=f'Precio actual ${CURRENT_PRICE:,.0f}')\n",
    "    \n",
    "    # Formatear eje x\n",
    "    if horizon_ticks <= 240:\n",
    "        ax.set_xlabel('Minutos')\n",
    "    elif horizon_ticks <= 1440:\n",
    "        xticks = np.arange(0, horizon_ticks + 1, 60)\n",
    "        ax.set_xticks(xticks)\n",
    "        ax.set_xticklabels([f'{int(x/60)}h' for x in xticks])\n",
    "        ax.set_xlabel('Horas')\n",
    "    else:\n",
    "        xticks = np.arange(0, horizon_ticks + 1, 1440)\n",
    "        ax.set_xticks(xticks)\n",
    "        ax.set_xticklabels([f'{int(x/1440)}d' for x in xticks])\n",
    "        ax.set_xlabel('Días')\n",
    "    \n",
    "    ax.set_ylabel('Precio (USDT)')\n",
    "    ax.set_title(f'BTC/USDT — Monte Carlo {N_SIMS:,} simulaciones — Horizonte {horizon_name}', fontsize=13)\n",
    "    ax.legend(loc='upper left', fontsize=8)\n",
    "    ax.grid(True, alpha=0.3)\n",
    "    plt.tight_layout()\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "# Fan charts para cada horizonte\n",
    "for name, ticks in HORIZONS.items():\n",
    "    plot_fan_chart(all_paths, ticks, name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Distribución del precio final por horizonte"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 120472 characters]",
      "text/plain": [
       "<Figure size 1400x1000 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data",
     "transient": {}
    }
   ],
   "source": [
    "fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n",
    "\n",
    "for ax, (name, ticks) in zip(axes.flat, HORIZONS.items()):\n",
    "    final_prices = all_paths[:, ticks - 1]\n",
    "    returns_pct = (final_prices / CURRENT_PRICE - 1) * 100\n",
    "    \n",
    "    ax.hist(returns_pct, bins=80, density=True, color='#3498db', alpha=0.6, edgecolor='white')\n",
    "    \n",
    "    # Percentiles\n",
    "    p5 = np.percentile(returns_pct, 5)\n",
    "    p50 = np.percentile(returns_pct, 50)\n",
    "    p95 = np.percentile(returns_pct, 95)\n",
    "    \n",
    "    ax.axvline(x=p5, color='red', linewidth=1.5, linestyle='--', label=f'p5: {p5:+.2f}%')\n",
    "    ax.axvline(x=p50, color='#2c3e50', linewidth=1.5, label=f'Mediana: {p50:+.2f}%')\n",
    "    ax.axvline(x=p95, color='green', linewidth=1.5, linestyle='--', label=f'p95: {p95:+.2f}%')\n",
    "    ax.axvline(x=0, color='gray', linewidth=0.8, alpha=0.5)\n",
    "    \n",
    "    ax.set_title(f'{name}', fontsize=12, fontweight='bold')\n",
    "    ax.set_xlabel('Retorno (%)')\n",
    "    ax.legend(fontsize=8)\n",
    "    ax.grid(True, alpha=0.3)\n",
    "\n",
    "fig.suptitle(f'Distribución de retornos por horizonte — {N_SIMS:,} simulaciones', fontsize=14)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Tabla de estimaciones por horizonte"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Precio actual: $66,760\n",
      "Modelo: GBM + Jump-diffusion (σ=0.000514, jumps=0.013)\n",
      "Simulaciones: 5,000\n",
      "\n",
      "Horizonte            P5        P25    Mediana        P75        P95    σ rango\n",
      "---------------------------------------------------------------------------\n",
      "1 hora       $   66,339 $   66,587 $   66,758 $   66,939 $   67,199  ±0.6%\n",
      "4 horas      $   65,908 $   66,404 $   66,758 $   67,115 $   67,633  ±1.3%\n",
      "1 día        $   64,630 $   65,882 $   66,754 $   67,612 $   68,920  ±3.2%\n",
      "1 semana     $   61,088 $   64,281 $   66,632 $   69,032 $   72,624  ±8.6%\n"
     ]
    }
   ],
   "source": [
    "print(f'Precio actual: ${CURRENT_PRICE:,.0f}')\n",
    "print(f'Modelo: GBM + Jump-diffusion (σ={CALIBRATED[\"sigma\"]}, jumps={CALIBRATED[\"jump_intensity\"]})')\n",
    "print(f'Simulaciones: {N_SIMS:,}')\n",
    "print()\n",
    "print(f'{\"Horizonte\":<12} {\"P5\":>10} {\"P25\":>10} {\"Mediana\":>10} {\"P75\":>10} {\"P95\":>10} {\"σ rango\":>10}')\n",
    "print('-' * 75)\n",
    "\n",
    "for name, ticks in HORIZONS.items():\n",
    "    fp = all_paths[:, ticks - 1]\n",
    "    p5, p25, p50, p75, p95 = np.percentile(fp, [5, 25, 50, 75, 95])\n",
    "    sigma_range = (p95 - p5) / CURRENT_PRICE * 100\n",
    "    print(f'{name:<12} ${p5:>9,.0f} ${p25:>9,.0f} ${p50:>9,.0f} ${p75:>9,.0f} ${p95:>9,.0f}  ±{sigma_range/2:.1f}%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Probabilidades de escenarios"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Horizonte       -10%     -5%     -2%     -1%     =0%     +1%     +2%     +5%    +10%\n",
      "------------------------------------------------------------------------------------\n",
      "1 hora          0.0%    0.0%    0.0%    0.5%   49.8%    0.6%    0.0%    0.0%    0.0%\n",
      "4 horas         0.0%    0.0%    0.6%    9.9%   49.8%   10.6%    0.7%    0.0%    0.0%\n",
      "1 día           0.0%    0.6%   15.1%   30.6%   49.9%   30.0%   15.0%    0.7%    0.0%\n",
      "1 semana        2.5%   17.1%   36.2%   43.9%   48.7%   41.2%   33.5%   16.5%    2.9%\n"
     ]
    }
   ],
   "source": [
    "scenarios = [-10, -5, -2, -1, 0, 1, 2, 5, 10]  # % de cambio\n",
    "\n",
    "print(f'{\"Horizonte\":<12}', end='')\n",
    "for s in scenarios:\n",
    "    label = f'{s:+d}%' if s != 0 else ' =0%'\n",
    "    print(f'{label:>8}', end='')\n",
    "print()\n",
    "print('-' * (12 + 8 * len(scenarios)))\n",
    "\n",
    "for name, ticks in HORIZONS.items():\n",
    "    fp = all_paths[:, ticks - 1]\n",
    "    returns = (fp / CURRENT_PRICE - 1) * 100\n",
    "    \n",
    "    print(f'{name:<12}', end='')\n",
    "    for s in scenarios:\n",
    "        if s < 0:\n",
    "            prob = np.mean(returns <= s) * 100\n",
    "        elif s > 0:\n",
    "            prob = np.mean(returns >= s) * 100\n",
    "        else:\n",
    "            prob = np.mean(returns >= 0) * 100\n",
    "        print(f'{prob:>7.1f}%', end='')\n",
    "    print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. Value at Risk (VaR) y Expected Shortfall (CVaR)\n",
    "\n",
    "- **VaR(95%):** pérdida máxima que no se supera el 95% del tiempo\n",
    "- **CVaR(95%):** pérdida promedio en el peor 5% de los casos (más conservador)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Horizonte      VaR 90%   CVaR 90%   VaR 95%   CVaR 95%   VaR 99%   CVaR 99%\n",
      "------------------------------------------------------------------------\n",
      "1 hora          -0.50%     -0.68%    -0.63%     -0.80%    -0.90%     -1.04%\n",
      "4 horas         -1.00%     -1.37%    -1.28%     -1.61%    -1.84%     -2.12%\n",
      "1 día           -2.46%     -3.42%    -3.19%     -4.03%    -4.57%     -5.20%\n",
      "1 semana        -6.57%     -9.03%    -8.50%    -10.58%   -11.76%    -13.57%\n",
      "\n",
      "En dólares (por 1 BTC):\n",
      "Horizonte        VaR 90%     CVaR 90%     VaR 95%     CVaR 95%     VaR 99%     CVaR 99%\n",
      "------------------------------------------------------------------------------------\n",
      "1 hora        $     -336   $     -455  $     -421   $     -534  $     -603   $     -696\n",
      "4 horas       $     -665   $     -912  $     -852   $   -1,072  $   -1,227   $   -1,413\n",
      "1 día         $   -1,642   $   -2,285  $   -2,130   $   -2,693  $   -3,049   $   -3,469\n",
      "1 semana      $   -4,387   $   -6,026  $   -5,672   $   -7,061  $   -7,853   $   -9,060\n"
     ]
    }
   ],
   "source": [
    "confidence_levels = [0.90, 0.95, 0.99]\n",
    "\n",
    "print(f'{\"Horizonte\":<12}', end='')\n",
    "for cl in confidence_levels:\n",
    "    print(f'{\"VaR \" + str(int(cl*100)) + \"%\":>10} {\"CVaR \" + str(int(cl*100)) + \"%\":>10}', end='')\n",
    "print()\n",
    "print('-' * (12 + 20 * len(confidence_levels)))\n",
    "\n",
    "for name, ticks in HORIZONS.items():\n",
    "    fp = all_paths[:, ticks - 1]\n",
    "    pnl = fp - CURRENT_PRICE  # P&L en dólares\n",
    "    pnl_pct = (fp / CURRENT_PRICE - 1) * 100\n",
    "    \n",
    "    print(f'{name:<12}', end='')\n",
    "    for cl in confidence_levels:\n",
    "        var_pct = np.percentile(pnl_pct, (1 - cl) * 100)\n",
    "        # CVaR = promedio de las pérdidas peores que VaR\n",
    "        cvar_pct = np.mean(pnl_pct[pnl_pct <= var_pct])\n",
    "        print(f'{var_pct:>+9.2f}% {cvar_pct:>+9.2f}%', end='')\n",
    "    print()\n",
    "\n",
    "print()\n",
    "print('En dólares (por 1 BTC):')\n",
    "print(f'{\"Horizonte\":<12}', end='')\n",
    "for cl in confidence_levels:\n",
    "    print(f'{\"VaR \" + str(int(cl*100)) + \"%\":>12} {\"CVaR \" + str(int(cl*100)) + \"%\":>12}', end='')\n",
    "print()\n",
    "print('-' * (12 + 24 * len(confidence_levels)))\n",
    "\n",
    "for name, ticks in HORIZONS.items():\n",
    "    fp = all_paths[:, ticks - 1]\n",
    "    pnl = fp - CURRENT_PRICE\n",
    "    \n",
    "    print(f'{name:<12}', end='')\n",
    "    for cl in confidence_levels:\n",
    "        var_usd = np.percentile(pnl, (1 - cl) * 100)\n",
    "        cvar_usd = np.mean(pnl[pnl <= var_usd])\n",
    "        print(f'  ${var_usd:>+9,.0f}   ${cvar_usd:>+9,.0f}', end='')\n",
    "    print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8. Impacto del matching engine: simulación completa vs GBM puro\n",
    "\n",
    "¿Cambian las estimaciones cuando incluimos el matching engine (makers + takers) en vez de solo GBM?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Simulaciones: 200\n",
      "Horizonte: 300 minutos (5h)\n"
     ]
    }
   ],
   "source": [
    "N_ENGINE_SIMS = 200  # menos porque el engine es más lento\n",
    "HORIZON_ENGINE = 300  # ticks\n",
    "\n",
    "# Con matching engine\n",
    "engine_finals = []\n",
    "for i in range(N_ENGINE_SIMS):\n",
    "    sim = run_market_sim(\n",
    "        initial_price=CURRENT_PRICE,\n",
    "        n_ticks=HORIZON_ENGINE,\n",
    "        sigma=CALIBRATED['sigma'],\n",
    "        mu=CALIBRATED['mu'],\n",
    "        jump_intensity=CALIBRATED['jump_intensity'],\n",
    "        jump_size_std=CALIBRATED['jump_size_std'],\n",
    "        n_makers=5,\n",
    "        maker_spread=0.01,\n",
    "        gamma=0.1,\n",
    "        n_takers_lambda=12.0,\n",
    "        taker_size_alpha=0.78,\n",
    "        hawkes_alpha=0.17,\n",
    "        hawkes_beta=0.015,\n",
    "        seed=i,\n",
    "    )\n",
    "    # Último midprice como precio final\n",
    "    engine_finals.append(sim['midprices'][-1] if sim['midprices'] else CURRENT_PRICE)\n",
    "\n",
    "engine_finals = np.array(engine_finals)\n",
    "\n",
    "# GBM puro (mismos parámetros, mismo horizonte)\n",
    "gbm_finals = all_paths[:N_ENGINE_SIMS, HORIZON_ENGINE - 1]\n",
    "\n",
    "print(f'Simulaciones: {N_ENGINE_SIMS}')\n",
    "print(f'Horizonte: {HORIZON_ENGINE} minutos ({HORIZON_ENGINE/60:.0f}h)')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 68460 characters]",
      "text/plain": [
       "<Figure size 1400x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data",
     "transient": {}
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GBM puro:   media=+0.040%, std=0.935%, kurtosis=3.5\n",
      "Con engine: media=+0.040%, std=0.935%, kurtosis=3.5\n"
     ]
    }
   ],
   "source": [
    "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
    "\n",
    "# Distribuciones comparadas\n",
    "ax = axes[0]\n",
    "gbm_ret = (gbm_finals / CURRENT_PRICE - 1) * 100\n",
    "eng_ret = (engine_finals / CURRENT_PRICE - 1) * 100\n",
    "\n",
    "ax.hist(gbm_ret, bins=40, density=True, alpha=0.5, color='#3498db', label=f'GBM puro (σ={np.std(gbm_ret):.3f}%)')\n",
    "ax.hist(eng_ret, bins=40, density=True, alpha=0.5, color='#e74c3c', label=f'Con engine (σ={np.std(eng_ret):.3f}%)')\n",
    "ax.set_xlabel('Retorno (%)')\n",
    "ax.set_title(f'Distribución a {HORIZON_ENGINE/60:.0f}h: GBM vs Engine')\n",
    "ax.legend(fontsize=9)\n",
    "ax.grid(True, alpha=0.3)\n",
    "\n",
    "# QQ plot\n",
    "ax = axes[1]\n",
    "gbm_sorted = np.sort(gbm_ret)\n",
    "eng_sorted = np.sort(eng_ret)\n",
    "min_len = min(len(gbm_sorted), len(eng_sorted))\n",
    "ax.scatter(gbm_sorted[:min_len], eng_sorted[:min_len], s=5, alpha=0.5, color='#9b59b6')\n",
    "lims = [min(gbm_sorted.min(), eng_sorted.min()), max(gbm_sorted.max(), eng_sorted.max())]\n",
    "ax.plot(lims, lims, 'k--', linewidth=0.8)\n",
    "ax.set_xlabel('GBM puro (%)')\n",
    "ax.set_ylabel('Con engine (%)')\n",
    "ax.set_title('QQ-Plot: GBM vs Engine\\n(en la diagonal = idénticos)')\n",
    "ax.grid(True, alpha=0.3)\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n",
    "\n",
    "print(f'GBM puro:   media={np.mean(gbm_ret):+.3f}%, std={np.std(gbm_ret):.3f}%, kurtosis={float(np.mean((gbm_ret-np.mean(gbm_ret))**4)/np.std(gbm_ret)**4):.1f}')\n",
    "print(f'Con engine: media={np.mean(eng_ret):+.3f}%, std={np.std(eng_ret):.3f}%, kurtosis={float(np.mean((eng_ret-np.mean(eng_ret))**4)/np.std(eng_ret)**4):.1f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 9. Resumen"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "======================================================================\n",
      "  ESTIMACIÓN MONTE CARLO — BTC/USDT\n",
      "  Precio actual: $66,760\n",
      "  Modelo: GBM + Jump-diffusion calibrado con 1M trades reales\n",
      "  Simulaciones: 5,000\n",
      "======================================================================\n",
      "\n",
      "  1 hora\n",
      "    Rango p5-p95:  $66,339 — $67,199\n",
      "    Mediana:       $66,758 (-0.00%)\n",
      "    P(sube):       49.8%\n",
      "    VaR 95%:       -0.63%  ($-421)\n",
      "\n",
      "  4 horas\n",
      "    Rango p5-p95:  $65,908 — $67,633\n",
      "    Mediana:       $66,758 (-0.00%)\n",
      "    P(sube):       49.8%\n",
      "    VaR 95%:       -1.28%  ($-852)\n",
      "\n",
      "  1 día\n",
      "    Rango p5-p95:  $64,630 — $68,920\n",
      "    Mediana:       $66,754 (-0.01%)\n",
      "    P(sube):       49.9%\n",
      "    VaR 95%:       -3.19%  ($-2,130)\n",
      "\n",
      "  1 semana\n",
      "    Rango p5-p95:  $61,088 — $72,624\n",
      "    Mediana:       $66,632 (-0.19%)\n",
      "    P(sube):       48.7%\n",
      "    VaR 95%:       -8.50%  ($-5,672)\n",
      "\n",
      "  NOTA: Estas estimaciones asumen que la volatilidad y la estructura\n",
      "  del mercado se mantienen constantes. En la realidad cambian.\n",
      "  Esto es un modelo probabilístico, NO una predicción.\n",
      "======================================================================\n"
     ]
    }
   ],
   "source": [
    "print('=' * 70)\n",
    "print(f'  ESTIMACIÓN MONTE CARLO — BTC/USDT')\n",
    "print(f'  Precio actual: ${CURRENT_PRICE:,.0f}')\n",
    "print(f'  Modelo: GBM + Jump-diffusion calibrado con 1M trades reales')\n",
    "print(f'  Simulaciones: {N_SIMS:,}')\n",
    "print('=' * 70)\n",
    "print()\n",
    "\n",
    "for name, ticks in HORIZONS.items():\n",
    "    fp = all_paths[:, ticks - 1]\n",
    "    ret = (fp / CURRENT_PRICE - 1) * 100\n",
    "    p5, p50, p95 = np.percentile(fp, [5, 50, 95])\n",
    "    prob_up = np.mean(fp > CURRENT_PRICE) * 100\n",
    "    var95 = np.percentile(ret, 5)\n",
    "    \n",
    "    print(f'  {name:}')\n",
    "    print(f'    Rango p5-p95:  ${p5:,.0f} — ${p95:,.0f}')\n",
    "    print(f'    Mediana:       ${p50:,.0f} ({(p50/CURRENT_PRICE - 1)*100:+.2f}%)')\n",
    "    print(f'    P(sube):       {prob_up:.1f}%')\n",
    "    print(f'    VaR 95%:       {var95:+.2f}%  (${var95/100 * CURRENT_PRICE:+,.0f})')\n",
    "    print()\n",
    "\n",
    "print('  NOTA: Estas estimaciones asumen que la volatilidad y la estructura')\n",
    "print('  del mercado se mantienen constantes. En la realidad cambian.')\n",
    "print('  Esto es un modelo probabilístico, NO una predicción.')\n",
    "print('=' * 70)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
