---
title: "2.1.4 Volatilidad: ARCH, GARCH, EGARCH y GJR"
description: "GARCH(1,1) con t, GJR y EGARCH con la librería arch: verosimilitud, persistencia, vida media, curva de impacto y previsión fuera de muestra con QLIKE."
url: "https://pythonparatrading.com/curso-python-trading/nivel-2/volatilidad-arch-garch-egarch-gjr/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-2/volatilidad-arch-garch-egarch-gjr.png"
---

# 2.1.4 · Modelos de volatilidad: ARCH, GARCH, EGARCH y GJR con la librería arch

> La volatilidad cambia, viene en rachas y sube más tras las caídas. Los modelos GARCH convierten esas regularidades en una previsión de la varianza de mañana. Esta lección ajusta cuatro variantes a un índice sintético cuya volatilidad verdadera se conoce, mide cuánto duran los choques y comprueba fuera de la muestra qué modelo prevé mejor.

- Curso: [Nivel 2: Estrategias sistemáticas y backtesting riguroso](https://pythonparatrading.com/curso-python-trading/nivel-2/)
- Módulo: 2.1 · Estadística y econometría para trading
- Duración: 70 min
- Requisitos: [1.6.1 · Rentabilidades, volatilidad, correlaciones móviles y drawdown](https://pythonparatrading.com/curso-python-trading/nivel-1/volatilidad-correlaciones-y-drawdown/), [1.6.2 · Hechos estilizados: colas gruesas, agrupamiento, autocorrelación y apalancamiento](https://pythonparatrading.com/curso-python-trading/nivel-1/hechos-estilizados-de-los-mercados/), [2.1.1 · Contrastes de hipótesis, p-valores, bootstrap y regresión con errores robustos](https://pythonparatrading.com/curso-python-trading/nivel-2/contrastes-bootstrap-y-errores-robustos/)

## Al terminar esta lección

- Escribir y estimar con `arch` un GARCH(1,1) con innovaciones normales y t de Student, un GJR-GARCH y un EGARCH, y compararlos por verosimilitud, AIC y BIC.
- Calcular la persistencia, la varianza de largo plazo y la vida media de un choque de volatilidad, y explicar por qué los cambios de régimen la inflan.
- Dibujar la curva de impacto de las noticias de cada modelo y relacionarla con el efecto apalancamiento.
- Evaluar previsiones de volatilidad fuera de la muestra con la pérdida QLIKE frente a la EWMA y una ventana móvil.

## De la EWMA al GARCH

La lección 1.6.1 estimó la volatilidad con una media móvil exponencial (EWMA) de los cuadrados de las rentabilidades,
y la 1.6.2 mostró por qué funciona: el tamaño de los movimientos tiene memoria (agrupamiento de volatilidad) y sube más
tras las caídas (efecto apalancamiento). Los modelos de la familia **GARCH** formalizan esas dos ideas y añaden una
tercera que la EWMA no tiene: la volatilidad **vuelve** a un nivel de largo plazo.

Con $\varepsilon_t = r_t - \mu$ la rentabilidad menos su media y $\sigma_t^2$ su varianza condicional (la que se prevé
para $t$ con la información hasta $t-1$):

$$
\sigma_t^2 = \omega + \alpha\,\varepsilon_{t-1}^2 + \beta\,\sigma_{t-1}^2
$$

- $\alpha$: cuánto mueve la noticia de ayer ($\varepsilon_{t-1}^2$) la varianza de hoy.
- $\beta$: cuánta varianza de ayer se arrastra.
- $\alpha + \beta < 1$: **persistencia**. La varianza vuelve a su nivel de largo plazo $\sigma^2_{LP} =
  \omega/(1-\alpha-\beta)$, y la desviación respecto a él se multiplica por $\alpha + \beta$ cada día.

La EWMA con parámetro $\lambda$ es el caso $\omega = 0$, $\alpha = 1-\lambda$, $\beta = \lambda$: persistencia 1, sin
nivel al que volver. Las variantes de esta lección cambian la reacción a las noticias (simétrica o no) y la
distribución de los choques:

```mermaid
flowchart TB
  A[ARCH q] --> G[GARCH 1,1]
  G --> J[GJR: asimétrico]
  G --> E[EGARCH: log var]
  G --> W[EWMA: sin nivel]
  G --> D[Choques N o t]
```

- **ARCH(q)** (Engle, 1982)[^1]: solo los $q$ últimos $\varepsilon^2$. Necesita muchos retardos para tanta memoria;
  GARCH (Bollerslev, 1986) la consigue con uno.
- **GJR-GARCH** (Glosten, Jagannathan y Runkle, 1993): añade $\gamma\,I_{t-1}\,\varepsilon_{t-1}^2$, con $I_{t-1} = 1$
  si $\varepsilon_{t-1} < 0$. Las caídas pesan $\alpha + \gamma$; las subidas, $\alpha$.
- **EGARCH** (Nelson, 1991): modela $\ln\sigma_t^2$, así que no necesita restricciones de signo. En la versión de
  `arch`, con $e_t = \varepsilon_t/\sigma_t$:

$$
\ln\sigma_t^2 = \omega + \alpha\left(|e_{t-1}| - \sqrt{2/\pi}\right) + \gamma\, e_{t-1} + \beta\ln\sigma_{t-1}^2
$$

  Un $\gamma$ negativo hace que las caídas suban más la volatilidad.

## Un índice con volatilidad conocida

Los datos son sintéticos (README del nivel 2). Se construye un índice equiponderado con las 120 acciones ficticias de
`universo2_ajustados.parquet`: la media diaria de las rentabilidades totales de las que cotizan cada sesión, de 2006 a
2025. Su volatilidad la domina el **factor de mercado** del generador, un GJR-GARCH(1,1) con innovaciones t de Student
de 6 grados de libertad y parámetros $\alpha = 0{,}03$, $\gamma = 0{,}11$, $\beta = 0{,}86$
(`curso-code/make_data_nivel2.py`), cuya volatilidad de largo plazo cambia por tramos: del 12 % anual en las épocas
tranquilas al 52 % en la crisis de 2008 y al 80 % en marzo de 2020 (tabla del README). A eso se suman factores
sectoriales e idiosincráticos con su propio GARCH, que la diversificación atenúa. La respuesta conocida es, por tanto,
aproximada: la de un factor que pesa mucho, no la de toda la serie.

```python
import numpy as np
import pandas as pd
from statsmodels.stats.diagnostic import het_arch

aj = pd.read_parquet("universo2_ajustados.parquet")
px = aj.pivot(index="fecha", columns="ticker",
              values="cierre_ajustado")
r = 100 * px.pct_change(fill_method=None).mean(axis=1).dropna()

print(f"{len(r)} sesiones, de {r.index[0]:%Y-%m-%d} "
      f"a {r.index[-1]:%Y-%m-%d}")
print(f"volatilidad anual: {r.std() * np.sqrt(252):.1f} %; "
      f"curtosis en exceso: {r.kurt():.1f}")
lm = het_arch(r - r.mean(), nlags=5, result_object=True)
print(f"ACF de r² en el retardo 1: {(r**2).autocorr(1):.3f}; "
      f"contraste ARCH-LM (5 retardos): {lm.lm:.0f}, "
      f"p = {lm.lmpval:.1e}")
```
```output
5030 sesiones, de 2006-01-04 a 2025-12-31
volatilidad anual: 20.1 %; curtosis en exceso: 10.8
ACF de r² en el retardo 1: 0.160; contraste ARCH-LM (5 retardos): 688, p = 2.1e-146
```

Volatilidad anual del 20 % y una curtosis de 10,8: colas gruesas. La autocorrelación de $r^2$ en el retardo 1 es de 0,16
y el contraste ARCH-LM de Engle (regresión de $\varepsilon_t^2$ sobre sus cinco retardos) rechaza con un p-valor del
orden de $10^{-146}$: la varianza de hoy depende de la de ayer. `result_object=True` pide un resultado con nombres
(`lm`, `lmpval`); sin él, statsmodels avisa con un `FutureWarning` de que la tupla actual cambiará. Es el punto de partida de cualquier GARCH.

## Cuatro modelos con arch

`arch_model` separa la media (`mean="Constant"`), la volatilidad (`vol="GARCH"` con `p` retardos de $\varepsilon^2$,
`o` asimétricos y `q` de $\sigma^2$; o `vol="EGARCH"`) y la distribución (`dist="normal"` o `"t"`). Se estima por máxima
verosimilitud. Conviene trabajar en porcentaje: con rentabilidades en tanto por uno, $\omega$ es del orden de
$10^{-6}$ y el optimizador sufre (la librería avisa).

```python
from arch import arch_model

especs = {
    "GARCH-N": dict(vol="GARCH", p=1, q=1, dist="normal"),
    "GARCH-t": dict(vol="GARCH", p=1, q=1, dist="t"),
    "GJR-t": dict(vol="GARCH", p=1, o=1, q=1, dist="t"),
    "EGARCH-t": dict(vol="EGARCH", p=1, o=1, q=1, dist="t"),
}
ajustes = {k: arch_model(r, mean="Constant", **v).fit(disp="off")
           for k, v in especs.items()}
tabla = pd.DataFrame({k: f.params for k, f in ajustes.items()}).T
tabla["logL"] = [f.loglikelihood for f in ajustes.values()]
tabla["AIC"] = [f.aic for f in ajustes.values()]
tabla["BIC"] = [f.bic for f in ajustes.values()]
cols = ["omega", "alpha[1]", "gamma[1]", "beta[1]", "nu", "logL",
        "BIC"]
tabla[cols].round(3)
```
```output
          omega  alpha[1]  gamma[1]  beta[1]     nu      logL        BIC
GARCH-N   0.039     0.100       NaN    0.871    NaN -7322.123  14678.339
GARCH-t   0.035     0.094       NaN    0.880  5.622 -7137.193  14317.001
GJR-t     0.032     0.033     0.100    0.892  5.888 -7110.901  14272.941
EGARCH-t  0.010     0.174    -0.068    0.979  5.759 -7122.623  14296.385
```

Tres lecturas de la tabla:

- **La t de Student importa más que cualquier otra cosa.** Pasar de innovaciones normales a t mejora la
  log-verosimilitud en 185 puntos con un solo parámetro más ($\nu = 5{,}6$ grados de libertad). Incluso con la
  volatilidad cambiante, los choques estandarizados tienen colas gruesas: la conclusión de la lección 1.6.2, ahora
  dentro del modelo.
- **La asimetría es clara.** El GJR mejora al GARCH-t en 26 puntos de log-verosimilitud; su $\gamma = 0{,}100$ está
  cerca del 0,11 del factor de mercado, y su $\alpha = 0{,}033$, del 0,03. El EGARCH, con $\gamma = -0{,}068$, también
  la recoge, aunque ajusta peor que el GJR (BIC de 14.296 frente a 14.273).
- **$\beta$ y la persistencia salen más altas que en el generador.** Se ve en la sección siguiente.

Un contraste de razón de verosimilitudes formaliza la asimetría: el GARCH-t es el GJR-t con $\gamma = 0$, así que
$2(\ln L_{GJR} - \ln L_{GARCH})$ se reparte como una $\chi^2$ con un grado de libertad si la nula es cierta.

```python
from scipy import stats

lr = 2 * (ajustes["GJR-t"].loglikelihood
          - ajustes["GARCH-t"].loglikelihood)
print(f"LR = {lr:.1f}; p = {stats.chi2.sf(lr, 1):.1e}")
```
```output
LR = 52.6; p = 4.1e-13
```

## Persistencia y vida media

Si hoy la varianza está por encima de su nivel de largo plazo, la previsión a $h$ días es

$$
\mathrm{E}_t\left[\sigma_{t+h}^2\right] = \sigma^2_{LP} + P^{\,h}\left(\sigma_t^2 - \sigma^2_{LP}\right)
$$

con $P$ la persistencia: $\alpha + \beta$ en el GARCH, $\alpha + \beta + \gamma/2$ en el GJR (con choques simétricos,
la mitad son negativos) y $\beta$ en el EGARCH (para el logaritmo de la varianza). La **vida media** de un choque es el
$h$ que deja la desviación en la mitad: $\ln 0{,}5 / \ln P$.

```python
def persistencia(f):
    p = f.params
    if "EGARCH" in f.model.volatility.name:
        return p["beta[1]"]
    return p["alpha[1]"] + p["beta[1]"] + p.get("gamma[1]", 0) / 2

filas = {}
for k, f in ajustes.items():
    P = persistencia(f)
    lp = (np.nan if "EGARCH" in k
          else np.sqrt(252 * f.params["omega"] / (1 - P)))
    filas[k] = {"persistencia": P, "vol_LP_%": lp,
                "vida_media": np.log(0.5) / np.log(P)}
filas["mercado (generador)"] = {
    "persistencia": 0.03 + 0.86 + 0.11 / 2, "vol_LP_%": np.nan,
    "vida_media": np.log(0.5) / np.log(0.945)}
pd.DataFrame(filas).T.round(3)
```
```output
                     persistencia  vol_LP_%  vida_media
GARCH-N                     0.971    18.420      23.289
GARCH-t                     0.974    18.404      26.563
GJR-t                       0.976    18.224      28.090
EGARCH-t                    0.979       NaN      32.829
mercado (generador)         0.945       NaN      12.253
```

El GJR estimado tiene una persistencia de 0,976 y una vida media de 28 sesiones; el factor de mercado del generador,
0,945 y 12 sesiones. El modelo no se equivoca por mala estimación: la serie tiene **dos** fuentes de persistencia y el
GARCH solo tiene un parámetro para las dos. Una es la dinámica de los choques (la del generador). La otra son los
**regímenes**: la volatilidad de largo plazo del guion cambia por tramos y se mantiene meses o años, y un GARCH con un
único $\sigma^2_{LP}$ la interpreta como choques que tardan mucho en disiparse. Es el resultado de Lamoureux y Lastrapes
(1990): los cambios estructurales en la varianza incondicional inflan la persistencia estimada.

Se comprueba estimando solo en tramos donde el guion mantiene constante la volatilidad de largo plazo (12 % anual):

```python
tramos = {"2012-06 a 2015-08": ("2012-06-01", "2015-08-14"),
          "2016-02 a 2018-09": ("2016-02-12", "2018-09-28"),
          "2006-2025 completo": ("2006-01-01", "2025-12-31")}
filas = {}
for k, (a, b) in tramos.items():
    f = arch_model(r[a:b], mean="Constant", **especs["GJR-t"]).fit(
        disp="off")
    P = persistencia(f)
    filas[k] = {"n": len(r[a:b]), "alpha": f.params["alpha[1]"],
                "gamma": f.params["gamma[1]"],
                "beta": f.params["beta[1]"], "persistencia": P,
                "vida_media": np.log(0.5) / np.log(P)}
pd.DataFrame(filas).T.round(3)
```
```output
                         n  alpha  gamma   beta  persistencia  vida_media
2012-06 a 2015-08    806.0  0.009  0.069  0.920         0.964      18.964
2016-02 a 2018-09    663.0  0.000  0.153  0.882         0.958      16.261
2006-2025 completo  5030.0  0.033  0.100  0.892         0.976      28.090
```

En los dos tramos tranquilos, la persistencia baja a 0,964 y 0,958 (vidas medias de 19 y 16 sesiones), más cerca del
0,945 del generador. Con 663 y 806 sesiones, los parámetros sueltos son ruidosos ($\alpha$ llega a cero en uno de
ellos, y $\gamma$ va de 0,07 a 0,15), pero la dirección es clara. En datos reales no se conocen los tramos; por eso
existen modelos con un componente lento de la volatilidad (por ejemplo, de dos componentes o con cambio de régimen,
lección 2.1.7).

La figura siguiente compara la volatilidad condicional del GJR, submuestreada cada 10 sesiones, con la volatilidad de
largo plazo del factor de mercado en cada tramo del guion:

*Fig. — Volatilidad condicional anual del GJR-t en el índice equiponderado sintético (cada 10 sesiones) frente a la volatilidad de largo plazo del factor de mercado en cada tramo del guion del generador, 2006-2025.* Una línea quebrada de volatilidad condicional y un escalón con la volatilidad de largo plazo del guion. El escalón pasa del 12 % en las épocas tranquilas al 52 % en la crisis de 2008, al 36 % en 2011 y al 80 % en marzo de 2020, el tramo destacado. La volatilidad condicional sigue los escalones con retraso y con picos propios: llega al 90 % en marzo de 2020 y baja despacio después.

## Curva de impacto de las noticias

La **curva de impacto de las noticias** (*news impact curve*, Engle y Ng, 1993) responde a una pregunta concreta: si hoy
la varianza está en su nivel de largo plazo y mañana el índice se mueve un $x$ %, ¿qué volatilidad prevé el modelo para
pasado mañana? Es la forma más directa de ver la asimetría, y es la figura de la cabecera.

```python
def impacto(f, eps):
    p = f.params
    if "EGARCH" in f.model.volatility.name:
        s2 = np.exp(p["omega"] / (1 - p["beta[1]"]))  # nivel medio
        e = eps / np.sqrt(s2)
        ln = (p["omega"]
              + p["alpha[1]"] * (np.abs(e) - np.sqrt(2 / np.pi))
              + p["gamma[1]"] * e + p["beta[1]"] * np.log(s2))
        return np.sqrt(252 * np.exp(ln))
    P = persistencia(f)
    s2 = p["omega"] / (1 - P)
    a = p["alpha[1]"] + p.get("gamma[1]", 0) * (eps < 0)
    return np.sqrt(252 * (p["omega"] + a * eps**2 + p["beta[1]"] * s2))

eps = np.array([-3.0, 0.0, 3.0])
pd.DataFrame({k: impacto(ajustes[k], eps)
              for k in ["GARCH-t", "GJR-t", "EGARCH-t"]},
             index=["caída 3 %", "sin noticia", "subida 3 %"]).round(1)
```
```output
             GARCH-t  GJR-t  EGARCH-t
caída 3 %       22.8   24.6      25.1
sin noticia     17.5   17.4      18.9
subida 3 %      22.8   19.5      21.4
```

En el GARCH-t, una caída y una subida del 3 % llevan la volatilidad al mismo sitio (22,8 %): la curva es una parábola
simétrica. En el GJR, la caída la lleva al 24,6 % y la subida al 19,5 %, cerca de un día sin noticias (17,4 %): con
$\alpha = 0{,}033$, las subidas apenas mueven la varianza. El EGARCH es asimétrico de la misma forma (25,1 % y
21,4 %), con una curva que crece de forma exponencial y no cuadrática en los extremos. La lección 1.6.2
encontró la misma asimetría sin modelo (el cuadrado estandarizado de mañana valía 1,84 tras una caída y 1,28 tras una
subida); aquí está cuantificada en un parámetro.

## Prever la volatilidad fuera de la muestra

Un modelo de volatilidad se usa para prever, así que se evalúa previendo. Se estiman los modelos con 2006-2018 y se
prevé la varianza de cada sesión de 2019-2025 con los parámetros fijos (`fit(last_obs=…)` y `forecast(start=…)`, que
usa en cada fecha solo la información hasta el día anterior). Las referencias son la EWMA de la lección 1.6.1
($\lambda = 0{,}94$) y la varianza de las 63 sesiones anteriores.

El dato real es $\varepsilon_t^2$, un indicador muy ruidoso de la varianza de ese día. Con un indicador ruidoso, no
todas las pérdidas ordenan los modelos igual que lo haría la varianza verdadera; Patton (2011) deriva cuáles sí, y
**QLIKE** es una de ellas:

$$
L(\varepsilon_t^2, h_t) = \frac{\varepsilon_t^2}{h_t} + \ln h_t
$$

con $h_t$ la varianza prevista (salvo una constante que no depende del modelo). Depende del cociente
$\varepsilon_t^2/h_t$, así que los días de crisis, con varianzas decenas de veces mayores, no dominan la media como en
el error cuadrático. Y castiga más quedarse corto que pasarse: prever poca volatilidad el día antes de un movimiento
grande cuesta caro, que es lo que importa para el riesgo.

```python
import statsmodels.api as sm

corte = "2019-01-01"
prev = {}
for k, v in especs.items():
    f = arch_model(r, mean="Constant", **v).fit(last_obs=corte,
                                                disp="off")
    prev[k] = f.forecast(start=corte, reindex=False).variance["h.1"]
prev = pd.DataFrame(prev)
mu = r[:"2018"].mean()
e2 = (r - mu) ** 2
prev["EWMA"] = e2.ewm(alpha=0.06, adjust=False).mean().shift(1)
prev["63 sesiones"] = e2.rolling(63).mean().shift(1)
real = e2[corte:]
prev = prev.reindex(real.index)

qlike = prev.apply(lambda h: real / h + np.log(h))
ecm = prev.apply(lambda h: (real - h) ** 2)
print(pd.DataFrame({"QLIKE": qlike.mean(), "ECM": ecm.mean()}).round(3))

def dm(a, b):  # Diebold-Mariano con Newey-West (lección 2.1.3)
    d = (qlike[a] - qlike[b]).to_numpy()
    f = sm.OLS(d, np.ones(len(d))).fit(cov_type="HAC",
                                       cov_kwds={"maxlags": 10})
    return f"t = {f.tvalues[0]:.2f}"

print("GARCH-t frente a EWMA:", dm("GARCH-t", "EWMA"))
print("GJR-t frente a GARCH-t:", dm("GJR-t", "GARCH-t"))
```
```output
             QLIKE     ECM
GARCH-N      0.849  16.496
GARCH-t      0.859  16.943
GJR-t        0.914  17.332
EGARCH-t     0.928  17.349
EWMA         1.100  22.017
63 sesiones  1.150  23.781
GARCH-t frente a EWMA: t = -6.21
GJR-t frente a GARCH-t: t = 4.34
```

*Fig. — Pérdida QLIKE media fuera de la muestra (2019-2025) de seis previsiones de la varianza diaria del índice equiponderado sintético; menor es mejor.* Barras horizontales: GARCH con normal 0,849 (la mejor, destacada), GARCH-t 0,859, GJR-t 0,914, EGARCH-t 0,928, EWMA 1,100 y varianza de 63 sesiones 1,150. Los cuatro GARCH quedan muy por debajo de la EWMA y de la ventana móvil.

Los cuatro GARCH baten con claridad a la EWMA y a la ventana de 63 sesiones: la pérdida QLIKE baja de 1,10 a 0,86 con
el GARCH-t, y el contraste de Diebold-Mariano da un t de −6,21. La diferencia está en el nivel de largo plazo: tras una
crisis, la EWMA tarda en bajar y antes de otra no tiene a qué volver.

El mejor en QLIKE es, de hecho, el GARCH con innovaciones normales (0,849): para prever la varianza de mañana, la
distribución de los choques importa poco; importa para la verosimilitud, el VaR y las colas.

El resultado incómodo es el otro: fuera de la muestra, el GJR prevé **peor** que el GARCH simétrico (t = 4,3), aunque
dentro de la muestra lo mejoraba en 26 puntos. ¿Dónde pierde?

```python
dif = (qlike["GJR-t"] - qlike["GARCH-t"]).sort_values()
peores = dif.index[-4:]
pd.DataFrame({
    "rent_%": r[peores],
    "vol_GARCH_%": np.sqrt(252 * prev.loc[peores, "GARCH-t"]),
    "vol_GJR_%": np.sqrt(252 * prev.loc[peores, "GJR-t"]),
    "dif_QLIKE": dif[peores]}).round(2)
```
```output
            rent_%  vol_GARCH_%  vol_GJR_%  dif_QLIKE
fecha
2025-02-04    2.95        23.75      14.92       4.95
2021-10-11    3.50        22.41      16.09       5.04
2019-09-04    3.24        20.47      12.57       9.32
2025-01-24    4.41        25.59      15.31      12.27
```

Son cuatro subidas grandes (del 2,95 % al 4,41 % en un día) para las que el GJR preveía una volatilidad anual del 13 %
al 16 % y el GARCH, del 20 % al 26 %: tras rachas alcistas, el GJR, que casi no sube la varianza con las subidas, espera
muy poco riesgo. QLIKE castiga mucho ese error. En el índice del curso, la
asimetría es real (está plantada y el contraste la detecta), pero no se traduce en mejores previsiones a un día; en los
futuros de índices del nivel 2 (IDA, IDB, IDC), GARCH y GJR empatan fuera de la muestra. Ganar dentro de la muestra
no basta: la comparación que cuenta es la de fuera, con una pérdida adecuada.

### Cuánto dura una crisis de volatilidad

La previsión a varios días sale de la fórmula de la persistencia, y `forecast(horizon=…)` la calcula. Desde el día de
mayor volatilidad condicional de 2020:

```python
gjr = ajustes["GJR-t"]
cv = gjr.conditional_volatility
pico = cv["2020"].idxmax()
f = gjr.forecast(start=pico, horizon=120, reindex=False)
v = f.variance.iloc[0]
p = gjr.params
lp = p["omega"] / (1 - persistencia(gjr))
mitad = int(np.argmax((v - lp) / (v.iloc[0] - lp) <= 0.5)) + 1
print(f"pico: {pico:%Y-%m-%d}, vol prevista a 1 día "
      f"{np.sqrt(252 * v.iloc[0]):.1f} %; largo plazo "
      f"{np.sqrt(252 * lp):.1f} %")
print(f"la desviación se reduce a la mitad en {mitad} sesiones")
np.sqrt(252 * v.iloc[[0, 9, 27, 59, 119]]).round(1)
```
```output
pico: 2020-03-26, vol prevista a 1 día 86.8 %; largo plazo 18.2 %
la desviación se reduce a la mitad en 30 sesiones
h.001    86.8
h.010    78.1
h.028    63.5
h.060    44.8
h.120    26.7
Name: 2020-03-26 00:00:00, dtype: float64
```

*Fig. — Volatilidad anual prevista por el GJR-t desde el pico del 26 de marzo de 2020 para cada uno de los 120 días siguientes, frente a su nivel de largo plazo (índice equiponderado sintético).* Curva decreciente con el horizonte de 1 a 120 días: empieza en el 86,8 % de volatilidad anual, pasa por el 62 % a 30 días (destacado: la desviación de la varianza respecto al largo plazo se ha reducido a la mitad), el 44,8 % a 60 y el 26,7 % a 120, acercándose a una línea discontinua del 18,2 %, la volatilidad de largo plazo.

El 26 de marzo de 2020, el GJR prevé una volatilidad anual del 87 % para el día siguiente, y la previsión baja hacia el
18 % de largo plazo con una vida media de 30 sesiones: un 63 % a 28 sesiones vista, un 27 % a 120. Para un sistema,
esa curva dice cuánto tiempo tendrá la posición reducida un *sizing* por volatilidad, y cuánto riesgo se espera en
el horizonte de la operación, no solo mañana.

## La volatilidad en un sistema

| Uso | Qué aporta un GARCH | Precaución |
|---|---|---|
| *Sizing* por volatilidad (lección 1.6.1) | Previsión con vuelta a la media: no se queda bajo tras una crisis | Reestimar con ventana larga; los parámetros cambian poco |
| VaR y stops | Volatilidad de mañana y distribución t con su ν | Colas: comprobar con los peores días reales (lección 2.5.7) |
| Horizonte de varios días | Estructura temporal de la previsión | La persistencia incluye los regímenes: vida media inflada |
| Datos sintéticos para estrés | Simular series con agrupamiento y asimetría (lección 2.5.7) | Los parámetros de un tramo no valen para otro |
| Opciones | Comparar la volatilidad prevista con la implícita (nivel 3) | GARCH es una previsión física, no neutral al riesgo |

```mermaid
flowchart LR
  R[Rentabilidades] --> M[GARCH t]
  M --> S[Vol. de mañana]
  S --> Z[Tamaño = obj./vol]
```

Dos errores frecuentes. El primero, estimar el GARCH con toda la muestra y usar su volatilidad condicional en el
backtest del sizing: los parámetros conocen el futuro (lección 2.5.1); hay que estimarlos con datos anteriores, como
en la sección de previsión. El segundo, aplicarlo a una acción con saltos en los resultados: el salto del día de la
presentación es predecible en fecha y el GARCH lo trata como un choque que eleva la volatilidad durante semanas
(ejercicio 3).

## Ejercicios

1. El ETF ficticio `KAPA` (`indice2_5min.parquet`) tiene una volatilidad diaria GJR-GARCH-t con $\alpha = 0{,}03$,
   $\gamma = 0{,}10$, $\beta = 0{,}86$ y $\nu = 6$ (README del nivel 2 y generador), pero solo cuatro años de datos.
   Construye sus rentabilidades diarias de cierre a cierre, ajusta un GJR-t y compara los parámetros con la verdad
   usando sus errores típicos.

**Solución.**

```python
k = pd.read_parquet("indice2_5min.parquet")
sesion = k["ts"].dt.tz_convert("America/New_York").dt.date
cierre = k.groupby(sesion)["cierre"].last()
rk = 100 * np.log(cierre).diff().dropna()
fk = arch_model(rk, mean="Constant", **especs["GJR-t"]).fit(
    disp="off")
pd.DataFrame({"estimado": fk.params, "error": fk.std_err,
              "verdad": [np.nan, np.nan, 0.03, 0.10, 0.86, 6]}
             ).round(3)
```
```output
          estimado  error  verdad
mu           0.078  0.032     NaN
omega        0.115  0.054     NaN
alpha[1]     0.052  0.034    0.03
gamma[1]     0.030  0.037    0.10
beta[1]      0.832  0.063    0.86
nu           9.130  2.895    6.00
```

Con 1.002 sesiones, los errores típicos son del tamaño de los propios parámetros: $\gamma$ sale en 0,030 con un error
de 0,037, así que el intervalo del 95 % va de −0,04 a 0,10 y no descarta ni la simetría ni la verdad. $\nu$ sale en 9,1
con un error de 2,9. Un GARCH necesita muchos años para estimar bien la asimetría y las colas; con series cortas, es
mejor fijar parámetros razonables o estimarlos con un activo parecido de historia larga.

2. Escala el índice para una volatilidad objetivo del 10 % anual con la volatilidad condicional del GJR y con la EWMA,
   $w_t = 10 / \hat\sigma_t$, y mide la volatilidad realizada de la serie escalada en conjunto y año a año. ¿Cuál se
   acerca más al objetivo?

**Solución.**

```python
s2 = {"GJR": ajustes["GJR-t"].conditional_volatility ** 2,
      "EWMA": ((r - r.mean()) ** 2).ewm(alpha=0.06,
                                        adjust=False).mean().shift(1)}
for nombre, v in s2.items():
    esc = (10 / np.sqrt(252 * v) * r).dropna()
    anual = esc.groupby(esc.index.year).std() * np.sqrt(252)
    print(f"{nombre:5}: total {esc.std() * np.sqrt(252):.2f} %; "
          f"años de {anual.min():.1f} a {anual.max():.1f} %")
bruta = r.groupby(r.index.year).std() * np.sqrt(252)
print(f"sin escalar: años de {bruta.min():.1f} a {bruta.max():.1f} %")
```
```output
GJR  : total 10.04 %; años de 8.6 a 11.5 %
EWMA : total 10.69 %; años de 9.8 a 12.8 %
sin escalar: años de 11.0 a 41.0 %
```

Las dos estabilizan mucho el riesgo: sin escalar, la volatilidad anual del índice va del 11 % al 41 %; escalada, del
8,6 % al 11,5 % con el GJR y del 9,8 % al 12,8 % con la EWMA. El GJR acierta más el nivel (10,04 % frente a 10,69 %),
porque la EWMA se queda corta en los arranques de las crisis. Ojo: aquí el GJR está estimado con toda la muestra, así
que su ventaja está algo inflada; en un backtest de verdad hay que estimarlo solo con el pasado.

3. En `universo2` cada acción tiene un salto el día de reacción a sus resultados (README del nivel 2). ¿Qué hace un
   GARCH estimado con las rentabilidades de una acción el día después de ese salto, y cómo lo corregirías?

**Solución.**

El salto entra como un $\varepsilon^2$ enorme y el GARCH, que no sabe que era un día de resultados, eleva la varianza
prevista para los días siguientes y la deja bajar con su vida media: sobrestima el riesgo durante semanas y un sizing
por volatilidad reduce la posición sin motivo. Además, los saltos inflan $\alpha$ y las colas de la t. Las correcciones
habituales son tres: añadir a la ecuación de la varianza una variable que valga 1 el día de resultados (`arch_model`
admite regresores `x` en la media, no en la varianza, así que hay que programar esa extensión), excluir esos días de la
estimación y tratar su riesgo por separado (lección 2.3.5), o estimar el GARCH sobre el índice o el sector, sin saltos
idiosincráticos, y sumar el riesgo del evento solo cuando toca.

[^1]: R. F. Engle, «Autoregressive Conditional Heteroscedasticity with Estimates of the Variance of United Kingdom
    Inflation», *Econometrica* 50(4), 1982, 987-1007, <https://doi.org/10.2307/1912773>; D. B. Nelson, «Conditional
    Heteroskedasticity in Asset Returns: A New Approach», *Econometrica* 59(2), 1991, 347-370,
    <https://doi.org/10.2307/2938260>; R. F. Engle y V. K. Ng, «Measuring and Testing the Impact of News on
    Volatility», *Journal of Finance* 48(5), 1993, 1749-1778, <https://doi.org/10.1111/j.1540-6261.1993.tb05127.x>.

## Autoevaluación

1. Un GARCH(1,1) tiene α = 0,10 y β = 0,88. ¿Cuál es la vida media de un choque de volatilidad?

   - A. Unas 2 sesiones
   - B. Unas 34 sesiones: la desviación de la varianza respecto a su nivel de largo plazo se multiplica por α + β = 0,98 cada día, y ln 0,5 / ln 0,98 ≈ 34
   - C. Unas 98 sesiones
   - D. Infinita

   *Respuesta B: Unas 34 sesiones: la desviación de la varianza respecto a su nivel de largo plazo se multiplica por α + β = 0,98 cada día, y ln 0,5 / ln 0,98 ≈ 34.* La previsión a h días de la varianza es la de largo plazo más (α + β) elevado a h por la desviación de hoy respecto a ella. La vida media es el h que deja la desviación en la mitad. Con persistencia 0,98, 34 sesiones; con la verdadera del factor de mercado de los datos (0,945), 12.

2. El GJR estimado en el índice equiponderado tiene una persistencia de 0,976, pero el factor de mercado del generador tiene 0,945. ¿Qué explica la diferencia?

   - A. Un error de la librería arch
   - B. Que la volatilidad de largo plazo del generador cambia por tramos (calma, crisis): un GARCH con un solo nivel de largo plazo interpreta esos cambios de régimen como choques muy persistentes
   - C. Que el índice tiene más acciones
   - D. Que la t de Student tiene colas gruesas

   *Respuesta B: Que la volatilidad de largo plazo del generador cambia por tramos (calma, crisis): un GARCH con un solo nivel de largo plazo interpreta esos cambios de régimen como choques muy persistentes.* Es un resultado conocido (Lamoureux y Lastrapes, 1990). En un tramo tranquilo con volatilidad de largo plazo constante, la persistencia estimada baja a 0,96. Una vida media de 28 sesiones mezcla la dinámica de los choques con la de los regímenes.

3. En la curva de impacto del GJR, una caída del 3 % lleva la volatilidad anual de mañana al 24,6 % y una subida del 3 %, al 19,5 %. ¿Qué parámetro produce la diferencia?

   - A. β
   - B. γ: el término que solo actúa cuando el choque es negativo
   - C. ω
   - D. Los grados de libertad de la t

   *Respuesta B: γ: el término que solo actúa cuando el choque es negativo.* En el GJR, el efecto de ε² sobre la varianza de mañana es α si ε > 0 y α + γ si ε < 0. Con α = 0,033 y γ = 0,100, una caída pesa cuatro veces más que una subida del mismo tamaño: es el efecto apalancamiento que la lección 1.6.2 midió sin modelo.

4. ¿Por qué se usa la pérdida QLIKE para comparar previsiones de varianza en lugar del error cuadrático?

   - A. Porque es más fácil de calcular
   - B. Porque el dato real (r²) es un indicador muy ruidoso de la varianza: QLIKE ordena los modelos igual que si se conociera la varianza verdadera y, al depender del cociente r²/h, no deja que unos pocos días de crisis dominen la media
   - C. Porque siempre favorece al GARCH
   - D. Porque no necesita la rentabilidad

   *Respuesta B: Porque el dato real (r²) es un indicador muy ruidoso de la varianza: QLIKE ordena los modelos igual que si se conociera la varianza verdadera y, al depender del cociente r²/h, no deja que unos pocos días de crisis dominen la media.* Patton (2011) deriva qué pérdidas ordenan los modelos igual que con la varianza verdadera aunque el indicador sea ruidoso; QLIKE es una de ellas. Como depende de r²/h, un día con una varianza cien veces mayor no pesa cien veces más, y castiga más quedarse corto que pasarse.

5. Dentro de la muestra, el GJR mejora la log-verosimilitud del GARCH en 26 puntos; fuera de la muestra (2019-2025), su QLIKE es peor. ¿Qué conclusión es correcta?

   - A. Que el GJR está mal programado
   - B. Que el mejor ajuste dentro de la muestra no garantiza mejor previsión: en este índice, el GJR prevé muy poca volatilidad tras rachas alcistas y lo pagan los días de movimientos grandes inesperados. La comparación honesta es fuera de la muestra
   - C. Que el efecto apalancamiento no existe en estos datos
   - D. Que hay que usar la EWMA

   *Respuesta B: Que el mejor ajuste dentro de la muestra no garantiza mejor previsión: en este índice, el GJR prevé muy poca volatilidad tras rachas alcistas y lo pagan los días de movimientos grandes inesperados. La comparación honesta es fuera de la muestra.* La asimetría es real (está plantada y el contraste de razón de verosimilitudes la detecta), pero prever es otra tarea. En los futuros de índices del curso, GARCH y GJR empatan fuera de la muestra; los dos baten con claridad a la EWMA y a la ventana de 63 sesiones.

## Ideas clave

- GARCH(1,1): varianza de hoy = ω + α · (choque de ayer)² + β · (varianza de ayer). α mide la reacción a la noticia; β, la memoria; α + β, la persistencia.
- La EWMA de la lección 1.6.1 es un GARCH sin ω ni nivel de largo plazo: no vuelve a ninguna media. El GARCH sí, a ω/(1 − α − β).
- La t de Student mejora la log-verosimilitud en 185 puntos frente a la normal: las colas gruesas no desaparecen al estandarizar.
- GJR y EGARCH recogen el efecto apalancamiento; en el índice del curso, γ = 0,100 frente al 0,11 del generador.
- La persistencia estimada (0,976, vida media de 28 sesiones) está inflada por los cambios de régimen de la volatilidad de largo plazo; en un tramo tranquilo baja a 0,96.
- Fuera de la muestra, los GARCH reducen la pérdida QLIKE frente a la EWMA y a la ventana móvil; la asimetría no siempre ayuda a prever.

## Recursos

### Research

- [Estimadores de Volatilidad](https://pythonparatrading.com/estimadores-de-volatilidad-para-trading/) · Research-0002 · 4 min

### Documentación

- [arch: modelos de volatilidad univariantes (documentación)](https://arch.readthedocs.io/en/latest/univariate/introduction.html) · arch.readthedocs.io

### Libros

- [Bollerslev, T. (1986): Generalized autoregressive conditional heteroskedasticity. Journal of Econometrics 31(3), 307-327](https://doi.org/10.1016/0304-4076(86)90063-1) · doi.org
- [Glosten, L. R., Jagannathan, R. y Runkle, D. E. (1993): On the Relation between the Expected Value and the Volatility of the Nominal Excess Return on Stocks. Journal of Finance 48(5), 1779-1801](https://doi.org/10.1111/j.1540-6261.1993.tb05128.x) · doi.org
- [Lamoureux, C. G. y Lastrapes, W. D. (1990): Persistence in Variance, Structural Change, and the GARCH Model. Journal of Business & Economic Statistics 8(2), 225-234](https://doi.org/10.1080/07350015.1990.10509794) · 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

### Datos

- [Datos del nivel 2: precios ajustados del universo sintético de 120 acciones (Parquet)](https://pythonparatrading.com/curso/datos/nivel-2/universo2_ajustados.parquet) · Descarga · universo2_ajustados.parquet

Lección anterior: [2.1.3 · Modelos ARMA y ARIMA: autocorrelación simple (ACF) y parcial (PACF)](https://pythonparatrading.com/curso-python-trading/nivel-2/modelos-arma-arima-acf-y-pacf/)

Lección siguiente: [2.1.5 · Cointegración: Engle-Granger, Johansen y vida media de la reversión](https://pythonparatrading.com/curso-python-trading/nivel-2/cointegracion-engle-granger-johansen-y-half-life/)
