518 lines
18 KiB
Plaintext
518 lines
18 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"# Monte Carlo: análisis de sensibilidad por parámetro\n",
|
||
"\n",
|
||
"Usamos las funciones del registry para correr cientos de simulaciones variando **un parámetro a la vez**.\n",
|
||
"Esto nos dice:\n",
|
||
"- Qué parámetros importan más\n",
|
||
"- Cómo responde el mercado simulado a cada cambio\n",
|
||
"- Qué rangos producen mercados realistas\n",
|
||
"\n",
|
||
"## Parámetros calibrados desde BTC/USDT real (notebook 06)\n",
|
||
"\n",
|
||
"| Parámetro | Valor calibrado | Confianza | Fuente |\n",
|
||
"|---|---|---|---|\n",
|
||
"| sigma | 0.000514 | Alta | Std retornos 1m |\n",
|
||
"| mu | ~0 | Alta | Media retornos |\n",
|
||
"| jump_intensity | 0.013 | Media | % retornos > 3σ |\n",
|
||
"| jump_size_std | 0.000356 | Media | Std de los jumps |\n",
|
||
"| n_takers_lambda | 12.0 | Media | aggTrades/segundo |\n",
|
||
"| taker_size_alpha | 0.78 | Media | Pareto MLE cola p90+ |\n",
|
||
"| hawkes_alpha | 0.17 | Baja | Fit exp sobre ACF |\n",
|
||
"| hawkes_beta | 0.015 | Baja | Fit exp sobre ACF |\n",
|
||
"| gamma | ? | No observable | Relación spread~vol |\n",
|
||
"| n_makers | ? | No observable | Capas de liquidez L2 |\n",
|
||
"| maker_spread | 0.01 | Alta | Spread real del book |"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Base params cargados\n",
|
||
"Test: 270 trades, spread=-0.057967\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 run_market_sim import run_market_sim\n",
|
||
"import numpy as np\n",
|
||
"import polars as pl\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"\n",
|
||
"# Parámetros base calibrados desde datos reales\n",
|
||
"BASE = dict(\n",
|
||
" initial_price=100.0,\n",
|
||
" n_ticks=300,\n",
|
||
" sigma=0.000514,\n",
|
||
" mu=0.0,\n",
|
||
" jump_intensity=0.013,\n",
|
||
" jump_size_std=0.000356,\n",
|
||
" n_makers=5,\n",
|
||
" maker_spread=0.01,\n",
|
||
" gamma=0.1,\n",
|
||
" maker_levels=3,\n",
|
||
" maker_qty=10.0,\n",
|
||
" n_takers_lambda=12.0,\n",
|
||
" taker_size_alpha=0.78,\n",
|
||
" taker_size_min=0.001,\n",
|
||
" taker_size_max=5.0,\n",
|
||
" hawkes_alpha=0.17,\n",
|
||
" hawkes_beta=0.015,\n",
|
||
")\n",
|
||
"\n",
|
||
"print('Base params cargados')\n",
|
||
"# Quick test\n",
|
||
"r = run_market_sim(**BASE, seed=0)\n",
|
||
"print(f'Test: {r[\"total_trades\"]} trades, spread={np.mean(r[\"spreads\"]):.6f}')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Herramientas de análisis"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def sweep_param(param_name: str, values: list, base_params: dict, n_seeds: int = 10) -> pl.DataFrame:\n",
|
||
" \"\"\"Corre simulaciones variando un parámetro. N seeds por valor para tener distribución.\"\"\"\n",
|
||
" records = []\n",
|
||
" total = len(values) * n_seeds\n",
|
||
" done = 0\n",
|
||
" for val in values:\n",
|
||
" for seed in range(n_seeds):\n",
|
||
" params = dict(base_params)\n",
|
||
" params[param_name] = val\n",
|
||
" params['seed'] = seed * 1000 + hash(str(val)) % 1000\n",
|
||
" sim = run_market_sim(**params)\n",
|
||
" \n",
|
||
" spreads = sim['spreads']\n",
|
||
" npt = sim['n_trades_per_tick']\n",
|
||
" tp = np.array(sim['trade_prices']) if sim['trade_prices'] else np.array([0.0])\n",
|
||
" fp = np.array(sim['fundamental_prices'])\n",
|
||
" \n",
|
||
" # Realized vol de trades\n",
|
||
" tp_pos = tp[tp > 0]\n",
|
||
" if len(tp_pos) > 2:\n",
|
||
" log_ret = np.diff(np.log(tp_pos))\n",
|
||
" rvol = float(np.std(log_ret))\n",
|
||
" else:\n",
|
||
" rvol = 0.0\n",
|
||
" \n",
|
||
" records.append({\n",
|
||
" 'param_value': float(val),\n",
|
||
" 'seed': seed,\n",
|
||
" 'total_trades': sim['total_trades'],\n",
|
||
" 'mean_spread': float(np.mean(spreads)),\n",
|
||
" 'std_spread': float(np.std(spreads)),\n",
|
||
" 'mean_trades_tick': float(np.mean(npt)),\n",
|
||
" 'max_trades_tick': int(np.max(npt)),\n",
|
||
" 'realized_vol': rvol,\n",
|
||
" 'price_return_pct': float((fp[-1] / fp[0] - 1) * 100),\n",
|
||
" 'maker_total_pnl': float(sum(sim['maker_pnls'])),\n",
|
||
" })\n",
|
||
" done += 1\n",
|
||
" \n",
|
||
" print(f'{param_name}: {done} simulaciones')\n",
|
||
" return pl.DataFrame(records)\n",
|
||
"\n",
|
||
"\n",
|
||
"def plot_sweep(df: pl.DataFrame, param_name: str, metrics: list[tuple[str, str]], title: str = ''):\n",
|
||
" \"\"\"Grafica métricas vs parámetro con bandas de confianza.\"\"\"\n",
|
||
" n = len(metrics)\n",
|
||
" fig, axes = plt.subplots(1, n, figsize=(5 * n, 4))\n",
|
||
" if n == 1:\n",
|
||
" axes = [axes]\n",
|
||
" \n",
|
||
" agg = df.group_by('param_value').agg(\n",
|
||
" *[pl.col(m).mean().alias(f'{m}_mean') for m, _ in metrics],\n",
|
||
" *[pl.col(m).std().alias(f'{m}_std') for m, _ in metrics],\n",
|
||
" ).sort('param_value')\n",
|
||
" \n",
|
||
" x = agg['param_value'].to_numpy()\n",
|
||
" \n",
|
||
" for i, (metric, label) in enumerate(metrics):\n",
|
||
" ax = axes[i]\n",
|
||
" y = agg[f'{metric}_mean'].to_numpy()\n",
|
||
" yerr = agg[f'{metric}_std'].to_numpy()\n",
|
||
" yerr = np.nan_to_num(yerr, nan=0.0)\n",
|
||
" \n",
|
||
" ax.fill_between(x, y - yerr, y + yerr, alpha=0.2, color='#3498db')\n",
|
||
" ax.plot(x, y, 'o-', color='#3498db', markersize=4, linewidth=1.5)\n",
|
||
" ax.set_xlabel(param_name)\n",
|
||
" ax.set_ylabel(label)\n",
|
||
" ax.grid(True, alpha=0.3)\n",
|
||
" \n",
|
||
" fig.suptitle(title or f'Sensibilidad a {param_name}', fontsize=12, fontweight='bold')\n",
|
||
" plt.tight_layout()\n",
|
||
" plt.show()\n",
|
||
"\n",
|
||
"\n",
|
||
"METRICS = [\n",
|
||
" ('mean_spread', 'Spread medio'),\n",
|
||
" ('total_trades', 'Total trades'),\n",
|
||
" ('realized_vol', 'Vol realizada'),\n",
|
||
" ('maker_total_pnl', 'PnL makers'),\n",
|
||
"]\n",
|
||
"\n",
|
||
"print('sweep_param() y plot_sweep() definidas')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 1. SIGMA (volatilidad)\n",
|
||
"\n",
|
||
"**Qué es:** cuánto se mueve el precio fundamental por tick. \n",
|
||
"**Calibrado:** 0.000514 (desde retornos 1m de BTC) \n",
|
||
"**Confianza:** ALTA — medición directa \n",
|
||
"**Hipótesis:** más σ → más oportunidades para takers → más trades, spread más ancho (makers se protegen)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"sigma_vals = [0.0001, 0.0003, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05]\n",
|
||
"df_sigma = sweep_param('sigma', sigma_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_sigma, 'sigma', METRICS, 'SIGMA — volatilidad del precio fundamental')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 2. GAMMA (aversión al riesgo del maker)\n",
|
||
"\n",
|
||
"**Qué es:** cuánto ajusta el maker sus precios por inventario acumulado. \n",
|
||
"**Calibrado:** NO directamente — se infiere de spread vs volatilidad \n",
|
||
"**Confianza:** BAJA \n",
|
||
"**Hipótesis:** más γ → spread más ancho → menos ejecuciones → makers más seguros pero mercado menos líquido"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"gamma_vals = [0.001, 0.005, 0.01, 0.05, 0.1, 0.3, 0.5, 1.0, 2.0, 5.0]\n",
|
||
"df_gamma = sweep_param('gamma', gamma_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_gamma, 'gamma', METRICS, 'GAMMA — aversión al riesgo del maker (Avellaneda-Stoikov)')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 3. N_TAKERS_LAMBDA (arrival rate de takers)\n",
|
||
"\n",
|
||
"**Qué es:** cuántos takers llegan por tick en promedio (base Poisson, amplificado por Hawkes). \n",
|
||
"**Calibrado:** 12.0 aggTrades/segundo \n",
|
||
"**Confianza:** MEDIA — medimos aggTrades, no órdenes originales \n",
|
||
"**Hipótesis:** más λ → más presión sobre el book → más trades, spreads más volátiles"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"lambda_vals = [0.5, 1, 2, 5, 10, 15, 20, 30, 50]\n",
|
||
"df_lambda = sweep_param('n_takers_lambda', lambda_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_lambda, 'n_takers_lambda', METRICS, 'LAMBDA — arrival rate de takers')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 4. HAWKES_ALPHA (contagio entre trades)\n",
|
||
"\n",
|
||
"**Qué es:** cuánto excita un trade la llegada de más trades (clustering). \n",
|
||
"**Calibrado:** 0.17 (fit exponencial sobre ACF) \n",
|
||
"**Confianza:** BAJA — el branching ratio salió >1, modelo simple no captura bien \n",
|
||
"**Hipótesis:** más α → ráfagas más intensas → max trades/tick explota, spread se estresa"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"hawkes_a_vals = [0.0, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7, 0.9]\n",
|
||
"df_hawkes_a = sweep_param('hawkes_alpha', hawkes_a_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_hawkes_a, 'hawkes_alpha', METRICS, 'HAWKES_ALPHA — contagio entre trades')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 5. TAKER_SIZE_ALPHA (cola de tamaños — ballenas)\n",
|
||
"\n",
|
||
"**Qué es:** exponente Pareto de los tamaños de órdenes. Bajo = más ballenas. \n",
|
||
"**Calibrado:** 0.78 (MLE sobre cola p90+) \n",
|
||
"**Confianza:** MEDIA — medimos fills agrupados, no órdenes originales \n",
|
||
"**Hipótesis:** α bajo → más órdenes grandes → más slippage, spread se abre más, más impacto en precio"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"size_a_vals = [0.3, 0.5, 0.78, 1.0, 1.5, 2.0, 3.0, 5.0]\n",
|
||
"df_size_a = sweep_param('taker_size_alpha', size_a_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_size_a, 'taker_size_alpha', METRICS, 'TAKER_SIZE_ALPHA — cola de tamaños (bajo = más ballenas)')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 6. N_MAKERS (número de market makers)\n",
|
||
"\n",
|
||
"**Qué es:** cuántos makers compiten poniendo liquidez. \n",
|
||
"**Calibrado:** NO directamente observable — se infiere de capas de liquidez en L2 \n",
|
||
"**Confianza:** BAJA \n",
|
||
"**Hipótesis:** más makers → más competencia → spread más tight, más liquidez, pero PnL por maker baja"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"nmakers_vals = [1, 2, 3, 5, 7, 10, 15, 20]\n",
|
||
"df_nmakers = sweep_param('n_makers', nmakers_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_nmakers, 'n_makers', METRICS, 'N_MAKERS — número de market makers')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 7. MAKER_SPREAD (spread base)\n",
|
||
"\n",
|
||
"**Qué es:** el spread mínimo que los makers intentan capturar. \n",
|
||
"**Calibrado:** $0.01 (spread real de BTC/USDT en Binance) \n",
|
||
"**Confianza:** ALTA — medición directa del book \n",
|
||
"**Hipótesis:** spread más ancho → menos ejecuciones → makers más rentables pero mercado menos eficiente"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"spread_vals = [0.001, 0.005, 0.01, 0.05, 0.1, 0.3, 0.5, 1.0, 2.0]\n",
|
||
"df_spread = sweep_param('maker_spread', spread_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_spread, 'maker_spread', METRICS, 'MAKER_SPREAD — spread base deseado por makers')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 8. JUMP_INTENSITY (frecuencia de saltos)\n",
|
||
"\n",
|
||
"**Qué es:** probabilidad de un movimiento brusco en cada tick. \n",
|
||
"**Calibrado:** 1.3% (retornos > 3σ) \n",
|
||
"**Confianza:** MEDIA — depende del threshold elegido \n",
|
||
"**Hipótesis:** más jumps → más volatilidad realizada, kurtosis sube, makers sufren más"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"jump_vals = [0.0, 0.005, 0.01, 0.02, 0.05, 0.1, 0.15, 0.2]\n",
|
||
"df_jump = sweep_param('jump_intensity', jump_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_jump, 'jump_intensity', METRICS, 'JUMP_INTENSITY — frecuencia de saltos bruscos')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 9. HAWKES_BETA (decaimiento del contagio)\n",
|
||
"\n",
|
||
"**Qué es:** qué tan rápido se calma la excitación después de una ráfaga. \n",
|
||
"**Calibrado:** 0.015 \n",
|
||
"**Confianza:** BAJA \n",
|
||
"**Hipótesis:** β bajo → ráfagas más largas → mercado más caótico. β alto → ráfagas cortas → más Poisson"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"hawkes_b_vals = [0.005, 0.01, 0.02, 0.05, 0.1, 0.3, 0.5, 1.0, 2.0]\n",
|
||
"df_hawkes_b = sweep_param('hawkes_beta', hawkes_b_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_hawkes_b, 'hawkes_beta', METRICS, 'HAWKES_BETA — decaimiento del contagio (alto = se calma rápido)')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 10. MAKER_LEVELS (profundidad del maker)\n",
|
||
"\n",
|
||
"**Qué es:** cuántos niveles de precio pone cada maker a cada lado. \n",
|
||
"**Calibrado:** se estima contando niveles con liquidez significativa en L2 \n",
|
||
"**Confianza:** BAJA \n",
|
||
"**Hipótesis:** más niveles → más profundidad → menos slippage para órdenes grandes"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"levels_vals = [1, 2, 3, 5, 7, 10, 15]\n",
|
||
"df_levels = sweep_param('maker_levels', levels_vals, BASE, n_seeds=10)\n",
|
||
"plot_sweep(df_levels, 'maker_levels', METRICS, 'MAKER_LEVELS — niveles de profundidad por maker')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"---\n",
|
||
"## 11. Resumen: sensibilidad relativa\n",
|
||
"\n",
|
||
"¿Qué parámetro afecta más a cada métrica?"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": null,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"# Calcular coeficiente de variación de cada métrica respecto a cada parámetro\n",
|
||
"all_sweeps = {\n",
|
||
" 'sigma': df_sigma,\n",
|
||
" 'gamma': df_gamma,\n",
|
||
" 'n_takers_lambda': df_lambda,\n",
|
||
" 'hawkes_alpha': df_hawkes_a,\n",
|
||
" 'taker_size_alpha': df_size_a,\n",
|
||
" 'n_makers': df_nmakers,\n",
|
||
" 'maker_spread': df_spread,\n",
|
||
" 'jump_intensity': df_jump,\n",
|
||
" 'hawkes_beta': df_hawkes_b,\n",
|
||
" 'maker_levels': df_levels,\n",
|
||
"}\n",
|
||
"\n",
|
||
"sensitivity = []\n",
|
||
"for pname, df in all_sweeps.items():\n",
|
||
" agg = df.group_by('param_value').agg(\n",
|
||
" pl.col('mean_spread').mean(),\n",
|
||
" pl.col('total_trades').mean(),\n",
|
||
" pl.col('realized_vol').mean(),\n",
|
||
" pl.col('maker_total_pnl').mean(),\n",
|
||
" )\n",
|
||
" for metric in ['mean_spread', 'total_trades', 'realized_vol', 'maker_total_pnl']:\n",
|
||
" vals = agg[metric].to_numpy()\n",
|
||
" vals = vals[~np.isnan(vals)]\n",
|
||
" if len(vals) > 1 and np.mean(np.abs(vals)) > 0:\n",
|
||
" cv = np.std(vals) / np.mean(np.abs(vals))\n",
|
||
" else:\n",
|
||
" cv = 0.0\n",
|
||
" sensitivity.append({'param': pname, 'metric': metric, 'cv': round(cv, 3)})\n",
|
||
"\n",
|
||
"sens_df = pl.DataFrame(sensitivity)\n",
|
||
"\n",
|
||
"# Heatmap\n",
|
||
"params_order = list(all_sweeps.keys())\n",
|
||
"metrics_order = ['mean_spread', 'total_trades', 'realized_vol', 'maker_total_pnl']\n",
|
||
"metrics_labels = ['Spread', 'Trades', 'Vol realizada', 'PnL makers']\n",
|
||
"\n",
|
||
"matrix = np.zeros((len(params_order), len(metrics_order)))\n",
|
||
"for row in sens_df.iter_rows(named=True):\n",
|
||
" i = params_order.index(row['param'])\n",
|
||
" j = metrics_order.index(row['metric'])\n",
|
||
" matrix[i, j] = row['cv']\n",
|
||
"\n",
|
||
"fig, ax = plt.subplots(figsize=(10, 8))\n",
|
||
"im = ax.imshow(matrix, cmap='YlOrRd', aspect='auto')\n",
|
||
"ax.set_xticks(range(len(metrics_labels)))\n",
|
||
"ax.set_xticklabels(metrics_labels, fontsize=10)\n",
|
||
"ax.set_yticks(range(len(params_order)))\n",
|
||
"ax.set_yticklabels(params_order, fontsize=10)\n",
|
||
"\n",
|
||
"for i in range(len(params_order)):\n",
|
||
" for j in range(len(metrics_order)):\n",
|
||
" ax.text(j, i, f'{matrix[i,j]:.2f}', ha='center', va='center', fontsize=9,\n",
|
||
" color='white' if matrix[i,j] > 0.5 else 'black')\n",
|
||
"\n",
|
||
"ax.set_title('Sensibilidad: coeficiente de variación por parámetro × métrica\\n(más alto = más impacto)', fontsize=12)\n",
|
||
"plt.colorbar(im, label='CV')\n",
|
||
"plt.tight_layout()\n",
|
||
"plt.show()\n",
|
||
"\n",
|
||
"# Top sensibilidades\n",
|
||
"print('\\nTop 10 combinaciones param × métrica más sensibles:')\n",
|
||
"top = sens_df.sort('cv', descending=True).head(10)\n",
|
||
"print(top)"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "Python 3 (ipykernel)",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"name": "python",
|
||
"version": "3.13.0"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 4
|
||
}
|