Saltar al contenido principal
Activa cookies de terceros para ver cotizaciones en tiempo real.

Tu primer backtest en Python en 40 líneas (y por qué el resultado es mentira)

Construimos un backtest completo en 40 líneas de Python y luego lo desmontamos: una sola línea mal escrita convierte un 1,28 % anual en un 7,10 %. Con código y datos reales.

Tu primer backtest en Python en 40 líneas (y por qué el resultado es mentira)

Escribir un backtest es sorprendentemente fácil. Cuarenta líneas de Python, tres librerías que se instalan en un minuto y veinte años de datos gratuitos bastan para poner a prueba una estrategia de inversión sobre el S&P 500. Lo difícil, y lo que casi nadie te cuenta, es que el número que aparece al final de esas cuarenta líneas es casi con total seguridad falso, y lo es en la dirección que más te gusta: hacia arriba.

Este artículo hace las dos cosas. Primero construimos el backtest, línea a línea, y lo ejecutamos de verdad sobre datos reales. Después lo desmontamos: te enseño, con cifras medidas y no con advertencias genéricas, cuánto puede inflar tu resultado un error de tres caracteres. En un caso que verás más abajo, una sola línea mal escrita convierte una rentabilidad anual del 1,28 % en un 7,10 %. La estrategia es idéntica. Lo único que cambia es que una versión mira, sin querer, hacia el futuro.

Qué vamos a construir

La estrategia es la más conocida del análisis técnico: el cruce de medias móviles. Se está invertido en el índice cuando la media de los últimos 50 días de cotización está por encima de la media de los últimos 200, y fuera del mercado cuando ocurre lo contrario. La idea de fondo es sencilla: participar de las tendencias alcistas y esquivar los mercados bajistas prolongados.

No la elijo porque funcione especialmente bien —ya verás que el resultado es discutible—, sino porque es transparente, tiene un solo tipo de señal y permite ver con claridad los cuatro errores que arruinan la mayoría de los backtests caseros. El activo será el SPY, el ETF que replica el S&P 500, entre enero de 2005 y julio de 2026: 5.428 sesiones que incluyen la crisis de 2008, la pandemia y todo lo que vino después.

El backtest, en 40 líneas

Necesitas tres librerías: pandas para manejar las series temporales, numpy para las operaciones matemáticas y yfinance para descargar los precios. Se instalan con pip install pandas numpy yfinance. Este es el programa completo:

import numpy as np
import pandas as pd
import yfinance as yf

# 1. Datos: 20 años de SPY (el ETF del S&P 500), ya ajustados por dividendos
datos = yf.download("SPY", start="2005-01-01", end="2026-08-01",
                    auto_adjust=True, progress=False)
precio = datos["Close"].squeeze()
retorno = precio.pct_change()

# 2. Regla: estar dentro si la media de 50 sesiones supera a la de 200
media_rapida = precio.rolling(50).mean()
media_lenta = precio.rolling(200).mean()
senal = (media_rapida > media_lenta).astype(int)

# 3. La línea que casi todo el mundo olvida: la señal de hoy se ejecuta mañana
posicion = senal.shift(1).fillna(0)

# 4. Costes: 5 puntos básicos cada vez que se entra o se sale
COSTE = 0.0005
operaciones = posicion.diff().abs().fillna(0)
retorno_estrategia = posicion * retorno - operaciones * COSTE

# 5. Curvas de capital
capital_estrategia = (1 + retorno_estrategia.fillna(0)).cumprod()
capital_indice = (1 + retorno.fillna(0)).cumprod()

# 6. Métricas
def resumir(capital, retornos, nombre):
    anos = (capital.index[-1] - capital.index[0]).days / 365.25
    cagr = capital.iloc[-1] ** (1 / anos) - 1
    volatilidad = retornos.std() * np.sqrt(252)
    sharpe = (retornos.mean() * 252) / volatilidad
    caida_maxima = (capital / capital.cummax() - 1).min()
    print(f"{nombre:20s} x{capital.iloc[-1]:5.2f}  CAGR {cagr:6.2%}  "
          f"Sharpe {sharpe:4.2f}  caída máxima {caida_maxima:7.2%}")

resumir(capital_estrategia, retorno_estrategia.dropna(), "Cruce de medias")
resumir(capital_indice, retorno.dropna(), "Comprar y mantener")
print(f"Operaciones: {int(operaciones.sum())} | Tiempo invertido: {posicion.mean():.1%}")

Cuarenta líneas exactas, comentarios incluidos. Al ejecutarlo, esto es lo que devuelve:

Cruce de medias      x 6.32  CAGR  8.92%  Sharpe 0.70  caída máxima -33.72%
Comprar y mantener   x 9.20  CAGR 10.84%  Sharpe 0.64  caída máxima -55.19%
Operaciones: 21 | Tiempo invertido: 75.9%

Detente un momento en estos números, porque son más interesantes de lo que parecen. La estrategia no bate al índice: quien simplemente compró y mantuvo multiplicó su capital por 9,20, mientras que el cruce de medias lo multiplicó por 6,32. Pero lo hizo con una caída máxima del 33,7 % frente al 55,2 % del índice, y con un ratio de Sharpe algo mejor (0,70 frente a 0,64). Es decir: menos rentabilidad, bastante menos dolor. Si eso es un buen negocio o no depende de una pregunta que ningún backtest puede responder por ti, que es cuánta caída eres capaz de aguantar sin vender.

Con veintiuna operaciones en veintiún años, además, es una estrategia perfectamente ejecutable por un inversor particular. Hasta aquí, todo razonable. El problema empieza cuando comparamos este código con el que la mayoría escribe la primera vez.

Primera mentira: tu código mira al futuro

Fíjate en el paso 3 del programa: posicion = senal.shift(1). Ese shift(1) desplaza la señal un día. Traducido: la decisión que tomas con el cierre de hoy solo puede aplicarse a partir de mañana, porque cuando conoces el precio de cierre el mercado ya ha cerrado. Quitar esas ocho letras es el error más común del mundo, y tiene nombre propio: sesgo de anticipación o look-ahead bias.

En el cruce de medias su efecto es modesto, porque la señal cambia veintiuna veces en dos décadas. Sin él, la rentabilidad anual pasa del 8,92 % al 9,22 %: tres décimas de mentira. Poco. Pero la fuga de información del futuro rara vez llega por una vía tan visible, y cuando llega por la puerta de atrás el efecto es devastador.

Este es un ejemplo real, y es un error que se comete constantemente. Supón que quieres comprar cuando el precio está «barato» respecto a su media histórica, y para medirlo normalizas la serie de precios:

# MAL: la media y la desviación se calculan sobre TODA la serie,
# incluidos los precios que en ese momento aún no existían
z = (precio - precio.mean()) / precio.std()

# BIEN: en cada fecha solo se usa la información disponible hasta ese día
z = (precio - precio.expanding(252).mean()) / precio.expanding(252).std()

Las dos líneas parecen equivalentes. No lo son. La primera calcula la media de veinte años de precios y la aplica al año 2005, cuando esa media era rigurosamente desconocida. Es como valorar si una acción está barata usando su cotización media de los próximos quince años. Ejecutando ambas versiones de la misma estrategia sobre los mismos datos:

Versión Capital final Rentabilidad anual Sharpe Tiempo invertido
Con fuga de datos x4,39 7,10 % 0,52 62,1 %
Sin fuga de datos x1,31 1,28 % 0,17 9,9 %

La misma idea, el mismo activo, el mismo periodo. Una versión rinde un 7,10 % anual y la otra un 1,28 %. Toda la diferencia es información que el inversor no podía tener. Si hubieras publicado la primera cifra en un foro, nadie habría sospechado nada: un 7 % anual con una estrategia sencilla es perfectamente creíble. Ese es justamente el peligro. Las mentiras de un backtest no son escandalosas; son verosímiles.

Segunda mentira: operar no es gratis

El paso 4 del código descuenta cinco puntos básicos —un 0,05 %— cada vez que se entra o se sale del mercado. Es una estimación conservadora que engloba comisión, horquilla de precios y deslizamiento. Con veintiuna operaciones en veintiún años, ese descuento se lleva apenas tres décimas de rentabilidad anual y resulta casi irrelevante.

Casi irrelevante aquí. Cambia la media rápida de 50 a 5 sesiones y la estrategia empieza a operar cientos de veces; entonces esos cinco puntos básicos se comen la estrategia entera. Esta es la asimetría que hay que entender: cuanto más atractivo parece un backtest de alta frecuencia, más determinante es el coste que no has modelado. Las estrategias que sobreviven al papel y mueren en la cuenta real casi siempre mueren aquí.

Y hay un coste que este código sigue sin recoger, y que en España es de primer orden: los impuestos. Cada venta con ganancia tributa en la base del ahorro del IRPF. Una estrategia que entra y sale veintiuna veces genera veintiún hechos imponibles que «comprar y mantener» no genera. Esa comparación de arriba, por tanto, favorece a la estrategia más de lo que debería.

Tercera mentira: elegiste los parámetros después de ver el resultado

¿Por qué 50 y 200? Porque son los números que todo el mundo usa. Pero podrían haber sido 45 y 160, o 20 y 120. Y aquí aparece el error más difícil de detectar, porque no se comete escribiendo código incorrecto, sino mirando demasiadas veces el mismo conjunto de datos.

Hice el experimento. Probé 399 combinaciones de medias rápida y lenta optimizando sobre el periodo 2005-2015, elegí la mejor, y luego comprobé cómo se habría comportado en el periodo siguiente, 2016-2026, que la optimización nunca vio. Los resultados merecen una lectura atenta:

  • La mejor combinación entre 2005 y 2015 fue SMA 45/160, con un Sharpe de 0,83.
  • Esa misma combinación, aplicada a 2016-2026, obtuvo un Sharpe de 0,75. Aguantó bastante bien.
  • Pero el Sharpe medio de las 399 combinaciones en ese segundo periodo fue de 0,73. Es decir: optimizar durante once años de datos aportó dos centésimas sobre elegir los parámetros al azar.
  • Y el remate: la combinación que mejor habría funcionado entre 2016 y 2026 fue la SMA 5/20, con un Sharpe de 1,12. En la clasificación del primer periodo ocupaba el puesto 399 de 399. La peor de todas.

Esto es más matizado que el discurso habitual sobre el overfitting, y por eso lo incluyo. En este caso concreto, la idea de fondo —seguir la tendencia— resultó ser razonablemente robusta: casi cualquier par de medias daba un Sharpe parecido. Lo que resultó ser puro azar fue la elección de los parámetros óptimos. Quien en 2016 hubiera presumido de haber «optimizado» su sistema habría estado presumiendo de nada, y quien hubiera descartado la SMA 5/20 por ser la peor de la muestra habría descartado la ganadora de la década siguiente.

La regla práctica: si tu estrategia solo funciona con unos parámetros muy concretos y se desmorona al moverlos un poco, no has encontrado una estrategia. Has encontrado una coincidencia.

Cuarta mentira: solo estás mirando a los supervivientes

Esta no aparece en nuestro backtest porque el SPY sigue vivo, pero es la que más daño hace en cuanto pasas de un índice a una cesta de acciones. Si descargas hoy las cotizaciones de las empresas del S&P 500 y pruebas tu estrategia sobre ellas desde 2005, estás probándola sobre una lista de ganadoras conocidas. Las empresas que quebraron, fueron excluidas del índice o desaparecieron en una fusión no están en tu descarga. Lehman Brothers no aparece.

El resultado es una estrategia que parece brillante porque se ha probado exclusivamente sobre las compañías que sobrevivieron. Corregirlo exige datos históricos de composición de índices que incluyan las bajas —normalmente de pago—, y es una de las razones por las que la mayoría de los backtests publicados en internet sobre carteras de acciones no valen nada.

Entonces, ¿para qué sirve un backtest?

Después de todo esto, la conclusión razonable no es tirar el código. Un backtest bien hecho sigue siendo la herramienta más útil que tienes, siempre que entiendas qué tipo de pregunta responde.

Un backtest no predice tu rentabilidad futura. Nunca. Lo que hace, y hace bien, es tres cosas: descartar ideas que no funcionan ni siquiera en el pasado —que son la inmensa mayoría—, mostrarte el perfil de riesgo de una estrategia (cuánto llega a caer, cuánto tiempo tarda en recuperarse, con qué frecuencia opera), y obligarte a formular tu idea con la precisión suficiente para escribirla en código, lo que por sí solo elimina buena parte del pensamiento mágico.

Dicho de otro modo: el backtest sirve para falsar, no para confirmar. Si tu estrategia no sobrevive al pasado, casi seguro que tampoco sobrevivirá al futuro. Que sobreviva al pasado no demuestra nada, solo te autoriza a seguir investigando.

Siete preguntas antes de creerte un backtest

Aplícalas a los tuyos y, sobre todo, a los que te enseñen otros:

  1. ¿Se ejecuta la señal después de conocerla? Busca el shift(1) o su equivalente. Si no está, el resultado no significa nada.
  2. ¿Se calcula algún estadístico sobre toda la muestra? Medias, desviaciones, máximos, normalizaciones: si usan datos posteriores a la fecha de la decisión, hay fuga.
  3. ¿Están descontados los costes? Comisión, horquilla y deslizamiento. Y si operas desde España, la fiscalidad de cada venta.
  4. ¿Cuántas variantes se probaron antes de enseñarte esta? Si la respuesta es «muchas», el resultado está contaminado por la propia búsqueda.
  5. ¿Aguanta con parámetros vecinos? Mueve las medias un 20 % arriba y abajo. Si el resultado se derrumba, era una casualidad.
  6. ¿Incluye el universo a los que desaparecieron? Si la cesta son acciones vivas de hoy, hay sesgo de supervivencia.
  7. ¿Se ha comprobado en un periodo que la optimización no vio? Sin datos fuera de muestra, no hay evidencia; hay una descripción del pasado.

El siguiente paso

Copia el código, ejecútalo y cámbiale cosas. Prueba con otro activo, mueve las medias, sube el coste por operación hasta que la estrategia deje de ser rentable —ese umbral es en sí mismo una medida de robustez muy informativa—. Y después vuelve a las siete preguntas y sé implacable con tu propio trabajo, porque nadie más va a serlo.

Todo lo que hemos visto aquí —la validación fuera de muestra, la medición correcta del riesgo, el problema de optimizar sobre datos que ya has mirado— es exactamente el contenido del Curso Básico para Quants, y en particular de su módulo de backtesting riguroso, donde hay un demostrador interactivo de overfitting que hace visible lo que aquí hemos tenido que medir a mano. Si has llegado hasta el final de este artículo, ese es tu terreno.

Todas las cifras de este artículo se han obtenido ejecutando el código sobre datos de cierre ajustados del SPY entre el 3 de enero de 2005 y el 31 de julio de 2026 (5.428 sesiones). Rentabilidades pasadas no garantizan rentabilidades futuras. Este contenido es divulgativo y no constituye asesoramiento de inversión.

Foto de Nicolás Arven

Escrito por

Analista independiente de mercados, inversión y estrategia bursátil en Alternativa Bursátil.

Nicolás Arven es analista independiente de mercados, inversión y estrategia bursátil en Alternativa Bursátil. Su enfoque combina análisis macroeconómico, lectura de mercado, gestión del riesgo y criterios de inversión a medio y largo plazo.

Este contenido tiene fines exclusivamente informativos y educativos. No constituye asesoramiento financiero personalizado ni recomendación de compra o venta de activos. Toda inversión implica riesgos y puede generar pérdidas.