Lección 1.6.1 · 60 min

Rentabilidades, volatilidad, correlaciones móviles y drawdown

La volatilidad no es una cifra, es una serie que cambia de régimen, y cada forma de estimarla llega a un régimen nuevo con un retraso distinto. Esta lección compara la ventana fija con la EWMA de RiskMetrics, mide qué correlaciones móviles son señal y cuáles ruido, y descompone el drawdown en profundidad y en tiempo bajo el agua.

Fig. — Volatilidad de ISX en 2020: EWMA con λ = 0,94 (línea clara) y desviación típica de 63 sesiones (gris), las dos con datos hasta el día anterior
Lecciones del nivel 1
  1. 1.1 · Cómo funcionan los mercados

    1. 1.1.1 Participantes y centros de negociación
    2. 1.1.2 Sesiones, subastas, festivos y calendarios
    3. 1.1.3 Libro de órdenes: bid, ask, spread y profundidad
    4. 1.1.4 Tipos de órdenes
    5. 1.1.5 Costes, liquidación, apalancamiento y margen
  2. 1.2 · Clases de activos

    1. 1.2.1 Acciones: splits, dividendos y eventos corporativos
    2. 1.2.2 ETFs: réplica, tracking error, apalancados y UCITS
    3. 1.2.3 Futuros: especificaciones, margen, roll y contango
    4. 1.2.4 Forex: pares, pips, lotes, rollover y CFD
    5. 1.2.5 Opciones: call, put, valor intrínseco y valor temporal
    6. 1.2.6 Cripto: spot, perpetuos, funding y custodia
    7. 1.2.7 Renta fija y tipos de interés; CFDs y productos apalancados
  3. 1.3 · Fuentes de datos de mercado

    1. 1.3.1 Tipos de datos de mercado: EOD, barras, ticks, libro y referencia
    2. 1.3.2 Fuentes de datos gratuitas
    3. 1.3.3 APIs de datos retail y prosumer
    4. 1.3.4 Proveedores de datos profesionales
    5. 1.3.5 Datos de cripto y on-chain
    6. 1.3.6 Brókeres como fuente de datos
    7. 1.3.7 Licencias y términos de uso de datos
    8. 1.3.8 Cómo elegir proveedor de datos
  4. 1.4 · Ingeniería de datos financieros

    1. 1.4.1 Descarga por REST y websockets: paginación, límites y reintentos
    2. 1.4.2 Limpieza de datos: faltantes, ticks erróneos, huecos y zonas horarias
    3. 1.4.3 Ajustes por splits y dividendos: precios ajustados y sin ajustar
    4. 1.4.4 Universos sin sesgo de supervivencia: deslistados, fusiones y cambios de símbolo
    5. 1.4.5 Construcción de barras: tiempo, ticks, volumen, dólar y desequilibrio
    6. 1.4.6 Series continuas de futuros: reglas de roll, ajuste por diferencia y ajuste por ratio
    7. 1.4.7 Almacenamiento: CSV, Parquet, particionado, DuckDB, TimescaleDB y ArcticDB
    8. 1.4.8 Pipeline reproducible: descarga, validación, actualización incremental y tests de calidad
  5. 1.5 · Datos fundamentales y macro

    1. 1.5.1 Estados financieros, ratios, XBRL y SEC EDGAR
    2. 1.5.2 Datos macro, calendarios económicos, estimaciones y transcripciones
  6. 1.6 · Análisis exploratorio financiero

    1. 1.6.1 Rentabilidades, volatilidad, correlaciones móviles y drawdown
    2. 1.6.2 Hechos estilizados: colas gruesas, agrupamiento, autocorrelación y apalancamiento
    3. 1.6.3 Estacionalidad: día de la semana, mes, cambio de mes y sesiones intradía
  7. 1.7 · Análisis técnico programado

    1. 1.7.1 Indicadores técnicos en Python
    2. 1.7.2 Implementación propia frente a TA-Lib: validar resultados
    3. 1.7.3 Patrones de velas y soportes y resistencias
    4. 1.7.4 Multi-timeframe sin anticipación

Dos índices, siete años

En el nivel 0 calculaste rentabilidades, la volatilidad de una ventana de 21 sesiones y el drawdown de un ETF ficticio (lecciones 0.5.3 y 0.7.1). Esta lección parte de ahí y va a lo que importa para operar: cómo cambia la volatilidad, con qué retraso la detecta cada estimador, cuánto se puede fiar uno de una correlación móvil y cómo es un drawdown por dentro.

Los datos son dos índices ficticios del nivel 1, con las sesiones reales de la NYSE de 2019 a 2025: ISX, un índice de rentabilidad total (etf_indice_diario.csv), y FIX, un índice de precios con futuros (fix_indice_diario.csv). Los dos tienen episodios de volatilidad alta en 2020, 2022 y la primavera de 2025.

IN [01]python
import numpy as np
import pandas as pd

isx = pd.read_csv("etf_indice_diario.csv", parse_dates=["fecha"],
                  index_col="fecha")["indice"]
fix = pd.read_csv("fix_indice_diario.csv", parse_dates=["fecha"],
                  index_col="fecha")["indice"]
px = pd.DataFrame({"ISX": isx, "FIX": fix})
r = np.log(px).diff().dropna()   # rentabilidades logarítmicas
print(px.index[0].date(), "a", px.index[-1].date(), len(px), "sesiones")
anual = pd.DataFrame({
    "media_%": 100 * 252 * r.mean(),
    "vol_%": 100 * np.sqrt(252) * r.std(),
    "peor_dia_%": 100 * r.min(),
    "mejor_dia_%": 100 * r.max()})
anual.round(2)
OUT [01]ISX y FIX
2019-01-02 a 2025-12-31 1760 sesiones
     media_%  vol_%  peor_dia_%  mejor_dia_%
ISX    12.83  20.87       -7.95         7.84
FIX    14.55  20.07       -8.98         8.15

Los dos tienen una volatilidad anual en torno al 20 %, peores días de −8 % y −9 %, mejores de +8 % y rentabilidades medias del 12,8 y el 14,6 % (en log, anualizadas por 252). Una volatilidad de toda la muestra dice poco: es la media de años muy tranquilos y meses muy agitados. Lo útil es la serie.

Volatilidad de ventana fija

La volatilidad histórica de n sesiones es la desviación típica de las n últimas rentabilidades, anualizada por √252. El tamaño de la ventana es un compromiso: corta, ruidosa y rápida; larga, estable y lenta.

IN [02]python
x = r["ISX"]
vol = pd.DataFrame({
    f"v{n}": x.rolling(n).std() * np.sqrt(252)
    for n in (21, 63, 252)})
(100 * vol.loc["2020-02-14":"2020-05-29"].iloc[::10]).round(1)
OUT [02]Tres ventanas en el desplome de 2020
             v21   v63  v252
fecha
2020-02-14  10.6  12.1  11.4
2020-03-02  17.5  13.2  11.7
2020-03-16  22.7  17.9  13.1
2020-03-30  27.7  23.4  15.1
2020-04-14  34.3  26.1  16.7
2020-04-28  38.2  29.8  18.4
2020-05-12  45.5  36.7  20.9
2020-05-27  37.2  38.6  21.7

A mediados de febrero de 2020 las tres ventanas ven un mercado tranquilo, en torno al 11-12 %. La de 21 sesiones llega al 34,3 % a mediados de abril y al 45,5 % en mayo; la de 63, al 26,1 % y al 36,7 %; la de 252, que lleva un año de días tranquilos dentro, no pasa del 21,7 % en todo el periodo. Una ventana rectangular pondera igual todos sus días: hasta que los días tranquilos salen de ella, infravalora el régimen nuevo.

EWMA: el peso decrece con la antigüedad

La media móvil exponencial (EWMA) de los cuadrados de las rentabilidades da a cada día un peso que decrece de forma geométrica. Es la de RiskMetrics, la metodología de riesgo de J.P. Morgan de los noventa, que fijó λ = 0,94 para los datos diarios y λ = 0,97 para los mensuales1Nota 1 · J.P. Morgan/Reuters, RiskMetrics — Technical Document, 4.ª edición, 1996: correlaciones y volatilidades con medias exponenciales y factores de decaimiento de 0,94 (datos diarios, «74 day cutoff 1%») y 0,97 (mensuales).:

σt2=λ σt−12+(1−λ) rt−12\sigma^2_t = \lambda\,\sigma^2_{t-1} + (1-\lambda)\,r^2_{t-1}

La rentabilidad de ayer pesa 1 − λ = 6 %; la de anteayer, 6 % × 0,94; la de hace k días, 0,06 × 0,94^(k−1). RiskMetrics supone media cero, razonable con datos diarios, y por eso usa r² en lugar de (r − media)².

IN [03]python
lam = 0.94
k = np.arange(1, 253)
w = (1 - lam) * lam ** (k - 1)
print(f"Vida media: {np.log(0.5) / np.log(lam):.1f} sesiones")
for n in (10, 21, 63, 74):
    print(f"Peso de las {n} últimas: {w[:n].sum():.1%}")
OUT [03]Pesos de la EWMA frente a una ventana de 63 sesiones
Vida media: 11.2 sesiones
Peso de las 10 últimas: 46.1%
Peso de las 21 últimas: 72.7%
Peso de las 63 últimas: 98.0%
Peso de las 74 últimas: 99.0%
Fig. — Peso de cada rentabilidad pasada en la varianza de hoy: EWMA con λ = 0,94 (barras) frente a una ventana de 63 sesiones (línea)

La mitad del peso está en las once últimas sesiones y el 99 % en las 74 últimas (la cifra de corte que da el propio documento de RiskMetrics para λ = 0,94). Una ventana de 63 sesiones da 1/63 = 1,6 % a cada día y 0 al 64.º: el día que sale de la ventana provoca un salto en la estimación aunque no pase nada hoy.

La recursión se escribe en un bucle o con ewm. Con adjust=False, pandas aplica exactamente la recursión; con alpha = 1 − λ:

IN [04]python
def ewma_var(x, lam=0.94, var0=None):
    """Varianza EWMA de cada día con datos hasta el día anterior."""
    v = np.empty(len(x))
    v[0] = np.var(x[:21]) if var0 is None else var0
    for t in range(1, len(x)):
        v[t] = lam * v[t - 1] + (1 - lam) * x[t - 1] ** 2
    return v

xa = x.to_numpy()
a_mano = ewma_var(xa)
# ewm(adjust=False) incluye el día t: se desplaza uno
con_ewm = (x.pow(2).ewm(alpha=0.06, adjust=False).mean()
           .shift(1).to_numpy())
dif = np.abs(np.sqrt(a_mano[300:]) - np.sqrt(con_ewm[300:]))
print(f"Diferencia máxima tras 300 sesiones: {dif.max():.2e}")
ewma = pd.Series(np.sqrt(252 * a_mano), index=x.index)
vol["ewma"] = ewma
(100 * vol.loc["2020-02-14":"2020-05-29"].iloc[::10]).round(1)
OUT [04]EWMA a mano y con ewm
Diferencia máxima tras 300 sesiones: 2.32e-11
             v21   v63  v252  ewma
fecha
2020-02-14  10.6  12.1  11.4  10.6
2020-03-02  17.5  13.2  11.7  17.4
2020-03-16  22.7  17.9  13.1  26.5
2020-03-30  27.7  23.4  15.1  34.5
2020-04-14  34.3  26.1  16.7  33.7
2020-04-28  38.2  29.8  18.4  37.4
2020-05-12  45.5  36.7  20.9  40.3
2020-05-27  37.2  38.6  21.7  37.7

Las dos versiones coinciden salvo por el valor inicial, cuyo efecto desaparece en unas semanas (0,94³⁰⁰ es despreciable). Y la diferencia de timing está en la tabla: la EWMA pasa del 10,6 % al 34,5 % en seis semanas y se estabiliza entre el 34 y el 40 % mientras la de 21 sesiones sigue subiendo hasta el 45,5 %.

La figura de la cabecera lo dibuja día a día con la ventana de 63 sesiones, las dos con datos hasta el día anterior. ¿Cuándo pasa cada una del 30 %?

IN [05]python
v63 = (x.rolling(63).std() * np.sqrt(252)).shift(1)
for nombre, serie in [("EWMA", ewma), ("Ventana 63", v63)]:
    s = serie.loc["2020"]
    print(f"{nombre:>10}: 30 % el {s[s >= 0.30].index[0].date()},"
          f" máximo {s.max():.1%} el {s.idxmax().date()}")
OUT [05]El retraso de la ventana, en fechas
      EWMA: 30 % el 2020-03-24, máximo 51.1% el 2020-06-16
Ventana 63: 30 % el 2020-04-30, máximo 45.1% el 2020-06-22

La EWMA cruza el 30 % el 24 de marzo; la ventana de 63 sesiones, el 30 de abril, cinco semanas después. Un sistema que reduce la exposición cuando la volatilidad supera un umbral habría reaccionado con más de un mes de diferencia según el estimador. A la salida pasa lo mismo al revés: la EWMA vuelve antes a niveles bajos y devuelve la exposición antes.

Un detalle de la celda importa más que el resto: la varianza del día t usa las rentabilidades hasta t − 1. Si la volatilidad se usa para dimensionar la posición de hoy (volatility targeting, lección 0.7.3), el estimador no puede incluir la rentabilidad de hoy, que aún no ha ocurrido cuando se decide. rolling(...).std() y ewm(...).mean() incluyen el día t: en un backtest, siempre shift(1).

r hasta t-1σ estimada en tPosición en tResultado r en t
Fuente Mermaid
flowchart LR
  R[r hasta t-1] --> S[σ estimada en t]
  S --> P[Posición en t]
  P --> G[Resultado r en t]

Qué estimador prevé mejor

Un estimador de volatilidad es una previsión: dice cuánto se va a mover mañana. Se puede evaluar como cualquier previsión, comparando la varianza estimada con el cuadrado de la rentabilidad del día siguiente. Dos medidas habituales: el error cuadrático medio y la pérdida QLIKE, que penaliza más quedarse corto que pasarse y es robusta al ruido de r²:

QLIKE=rt2σt2−ln⁡rt2σt2−1\text{QLIKE} = \frac{r_t^2}{\sigma_t^2} - \ln\frac{r_t^2}{\sigma_t^2} - 1

Otra forma, más intuitiva: dividir cada rentabilidad por la volatilidad prevista. Si el estimador es bueno, las rentabilidades estandarizadas tienen desviación típica cercana a 1, la curtosis más baja posible y una proporción de días por debajo de −2,33 desviaciones cercana a la que espera la normal (1 %).

IN [06]python
prev = pd.DataFrame({
    "v21": x.rolling(21).std(), "v63": x.rolling(63).std(),
    "v252": x.rolling(252).std(),
    "ewma94": np.sqrt(x.pow(2).ewm(alpha=0.06, adjust=False).mean()),
    "ewma97": np.sqrt(x.pow(2).ewm(alpha=0.03, adjust=False).mean()),
}).shift(1)                      # previsión para el día siguiente
d = prev.join(x.rename("r")).dropna()
filas = {}
for c in prev:
    s2, r2 = d[c] ** 2, d["r"] ** 2
    z = d["r"] / d[c]
    filas[c] = {"qlike": (r2 / s2 - np.log(r2 / s2) - 1).mean(),
                "z_desv": z.std(), "z_curtosis": z.kurt(),
                "bajo_-2,33_%": 100 * (z < -2.326).mean()}
pd.DataFrame(filas).T.round(3)
OUT [06]Cinco estimadores evaluados como previsión
        qlike  z_desv  z_curtosis  bajo_-2,33_%
v21     1.818   1.195      10.664         2.190
v63     1.827   1.122       9.979         2.057
v252    2.180   1.193      11.176         2.588
ewma94  1.696   1.088       7.326         1.924
ewma97  1.751   1.061       9.304         1.924

La EWMA con λ = 0,94 tiene la QLIKE más baja (1,696) y la curtosis más baja (7,3 frente a los 10-11 de las ventanas); la de λ = 0,97 deja la desviación típica más cerca de 1 (1,06 frente a 1,09), y las dos empatan en días bajo −2,33 (1,9 %). Las ventanas quedan por detrás en todo. Ninguna llega al 1 % de días bajo −2,33: las colas siguen siendo más gruesas que las de la normal incluso después de estandarizar (lección 1.6.2). La ventana de 252 sesiones es la peor en QLIKE, curtosis y días bajo −2,33: una volatilidad «del último año» es una mala previsión de la de mañana.

Correlaciones móviles: señal y ruido

La correlación entre dos activos también cambia, y una cartera diversificada depende de ella. La correlación móvil de 63 sesiones entre ISX y FIX:

IN [07]python
rc = r["ISX"].rolling(63).corr(r["FIX"])
banda = 1.96 / np.sqrt(63)
print(f"Correlación de toda la muestra: {r.corr().iloc[0, 1]:.3f}")
print(rc.describe().round(3).to_string())
print(f"Banda del 95 % si la correlación real es 0: ±{banda:.3f}")
print(f"Fuera de la banda: {(rc.abs() > banda).mean():.1%}")
print("Máximo:", rc.idxmax().date(), round(rc.max(), 3))
OUT [07]Correlación móvil de 63 sesiones
Correlación de toda la muestra: 0.066
count    1697.000
mean        0.010
std         0.155
min        -0.438
25%        -0.080
50%        -0.006
75%         0.099
max         0.511
Banda del 95 % si la correlación real es 0: ±0.247
Fuera de la banda: 10.1%
Máximo: 2025-04-23 0.511
Fig. — Correlación móvil de 63 sesiones entre las rentabilidades diarias de ISX y FIX, 2019–2025 (último valor de cada semana), con la banda del 95 % para una correlación real nula

La correlación de toda la muestra es casi nula y la móvil oscila de −0,44 a +0,51. ¿Cambia la relación entre los dos índices o es ruido? Con 63 datos y una correlación real de cero, el 95 % de las estimaciones cae dentro de ±0,247 (error típico ≈ 1/√n). Aquí cae fuera el 10,1 % del tiempo: el doble del 5 % que daría el azar, pero lejos de lo que haría falta para leer cada pico como un cambio. El máximo, de 0,51, es del 23 de abril de 2025, en plena caída de los dos índices, y el pico de marzo de 2020 llega casi a 0,50: los dos episodios de estrés son los dos picos.

Para comprobar cuánto oscila una correlación móvil sin ninguna relación, basta con simularla:

IN [08]python
rng = np.random.default_rng(7)
ind = pd.DataFrame(rng.standard_normal((len(r), 2)))
rc0 = ind[0].rolling(63).corr(ind[1])
print(f"Mínimo {rc0.min():.3f}, máximo {rc0.max():.3f}")
print(f"Fuera de la banda: {(rc0.abs() > banda).mean():.1%}")
OUT [08]Dos series independientes, misma longitud
Mínimo -0.255, máximo 0.300
Fuera de la banda: 1.2%

Dos series sin ninguna relación producen una correlación móvil que va de −0,26 a +0,30. En esta simulación solo el 1,2 % del tiempo queda fuera de la banda: con ventanas solapadas, los valores de días seguidos no son independientes y la fracción de un solo camino varía mucho alrededor del 5 %. Cualquier gráfico de correlación móvil hay que leerlo con esa referencia al lado.

¿Y los periodos de estrés? En los mercados reales suele observarse que las correlaciones entre activos de riesgo suben en las caídas, justo cuando la diversificación haría más falta. En estos datos hay una huella de eso, pero viene de la frecuencia, no del día a día:

IN [09]python
for regla, nombre in [("W-FRI", "semanal"), ("ME", "mensual"),
                      ("QE", "trimestral")]:
    rr = np.log(px.resample(regla).last()).diff().dropna()
    print(f"{nombre:>10}: {rr.corr().iloc[0, 1]:.3f}"
          f"  ({len(rr)} datos)")
vol21 = r.rolling(21).std()
print(f"Correlación de las volatilidades de 21 sesiones: "
      f"{vol21['ISX'].corr(vol21['FIX']):.3f}")
OUT [09]La correlación depende de la frecuencia
   semanal: 0.156  (365 datos)
   mensual: 0.453  (83 datos)
trimestral: 0.819  (27 datos)
Correlación de las volatilidades de 21 sesiones: 0.650

En diario la correlación es 0,06; en semanal, 0,16; en mensual, 0,45, y en trimestral, 0,82. Los dos índices comparten las grandes tendencias del calendario (las caídas de 2020, 2022 y 2025 y las subidas posteriores), pero no los movimientos de cada día, que en estos datos son independientes. Al agregar, el ruido diario se cancela y queda la parte común. Y sus volatilidades sí van juntas (0,65): los regímenes de volatilidad alta son los mismos. Para una cartera, la consecuencia es clara: la diversificación que se mide con datos diarios puede no existir en el horizonte que de verdad importa, el de meses.

Drawdown y tiempo bajo el agua

El drawdown de cada día es la caída desde el máximo anterior:

DDt=Ptmax⁡s≤tPs−1DD_t = \frac{P_t}{\max_{s \le t} P_s} - 1

Su mínimo es el drawdown máximo, la cifra que todo el mundo cita. Pero un drawdown tiene dos dimensiones: cuánto cae y cuánto dura. Un episodio bajo el agua empieza el día en que el precio cae por debajo de su máximo y termina el día en que lo supera de nuevo.

cae del máximosigue por debajosupera el máximoEnMaximoBajoElAgua
Fuente Mermaid
stateDiagram-v2
  [*] --> EnMaximo
  EnMaximo --> BajoElAgua: cae del máximo
  BajoElAgua --> BajoElAgua: sigue por debajo
  BajoElAgua --> EnMaximo: supera el máximo
IN [10]python
def episodios(precio):
    pico = precio.cummax()
    dd = precio / pico - 1
    bajo = dd < 0
    grupo = (~bajo).cumsum()         # un número por episodio
    filas = []
    for _, e in dd[bajo].groupby(grupo[bajo]):
        fin = e.index[-1]
        sig = precio.index.searchsorted(fin, side="right")
        recuperado = sig < len(precio)
        filas.append({
            "inicio": e.index[0].date(),
            "minimo": e.idxmin().date(),
            "recupera": precio.index[sig].date()
                        if recuperado else None,
            "profundidad_%": 100 * e.min(),
            "sesiones_hasta_minimo": e.index.get_loc(e.idxmin()) + 1,
            "sesiones_bajo_agua": len(e)})
    return pd.DataFrame(filas), dd

ep, dd_isx = episodios(px["ISX"])
print(f"Sesiones bajo el agua: {(dd_isx < 0).mean():.1%}")
print(f"Episodios: {len(ep)}")
ep.sort_values("profundidad_%").head(5).round(1)
OUT [10]Episodios bajo el agua de ISX
Sesiones bajo el agua: 92.9%
Episodios: 54
        inicio      minimo    recupera  profundidad_%  sesiones_hasta_minimo  sesiones_bajo_agua
22  2020-02-20  2020-04-21  2021-03-29          -43.2                     43                 278
31  2022-02-15  2022-05-25  2023-04-17          -28.9                     70                 292
41  2025-02-20  2025-04-04  2025-06-25          -28.0                     32                  86
35  2023-07-17  2024-01-02  2024-10-03          -18.6                    118                 307
36  2024-10-09  2024-11-26  2025-01-13           -7.2                     35                  64
Fig. — Drawdown de ISX, 2019–2025 (peor valor de cada semana): el episodio más profundo no es el más largo

El 92,9 % de las sesiones ISX está por debajo de su máximo anterior: casi siempre se está «bajo el agua», aunque sea por poco. Lo que distingue a un episodio es su profundidad y su duración, y no van juntas:

IN [11]python
ep.sort_values("sesiones_bajo_agua", ascending=False).head(4)[
    ["inicio", "recupera", "profundidad_%",
     "sesiones_hasta_minimo", "sesiones_bajo_agua"]].round(1)
OUT [11]Los más largos no son los más profundos
        inicio    recupera  profundidad_%  sesiones_hasta_minimo  sesiones_bajo_agua
35  2023-07-17  2024-10-03          -18.6                    118                 307
31  2022-02-15  2023-04-17          -28.9                     70                 292
22  2020-02-20  2021-03-29          -43.2                     43                 278
41  2025-02-20  2025-06-25          -28.0                     32                  86

El desplome de 2020 es el más profundo (−43,2 %) pero tarda 278 sesiones en recuperarse, menos que el episodio que empieza en julio de 2023, con solo −18,6 % de profundidad y 307 sesiones bajo el agua: una caída moderada seguida de una recuperación lenta. La caída de 2022 dura 292. Para quien sigue una estrategia, catorce meses sin un máximo nuevo pesan tanto como una caída grande: es cuando se abandonan los sistemas. Por eso las métricas de riesgo del nivel 2 (lección 2.6.1) incluyen la duración del drawdown y el Ulcer Index, que combina profundidad y duración.

Lo que esto cambia en un sistema

  • Volatilidad con shift(1). Toda estimación que alimente una decisión del día t usa datos hasta t − 1. Con rolling y ewm de pandas, el día t está dentro: desplázalo.
  • Elige el estimador por su uso. Para dimensionar posiciones día a día, uno rápido (EWMA, λ = 0,94 o ventana corta); para comparar estrategias o fijar límites de riesgo, uno lento y estable.
  • Ninguna correlación sin su banda. Con n datos, el ruido es del orden de ±2/√n. Y mide la correlación a la frecuencia de tu horizonte, no solo en diario.
  • Profundidad y duración. Informa el drawdown máximo y el episodio más largo bajo el agua. Son riesgos distintos.

Ejercicios

  1. Repite la evaluación de estimadores con FIX y con λ = 0,90, 0,94 y 0,97. ¿Qué λ da la QLIKE más baja en cada índice?
Solución
IN [12]python
def qlike(serie, lam):
    s2 = serie.pow(2).ewm(alpha=1 - lam, adjust=False).mean()
    dd = pd.DataFrame({"s2": s2.shift(1),
                       "r2": serie.pow(2)}).iloc[63:]
    dd = dd[dd["r2"] > 0]   # días sin variación: ln(0)
    q = dd["r2"] / dd["s2"]
    return (q - np.log(q) - 1).mean()

tabla = pd.DataFrame({ind: {lam: qlike(r[ind], lam)
                            for lam in (0.90, 0.94, 0.97)}
                      for ind in ["ISX", "FIX"]})
tabla.round(4)
OUT [12]λ óptimo por índice
         ISX     FIX
0.90  1.6955  1.6794
0.94  1.6973  1.6746
0.97  1.7454  1.7173

Las diferencias son pequeñas y el λ óptimo depende del índice: en ISX gana 0,90 por dos milésimas; en FIX, 0,94. Con siete años de un solo activo, elegir λ por la QLIKE es una optimización en la muestra: el 0,94 de RiskMetrics es un valor por defecto razonable precisamente porque no se ajustó a tus datos.

  1. Calcula la correlación EWMA (λ = 0,94) entre ISX y FIX y compárala con la de ventana de 63 sesiones: ¿cuál oscila más?
Solución
IN [13]python
a = 0.06
cov = (r["ISX"] * r["FIX"]).ewm(alpha=a, adjust=False).mean()
var_i = r["ISX"].pow(2).ewm(alpha=a, adjust=False).mean()
var_f = r["FIX"].pow(2).ewm(alpha=a, adjust=False).mean()
corr_ewma = (cov / np.sqrt(var_i * var_f)).iloc[63:]
pd.DataFrame({"ventana_63": rc.iloc[63:].describe(),
              "ewma_094": corr_ewma.describe()}).round(3)
OUT [13]Correlación EWMA
       ventana_63  ewma_094
count    1696.000  1696.000
mean        0.010     0.020
std         0.155     0.182
min        -0.438    -0.517
25%        -0.080    -0.083
50%        -0.006     0.006
75%         0.099     0.110
max         0.511     0.699

La EWMA oscila más: su vida media de 11 sesiones equivale a una ventana mucho más corta que 63, y su ruido de muestreo es mayor. Es la otra cara de reaccionar antes. Las primeras 63 sesiones se descartan porque la recursión arranca de un solo dato y da correlaciones de ±1 al principio.

  1. Una estrategia tiene un drawdown máximo del −15 % y otra del −25 %. ¿Qué más necesitas saber antes de decir cuál es menos arriesgada?
Solución

La duración de sus episodios bajo el agua (el más largo y el típico), la volatilidad de cada una (un −15 % con una volatilidad del 5 % es un evento de tres desviaciones; un −25 % con un 25 %, uno normal) y cuántos años cubre el histórico: el drawdown máximo crece con la longitud de la muestra. También importa si el drawdown viene de un único desplome o de una sangría lenta, porque se gestionan de forma distinta (stops frente a revisión del sistema).

Footnotes

  1. J.P. Morgan/Reuters, RiskMetrics — Technical Document, 4.ª edición, 1996: correlaciones y volatilidades con medias exponenciales y factores de decaimiento de 0,94 (datos diarios, «74 day cutoff 1%») y 0,97 (mensuales). ↩

Respuesta plegada en cada pregunta

Autoevaluación

  1. Con λ = 0,94, ¿qué peso tiene en la EWMA la rentabilidad de ayer?

    • AEl 94 %
    • BEl 6 %: (1 − λ)
    • C1/63, como en una ventana de 63 sesiones
    • DDepende del nivel de volatilidad
    Ver respuesta

    Respuesta B. El 6 %: (1 − λ)

    σ²(t) = λ σ²(t−1) + (1 − λ) r²(t−1): la última rentabilidad pesa 1 − λ = 6 %, la anterior 6 % × 0,94, y así sucesivamente. La vida media del peso es ln 0,5 / ln 0,94 ≈ 11,2 sesiones.

  2. En marzo de 2020, ¿por qué la volatilidad de ventana de 63 sesiones va por detrás de la EWMA?

    • APorque está mal calculada
    • BPorque pondera igual los 63 días: los días tranquilos de enero y febrero pesan lo mismo que los del desplome
    • CPorque usa rentabilidades simples
    • DPorque la EWMA usa información futura
    Ver respuesta

    Respuesta B. Porque pondera igual los 63 días: los días tranquilos de enero y febrero pesan lo mismo que los del desplome

    Una ventana rectangular reparte el peso por igual. Hasta que los días tranquilos salen de la ventana, el estimador infravalora el régimen nuevo; la EWMA da más peso a lo reciente y reacciona antes. Ninguna de las dos usa el día que estima si se desplaza un día.

  3. La correlación móvil de 63 sesiones entre dos índices oscila entre −0,44 y +0,51 con una media de 0,01. ¿Qué es lo más probable?

    • AQue la relación entre los dos índices cambia de signo cada pocos meses
    • BQue gran parte de la oscilación es ruido de muestreo: con 63 datos y correlación cero, el 95 % de las estimaciones cae entre ±0,25
    • CQue hay un error en el cálculo
    • DQue hay que usar una ventana de 5 sesiones
    Ver respuesta

    Respuesta B. Que gran parte de la oscilación es ruido de muestreo: con 63 datos y correlación cero, el 95 % de las estimaciones cae entre ±0,25

    El error típico de una correlación cercana a cero con n datos es aproximadamente 1/√n. Antes de interpretar un cambio de correlación hay que compararlo con esa banda.

  4. La correlación diaria de ISX y FIX es 0,06 y la trimestral 0,82. ¿Cómo puede ser?

    • APor un error de redondeo
    • BPorque los dos índices comparten las grandes tendencias (las caídas de 2020, 2022 y 2025) pero no los movimientos diarios: a frecuencias bajas domina la parte común
    • CPorque la correlación trimestral siempre es mayor
    • DPorque hay menos días festivos
    Ver respuesta

    Respuesta B. Porque los dos índices comparten las grandes tendencias (las caídas de 2020, 2022 y 2025) pero no los movimientos diarios: a frecuencias bajas domina la parte común

    La correlación depende de la frecuencia. Lo que domina el día a día de estos índices es ruido independiente; al agregar, ese ruido se cancela y queda la tendencia común. Con 27 trimestres, además, la estimación es muy imprecisa.

  5. El drawdown más profundo de ISX es del −43,2 %, pero su episodio más largo bajo el agua no es ese. ¿Qué mide cada cosa?

    • ALo mismo
    • BLa profundidad mide cuánto se pierde desde el máximo; la duración, cuánto tiempo se tarda en volver a él. Una caída rápida con rebote rápido puede durar menos que una caída suave sin rebote
    • CLa duración solo cuenta días con pérdidas
    • DEl drawdown no se puede medir en días
    Ver respuesta

    Respuesta B. La profundidad mide cuánto se pierde desde el máximo; la duración, cuánto tiempo se tarda en volver a él. Una caída rápida con rebote rápido puede durar menos que una caída suave sin rebote

    En ISX, la caída de 2020 se recupera en 278 sesiones; el episodio de 2023-2024, con solo −18,6 % de profundidad, dura 307. Para quien vive la estrategia, las dos dimensiones duelen de forma distinta.

Ideas clave

  • Una ventana fija pondera igual todos sus días y llega tarde a los regímenes nuevos; la EWMA con λ = 0,94 (RiskMetrics, datos diarios) tiene una vida media de unas 11 sesiones.
  • Para usar una volatilidad en el sizing de hoy, se estima con datos hasta ayer: shift(1). Un estimador se evalúa como previsión, con QLIKE o con rentabilidades estandarizadas.
  • Una correlación móvil de n datos tiene un ruido de en torno a ±2/√n: con 63 sesiones, ±0,25. Antes de leer un cambio, compáralo con esa banda.
  • La correlación depende de la frecuencia: ISX y FIX tienen 0,06 en diario y 0,82 en trimestral.
  • El drawdown tiene profundidad y duración. En ISX, el episodio más profundo (−43,2 %) no es el más largo (307 sesiones bajo el agua).

Recursos

Documentación

Datos