Quant500 Academy

1. Sesgo de supervivencia: el cementerio invisible

Nivel 2: Los sesgos que rompen un backtest

Descargas hoy la lista de componentes del S&P 500, bajas veinte años de precios de esos quinientos tickers y montas el backtest. Es la primera línea de código de casi todos los backtests amateur que existen, y con ella ya has perdido. No por un fallo de programación: el script se ejecuta, la tabla tiene la forma correcta y la curva de capital sube. Has perdido porque la muestra sobre la que estás midiendo se seleccionó con información que en 2006 nadie tenía.

La muestra ya te llega filtrada

Para que una empresa aparezca hoy en la lista de componentes de un índice tiene que haber cumplido una condición muy exigente: seguir existiendo, seguir cotizando y seguir siendo lo bastante grande y líquida como para merecer un sitio. Aplicar esa lista hacia atrás significa que tu universo de 2006 está formado en exclusiva por empresas de las que sabemos, desde el futuro, que iban a llegar vivas hasta hoy.

Las que no llegaron no están, y desaparecieron de tu muestra por vías muy distintas:

Un índice grande rota del orden de veinte componentes al año; en dos décadas eso significa varios cientos de altas y bajas, y ninguna de las que salieron por la puerta de atrás aparece en tu descarga.

El filtro nunca apunta en las dos direcciones

La tentación es pensar que las bajas se reparten al azar y que el efecto se compensa. No se reparte al azar: la vía principal de salida de un índice es haber ido mal. Eliminar sistemáticamente a las que fueron mal deja una muestra cuya rentabilidad media está inflada por construcción, y la inflación no es un simple desplazamiento, porque también borra la cola izquierda de la distribución: desaparecen las caídas del noventa por ciento y con ellas la volatilidad, el drawdown y la asimetría reales.

Hay un matiz que conviene tener presente, porque es el argumento más razonable en contra: una parte de las bajas son adquisiciones con prima, y esas sí empujan en sentido contrario. Es cierto, y no basta. En agregado el efecto neto es claramente positivo, y hay un detalle que lo agrava: el sesgo interactúa con el criterio de tu estrategia. Si compras lo más barato por valor contable, o lo más castigado del último año, estás pidiendo justo las candidatas a desaparecer, y el universo actual ya te las ha quitado del plato.

Construir el universo con fecha

La solución no es estadística, es de datos: necesitas una tabla histórica de altas y bajas, y recalcular el universo en cada fecha de decisión.

import pandas as pd

# Una fila por pertenencia: ticker, fecha_alta, fecha_baja (NaT si sigue dentro)
historico = pd.read_csv(
    "sp500_composicion.csv",
    parse_dates=["fecha_alta", "fecha_baja"],
)

def universo_en(fecha):
    """Tickers que estaban en el indice ESE dia, no los de hoy."""
    dentro = (historico["fecha_alta"] <= fecha) & (
        historico["fecha_baja"].isna() | (historico["fecha_baja"] > fecha)
    )
    return set(historico.loc[dentro, "ticker"])

fechas = pd.date_range("2006-01-31", "2025-12-31", freq="ME")
universos = {f: universo_en(f) for f in fechas}

primero, ultimo = universos[fechas[0]], universos[fechas[-1]]
print(len(primero), len(ultimo), len(primero & ultimo))
# el solapamiento entre los dos extremos es mucho menor de lo que parece

Fíjate en la condición del NaT. Las empresas que siguen dentro tienen fecha_baja vacía, y cualquier comparación con NaT devuelve False, así que sin ese isna() las estarías expulsando a todas. Es el error más común al escribir esta función y no lanza ninguna excepción: simplemente te devuelve un universo cada vez más pequeño.

Las bajas también tienen retorno

Reconstruir el universo no basta si tus precios se acaban el día en que la empresa deja de cotizar. Si el código hace pct_change() y luego dropna(), la posición se evapora a su último precio conocido y el desplome final nunca se contabiliza. Necesitas el retorno de baja.

retornos = precios.pct_change()

for _, fila in bajas.iterrows():
    tk = fila["ticker"]
    dia = retornos.index[retornos.index <= fila["fecha_baja"]][-1]
    if fila["motivo"] == "exclusion_o_quiebra":
        # liquidacion o paso al mercado gris: retorno de salida muy negativo
        retornos.loc[dia, tk] = fila["retorno_baja"]
    elif fila["motivo"] == "opa":
        # se cobra el precio de la oferta, no cero
        retornos.loc[dia, tk] = fila["precio_oferta"] / fila["ultimo_precio"] - 1

El trabajo clásico de Shumway sobre el sesgo de exclusión en las bases de datos de precios estadounidenses situó el retorno medio de las bajas por motivos de rendimiento en el entorno del treinta por ciento negativo en los mercados tradicionales, y en cifras aún peores en el mercado electrónico. Ignorar ese número equivale a suponer que las empresas que quiebran se venden a precio de mercado el día antes.

Caso real: cuánta rentabilidad falsa se está midiendo

El terreno donde mejor se ha medido este sesgo no son las acciones sino los fondos, porque las bases de datos comerciales borraban los fondos cerrados. La literatura de los años noventa, con Malkiel y con Elton, Gruber y Blake entre los nombres de referencia, situó el exceso de rentabilidad introducido por la desaparición de los fondos muertos en un orden de magnitud que va de menos de un punto a alrededor de punto y medio anual. En las bases de datos de hedge funds las estimaciones son bastante mayores, del orden de varios puntos al año, porque allí se suma que los gestores solo reportan cuando les conviene.

Traslada eso a un backtest de acciones con el universo actual aplicado hacia atrás, cruzado además con un criterio que busca empresas en dificultades, y el orden de magnitud deja de ser decorativo: es perfectamente capaz de convertir una estrategia mediocre en una que parece batir al mercado con holgura. La regla operativa es simple y no admite atajos: si tu proveedor no te da composición histórica y datos de empresas dadas de baja, no tienes datos para hacer este backtest.

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.