Quant500 Academy

4. Cómo detectar que tu backtest está contaminado

Nivel 2: Los sesgos que rompen un backtest

Los tres módulos anteriores describen enfermedades. Este es el diagnóstico: un conjunto de comprobaciones que puedes ejecutar sobre cualquier backtest antes de creértelo. Ninguna prueba individual demuestra que un resultado sea válido, pero todas juntas descartan la inmensa mayoría de los que no lo son, y son baratas.

Las señales que deberían ponerte en alerta

Un diagnóstico ejecutable

import numpy as np
import pandas as pd

def diagnostico(pnl_diario):
    """pnl_diario: Serie de retornos netos de la estrategia."""
    d = {}
    d["sharpe"] = pnl_diario.mean() / pnl_diario.std() * 252 ** 0.5
    d["pct_dias_ganadores"] = (pnl_diario > 0).mean()

    curva = (1 + pnl_diario).cumprod()
    d["max_drawdown"] = (curva / curva.cummax() - 1).min()

    # Que fraccion del beneficio total sale del 1% de dias mejores
    top = pnl_diario.nlargest(max(1, int(len(pnl_diario) * 0.01))).sum()
    d["concentracion_top1pct"] = top / pnl_diario.sum()

    # Linealidad: R2 de la curva de capital contra el tiempo
    y = np.log(curva.values)
    x = np.arange(len(y))
    d["r2_curva"] = np.corrcoef(x, y)[0, 1] ** 2
    return pd.Series(d)

Un r2_curva por encima de 0,98 en una estrategia de acciones es una bandera roja, no un logro: significa que el beneficio llega con una regularidad que el mercado no tiene. Y una concentracion_top1pct cercana o superior a uno indica que sin un puñado de días la estrategia no existe.

Estas cifras se leen en conjunto y contra una referencia, nunca sueltas. La referencia más barata es el propio índice sobre el mismo periodo: calcula el diagnóstico para comprar y mantener y compara. Si tu estrategia tiene la mitad de drawdown, el triple de Sharpe y un porcentaje de días ganadores diez puntos superior, no has encontrado un borde, has encontrado un error. La segunda referencia es una versión aleatorizada de tu propia señal: baraja las posiciones manteniendo la exposición media y repite el cálculo unas cuantas veces. Si el resultado barajado se parece demasiado al original, lo que estás midiendo es la exposición al mercado, no tu señal.

La prueba del retraso, que es la más informativa de todas

for retraso in [0, 1, 2, 3]:
    pnl = senal.shift(retraso) * retornos
    s = pnl.mean() / pnl.std() * 252 ** 0.5
    print(retraso, round(s, 2))

# 0 -> 3.10   1 -> 0.28   2 -> 0.21   3 -> 0.19

Ese patrón es inconfundible: todo el resultado vive en el retraso cero, es decir, en información del mismo instante de la decisión. Una estrategia con contenido real degrada de forma suave, porque su señal predice varios días hacia adelante y no solo el siguiente. Si tu resultado se desploma al añadir un día, el problema no es que el mercado sea rápido: es que estabas mirando la respuesta.

Meseta contra pico

rejilla = {}
for ventana in range(5, 61, 5):
    for umbral in [0.5, 1.0, 1.5, 2.0]:
        rejilla[(ventana, umbral)] = backtest(ventana, umbral).sharpe

tabla = pd.Series(rejilla).unstack()
print(tabla.round(2))
print("fraccion de celdas por encima de 1:", (tabla > 1).mean().mean())

Lo que buscas no es la celda máxima, es una región contigua de celdas decentes. Un efecto económico real no se apaga porque cambies la ventana de veintiuno a veintitrés. Si la rejilla es un desierto con un géiser, la conclusión no es que hayas encontrado el parámetro óptimo, sino que has encontrado la coincidencia que mejor encaja con este histórico concreto.

Lo que ninguna de estas pruebas arregla

Conviene ser honesto sobre los límites. Estas comprobaciones detectan fugas y sobreajuste; no detectan que has probado cuatrocientas ideas y estás presentando la mejor, porque para eso hace falta un dato que no está en el backtest: el recuento de intentos. Tampoco detectan una fuga que actúe de forma uniforme en toda la muestra, como una normalización global o un universo formado con la composición de hoy, porque esas sobreviven a la prueba del retraso sin inmutarse. Por eso el diagnóstico se combina con el registro previo del que hablaba el módulo anterior.

Caso real: cuántos intentos hacen falta para fabricar un Sharpe

Bailey, Borwein, Lopez de Prado y Zhu demostraron algo que conviene tener siempre presente: con un histórico corto, del orden de unos pocos años de datos diarios, y un número modesto de variantes probadas, se puede obtener un backtest con un Sharpe en el entorno de uno cuyo rendimiento esperado fuera de muestra es exactamente cero. No hace falta mala fe ni miles de intentos; basta con la exploración normal de un investigador razonable que no lleva la cuenta.

De ahí nace el Sharpe deflactado, que ajusta el resultado por el número de pruebas realizadas y por la longitud y la forma de la serie. La conclusión práctica coincide con la de Campbell Harvey al pedir listones de significación más altos: un backtest sin el recuento de intentos que lo acompaña no es un resultado, es una anécdota bien formateada. Antes de arriesgar dinero, ejecuta el diagnóstico, ejecuta la prueba del retraso, mira la rejilla entera y escribe cuántas veces lo intentaste.

Para ampliar esta lección

El resto de Quant500

Lo que estás aprendiendo, aplicado al índice todos los días:

La cartera del modelo, cada semana en tu correo

Cada sábado, su semana en un minuto: cómo fue, lo mejor y lo peor, y qué directivos compraron con su dinero. Y el día que el modelo se rehace, qué entra y qué sale. Es una simulación, no un consejo. Como mucho un correo a la semana, sin publicidad y sin cuenta; te das de baja con un clic. Lo hace una persona: Pol Gracia.

Cartera modelo con resultados simulados, no una recomendación personalizada. Tu correo se usa solo para esto: privacidad.

Detrás de la Academia y del resto de Quant500 hay una sola persona: Pol Gracia Serrahima.