Lección 2.1.4 · 70 min
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.
- Módulo
- 2.1 · Estadística y econometría para trading
- Duración
- 70 min
Lecciones del nivel 2
2.1 · Estadística y econometría para trading
- 2.1.1 Contrastes de hipótesis, p-valores, bootstrap y regresión con errores robustos
- 2.1.2 Estacionariedad: ADF, KPSS, diferenciación y diferenciación fraccional
- 2.1.3 Modelos ARMA y ARIMA: autocorrelación simple (ACF) y parcial (PACF)
- 2.1.4 Modelos de volatilidad: ARCH, GARCH, EGARCH y GJR con la librería arch
- 2.1.5 Cointegración: Engle-Granger, Johansen y vida media de la reversión
- 2.1.6 Filtro de Kalman para hedge ratios dinámicos
- 2.1.7 Detección de regímenes: modelos de Markov ocultos y cambios estructurales
- 2.1.8 El problema de las pruebas múltiples: Bonferroni, FDR y el «factor zoo»
2.2 · Anatomía de un sistema de trading
- 2.2.1 Hipótesis de ventaja: riesgo, comportamiento, estructura e información
- 2.2.2 Universo, señal, filtro, entrada, salida, sizing y gestión del riesgo
- 2.2.3 Frecuencias: intradía, swing y posicional, y la capacidad de una estrategia
- 2.2.4 Diario de investigación y registro de experimentos contra el data snooping
2.3 · Familias de estrategias
- 2.3.1 Seguimiento de tendencia y momentum
- 2.3.2 Reversión a la media: RSI y Bollinger, reversión intradía y huecos
- 2.3.3 Pairs trading y arbitraje estadístico: distancia, cointegración y Kalman
- 2.3.4 Carry en divisas, futuros y bonos; estacionalidad y efectos calendario
- 2.3.5 Factores (value, calidad, baja volatilidad, tamaño) y estrategias de eventos
- 2.3.6 Introducción al market making: inventario, spread y selección adversa (Avellaneda-Stoikov)
- 2.3.7 Estrategias específicas de cripto: momentum, arbitraje de funding y basis
2.4 · Backtesting: motores y paradigmas
- 2.4.1 Backtest vectorizado desde cero: matrices, desfase, costes y límites
- 2.4.2 Backtest por eventos desde cero: bucle, cartera, órdenes y fills
- 2.4.3 vectorbt: barridos masivos de parámetros y simulación de carteras
- 2.4.4 backtesting.py y backtrader: dos motores didácticos y su estado
- 2.4.5 NautilusTrader: arquitectura, datos, estrategias y el mismo código en backtest y en real
- 2.4.6 LEAN/QuantConnect y Zipline Reloaded; bt para asignación de activos
- 2.4.7 Modelado de costes: comisiones, horquilla, impacto, financiación y préstamo de títulos
- 2.4.8 Modelado de fills: órdenes limitadas, cola, fills parciales, huecos y stops
2.5 · Sesgos y validación
- 2.5.1 Sesgos de backtest: anticipación, supervivencia, selección y narrativa
- 2.5.2 In-sample y out-of-sample: walk-forward anclado y móvil
- 2.5.3 Validación cruzada combinatoria purgada para estrategias
- 2.5.4 Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO)
- 2.5.5 Monte Carlo: remuestreo de trades, bootstrap de bloques y permutaciones de señales
- 2.5.6 Sensibilidad de parámetros: mesetas frente a picos, robustez entre activos y periodos
- 2.5.7 Datos sintéticos para pruebas de estrés: GBM, GARCH y bootstrapping
- 2.5.8 Checklist de «backtest honesto» antes de pasar a paper trading
2.6 · Métricas de rendimiento
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 la rentabilidad menos su media y su varianza condicional (la que se prevé para con la información hasta ):
- : cuánto mueve la noticia de ayer () la varianza de hoy.
- : cuánta varianza de ayer se arrastra.
- : persistencia. La varianza vuelve a su nivel de largo plazo , y la desviación respecto a él se multiplica por cada día.
La EWMA con parámetro es el caso , , : 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:
Fuente 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)1Nota 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.: solo los últimos . Necesita muchos retardos para tanta memoria; GARCH (Bollerslev, 1986) la consigue con uno.
- GJR-GARCH (Glosten, Jagannathan y Runkle, 1993): añade , con si . Las caídas pesan ; las subidas, .
- EGARCH (Nelson, 1991): modela , así que no necesita restricciones de signo. En la versión de
arch, con :
Un 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 , ,
(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.
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}")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-146Volatilidad anual del 20 % y una curtosis de 10,8: colas gruesas. La autocorrelación de en el retardo 1 es de 0,16
y el contraste ARCH-LM de Engle (regresión de sobre sus cinco retardos) rechaza con un p-valor del
orden de : 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 ,
o asimétricos y q de ; 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, es del orden de
y el optimizador sufre (la librería avisa).
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) 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.385Tres 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 ( 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 está cerca del 0,11 del factor de mercado, y su , del 0,03. El EGARCH, con , también la recoge, aunque ajusta peor que el GJR (BIC de 14.296 frente a 14.273).
- 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 , así que se reparte como una con un grado de libertad si la nula es cierta.
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}")LR = 52.6; p = 4.1e-13Persistencia y vida media
Si hoy la varianza está por encima de su nivel de largo plazo, la previsión a días es
con la persistencia: en el GARCH, en el GJR (con choques simétricos, la mitad son negativos) y en el EGARCH (para el logaritmo de la varianza). La vida media de un choque es el que deja la desviación en la mitad: .
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) 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.253El 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 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):
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) 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.090En 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 ( llega a cero en uno de ellos, y 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:
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 %, ¿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.
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) 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.4En 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 , 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
() y la varianza de las 63 sesiones anteriores.
El dato real es , 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:
con la varianza prevista (salvo una constante que no depende del modelo). Depende del cociente , 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.
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")) 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.34Los 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?
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) 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.27Son 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:
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)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: float64El 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 |
Fuente 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
- El ETF ficticio
KAPA(indice2_5min.parquet) tiene una volatilidad diaria GJR-GARCH-t con , , y (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
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) 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.00Con 1.002 sesiones, los errores típicos son del tamaño de los propios parámetros: 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. 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.
- Escala el índice para una volatilidad objetivo del 10 % anual con la volatilidad condicional del GJR y con la EWMA, , 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
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} %")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.
- En
universo2cada 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 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 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.
Footnotes
-
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. ↩
Respuesta plegada en cada pregunta
Autoevaluación
Un GARCH(1,1) tiene α = 0,10 y β = 0,88. ¿Cuál es la vida media de un choque de volatilidad?
- AUnas 2 sesiones
- BUnas 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
- CUnas 98 sesiones
- DInfinita
Ver respuesta
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.
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?
- AUn error de la librería arch
- BQue 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
- CQue el índice tiene más acciones
- DQue la t de Student tiene colas gruesas
Ver respuesta
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.
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ω
- DLos grados de libertad de la t
Ver respuesta
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.
¿Por qué se usa la pérdida QLIKE para comparar previsiones de varianza en lugar del error cuadrático?
- APorque es más fácil de calcular
- BPorque 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
- CPorque siempre favorece al GARCH
- DPorque no necesita la rentabilidad
Ver respuesta
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.
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?
- AQue el GJR está mal programado
- BQue 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
- CQue el efecto apalancamiento no existe en estos datos
- DQue hay que usar la EWMA
Ver respuesta
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
Libros
- Bollerslev, T. (1986): Generalized autoregressive conditional heteroskedasticity. Journal of Econometrics 31(3), 307-327
- 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
- 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
- Patton, A. J. (2011): Volatility forecast comparison using imperfect volatility proxies. Journal of Econometrics 160(1), 246-256