2. Anatomía de un backtest con Backtrader
Nivel 12: Backtesting y Métricas de Riesgo Reales
El backtesting es un componente clave del desarrollo de sistemas de trading efectivos. Se lleva a cabo reconstruyendo, con datos históricos, las operaciones que habrían ocurrido en el pasado siguiendo las reglas de una estrategia dada. La librería Backtrader proporciona las herramientas para hacer este tipo de pruebas en Python.
El enfoque general tiene tres pasos: reunir los datos, definir la estrategia de backtesting y extraer los estadísticos de rendimiento para compararlos con un benchmark.
!pip install backtraderEmpecemos por el ejemplo canónico: un cruce de medias móviles.
import backtrader as bt
import yfinance as yf
import datetime
class MovingAverageCrossStrategy(bt.Strategy):
params = (
('short_window', 40),
('long_window', 100),
)
def __init__(self):
self.data_close = self.datas[0].close
self.short_ma = bt.indicators.SimpleMovingAverage(
self.data_close, period=self.params.short_window
)
self.long_ma = bt.indicators.SimpleMovingAverage(
self.data_close, period=self.params.long_window
)
self.crossover = bt.indicators.CrossOver(self.short_ma, self.long_ma)
def next(self):
if self.crossover > 0: # la media corta cruza por encima
self.buy()
elif self.crossover < 0: # la media corta cruza por debajo
self.sell()Dos métodos y ya tienes una estrategia. Conviene entender qué hace cada uno.
El método __init__ se ejecuta una sola vez, antes de que empiece la simulación. Aquí declaras los indicadores. Y aquí está la primera gran diferencia con el backtest ingenuo que hiciste en el Nivel 5 con pandas: cuando escribes SimpleMovingAverage(self.data_close, period=40) no estás calculando un número, estás declarando una línea que Backtrader irá rellenando barra a barra. CrossOver devuelve +1 en la barra donde la primera línea cruza al alza a la segunda, -1 cuando cruza a la baja y 0 el resto del tiempo.
El método next se ejecuta una vez por cada barra, en orden cronológico, y solo tiene acceso a la información disponible hasta esa barra incluida. Esto es lo que hace que el motor sea dirigido por eventos y lo que te protege del sesgo de anticipación: por construcción no puedes leer el futuro, porque el futuro todavía no se ha cargado en las líneas.
Un detalle importante: next no empieza en la primera barra del histórico. Backtrader espera a que todos los indicadores tengan datos suficientes. Con una media de 100 periodos, las primeras 99 barras se consumen en el periodo de calentamiento y la simulación empieza efectivamente en la barra 100.
Ahora el montaje. Cerebro es el motor que lo orquesta todo: datos, estrategia, bróker simulado y analizadores.
data_df = yf.download('AAPL', start='2020-01-01', end='2022-01-01')
data = bt.feeds.PandasData(dataname=data_df)
cerebro = bt.Cerebro()
cerebro.addstrategy(MovingAverageCrossStrategy)
cerebro.adddata(data)
cerebro.broker.set_cash(100000)
cerebro.addsizer(bt.sizers.FixedSize, stake=1000)
cerebro.broker.setcommission(commission=0.001)
print('Valor inicial de la cartera: %.2f' % cerebro.broker.getvalue())
cerebro.run()
print('Valor final de la cartera: %.2f' % cerebro.broker.getvalue())
cerebro.plot()Fíjate en que self.buy() no lleva argumentos: el tamaño lo decide el sizer. Con FixedSize y stake=1000, cada orden es de 1000 acciones, llueva o truene. Es una decisión de gestión de riesgo escondida en una línea, y volveremos sobre ella en el módulo siguiente.
Backtrader no trae estrategias predefinidas: te da un marco robusto e indicadores incorporados para que definas tú la lógica. Con ese marco puedes implementar cruces de medias, reversión a la media, momentum, Bandas de Bollinger, MACD, RSI, pairs trading, estrategias basadas en machine learning, rebalanceo de carteras o seguimiento de tendencia, entre muchas otras.
Para ampliar esta lección
- Backtesten el glosario