---
title: "5.8.5 Laboratorio: fundacional frente a GARCH y LightGBM"
description: "Chronos-Bolt y TimesFM-2.5 sin reentrenar frente a HAR, GARCH y LightGBM en la volatilidad realizada: QLIKE, Diebold-Mariano, MCS y coste."
url: "https://pythonparatrading.com/curso-python-trading/nivel-5/laboratorio-fundacional-frente-a-garch-y-lightgbm/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-5/laboratorio-fundacional-frente-a-garch-y-lightgbm.png"
---

# 5.8.5 · Laboratorio: modelos fundacionales frente a GARCH, HAR y LightGBM

> La volatilidad es donde un modelo fundacional tiene más opciones en finanzas. Este laboratorio lo pone a prueba con datos cuya respuesta se conoce: prevé la varianza realizada de tres activos con Chronos-Bolt y TimesFM-2.5 sin reentrenar, un GARCH, un HAR y un LightGBM, con origen móvil y sin fugas, y compara con QLIKE, Diebold-Mariano y el Model Confidence Set. El HAR, que aquí es casi el modelo verdadero, marca el listón.

- Curso: [Nivel 5: Machine learning y deep learning (Keras/PyTorch)](https://pythonparatrading.com/curso-python-trading/nivel-5/)
- Módulo: 5.8 · Modelos fundacionales de series temporales
- Duración: 75 min
- Requisitos: [5.8.4 · Modelos fundacionales en finanzas: qué dice la evidencia](https://pythonparatrading.com/curso-python-trading/nivel-5/evidencia-de-los-modelos-fundacionales-en-finanzas/), [5.5.3 · Gradient boosting: XGBoost, LightGBM y CatBoost](https://pythonparatrading.com/curso-python-trading/nivel-5/xgboost-lightgbm-y-catboost/), [5.4.3 · Walk-forward con reentrenamiento: ventanas y frecuencia](https://pythonparatrading.com/curso-python-trading/nivel-5/walk-forward-con-reentrenamiento/)

## Al terminar esta lección

- Montar una evaluación con origen móvil y reestimación anual sin fugas para cinco familias de modelos de volatilidad, incluidos dos modelos fundacionales sin reentrenar.
- Usar QLIKE y ECM frente a una medida ruidosa de la varianza, y saber por qué la QLIKE es más estable.
- Convertir la previsión de un modelo fundacional en log-varianza en una previsión de la media con sus propios cuantiles.
- Comparar modelos con Diebold-Mariano (varianza de Newey-West) y con un Model Confidence Set implementado a mano y contrastado con arch.
- Medir el coste de cada modelo y decidir con honestidad cuándo compensa uno fundacional.

## El banco de pruebas

La lección 5.8.4 terminó con una pista: el mismo Chronos-Bolt que no predice el rendimiento explica casi la mitad de la
varianza del logaritmo de la varianza realizada. Falta saber si eso es **bueno**, es decir, si mejora lo que ya hacen
los modelos de volatilidad de siempre. Este laboratorio lo mide con cinco familias de modelos:

| Modelo | Qué es | Entrenamiento | Lección |
|---|---|---|---|
| Ingenua | La RV de ayer | Ninguno | 1.6.1 |
| GARCH(1,1) | Varianza condicional de los rendimientos diarios, llevada a la escala de la RV | Máxima verosimilitud cada enero | 2.1.4 |
| HAR | MCO del log de la RV sobre sus medias de 1, 5 y 22 días (Corsi, 2009) | MCO cada enero | 5.6.1 |
| LightGBM | Árboles con retardos del log de la RV y de los rendimientos | Cada enero | 5.5.3 |
| Chronos-Bolt y TimesFM-2.5 | Modelos fundacionales sobre las 512 sesiones anteriores del log de la RV | Ninguno (zero-shot) | 5.8.1-5.8.3 |

Los datos son los de `vol5` (README de los datos del nivel 5, sección «Volatilidad»), sintéticos. El proceso verdadero
es un HAR en el logaritmo de la varianza integrada, con efecto apalancamiento, saltos, cambios de nivel y choques en
fechas conocidas. Eso tiene dos consecuencias que hay que tener presentes al leer los resultados: un HAR bien estimado
está **muy cerca del óptimo**, y existe un **oráculo**, la esperanza exacta de la RV con la información del día
anterior, que marca el suelo de lo alcanzable. Un modelo por debajo del oráculo tendría una fuga.

Se usan tres activos con perfiles distintos: el **índice** (apalancamiento fuerte, γ = 0,18, sin saltos), el **bono**
(sin apenas apalancamiento y con un cambio de nivel de +0,60 en log-varianza el 3 de enero de 2022) y la **cripto**
(12 saltos al año de desviación 7 % y un cambio de nivel de −0,60 en 2019). La prueba cubre 2018-2025.

```mermaid
flowchart TB
  D["vol5: 3 activos"] --> O["Orígenes 2018-2025"]
  O --> E["HAR y GARCH"]
  O --> G["LightGBM"]
  O --> F["Fundacionales"]
  E --> P["QLIKE y ECM"]
  G --> P
  F --> P
  P --> T["DM y MCS"]
```

El protocolo es de **origen móvil sin fugas**. Los modelos que se estiman (GARCH, HAR, LightGBM) se reestiman cada 1 de
enero con todos los datos anteriores y prevén con esos parámetros cada día del año; sus features en el día $t$ solo
usan información hasta $t-1$. Los fundacionales no se estiman: en cada origen reciben las 512 sesiones anteriores. Como
cada previsión de TimesFM cuesta del orden de una décima de segundo en CPU, los modelos se comparan en **una de cada 20
sesiones** (101 orígenes por activo); al final se comprueba con todas las sesiones lo que dicen los modelos baratos.

```python
import numpy as np
import pandas as pd

d = pd.read_parquet("vol5_diario.parquet")
v = pd.read_parquet("vol5_verdad.parquet")
ACT = ["indice", "bono", "cripto"]
# parte nocturna ω, saltos por año y desviación del salto (README)
PAR = {"indice": (0.20, 0, 0.0), "bono": (0.15, 0, 0.0),
       "cripto": (0.0, 12, 0.07)}
fechas = pd.DatetimeIndex(d.loc[d["activo"] == "indice", "fecha"])
n = len(fechas)
corte = fechas.searchsorted(pd.Timestamp("2018-01-02"))
orig = np.arange(corte, n, 20)              # una de cada 20 sesiones
inicio = {y: fechas.searchsorted(pd.Timestamp(f"{y}-01-01"))
          for y in range(2018, 2027)}
r = {a: d.loc[d["activo"] == a, "rendimiento"].to_numpy() for a in ACT}
rv = {a: d.loc[d["activo"] == a, "rv_5min"].to_numpy() for a in ACT}
lrv = {a: np.log(rv[a]) for a in ACT}
print(f"{len(orig)} orígenes por activo de {fechas[orig[0]].date()} "
      f"a {fechas[orig[-1]].date()}; {n - corte} sesiones en 2018-2025")
```
```output
101 orígenes por activo de 2018-01-02 a 2025-12-16; 2011 sesiones en 2018-2025
```

## Pérdidas, oráculo y HAR

La varianza verdadera no se observa; se compara con la RV de 5 minutos, que la mide con error. Patton (2011) demostró
que no todas las pérdidas sirven con un proxy ruidoso: el **ECM** y la **QLIKE** sí ordenan los modelos igual que si se
conociera la varianza verdadera. La QLIKE, con $h$ la previsión,

$$
L(RV, h) = \frac{RV}{h} - \log\frac{RV}{h} - 1,
$$

vale 0 cuando $h = RV$, castiga más quedarse corto que pasarse y depende solo del cociente $RV/h$, así que un día de
varianza extrema no la domina como al ECM. El HAR se estima en logaritmos y su previsión de la media lleva la corrección
lognormal $e^{\hat x + \hat\sigma^2/2}$, con $\hat\sigma^2$ la varianza de los residuos del entrenamiento. El oráculo
es la fórmula del README: $E[RV_t \mid t-1] = (1-\omega)\,E[IV_t \mid t-1] + \lambda\,\sigma_J^2$, con la parte nocturna
$\omega$, la intensidad de saltos $\lambda$ (por sesión) y su varianza $\sigma_J^2$.

```mermaid
flowchart LR
  A["1 de enero"] --> B["Reestimar con < t"]
  B --> C["Prever cada día"]
  C --> D["Año siguiente"]
  D --> A
```

```python
def qlike(obs, f):
    """QLIKE de Patton: obs/f - log(obs/f) - 1 (0 si f = obs)."""
    return obs / f - np.log(obs / f) - 1

def medias_har(x):
    s = pd.Series(x)
    return pd.DataFrame({"d1": s.shift(1),
                         "s5": s.rolling(5).mean().shift(1),
                         "m22": s.rolling(22).mean().shift(1)})

prev, coef = {a: {} for a in ACT}, {}
for a in ACT:
    om, nj, sj = PAR[a]
    iv_prev = v.loc[v["activo"] == a, "iv_prevista"].to_numpy()
    prev[a]["oraculo"] = iv_prev * (1 - om) + nj / 252 * sj ** 2
    prev[a]["ingenua"] = np.r_[np.nan, rv[a][:-1]]
    X = medias_har(lrv[a]).to_numpy()
    har = np.full(n, np.nan)
    for y in range(2018, 2026):             # reestimado cada enero
        t0, t1 = inicio[y], inicio[y + 1]
        ok = np.isfinite(X[:t0]).all(axis=1)
        A = np.column_stack([np.ones(ok.sum()), X[:t0][ok]])
        b, *_ = np.linalg.lstsq(A, lrv[a][:t0][ok], rcond=None)
        s2 = np.var(lrv[a][:t0][ok] - A @ b)
        har[t0:t1] = np.exp(b[0] + X[t0:t1] @ b[1:] + s2 / 2)
    prev[a]["har"], coef[a] = har, b
pd.DataFrame(coef, index=["c", "d1", "s5", "m22"]).round(3)
```
```output
     indice   bono  cripto
c    -0.808 -1.215  -0.615
d1    0.373  0.273   0.137
s5    0.307  0.385   0.274
m22   0.238  0.236   0.503
```

Los coeficientes del último ajuste (enero de 2025) muestran la estructura HAR: el peso se reparte entre la RV de ayer,
la media semanal y la mensual, con una suma de 0,92 en el índice, 0,89 en el bono y 0,91 en la cripto, cerca de la
persistencia verdadera (0,92). En la cripto pesa más la media mensual (0,503): los saltos meten en la RV diaria un ruido
que la media larga filtra.

## GARCH con arch

El GARCH(1,1) se estima con los rendimientos diarios, no con la RV. `arch_model(...).fit(last_obs=t0)` estima con los
datos anteriores a `t0` y `forecast(start=t0 - 1, ...)` filtra el resto del año con esos parámetros fijos, de modo que
la previsión para el día $t$ solo usa rendimientos hasta $t-1$ (lección 2.1.4). La varianza del GARCH es de cierre a
cierre; la RV no ve la parte nocturna, así que se reescala con el cociente $k$ entre la RV media y la varianza de los
rendimientos del entrenamiento, como en el README.

```python
from arch import arch_model

for a in ACT:
    gar = np.full(n, np.nan)
    for y in range(2018, 2026):
        t0, t1 = inicio[y], inicio[y + 1]
        am = arch_model(100 * r[a][:t1], mean="Constant", vol="GARCH",
                        p=1, q=1, dist="normal")
        res = am.fit(last_obs=t0, disp="off")
        # la fila de t usa datos hasta t y prevé t + 1
        h = res.forecast(start=t0 - 1, horizon=1, reindex=False)
        h = h.variance.to_numpy()[: t1 - t0, 0] / 1e4
        k = rv[a][:t0].mean() / np.var(res.resid[:t0] / 100)
        gar[t0:t1] = k * h        # a la escala de la RV (sin la noche)
    prev[a]["garch"] = gar
print(res.params.round(4).to_dict(), f"k = {k:.2f}")
```
```output
{'mu': 0.1113, 'omega': 0.1264, 'alpha[1]': 0.0183, 'beta[1]': 0.9706} k = 1.04
```

La salida es la del último ajuste, la cripto en enero de 2025: α = 0,018 y β = 0,971, una persistencia de 0,989 típica
de un GARCH diario, y $k$ = 1,04 (en la cripto no hay parte nocturna: ω = 0). El GARCH reacciona solo a los
rendimientos al cuadrado, una medida de la varianza mucho más ruidosa que la RV; por eso, en datos con RV, el HAR suele
ganarle con holgura.

## LightGBM con retardos

El modelo de árboles recibe más información que el HAR: cinco retardos del log de la RV, las medias de 5, 22 y 66
sesiones, el rendimiento de ayer y la parte negativa de los rendimientos del último día y de la última semana, que
es lo que necesita para captar el **efecto apalancamiento** (la varianza sube tras las caídas). Configuración pequeña y
determinista (lección 5.5.3), reestimada cada enero; la previsión de la media lleva la misma corrección lognormal que el
HAR.

```python
import lightgbm as lgb

def features(a):
    s, ret = pd.Series(lrv[a]), pd.Series(r[a])
    F = medias_har(lrv[a])
    for k in range(2, 6):
        F[f"d{k}"] = s.shift(k)
    F["m66"] = s.rolling(66).mean().shift(1)
    F["r1"] = ret.shift(1)
    F["neg1"] = np.minimum(ret, 0).shift(1)
    F["neg5"] = np.minimum(ret, 0).rolling(5).sum().shift(1)
    return F

def lgbm():
    return lgb.LGBMRegressor(
        n_estimators=300, learning_rate=0.03, num_leaves=8,
        min_child_samples=100, subsample=0.8, subsample_freq=1,
        colsample_bytree=0.8, random_state=0, n_jobs=4,
        deterministic=True, force_row_wise=True, verbose=-1)

ult = {}
for a in ACT:
    F = features(a)
    ok = F.notna().all(axis=1).to_numpy()
    p = np.full(n, np.nan)
    for y in range(2018, 2026):
        t0, t1 = inicio[y], inicio[y + 1]
        tr = ok & (np.arange(n) < t0)
        m = lgbm().fit(F[tr], lrv[a][tr])
        s2 = np.var(lrv[a][tr] - m.predict(F[tr]))
        p[t0:t1] = np.exp(m.predict(F[t0:t1]) + s2 / 2)
    prev[a]["lgbm"], ult[a] = p, m
imp = pd.Series(ult["indice"].booster_.feature_importance("gain"),
                index=F.columns)
(imp / imp.sum()).sort_values(ascending=False).head(5).round(3)
```
```output
s5      0.437
d1      0.334
m22     0.096
r1      0.042
neg1    0.032
dtype: float64
```

La importancia por ganancia del modelo del índice en 2025 la dominan las mismas variables que el HAR: la media semanal
(43,7 %), la RV de ayer (33,4 %) y la media mensual (9,6 %). El rendimiento de ayer (4,2 %) y su parte negativa (3,2 %)
pesan poco, pero son información que un HAR-RV no tiene; si LightGBM gana en algún activo, será por ahí.

## Los modelos fundacionales sobre el log de la RV

Los dos modelos reciben el logaritmo de la RV, no la RV: en logaritmos la serie es casi gaussiana y su escala no cambia
con el nivel (el ejercicio 1 prueba la RV en niveles). Los dos devuelven cuantiles; la **mediana** del log, exponenciada,
es la mediana de la RV, que queda por debajo de su media porque la distribución de la RV tiene cola a la derecha. Para
prever la media, que es lo que premian la QLIKE y el ECM, se usa la corrección lognormal con la dispersión que da el
propio modelo: si el log es aproximadamente normal, $\sigma \approx (q_{0,9} - q_{0,1}) / (2 \cdot 1{,}2816)$ y la media es
$e^{q_{0,5} + \sigma^2/2}$. Las 303 series (101 orígenes × 3 activos) van en un solo lote.

```python
import torch

torch.manual_seed(0)
torch.use_deterministic_algorithms(True)
from chronos import BaseChronosPipeline
import timesfm

CTX = 512
ctx = [lrv[a][t - CTX:t].astype("float32") for a in ACT for t in orig]
Z = 2 * 1.2815515655446004            # distancia entre q10 y q90 en σ

chronos = BaseChronosPipeline.from_pretrained(
    "amazon/chronos-bolt-small", device_map="cpu", dtype=torch.float32)
q, _ = chronos.predict_quantiles(
    inputs=[torch.tensor(c) for c in ctx], prediction_length=1,
    quantile_levels=[0.1, 0.5, 0.9])
q = q[:, 0, :].numpy().astype(float)
ch_med, ch_s = q[:, 1], (q[:, 2] - q[:, 0]) / Z

tfm = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
    "google/timesfm-2.5-200m-pytorch", torch_compile=False)
tfm.compile(timesfm.ForecastConfig(
    max_context=CTX, max_horizon=16, normalize_inputs=True,
    use_continuous_quantile_head=True, per_core_batch_size=len(ctx)))
punto, cuant = tfm.forecast(horizon=1, inputs=ctx)
tf_med = punto[:, 0].astype(float)
tf_s = (cuant[:, 0, 9] - cuant[:, 0, 1]).astype(float) / Z

k = len(orig)
for i, a in enumerate(ACT):
    sl = slice(i * k, (i + 1) * k)
    for nom, med, s in [("chronos", ch_med, ch_s),
                        ("timesfm", tf_med, tf_s)]:
        prev[a][nom + "_mediana"] = np.exp(med[sl])
        prev[a][nom] = np.exp(med[sl] + s[sl] ** 2 / 2)
print("σ log prevista (media):", round(ch_s.mean(), 3),
      round(tf_s.mean(), 3))
```
```output
σ log prevista (media): 0.409 0.42
```

La dispersión media prevista del log de la RV es 0,41 en Chronos y 0,42 en TimesFM: la corrección multiplica la mediana
por $e^{0{,}41^2/2} \approx 1{,}09$, un 9 % más de varianza prevista.

## Resultados: QLIKE y ECM

```python
MOD = ["ingenua", "garch", "har", "lgbm", "chronos_mediana",
       "chronos", "timesfm_mediana", "timesfm", "oraculo"]

def en_orig(a, nom):
    f = prev[a][nom]
    return f if len(f) == len(orig) else f[orig]

perd = {a: {m: qlike(rv[a][orig], en_orig(a, m)) for m in MOD}
        for a in ACT}
tq = pd.DataFrame({a: {m: perd[a][m].mean() for m in MOD} for a in ACT})
ecm = pd.DataFrame({a: {m: np.mean((rv[a][orig] - en_orig(a, m)) ** 2)
                        for m in MOD} for a in ACT})
te = ecm / ecm.loc["oraculo"]
pd.concat({"QLIKE": tq, "ECM / ECM oráculo": te}, axis=1).round(3)
```
```output
                 QLIKE               ECM / ECM oráculo
                indice   bono cripto            indice   bono cripto
ingenua          0.105  0.082  0.662             1.644  2.160  3.004
garch            0.135  0.119  0.362             1.790  1.843  1.013
har              0.080  0.056  0.383             1.191  0.995  1.029
lgbm             0.074  0.054  0.398             1.155  0.917  1.028
chronos_mediana  0.085  0.060  0.461             1.375  1.071  1.049
chronos          0.082  0.059  0.408             1.275  1.139  1.045
timesfm_mediana  0.089  0.060  0.465             1.324  1.025  1.049
timesfm          0.085  0.059  0.414             1.234  1.030  1.037
oraculo          0.069  0.054  0.350             1.000  1.000  1.000
```

La figura de la cabecera resume la tabla dividiendo cada QLIKE por la del HAR del mismo activo. Lo que dice:

- **El oráculo es el suelo** (0,069, 0,054 y 0,350) y nadie lo cruza en QLIKE: no hay fugas.
- **El HAR queda cerca del suelo** en el índice y el bono, como anuncia el README: es casi el modelo verdadero.
- **LightGBM** mejora al HAR en el índice (0,074 frente a 0,080) y en el bono (0,054 frente a 0,056), y queda detrás
  en la cripto (0,398 frente a 0,383).
- **Los dos fundacionales**, sin entrenar nada, quedan entre la ingenua y el HAR: Chronos 0,082, 0,059 y 0,408;
  TimesFM 0,085, 0,059 y 0,414. Están un 2-8 % por encima del HAR y muy por debajo de la ingenua (0,105, 0,082 y 0,662).
  Chronos, con 48 millones de parámetros, queda ligeramente por delante de TimesFM, con 231 millones.
- **La corrección de la media importa**, sobre todo con saltos: en la cripto, la QLIKE de Chronos baja de 0,461 con la
  mediana a 0,408 con la media; la de TimesFM, de 0,465 a 0,414.
- **El GARCH** es el peor en el índice y el bono (0,135 y 0,119) y el mejor no oráculo en la cripto (0,362). La
  sección «Una muestra corta engaña» vuelve sobre este resultado.

El ECM relativo al oráculo enseña por qué no se usa como pérdida principal: en el bono, LightGBM (0,917) y el HAR
(0,995) salen **por debajo del oráculo**, algo imposible en esperanza. En 101 orígenes el ECM lo deciden unos pocos días
de varianza extrema; la QLIKE, que mira el cociente, es mucho más estable.

*Fig. — Bono sintético de vol5, 2021-2023: volatilidad realizada en los orígenes de la prueba y previsiones a un día del HAR y de TimesFM-2.5; la línea vertical marca el cambio de nivel de enero de 2022.* Serie de 38 puntos entre 2021 y 2023. La volatilidad realizada salta de alrededor del 4-5 % a valores de 5 a 10 % tras el cambio de nivel de enero de 2022 (destacado). Las previsiones del HAR y de TimesFM siguen casi el mismo camino, más suave que la realizada, y se ajustan al nuevo nivel en pocas semanas.

La figura muestra el bono entre 2021 y 2023. El 3 de enero de 2022 su volatilidad sube de forma permanente (+0,60 en
log-varianza, ≈ 35 % más de volatilidad). Ni el HAR, reestimado con todo el pasado, ni TimesFM, que normaliza cada
contexto de 512 sesiones, saben que el cambio ha ocurrido; los dos lo absorben a través de la RV reciente y siguen
casi el mismo camino. El modelo fundacional no tiene ventaja de adaptación sobre un HAR, que también se alimenta de las
medias de 1, 5 y 22 días.

## ¿Son significativas las diferencias?

Con 101 orígenes por activo, cada diferencia tiene su error. Primero, Diebold-Mariano de cada modelo frente al HAR con
la QLIKE, con varianza de Newey-West (4 retardos, aunque con orígenes separados 20 sesiones la autocorrelación de las
pérdidas es pequeña) y contraste bilateral. Un $t$ positivo es que el modelo pierde más que el HAR.

```python
from scipy import stats

def dm_hac(l1, l2, rezagos=4):
    """t de Diebold-Mariano con varianza de Newey-West (bilateral)."""
    dd = l1 - l2
    u = dd - dd.mean()
    nn = len(dd)
    var = u @ u / nn
    for j in range(1, rezagos + 1):
        var += 2 * (1 - j / (rezagos + 1)) * (u[j:] @ u[:-j]) / nn
    t = dd.mean() / np.sqrt(var / nn)
    return t, 2 * stats.t.sf(abs(t), nn - 1)

COMP = ["ingenua", "garch", "lgbm", "chronos", "timesfm", "oraculo"]
pd.DataFrame({a: {m: "{:+.2f} ({:.2f})".format(
                     *dm_hac(perd[a][m], perd[a]["har"]))
                  for m in COMP} for a in ACT})
```
```output
               indice          bono        cripto
ingenua  +2.33 (0.02)  +2.73 (0.01)  +1.93 (0.06)
garch    +3.74 (0.00)  +5.56 (0.00)  -0.19 (0.85)
lgbm     -0.97 (0.33)  -1.04 (0.30)  +0.83 (0.41)
chronos  +0.59 (0.56)  +1.33 (0.19)  +0.67 (0.50)
timesfm  +1.66 (0.10)  +1.14 (0.26)  +0.76 (0.45)
oraculo  -1.94 (0.06)  -1.01 (0.31)  -0.88 (0.38)
```

Los fundacionales pierden frente al HAR en los tres activos, pero ninguna diferencia es significativa: $t$ entre +0,59
y +1,66 (TimesFM en el índice, p = 0,10). LightGBM mejora al HAR en el índice y el bono sin significación ($t$ = −0,97 y
−1,04). Solo el GARCH en el índice y el bono ($t$ = +3,74 y +5,56) y la ingenua en el índice y el bono son claramente
peores. La fila del oráculo es la más reveladora: ni siquiera la **previsión perfecta** bate al HAR con significación
en esta muestra ($t$ = −1,94 en el índice, p = 0,06). Con 101 puntos, lo que se puede afirmar es poco.

Diebold-Mariano compara parejas. Para preguntar qué modelos podrían ser **el mejor** a la vez, sin elegir uno de
referencia, está el **Model Confidence Set** de Hansen, Lunde y Nason (2011): parte de todos los modelos, contrasta si
todos tienen la misma pérdida esperada y, si se rechaza, elimina el peor y repite. Los que quedan forman un conjunto que
contiene al mejor modelo con probabilidad $1-\alpha$. La versión de la celda usa el estadístico $T_{max}$: para cada
modelo, su pérdida media menos la media de los que siguen vivos, dividida por su error típico; la distribución bajo la
nula sale de un bootstrap de bloques móviles (bloques de 5, para respetar la dependencia). La librería `arch` trae la
misma prueba (`arch.bootstrap.MCS`), que sirve para contrastar la implementación propia.

```python
from arch.bootstrap import MCS

def mcs(L, alfa=0.10, B=2000, bloque=5, semilla=0):
    """MCS de Hansen, Lunde y Nason con el estadístico T_max y
    bootstrap de bloques móviles. Devuelve p del MCS por modelo."""
    rng = np.random.default_rng(semilla)
    nn, _ = L.shape
    nb = -(-nn // bloque)
    ini = rng.integers(0, nn - bloque + 1, size=(B, nb))
    idx = (ini[:, :, None] + np.arange(bloque)).reshape(B, -1)[:, :nn]
    vivos, p_mcs, pmax = list(L.columns), {}, 0.0
    while len(vivos) > 1:
        X = L[vivos].to_numpy()
        di = X.mean(0) - X.mean()          # pérdida frente a la media
        Xb = X[idx].mean(1)
        dib = Xb - Xb.mean(1, keepdims=True)
        se = np.sqrt(((dib - di) ** 2).mean(0))
        t = di / se
        Tb = ((dib - di) / se).max(1)       # T_max bajo la nula
        pmax = max(pmax, float(np.mean(Tb >= t.max())))
        if pmax >= alfa:
            break
        peor = vivos[int(np.argmax(t))]
        p_mcs[peor] = pmax
        vivos.remove(peor)
    return {**p_mcs, **{m: 1.0 for m in vivos}}

CAND = ["ingenua", "garch", "har", "lgbm", "chronos", "timesfm"]
for a in ACT:
    L = pd.DataFrame({m: perd[a][m] for m in CAND})
    propio = mcs(L)
    ref = MCS(L, size=0.10, reps=2000, block_size=5, method="max",
              bootstrap="mbb", seed=0)
    ref.compute()
    dentro = sorted(m for m in CAND if propio[m] >= 0.1)
    print(f"{a:7s} propio: {dentro}\n"
          f"        arch:   {sorted(ref.included)}")
```
```output
indice  propio: ['chronos', 'har', 'lgbm']
        arch:   ['chronos', 'har', 'lgbm']
bono    propio: ['chronos', 'har', 'lgbm', 'timesfm']
        arch:   ['chronos', 'har', 'lgbm', 'timesfm']
cripto  propio: ['chronos', 'garch', 'har', 'ingenua', 'lgbm', 'timesfm']
        arch:   ['chronos', 'garch', 'har', 'ingenua', 'lgbm', 'timesfm']
```

La implementación propia y la de `arch` dan los mismos conjuntos al 10 %. En el índice quedan HAR, LightGBM y Chronos;
en el bono, esos tres y TimesFM; en la cripto no se puede descartar ninguno, ni siquiera la ingenua, cuya QLIKE es
1,7 veces la del HAR: con saltos de un 7 % de desviación, unos pocos días dominan las pérdidas y 101 orígenes no bastan
para separar nada. El MCS no da un ganador; da los modelos que los datos no permiten descartar.

## Una muestra corta engaña

Los modelos baratos se pueden evaluar en las 2011 sesiones de 2018-2025 en vez de en 101. La celda lo hace y repite
Diebold-Mariano de LightGBM frente al HAR, ahora con 10 retardos de Newey-West porque las sesiones son consecutivas.

```python
BAR = ["ingenua", "garch", "har", "lgbm", "oraculo"]
todo = pd.DataFrame({a: {m: qlike(rv[a][corte:], prev[a][m][corte:])
                         .mean() for m in BAR} for a in ACT})
t_lg = {a: float(round(dm_hac(
            qlike(rv[a][corte:], prev[a]["lgbm"][corte:]),
            qlike(rv[a][corte:], prev[a]["har"][corte:]),
            rezagos=10)[0], 2)) for a in ACT}
print(f"t de LightGBM frente al HAR, {n - corte} sesiones:", t_lg)
todo.round(3)
```
```output
t de LightGBM frente al HAR, 2011 sesiones: {'indice': -2.41, 'bono': 3.07, 'cripto': 1.33}
         indice   bono  cripto
ingenua   0.126  0.101   0.525
garch     0.149  0.113   0.385
har       0.089  0.067   0.366
lgbm      0.086  0.069   0.377
oraculo   0.080  0.065   0.325
```

*Fig. — El ranking frente al HAR con la submuestra de 101 orígenes por activo (marca grande) y con las 2011 sesiones de 2018-2025 (marca pequeña): con pocos puntos, LightGBM parece mejor que el HAR en el bono y GARCH en la cripto; con todos, los dos pierden.* Seis filas con dos marcas unidas por una línea. GARCH: índice 1,70 y 1,67; bono 2,12 y 1,70; cripto 0,95 en la submuestra y 1,05 con todas las sesiones. LightGBM: índice 0,92 y 0,96; bono 0,96 y 1,03 (destacado: cruza la línea del HAR); cripto 1,04 y 1,03.

Dos órdenes de la submuestra se invierten. En el bono, LightGBM pasa de 0,96 veces la QLIKE del HAR a 1,03, y con 2011
sesiones la diferencia es significativa en contra ($t$ = +3,07). En la cripto, el GARCH pasa de 0,95 a 1,05. En el
índice, en cambio, LightGBM sigue por delante (0,086 frente a 0,089) y ahora con significación ($t$ = −2,41). La
diferencia entre los dos activos tiene una explicación plausible: el índice tiene un efecto apalancamiento fuerte (γ = 0,18) que
LightGBM capta con la parte negativa de los rendimientos y el HAR-RV no ve; en el bono (γ = 0,04) esa información no
existe y la flexibilidad de los árboles solo añade varianza. No es una prueba: las dos
features de rendimientos pesan solo un 7 % de la ganancia del modelo. Las QLIKE de la ingenua, el HAR y el oráculo coinciden con
las de la tabla del README (0,126, 0,089 y 0,080 en el índice); la del GARCH difiere un poco porque aquí se reestima
cada año con `arch`.

<aside class="callout" data-kind="aviso">

La comparación de los fundacionales con el resto se ha hecho solo en 101 orígenes por activo, por su coste en CPU. Con
todas las sesiones, el orden entre Chronos, TimesFM y el HAR podría cambiar en el detalle, como ha cambiado el de
LightGBM en el bono. Para una decisión de verdad hay que evaluar todos los días (en GPU o con más tiempo) y en activos
reales.

</aside>

## El coste de cada modelo

```python
tam = {
    "HAR": 4, "GARCH(1,1)": 4,
    "LightGBM (hojas)": int(sum(
        t["num_leaves"] for t in
        ult["indice"].booster_.dump_model()["tree_info"])),
    "Chronos-Bolt small": sum(x.numel() for x in
                              chronos.model.parameters()),
    "TimesFM-2.5": sum(x.numel() for x in tfm.model.parameters()),
}
pd.Series(tam).map("{:,}".format)
```
```output
HAR                             4
GARCH(1,1)                      4
LightGBM (hojas)            2,400
Chronos-Bolt small     47,718,016
TimesFM-2.5           231,289,280
dtype: str
```

*Fig. — Tamaño de cada modelo frente a su QLIKE media relativa al HAR en los tres activos del laboratorio: cincuenta millones de veces más parámetros no compran precisión en una serie cuyo proceso es un HAR.* Cinco puntos en escala logarítmica de parámetros. HAR, 4 parámetros, 1,00 (destacado); GARCH, 4, 1,59; LightGBM, 2 400 hojas, 0,97; Chronos-Bolt, 48 millones, 1,05; TimesFM-2.5, 231 millones, 1,06.

El HAR y el GARCH tienen cuatro parámetros cada uno; LightGBM, 2 400 hojas (300 árboles de 8); Chronos-Bolt small,
47,7 millones de parámetros, y TimesFM-2.5, 231,3 millones. La figura pone el tamaño frente a la QLIKE media relativa al
HAR: cincuenta millones de veces más parámetros no compran precisión en una serie cuyo proceso es un HAR.

El tiempo depende de la máquina y de su carga, así que el curso no lo ejecuta; este es el código para medirlo:

```python skip
import time

t0 = time.perf_counter()
chronos.predict_quantiles(inputs=[torch.tensor(c) for c in ctx],
                          prediction_length=1, quantile_levels=[0.5])
print(f"Chronos, {len(ctx)} previsiones: "
      f"{time.perf_counter() - t0:.1f} s")
t0 = time.perf_counter()
tfm.forecast(horizon=1, inputs=ctx)
print(f"TimesFM, {len(ctx)} previsiones: "
      f"{time.perf_counter() - t0:.1f} s")
```

En la máquina donde se escribió la lección (16 núcleos compartidos con otros procesos, carga ≈ 12, 4 hilos por
proceso), las 24 reestimaciones del HAR (3 activos × 8 años) tardaron 0,02 s; las del GARCH con sus previsiones, 0,9 s;
las de LightGBM, 1,9 s; las 303 previsiones de Chronos-Bolt, 3,1 s, y las de TimesFM-2.5, 35 s (62 s con la máquina
más cargada). La diferencia de fondo no es el entrenamiento sino la **inferencia**: el HAR prevé con un producto escalar,
y TimesFM necesita una pasada de 231 millones de parámetros por cada previsión, unos 0,1-0,2 s en esta CPU. Prever las
2011 sesiones de los tres activos serían unas 6 000 pasadas, del orden de 10-20 minutos aquí; en GPU (`device="cuda"`)
bajaría mucho, pero sigue sin competir con cuatro multiplicaciones.

## Qué hacer con esto

| Situación | Lo razonable |
|---|---|
| Hay RV de calidad y años de historia | Un HAR (o HAR con apalancamiento) es la baseline que hay que batir; un fundacional sin ajuste no la bate aquí |
| Hay información que el HAR no ve (apalancamiento, covariables) | Un modelo con esas features (LightGBM o un HAR ampliado) puede ganar, y se comprueba con todas las sesiones |
| Serie nueva o con poca historia, muchas series a la vez | Un fundacional sin ajuste da una previsión razonable sin estimar nada; es su punto fuerte |
| Hay que elegir entre varios | MCS o Diebold-Mariano con muestra suficiente; con 100 puntos casi nada se distingue |
| Se quiere usar el fundacional en producción | Comparar con el HAR en las mismas fechas, vigilar el coste de inferencia y considerar el ajuste fino (lección 5.8.3) |

Es la misma conclusión que el estudio de Brini (2026) con 50 activos reales, citado en la lección 5.8.4: los modelos
fundacionales no dan una mejora uniforme sobre un Log-HAR bien especificado, y el tamaño o la arquitectura importan
menos que tener una baseline econométrica seria.

## Errores típicos

- **Comparar con baselines débiles.** Frente a la ingenua, los fundacionales parecen excelentes (del 22 al 38 % menos
  de QLIKE); frente al HAR, pierden.
- **Usar la mediana como previsión de la varianza.** Con QLIKE o ECM se prevé la media; la mediana de una distribución
  con cola a la derecha se queda corta, y más con saltos.
- **Fiarse del ECM con pocas observaciones.** En el bono, dos modelos «baten al oráculo» en ECM.
- **Sacar conclusiones de 100 puntos.** Ni el oráculo es significativamente mejor que el HAR; el orden de LightGBM y el
  GARCH se invierte con la muestra completa.
- **Fugas en la evaluación.** Features con el día $t$, parámetros estimados con el año de prueba, normalizaciones con
  toda la serie (lección 5.2.4): el oráculo marca un suelo que ningún modelo honesto cruza.
- **Ignorar la inferencia.** Un modelo de 231 millones de parámetros cuesta en cada previsión, no solo al entrenar.

## Ejercicios

1. Repite la previsión de Chronos-Bolt pasándole la RV en niveles en lugar de su logaritmo (y su mediana como
   previsión). ¿Qué representación le conviene más?

**Solución.**

```python
ctx_n = [rv[a][t - CTX:t].astype("float32") for a in ACT for t in orig]
qn, _ = chronos.predict_quantiles(
    inputs=[torch.tensor(c) for c in ctx_n], prediction_length=1,
    quantile_levels=[0.5])
qn = qn[:, 0, 0].numpy().astype(float)
pd.DataFrame({a: {"niveles": qlike(rv[a][orig],
                                   qn[i * k:(i + 1) * k]).mean(),
                  "log (mediana)": perd[a]["chronos_mediana"].mean(),
                  "log (media)": perd[a]["chronos"].mean()}
              for i, a in enumerate(ACT)}).round(3)
```
```output
               indice   bono  cripto
niveles         0.096  0.061   0.499
log (mediana)   0.085  0.060   0.461
log (media)     0.082  0.059   0.408
```

En niveles, Chronos empeora en los tres activos: 0,096 frente a 0,085 en el índice, 0,061 frente a 0,060 en el bono y
0,499 frente a 0,461 en la cripto, comparando siempre medianas. La RV en niveles es muy asimétrica y con saltos: el
modelo normaliza el contexto con su media y su escala, y unos pocos días extremos lo distorsionan. En logaritmos la
serie es casi gaussiana, y además permite pasar a la media con la corrección lognormal (0,082, 0,059 y 0,408). Es la
misma razón por la que el HAR se estima en logaritmos.

2. La recalibración de Mincer-Zarnowitz regresa la variable sobre la previsión: si la previsión es insesgada, la
   pendiente es 1 y la constante 0. Calcula, para cada activo, la pendiente de la regresión del log de la RV sobre el
   log de la previsión del HAR, LightGBM, Chronos y TimesFM en los orígenes. ¿Qué indica una pendiente menor que 1? ¿Se
   podría usar esa regresión para corregir las previsiones?

**Solución.**

```python
mz = {}
for a in ACT:
    yv = np.log(rv[a][orig])
    mz[a] = {m: np.polyfit(np.log(en_orig(a, m)), yv, 1)[0]
             for m in ["har", "lgbm", "chronos", "timesfm"]}
pd.DataFrame(mz).round(2)
```
```output
         indice  bono  cripto
har        1.07  1.06    1.06
lgbm       1.11  1.11    1.06
chronos    1.01  0.96    0.92
timesfm    1.05  1.01    1.00
```

Las pendientes del HAR y de LightGBM son algo mayores que 1 (1,06-1,11): sus previsiones son un poco **demasiado
suaves**, se mueven menos que la RV. Las de los fundacionales están más cerca de 1 o por debajo: TimesFM entre 1,00 y
1,05, Chronos entre 0,92 (cripto) y 1,01; una pendiente menor que 1 indica que la previsión **reacciona de más**, que sus
variaciones exageran las de la RV, lo que con saltos se paga en QLIKE. Usar la regresión para corregir solo es legítimo
si sus coeficientes se estiman con datos anteriores a cada previsión (por ejemplo, reestimándola cada enero como los
demás modelos); estimada con los mismos orígenes con que se evalúa, sería una fuga. Por eso Brini (2026) la usa como
diagnóstico: separa la parte de la ventaja que es de escala de la que es de dinámica.

3. Sin código: tu equipo usa un HAR para dimensionar posiciones con volatility targeting (lección 4.2.2) en 40 futuros.
   Alguien propone sustituirlo por TimesFM-2.5 «porque es el estado del arte». Con lo que has medido, ¿qué le
   responderías y qué prueba pedirías antes de decidir?

**Solución.**

Que en este laboratorio, con datos cuyo proceso verdadero se conoce, TimesFM sin ajuste queda un 4-8 % por encima del
HAR en QLIKE, sin diferencia significativa con 101 orígenes, y cuesta del orden de una décima de segundo por previsión
frente a microsegundos. «Estado del arte» en bancos de pruebas generales no implica ventaja en volatilidad financiera:
en 50 activos reales, Brini (2026) no encuentra mejora uniforme sobre un Log-HAR. Antes de decidir pediría una
evaluación con origen móvil sin fugas en los 40 futuros reales, todos los días de varios años, con QLIKE, Diebold-
Mariano o MCS frente al HAR (y frente a un HAR con apalancamiento, que es la mejora barata), y una medida del efecto en
lo que importa: el error de la volatilidad realizada de la cartera frente al objetivo y la rotación que induce. Si
TimesFM no entra en el MCS o solo empata, el HAR se queda; si gana, una combinación sencilla de los dos (la media de
previsiones) suele ser más robusta que cambiar uno por otro.

## Autoevaluación

1. ¿Por qué se usa la QLIKE como pérdida principal para comparar previsiones de varianza frente a la varianza realizada?

   - A. Porque es la única pérdida que admite el test de Diebold-Mariano
   - B. Porque premia más las previsiones altas que las bajas, y así es conservadora
   - C. Porque no necesita la varianza realizada, solo los rendimientos
   - D. Porque, como el ECM, ordena bien los modelos aunque la varianza realizada mida la verdadera con error, y depende del cociente RV/h, así que pesa menos que el ECM los pocos días extremos

   *Respuesta D: Porque, como el ECM, ordena bien los modelos aunque la varianza realizada mida la verdadera con error, y depende del cociente RV/h, así que pesa menos que el ECM los pocos días extremos.* Patton (2011) muestra que el ECM y la QLIKE pertenecen a la familia de pérdidas que da el mismo orden con un proxy ruidoso que con la varianza verdadera. El ECM está dominado por los días de varianza extrema: en el bono, LightGBM y el HAR salen con menos ECM que el oráculo en 101 orígenes, algo imposible en esperanza.

2. Chronos-Bolt y TimesFM-2.5 sin reentrenar tienen una QLIKE un 5-6 % mayor que la del HAR en media, con t de Diebold-Mariano entre +0,59 y +1,66, y Chronos entra en el MCS del índice y del bono. ¿Qué conclusión es honesta?

   - A. Que los modelos fundacionales son tan buenos como el HAR y se puede sustituir uno por otro
   - B. Que en las estimaciones puntuales el HAR es mejor en los tres activos, pero con 101 orígenes por activo la diferencia no es significativa; sin entrenar nada, los fundacionales son una baseline razonable, por delante de la ingenua y del GARCH en el índice y el bono
   - C. Que los modelos fundacionales no sirven para la volatilidad
   - D. Que hay que reentrenar los modelos fundacionales con todos los datos de prueba

   *Respuesta B: Que en las estimaciones puntuales el HAR es mejor en los tres activos, pero con 101 orígenes por activo la diferencia no es significativa; sin entrenar nada, los fundacionales son una baseline razonable, por delante de la ingenua y del GARCH en el índice y el bono.* Con 101 observaciones ni el oráculo bate al HAR con significación (t = −1,94 en el índice). La respuesta conocida dice que el HAR es casi el modelo verdadero, así que lo esperable es que gane; que los fundacionales queden cerca sin ajuste es lo que tienen de útil.

3. El modelo fundacional prevé la mediana del logaritmo de la RV. ¿Por qué se usa exp(mediana + s²/2), con s sacado de sus cuantiles 0,1 y 0,9, en vez de exp(mediana)?

   - A. Porque exp(mediana) es la mediana de la RV, no su media; si el log es aproximadamente normal, la media es exp(μ + σ²/2), y la QLIKE y el ECM premian prever la media
   - B. Porque los cuantiles del modelo están en otra escala
   - C. Porque así la previsión es siempre mayor que la RV
   - D. Porque TimesFM lo exige en su configuración

   *Respuesta A: Porque exp(mediana) es la mediana de la RV, no su media; si el log es aproximadamente normal, la media es exp(μ + σ²/2), y la QLIKE y el ECM premian prever la media.* Las dos pérdidas se minimizan en esperanza con la media condicional. Con la corrección, la QLIKE de Chronos en la cripto baja de 0,461 a 0,408; es la misma corrección exp(σ²/2) que lleva el HAR en logaritmos.

4. En los 101 orígenes, LightGBM tiene menos QLIKE que el HAR en el bono (0,054 frente a 0,056); en las 2011 sesiones de 2018-2025 tiene más (0,069 frente a 0,067), con t = +3,07. ¿Qué enseña?

   - A. Que la submuestra tiene una fuga
   - B. Que LightGBM empeora con más datos de prueba
   - C. Que con 101 puntos una diferencia del 4 % es ruido y el orden puede invertirse; con la muestra completa, el HAR, más sencillo y cercano al modelo verdadero, gana
   - D. Que el HAR está sobreajustado al bono

   *Respuesta C: Que con 101 puntos una diferencia del 4 % es ruido y el orden puede invertirse; con la muestra completa, el HAR, más sencillo y cercano al modelo verdadero, gana.* Lo mismo pasa con el GARCH en la cripto (mejor que el HAR en la submuestra, peor con todas las sesiones). Por eso la submuestra se usa aquí solo por el coste de los modelos fundacionales, y las conclusiones sobre los modelos baratos se comprueban con todas las sesiones.

5. ¿Cuál es la explicación más plausible de que LightGBM bata al HAR en el índice con todas las sesiones (t = −2,41) y no en el bono?

   - A. Porque LightGBM tiene más parámetros
   - B. Porque ve la parte negativa del rendimiento de los días anteriores y en el índice el efecto apalancamiento plantado es fuerte (γ = 0,18); en el bono es casi nulo (γ = 0,04) y la flexibilidad solo añade varianza
   - C. Porque el índice no tiene saltos
   - D. Porque el bono tiene un cambio de nivel en 2022 que LightGBM no puede aprender

   *Respuesta B: Porque ve la parte negativa del rendimiento de los días anteriores y en el índice el efecto apalancamiento plantado es fuerte (γ = 0,18); en el bono es casi nulo (γ = 0,04) y la flexibilidad solo añade varianza.* Un HAR-RV solo mira la RV pasada. El README de los datos publica γ por activo: 0,18 en el índice, 0,04 en el bono y 0,06 en la cripto. Una feature con información que el HAR no tiene es lo único que puede dar ventaja a un modelo más flexible (en el modelo del índice, rendimiento de ayer y su parte negativa suman un 7 % de la ganancia); el modelo fundacional univariante, que solo ve la serie de RV, tampoco la tiene.

## Ideas clave

- En 101 orígenes por activo, la QLIKE media frente al HAR es 1,05 para Chronos-Bolt y 1,06 para TimesFM-2.5, sin entrenar nada: mejor que la previsión ingenua (1,50) y que el GARCH (1,59), peor que el HAR y que LightGBM (0,97). El oráculo, el suelo, está en 0,91.
- Un modelo fundacional prevé la mediana del log de la RV; para la QLIKE hay que llevarla a la media con sus propios cuantiles, exp(mediana + s²/2). En la cripto, con saltos, la QLIKE de Chronos pasa de 0,461 a 0,408.
- Con 101 puntos casi nada es significativo: Diebold-Mariano no separa a los fundacionales del HAR (t de +0,59 a +1,66), ni siquiera al oráculo (−1,94 en el índice), y el MCS al 10 % conserva a los seis modelos en la cripto. El MCS propio y el de arch coinciden.
- Con las 2011 sesiones, el HAR gana a LightGBM en el bono (t = +3,07) y a GARCH en la cripto, al revés que en la submuestra; LightGBM gana en el índice (t = −2,41), probablemente por el efecto apalancamiento, que el HAR no ve.
- Coste: el HAR tiene 4 parámetros y se reestima en milisegundos; TimesFM-2.5 tiene 231 millones y tarda del orden de una décima de segundo por previsión en CPU. En una serie con estructura HAR, ese tamaño no compra precisión.

## Recursos

### Documentación

- [arch: comparaciones múltiples (SPA, StepM y MCS)](https://arch.readthedocs.io/en/latest/multiple-comparison/multiple-comparisons.html) · arch.readthedocs.io

### Libros

- [Corsi, F. (2009): A Simple Approximate Long-Memory Model of Realized Volatility. Journal of Financial Econometrics 7(2), 174-196](https://doi.org/10.1093/jjfinec/nbp001) · doi.org
- [Patton, A. J. (2011): Volatility forecast comparison using imperfect volatility proxies. Journal of Econometrics 160(1), 246-256](https://doi.org/10.1016/j.jeconom.2010.03.034) · doi.org
- [Hansen, P. R., Lunde, A. y Nason, J. M. (2011): The Model Confidence Set. Econometrica 79(2), 453-497](https://doi.org/10.3982/ECTA5771) · doi.org
- [Brini, A. (2026): Forecasting Realized Volatility with Time Series Foundation Models: A Comparison with Econometric Benchmarks (arXiv:2607.05291)](https://arxiv.org/abs/2607.05291) · arxiv.org

### Datos

- [vol5_diario.parquet: rendimientos y varianza realizada de seis activos (datos sintéticos)](https://pythonparatrading.com/curso/datos/nivel-5/vol5_diario.parquet) · Descarga · vol5_diario.parquet
- [vol5_verdad.parquet: varianza integrada y su previsión exacta (la respuesta conocida)](https://pythonparatrading.com/curso/datos/nivel-5/vol5_verdad.parquet) · Descarga · vol5_verdad.parquet

Lección anterior: [5.8.4 · Modelos fundacionales en finanzas: qué dice la evidencia](https://pythonparatrading.com/curso-python-trading/nivel-5/evidencia-de-los-modelos-fundacionales-en-finanzas/)

Lección siguiente: [5.9.1 · MDP, Q-learning, PPO y entornos Gymnasium](https://pythonparatrading.com/curso-python-trading/nivel-5/mdp-q-learning-ppo-y-entornos-gymnasium/)
