505 lines
18 KiB
Plaintext
505 lines
18 KiB
Plaintext
{
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"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": {
|
||
"name": "python",
|
||
"version": "3.13.0"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
}
|