Lección 2.1.8 · 70 min
El problema de las pruebas múltiples: Bonferroni, FDR y el «factor zoo»
Quien prueba mil estrategias encuentra decenas «significativas» aunque ninguna funcione. Esta lección lo mide con mil estrategias sintéticas de las que se sabe cuáles tienen ventaja, aplica las correcciones clásicas y enseña su precio: con cuatro años de datos, ni las buenas sobreviven. Después, lo mismo con las características de un universo de acciones.
- 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
Mil estrategias, cinco buenas
Un p-valor responde a una pregunta concreta: si esta estrategia no tuviera ventaja, ¿qué probabilidad habría de ver un resultado tan bueno como el observado? Con un umbral de 0,05, una estrategia sin ventaja pasa el contraste una de cada veinte veces. Si se prueba una, es un riesgo razonable. Si se prueban mil y se presenta la mejor, el p-valor ya no responde a la pregunta que importa, que es otra: ¿qué probabilidad hay de que la mejor de mil parezca así de buena por azar? Casi toda.
Este es el corazón de la investigación cuantitativa honesta, y la razón de las lecciones 2.2.4 (registrar cada
experimento), 2.5.4 (Deflated Sharpe y PBO) y 2.5.2 (validación fuera de la muestra). Aquí se mide con
ruido2_estrategias.parquet: los rendimientos diarios de 1.000 «estrategias» sintéticas de 2022 a 2025, en 50
familias de 20 variantes correladas entre sí, como las que salen de barrer los parámetros de una misma idea. Todas
tienen ventaja cero salvo cinco, cada una en una familia distinta, y ruido2_verdad.csv dice cuáles: la respuesta
conocida. Son datos inventados; ninguna cifra de esta lección dice nada de una estrategia real.
Fuente Mermaid
flowchart TB
I[Una idea] --> V[Veinte variantes]
V --> F[Cincuenta familias]
F --> T[Mil contrastes]
T --> M[Se elige la mejor]
M --> P{"¿p < 0,05?"}
P -- casi siempre --> E[Parece un hallazgo]import numpy as np
import pandas as pd
from scipy import stats
ruido = pd.read_parquet("ruido2_estrategias.parquet")
verdad = pd.read_csv("ruido2_verdad.csv", index_col="estrategia")
R = ruido.pivot(index="fecha", columns="estrategia",
values="rendimiento")
n = len(R)
sharpe = R.mean() / R.std() * np.sqrt(252)
t, p = stats.ttest_1samp(R, 0.0)
t = pd.Series(t, index=R.columns)
p = pd.Series(p, index=R.columns)
print(n, "sesiones,", R.shape[1], "estrategias")
print("con p < 0,05:", (p < 0.05).sum(),
"| esperadas si ninguna tuviera ventaja:", 0.05 * R.shape[1])
print("de ellas, con rendimiento medio positivo:",
((p < 0.05) & (t > 0)).sum())1003 sesiones, 1000 estrategias
con p < 0,05: 46 | esperadas si ninguna tuviera ventaja: 50.0
de ellas, con rendimiento medio positivo: 2546 estrategias pasan el umbral del 5 % en un contraste bilateral, cerca de las 50 que daría el puro azar, y casi la mitad (21) lo pasan por ser significativamente malas. El contraste es el de la media de los rendimientos diarios (una t de Student), que equivale a contrastar el Sharpe: con sesiones, , es decir, Sharpe anual por la raíz de los años. ¿Dónde están las cinco buenas?
buenas = verdad.index[verdad["sharpe_real"] > 0]
puesto = sharpe.rank(ascending=False).astype(int)
pd.DataFrame({"sharpe_real": verdad.loc[buenas, "sharpe_real"],
"sharpe_medido": sharpe[buenas], "t": t[buenas],
"p": p[buenas], "puesto": puesto[buenas]}).round(4) sharpe_real sharpe_medido t p puesto
estrategia
e0080 0.5 0.9508 1.8969 0.0581 31
e0133 0.8 1.4586 2.9099 0.0037 3
e0258 0.7 0.6216 1.2402 0.2152 121
e0518 1.5 1.5791 3.1504 0.0017 1
e0751 0.6 0.8320 1.6599 0.0973 56Dos de las cinco tienen p < 0,05; las otras tres, con Sharpes verdaderos de 0,5 a 0,7, no se distinguen del ruido en cuatro años. Y en la parte alta de la clasificación la verdad se mezcla con el azar:
top = sharpe.nlargest(8)
pd.DataFrame({"sharpe": top, "p": p[top.index],
"familia": verdad.loc[top.index, "familia"],
"ventaja_real": verdad.loc[top.index, "sharpe_real"] > 0}
).round(4) sharpe p familia ventaja_real
estrategia
e0518 1.5791 0.0017 f03 True
e0598 1.5030 0.0028 f22 False
e0133 1.4586 0.0037 f37 True
e0316 1.4227 0.0046 f22 False
e0124 1.4105 0.0050 f35 False
e0462 1.4089 0.0050 f35 False
e0542 1.3857 0.0058 f22 False
e0401 1.3549 0.0070 f24 FalseLa figura de la cabecera ordena las sesenta mejores. La segunda de las 1.000, e0598, tiene un Sharpe medido de 1,50
en cuatro años y ninguna ventaja: es lo mejor del ruido. Seis de las ocho primeras son ruido. Un analista que eligiera
«la mejor» tendría casi las mismas posibilidades de quedarse con una estrategia vacía que con la buena, y no tendría
forma de saberlo mirando los números.
Corregir: error por familia y falsos descubrimientos
Las correcciones clásicas responden a dos preguntas distintas.
Error por familia (FWER, family-wise error rate): la probabilidad de cometer al menos un falso positivo entre los contrastes. Bonferroni lo controla exigiendo a cada uno: con y , . Holm hace lo mismo de forma escalonada y algo más potente: ordena los p-valores de menor a mayor y compara el -ésimo con , parando en el primero que no pasa.
Tasa de falsos descubrimientos (FDR, false discovery rate): la proporción esperada de falsos positivos entre los descubrimientos. Es una exigencia más suave, pensada para cribas en las que se acepta que alguno de los elegidos sea falso. Benjamini-Hochberg (1995) ordena los p-valores, , y declara descubrimientos los primeros, con
Garantiza FDR ≤ q con contrastes independientes o con dependencia positiva; para dependencia arbitraria, Benjamini-Yekutieli divide además q por (unos 7,5 con ).
Fuente Mermaid
flowchart TB
Q{"¿Qué error duele?"} -- uno solo --> W[FWER]
Q -- una proporción --> D[FDR]
W --> B[Bonferroni, Holm]
D --> H{"¿Dependencia?"}
H -- positiva --> BH[Benjamini-Hochberg]
H -- cualquiera --> BY[Benjamini-Yekutieli]statsmodels tiene las cuatro en multipletests:
from statsmodels.stats.multitest import multipletests
es_buena = R.columns.isin(buenas)
pb = p[es_buena]
filas = {"sin corregir": {"descubrimientos": int((p < 0.05).sum()),
"con_ventaja": int((pb < 0.05).sum()),
"p_min_ajustado": p.min()}}
for metodo, nombre in [("bonferroni", "Bonferroni"), ("holm", "Holm"),
("fdr_bh", "Benjamini-Hochberg"),
("fdr_by", "Benjamini-Yekutieli")]:
rechaza, p_aj, _, _ = multipletests(p, alpha=0.05, method=metodo)
filas[nombre] = {"descubrimientos": int(rechaza.sum()),
"con_ventaja": int(rechaza[es_buena].sum()),
"p_min_ajustado": p_aj.min()}
pd.DataFrame(filas).T.astype({"descubrimientos": int,
"con_ventaja": int}) descubrimientos con_ventaja p_min_ajustado
sin corregir 46 2 0.001679
Bonferroni 0 0 1.000000
Holm 0 0 1.000000
Benjamini-Hochberg 0 0 0.829541
Benjamini-Yekutieli 0 0 1.000000Ninguna sobrevive a ninguna corrección, tampoco las cinco buenas. El p-valor más pequeño de las 1.000 (0,0017, el de
e0518, que sí tiene ventaja) se convierte en 1 tras Bonferroni y en un valor muy por encima de 0,05 tras
Benjamini-Hochberg. Ver el procedimiento de Benjamini-Hochberg en los primeros puestos ayuda a entender por qué:
orden = p.sort_values()
k = np.arange(1, 9)
paso = pd.DataFrame(
{"p": orden.values[:8],
"umbral_bh": 0.05 * k / len(p),
"ventaja_real": verdad.loc[orden.index[:8], "sharpe_real"]
.to_numpy() > 0},
index=pd.Index(orden.index[:8], name="estrategia"))
t_bonf = stats.t.isf(0.05 / len(p) / 2, n - 1)
print(f"Bonferroni exige |t| > {t_bonf:.2f}, un Sharpe anual de "
f"{t_bonf * np.sqrt(252 / n):.2f} en {n} sesiones")
print(f"sin corregir basta |t| > {stats.t.isf(0.025, n - 1):.2f}: "
f"Sharpe {stats.t.isf(0.025, n - 1) * np.sqrt(252 / n):.2f}")
pasoBonferroni exige |t| > 4.07, un Sharpe anual de 2.04 en 1003 sesiones
sin corregir basta |t| > 1.96: Sharpe 0.98
p umbral_bh ventaja_real
estrategia
e0518 0.001679 0.00005 True
e0598 0.002780 0.00010 False
e0133 0.003695 0.00015 True
e0316 0.004628 0.00020 False
e0124 0.004988 0.00025 False
e0462 0.005038 0.00030 False
e0542 0.005807 0.00035 False
e0401 0.006986 0.00040 FalseEl umbral de Benjamini-Hochberg para el primer p-valor es , el mismo que el de Bonferroni, y crece despacio: para el octavo, 0,0004. Los p-valores de los primeros puestos están entre 17 y 34 veces por encima de su umbral. Traducido a Sharpe: sin corregir, en cuatro años basta un Sharpe de 0,98; con Bonferroni sobre 1.000 pruebas hace falta 2,04, y ninguna de las 1.000 llega, ni siquiera la que tiene un Sharpe verdadero de 1,5.
El precio: potencia y años de datos
Las correcciones protegen de los falsos positivos y lo pagan en potencia: la probabilidad de detectar una ventaja que sí existe. Con Sharpe · , los años necesarios para que una ventaja real supere un umbral son :
umbrales = {"t 1,96 (una prueba)": 1.96, "t 3,0 (HLZ)": 3.0,
f"t {t_bonf:.2f} (Bonferroni, 1.000)": t_bonf}
anos = pd.DataFrame({nombre: {sr: (u / sr) ** 2
for sr in [0.5, 0.8, 1.0, 1.5]}
for nombre, u in umbrales.items()})
anos.rename_axis("sharpe_verdadero").round(1) t 1,96 (una prueba) t 3,0 (HLZ) t 4.07 (Bonferroni, 1.000)
sharpe_verdadero
0.5 15.4 36.0 66.4
0.8 6.0 14.1 25.9
1.0 3.8 9.0 16.6
1.5 1.7 4.0 7.4Un Sharpe verdadero de 0,5, que sería una buena estrategia en la práctica, necesita 15 años de datos para pasar una sola prueba al 5 % y 66 para pasar Bonferroni con 1.000 pruebas. Incluso un Sharpe de 1,5 necesita más de siete años. Son años de datos diarios suponiendo que la ventaja no cambia en todo ese tiempo, algo que casi nunca se cumple. La conclusión no es que haya que renunciar a las correcciones, sino a probar mil cosas: el número de pruebas es una decisión de diseño de la investigación (lección 2.2.4) y cada prueba de más sube el listón de todas.
Lo que dice la distribución de los p-valores
Si ninguna estrategia tuviera ventaja, los p-valores se repartirían de forma uniforme entre 0 y 1: unos 50 en cada tramo de 0,05. Las estrategias con ventaja deberían acumularse cerca de 0. El histograma da una idea de cuántas hipótesis nulas son verdaderas, sin saber cuáles:
conteo, _ = np.histogram(p, bins=20, range=(0, 1))
print("p-valores por tramos de 0,05:", conteo.tolist())
pi0 = (p > 0.5).mean() / 0.5
print(f"proporción estimada de nulas verdaderas (Storey, "
f"lambda = 0,5): {pi0:.2f}")p-valores por tramos de 0,05: [46, 45, 49, 48, 54, 53, 42, 57, 58, 49, 48, 48, 56, 47, 39, 57, 47, 55, 50, 52]
proporción estimada de nulas verdaderas (Storey, lambda = 0,5): 1.00El histograma es plano: el primer tramo tiene 46 p-valores, menos de los 50 que daría el azar solo. La estimación de Storey (el doble de la proporción de p-valores mayores que 0,5) sale cerca de 1: los datos son compatibles con que prácticamente todas las hipótesis nulas sean ciertas, y lo son, salvo cinco de mil. Cuando hay muchas ventajas reales, el primer tramo destaca claramente sobre los demás; aquí no hay nada que destaque.
Una última precaución: las 20 variantes de cada familia están correladas, así que 1.000 contrastes no son 1.000 pruebas independientes.
familia = verdad.loc[R.columns, "familia"].to_numpy()
C = R.corr().to_numpy()
diag = np.eye(len(familia), dtype=bool)
misma = (familia[:, None] == familia[None, :]) & ~diag
print(f"correlación media dentro de la familia: {C[misma].mean():.2f};"
f" entre familias: {C[~misma & ~diag].mean():.2f}")
mejor_de_familia = sharpe.groupby(familia).max()
print("familias cuya mejor variante supera un Sharpe de 0,98:",
(mejor_de_familia > 0.98).sum(), "de", len(mejor_de_familia))correlación media dentro de la familia: 0.50; entre familias: 0.00
familias cuya mejor variante supera un Sharpe de 0,98: 13 de 50Con una correlación de 0,5 dentro de cada familia, el número «efectivo» de pruebas está entre 50 y 1.000: Bonferroni es aún más conservador de lo necesario. Pero el problema de fondo no cambia: en 13 de las 50 familias la mejor variante supera el umbral sin corregir (un Sharpe de 0,98), y solo tres de esas trece contienen una estrategia con ventaja; en una de las tres, la variante que lo supera ni siquiera es la buena. Quien solo mira la mejor variante de su familia preferida no sabe que las otras diecinueve existían. Las técnicas que tienen en cuenta la dependencia (el Deflated Sharpe con un número efectivo de pruebas, el PBO, el reality check por bootstrap) son materia de las lecciones 2.5.4 y 2.5.5.
El «factor zoo»
En la investigación académica de la sección cruzada de rentabilidades, el problema tiene nombre. Harvey, Liu y Zhu (2016) recopilan cientos de factores publicados para explicar las rentabilidades esperadas de las acciones, argumentan que con esa minería de datos el criterio habitual de una t mayor que 2 no tiene sentido y estiman que un factor nuevo debería superar una t de 31Nota 1 · Resumen del documento de trabajo NBER w20592 (octubre de 2014), publicado en la Review of Financial Studies 29(1), 2016, pp. 5-68: «a newly discovered factor needs to clear a much higher hurdle, with a t-ratio greater than 3.0».. Es el «zoo de factores».
El universo de acciones sintético del nivel 2 permite hacer el ejercicio con la respuesta conocida
(universo2_caracteristicas.parquet, una fila por acción y fin de mes, point-in-time). El generador planta tres
primas: momentum 12-1, value (patrimonio entre capitalización) y baja volatilidad. Las demás
características no tienen prima propia: calidad (ROE), tamaño, reversión a un mes y la puntuación ESG,
un placebo puro, con dos variantes (su cambio a 1 y a 12 meses) como las que probaría cualquiera que buscara «algo»
en esa columna. Para cada característica, el IC de rangos mensual (la correlación de Spearman entre la característica a
fin de mes y la rentabilidad del mes siguiente) y su t:
car = pd.read_parquet("universo2_caracteristicas.parquet")
car = car.sort_values(["ticker", "fecha"]).reset_index(drop=True)
g = car.groupby("ticker")
# la rentabilidad del mes siguiente incluye la fila final de cada
# deslistado (sin precio, con la rentabilidad de deslistado)
car["rent_sig"] = g["rentabilidad_mes"].shift(-1)
con_precio = car["cierre"].notna()
log_r = np.log1p(car["rentabilidad_mes"].where(con_precio))
mom = log_r.groupby(car["ticker"]).transform(
lambda s: s.shift(1).rolling(11).sum())
car["momentum_12_1"] = np.expm1(mom)
pn = car["patrimonio_neto"].where(car["patrimonio_neto"] > 0)
car["value"] = np.log(pn / car["capitalizacion"])
car["calidad_roe"] = car["roe"]
car["tamano"] = np.log(car["capitalizacion"])
car["reversion_1m"] = car["rentabilidad_mes"]
car["esg"] = car["puntuacion_esg"]
car["esg_cambio_1m"] = g["puntuacion_esg"].diff()
car["esg_cambio_12m"] = g["puntuacion_esg"].diff(12)
# baja volatilidad: desviación de 252 sesiones con precios diarios
aj = pd.read_parquet("universo2_ajustados.parquet")
aj = aj.sort_values(["ticker", "fecha"])
rd = aj.groupby("ticker")["cierre_ajustado"].pct_change()
aj["vol"] = rd.groupby(aj["ticker"]).transform(
lambda s: s.rolling(252, min_periods=126).std())
car = car.merge(aj[["fecha", "ticker", "vol"]], how="left",
on=["fecha", "ticker"])
car["baja_vol"] = -car["vol"]
car = car[car["cierre"].notna()] # señales solo con precio
def ic_mensual(col):
d = car.dropna(subset=[col, "rent_sig"])
return d.groupby("fecha")[[col, "rent_sig"]].apply(
lambda x: x[col].corr(x["rent_sig"], method="spearman"))
senales = ["momentum_12_1", "value", "baja_vol", "calidad_roe",
"tamano", "reversion_1m", "esg", "esg_cambio_1m",
"esg_cambio_12m"]
zoo = {}
for s in senales:
ic = ic_mensual(s)
t_ic = ic.mean() / ic.std() * np.sqrt(len(ic))
zoo[s] = {"meses": len(ic), "ic_medio": ic.mean(), "t": t_ic,
"p": 2 * stats.norm.sf(abs(t_ic))}
zoo = pd.DataFrame(zoo).T
zoo["prima_real"] = ["sí", "sí", "sí", "no", "no", "no", "placebo",
"placebo", "placebo"]
zoo.round(4) meses ic_medio t p prima_real
momentum_12_1 227.0 0.0282 2.9587 0.0031 sí
value 239.0 0.0254 3.0688 0.0021 sí
baja_vol 233.0 0.0130 1.0602 0.2890 sí
calidad_roe 239.0 -0.0324 -4.5933 0.0000 no
tamano 239.0 -0.0064 -0.8239 0.4100 no
reversion_1m 238.0 0.0246 2.8755 0.0040 no
esg 239.0 0.0103 1.5370 0.1243 placebo
esg_cambio_1m 238.0 0.0058 0.9728 0.3306 placebo
esg_cambio_12m 227.0 0.0065 0.9939 0.3203 placeboLa tabla tiene los tres errores del zoo a la vez:
- El azar. El placebo ESG da una t de 1,54: no llega a 1,96, pero con unas pocas variantes más (otra ventana, otro universo, otro periodo) alguna lo pasaría, y su p de 0,12 ya «sugiere» algo en un informe poco exigente.
- La confusión. La característica con la t más alta de las nueve es la calidad, con −4,59, y su prima verdadera es cero. El ROE es beneficio entre patrimonio, y en este universo su correlación de rangos con el value es de −0,79: hereda la prima del value con el signo cambiado. Supera el umbral de t > 3 y cualquier corrección por pruebas múltiples, porque no es azar.
- Los efectos que no se ven. La baja volatilidad tiene una prima plantada y da una t de 1,06: las acciones volátiles tienen más beta, y en rentabilidad bruta las dos cosas se compensan. Solo aparece controlando por beta (lección 2.3.5). La reversión a un mes (t = 2,88) no tiene prima propia: hereda la continuación del momentum, de la deriva tras resultados y de las quiebras.
Las correcciones sobre estas nueve pruebas no arreglan nada de eso:
for metodo in ["bonferroni", "fdr_bh"]:
rechaza = multipletests(zoo["p"].astype(float), alpha=0.05,
method=metodo)[0]
print(f"{metodo:10}", list(zoo.index[rechaza]))
print("pasan t > 3:", list(zoo.index[zoo["t"].abs() > 3]))
def z_rangos(s):
r = s.rank()
return stats.norm.ppf((r - 0.5) / r.count())
def fama_macbeth(cols):
"""t de la media de las pendientes transversales de cada mes."""
d = car.dropna(subset=cols + ["rent_sig"]).copy()
for c in cols:
d[c] = d.groupby("fecha")[c].transform(z_rangos)
pend = []
for _, x in d.groupby("fecha"):
X = np.column_stack([np.ones(len(x)), x[cols]])
b = np.linalg.lstsq(X, x["rent_sig"], rcond=None)[0]
pend.append(b[1:])
pend = pd.DataFrame(pend, columns=cols)
return pend.mean() / pend.std() * np.sqrt(len(pend))
fm = ["momentum_12_1", "value", "baja_vol", "calidad_roe", "tamano",
"esg"]
pd.DataFrame({
"una_a_una": pd.concat([fama_macbeth([c]) for c in fm]),
"value_y_calidad": fama_macbeth(["value", "calidad_roe"]),
"todas": fama_macbeth(fm)}).reindex(fm).round(2)bonferroni ['momentum_12_1', 'value', 'calidad_roe', 'reversion_1m']
fdr_bh ['momentum_12_1', 'value', 'calidad_roe', 'reversion_1m']
pasan t > 3: ['value', 'calidad_roe']
una_a_una value_y_calidad todas
momentum_12_1 4.43 NaN 5.79
value 2.30 -0.15 2.10
baja_vol 1.02 NaN 0.95
calidad_roe -3.26 -1.80 0.17
tamano 0.05 NaN 0.29
esg 1.67 NaN 1.63Bonferroni y Benjamini-Hochberg sobre las nueve dejan las mismas cuatro: momentum, value, calidad y reversión a un mes. Dos de las cuatro no tienen prima propia, y falta la baja volatilidad, que sí la tiene. El umbral de t > 3 deja solo value y calidad: una real y una falsa, y fuera el momentum, que es real. Las regresiones de Fama-MacBeth (cada mes, una regresión transversal de la rentabilidad del mes siguiente sobre las puntuaciones por rangos de las señales, y la t de la media de las pendientes) cuentan la historia completa. Una a una, la calidad da −3,26. Controlar solo por value no la arregla: con value y calidad juntos, la calidad queda en −1,80 y el value en −0,15. Con dos señales correladas −0,79, la regresión atribuye el efecto a la que no es. Solo con todas las señales a la vez la calidad cae a 0,17, el value queda en 2,10 y el momentum sube a 5,79, porque el value, correlado negativamente con él, ya deja de atenuarlo (parte de ese momentum, según el README de los datos, viene de las cuatro quiebras, cuyos precios se guían a la baja durante más de un año). La baja volatilidad sigue sin verse (0,95: le falta el control por beta) y el placebo ESG da 1,63, cerca del umbral. Lo que separa aquí lo real de lo falso es la respuesta conocida; en datos reales, una hipótesis económica previa y otros mercados y periodos (lección 2.2.1).
Errores típicos
- Contar solo las pruebas que funcionaron. Bonferroni con porque «solo se presentan tres» no corrige nada si se probaron cuarenta.
- Leer «no significativo» como «no funciona». En esta muestra, la estrategia con Sharpe verdadero de 0,8 sale con p = 0,004 y la de 0,6, con p = 0,097: ventajas parecidas a un lado y a otro del umbral. La ausencia de evidencia no es evidencia de ausencia.
- Elegir la corrección después de ver los resultados. FWER o FDR, y qué α, se deciden antes.
- Corregir y olvidar la confusión. La calidad pasa cualquier corrección.
- Confiar en el mejor de una familia. Veinte variantes de la misma idea son veinte pruebas.
- Repetir el estudio sobre los mismos datos. Cada vuelta a la misma muestra es otra prueba, aunque no esté en el código. La única muestra limpia es la que no se ha mirado (lección 2.5.2).
Ejercicios
- Repite Benjamini-Hochberg sobre las 1.000 estrategias con q = 0,10, 0,20 y 0,30. ¿Cuántos descubrimientos hay en cada caso y cuántos son estrategias con ventaja?
Solución
for q in [0.10, 0.20, 0.30]:
rechaza = multipletests(p, alpha=q, method="fdr_bh")[0]
print(f"q = {q:.2f}: {rechaza.sum()} descubrimientos, "
f"{rechaza[es_buena].sum()} con ventaja")q = 0.10: 0 descubrimientos, 0 con ventaja
q = 0.20: 0 descubrimientos, 0 con ventaja
q = 0.30: 0 descubrimientos, 0 con ventajaNi con q = 0,30 hay descubrimientos: el primer umbral de Benjamini-Hochberg sería 0,30/1.000 = 0,0003, y el p-valor más pequeño es 0,0017; el umbral crece con el puesto, pero los p-valores siguientes crecen más deprisa. Subir q es aceptar una proporción mayor de falsos entre los elegidos a cambio de potencia, y aun así no alcanza. El problema de estos datos no es el método: cuatro años no contienen información suficiente para separar del ruido Sharpes de 0,5 a 1,5 entre mil candidatos.
- Elige las 20 estrategias con mejor Sharpe en 2022-2023 y mide su Sharpe en 2024-2025. ¿Cuántas siguen por encima de cero? ¿Qué les pasa a las que tienen ventaja?
Solución
primera = R.loc[:"2023-12-31"]
segunda = R.loc["2024-01-01":]
sr = lambda x: x.mean() / x.std() * np.sqrt(252)
elegidas = sr(primera).nlargest(20).index
fuera = sr(segunda[elegidas])
print(f"Sharpe medio de las 20 elegidas: dentro "
f"{sr(primera[elegidas]).mean():.2f}, fuera {fuera.mean():.2f}")
print("fuera de muestra con Sharpe > 0:", (fuera > 0).sum(), "de 20")
print("con ventaja entre las elegidas:",
list(elegidas[elegidas.isin(buenas)]))
print("Sharpe fuera de las cinco buenas:",
sr(segunda[buenas]).round(2).to_dict())Sharpe medio de las 20 elegidas: dentro 2.01, fuera 0.10
fuera de muestra con Sharpe > 0: 12 de 20
con ventaja entre las elegidas: ['e0133', 'e0518']
Sharpe fuera de las cinco buenas: {'e0080': 1.33, 'e0133': -0.04, 'e0258': 0.33, 'e0518': 0.85, 'e0751': 1.01}Dentro de la muestra las 20 elegidas tienen un Sharpe medio de 2,01, porque se eligieron por eso; fuera, de 0,10, y
12 de las 20 quedan por encima de cero, poco más de lo que daría el azar. Solo dos de las cinco con ventaja entran en
la selección (e0133 y e0518), y fuera dan −0,04 y 0,85: con dos años, incluso una ventaja real puede no verse. Las
otras tres, que no se eligieron, dan entre 0,33 y 1,33 en la segunda mitad. Es la forma más simple de validación (lección 2.5.2) y el motivo de que
el Sharpe dentro de la muestra de una estrategia elegida no sea una estimación de su Sharpe futuro.
- Un compañero ha probado 40 variantes de un indicador y presenta la mejor con t = 2,6 (p = 0,009). ¿Qué le dirías?
Solución
Que con 40 pruebas el p-valor de la mejor no es 0,009. Con Bonferroni, el umbral al 5 % sería 0,05/40 = 0,00125, una t de unos 3,2; con Holm, el mismo para la primera. Por azar, con 40 variantes sin ventaja se esperan unas 2 con p < 0,05, y la mejor de 40 nulas independientes pasa p = 0,009 con una probabilidad de . Si las variantes están correladas, el número efectivo de pruebas es menor y el problema algo más suave, pero sigue ahí. Le pediría el registro de las 40 pruebas, una hipótesis de por qué debería funcionar, la t corregida o un Deflated Sharpe (lección 2.5.4) y una validación en datos que no haya usado para elegir.
Footnotes
-
Resumen del documento de trabajo NBER w20592 (octubre de 2014), publicado en la Review of Financial Studies 29(1), 2016, pp. 5-68: «a newly discovered factor needs to clear a much higher hurdle, with a t-ratio greater than 3.0». ↩
Respuesta plegada en cada pregunta
Autoevaluación
Pruebas 1.000 estrategias sin ninguna ventaja con un contraste al 5 %. ¿Cuántas esperas que salgan «significativas»?
- ANinguna
- BUnas 50: el 5 % de las nulas se rechaza por azar
- CUnas 5
- DDepende del Sharpe de cada una
Ver respuesta
Respuesta B. Unas 50: el 5 % de las nulas se rechaza por azar
Esa es la definición del nivel de significación. En los datos del curso salen 46 con p < 0,05, de las que solo dos tienen ventaja real; las otras 44 son azar.
¿Qué controla la corrección de Bonferroni y qué controla la de Benjamini-Hochberg?
- ALas dos controlan lo mismo con fórmulas distintas
- BBonferroni, la probabilidad de cometer al menos un falso positivo (FWER); Benjamini-Hochberg, la proporción esperada de falsos positivos entre los descubrimientos (FDR)
- CBonferroni, la potencia; Benjamini-Hochberg, el tamaño
- DBonferroni solo sirve con contrastes independientes y Benjamini-Hochberg con cualquiera
Ver respuesta
Respuesta B. Bonferroni, la probabilidad de cometer al menos un falso positivo (FWER); Benjamini-Hochberg, la proporción esperada de falsos positivos entre los descubrimientos (FDR)
Bonferroni exige p < α/m a cada contraste. Benjamini-Hochberg ordena los p-valores y acepta el mayor k con p_(k) ≤ k·q/m: admite algunos falsos positivos a cambio de más potencia. Con dependencia arbitraria, la versión segura es Benjamini-Yekutieli.
La mejor estrategia de las 1.000 tiene un Sharpe medido de 1,58 en cuatro años y p = 0,0017. Tras Bonferroni no es significativa. ¿Significa que no tiene ventaja?
- ASí: Bonferroni demuestra que es ruido
- BNo: significa que con 1.000 pruebas y cuatro años no hay evidencia suficiente. De hecho tiene ventaja (Sharpe verdadero 1,5), pero haría falta t > 4,07, unos siete años de datos con ese Sharpe
- CSí, porque el Sharpe es menor que 2
- DNo, porque el p-valor es menor que 0,05
Ver respuesta
Respuesta B. No: significa que con 1.000 pruebas y cuatro años no hay evidencia suficiente. De hecho tiene ventaja (Sharpe verdadero 1,5), pero haría falta t > 4,07, unos siete años de datos con ese Sharpe
No rechazar no es demostrar la nula. Con t ≈ Sharpe·√años, superar 4,07 con un Sharpe de 1,5 exige (4,07/1,5)² ≈ 7,4 años; con un Sharpe de 0,5, 66 años. Las correcciones protegen de los falsos positivos al precio de no ver ventajas pequeñas.
En el universo de acciones del curso, la calidad (ROE) tiene la t más alta de todas las características (−4,59) y su prima verdadera es cero. ¿Qué la explica?
- AEl azar, que una corrección de Bonferroni eliminaría
- BLa confusión: el ROE está correlado −0,79 con el value, que sí tiene prima, y hereda su efecto con signo contrario. Ninguna corrección por pruebas múltiples lo arregla
- CUn error en los datos
- DQue la calidad tiene una prima negativa
Ver respuesta
Respuesta B. La confusión: el ROE está correlado −0,79 con el value, que sí tiene prima, y hereda su efecto con signo contrario. Ninguna corrección por pruebas múltiples lo arregla
Su t de −4,59 supera cualquier umbral de pruebas múltiples, incluido el t > 3 de Harvey, Liu y Zhu. Las correcciones tratan el azar, no la confusión. Controlar solo por value no basta (la calidad queda en −1,80); con todas las señales plantadas en la regresión cae a 0,17. Y en datos reales hace falta, sobre todo, una hipótesis económica.
¿Qué propone Harvey, Liu y Zhu (2016) para un factor nuevo en la sección cruzada de rentabilidades?
- AUsar t > 2, como siempre
- BExigir un umbral más alto, t > 3, porque cientos de factores se han probado sobre los mismos datos
- CNo publicar factores nuevos
- DUsar solo datos fuera de EE. UU.
Ver respuesta
Respuesta B. Exigir un umbral más alto, t > 3, porque cientos de factores se han probado sobre los mismos datos
Su resumen (NBER w20592; Review of Financial Studies, 2016) argumenta que con tanta minería de datos el criterio habitual de t > 2 no tiene sentido y estima que un factor nuevo debería superar t > 3.
Ideas clave
- Con 1.000 estrategias sin ventaja, unas 50 pasan p < 0,05. En los datos del curso pasan 46, y solo dos de ellas tienen ventaja real.
- Lo mejor del ruido parece talento: la segunda mejor estrategia de las 1.000 (Sharpe 1,50) no tiene ninguna ventaja.
- Bonferroni y Holm controlan la probabilidad de algún falso positivo; Benjamini-Hochberg, la proporción de falsos entre los descubrimientos. Aquí no sobrevive ninguna, ni siquiera las cinco buenas.
- El umbral es exigente porque la evidencia crece con la raíz del tiempo: t ≈ Sharpe·√años. Un Sharpe de 0,5 necesita 66 años para superar Bonferroni con 1.000 pruebas.
- En un factor zoo el azar no es el único problema: la confusión (calidad frente a value) y los efectos que no se ven sin controles (baja volatilidad) no los resuelve ninguna corrección.
Recursos
Documentación
Libros
- Harvey, C. R., Liu, Y. y Zhu, H. (2016): … and the Cross-Section of Expected Returns. Review of Financial Studies 29(1), 5-68
- Harvey, Liu y Zhu: … and the Cross-Section of Expected Returns (documento de trabajo NBER w20592, resumen)
- Benjamini, Y. y Hochberg, Y. (1995): Controlling the False Discovery Rate: A Practical and Powerful Approach to Multiple Testing. JRSS B 57(1), 289-300