Backtrader: Primeros pasos con Backtesting – Analizando Alpha

retrotrader es un framework de Python de código abierto para trading y pruebas retrospectivas. Backtrader le permite concentrarse en escribir reutilizable trading estrategias, indicadores y analizadores en lugar de tener que dedicar tiempo a construir infraestructura.

Pienso en Backtrader como una navaja suiza para realizar pruebas retrospectivas. es compatible en vivo trading y rápido análisis de trading estrategias. Solía ​​usar Backtrader para mi vida trading y pruebas retrospectivas.

Backtrader también tiene gran documentación y un activo trading comunidad. Puede descargar el código de esta publicación en el Analizando Alfa Github.

Instalación de backtrader

Deberá crear un entorno conda o pip y luego instalar los paquetes que usará. Estoy agregando Pandas y SQLAlchemy ya que usaré datos de mi base de datos de valores local.

Si está interesado en compilar desde el código fuente, consulte la Guía de instalación de backtrader.

conda create --name backtrader
conda activate backtrader
conda install pandas matplotlib
conda install -c anaconda sqlalchemy
pip install backtrader

Uso de Backtrader: una descripción general rápida

Comprendamos cómo usar Backtrader. Si tiene alguna pregunta, lea el Inicio rápido de backtrader. La mayor parte de lo que he escrito aquí se puede encontrar allí.

Desarrollando y probando un trading La estrategia en Backtrader generalmente sigue cinco pasos:

  • Inicializar el motor
  • Configurar el intermediario
  • Agrega los datos
  • Crea la estrategia
  • Analizar el rendimiento
  • Inicializar el motor

    A continuación, importamos backtrader y luego creamos una instancia de él usando backtrader.Cerebro(). Luego verificamos si nuestro programa backtrader_initialize.py es la programación principal en ejecución. Si no es así y nuestro programa fue importado a otro programa, el nombre no sería main, sería __name__ == backtrader_initialize.

    Configurar el corredor

    Crear una instancia de backtrader usando backtrader.Cerebro() crea una instancia de corredor en segundo plano para mayor comodidad. Establecimos el saldo inicial en $ 1,337.00. Si no configuramos el efectivo, el saldo predeterminado es 10k. Si bien solo configuramos el efectivo en nuestra instancia de corredor, el Corredor retrotrader es robusto y admite diferentes tipos de órdenes, modelos de deslizamiento y esquemas de comisiones.

    import backtrader as bt
    
    if __name__ == '__main__':
        cerebro = bt.Cerebro()
        cerebro.broker.setcash(1337.0)
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
        cerebro.run()
        print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

    Agregar los datos

    Backtrader proporciona un montón de opciones de alimentación de datos integradas y la capacidad de crear las suyas propias. Por ejemplo, podemos agregar fácilmente datos de Yahoo Finance agregando feeds.YahooFinanceData.

    Adición de datos de Yahoo

    data = bt.feeds.YahooFinanceData(dataname='AAPL',
         fromdate=datetime(2017, 1, 1),
         todate=datetime(2017, 12, 31))

    Adición de datos CSV de Yahoo

    También podríamos haber agregado los datos de Yahoo desde un archivo CSV.

    data = btfeeds.YahooFinanceCSVData(dataname='yahoo_finance_aapl.csv')

    Adición de datos de Pandas

    Para aquellos de ustedes que siguen mi blog, saben que disfruto usando Pitón y Pandas. Podemos crear un Pandas Datafeed en Backtrader heredándolo de la clase base feed.DataBase.

    En el indicador RSI de Connors se muestra un ejemplo que utiliza datos de PostgreSQL y Pandas:

    class PandasData(feed.DataBase):
        '''
        The ``dataname`` parameter inherited from ``feed.DataBase`` is the pandas
        DataFrame
        '''
        params = (
            # Possible values for datetime (must always be present)
            #  None : datetime is the "index" in the Pandas Dataframe
            #  -1 : autodetect position or case-wise equal name
            #  >= 0 : numeric index to the colum in the pandas dataframe
            #  string : column name (as index) in the pandas dataframe
            ('datetime', None),
    
            # Possible values below:
            #  None : column not present
            #  -1 : autodetect position or case-wise equal name
            #  >= 0 : numeric index to the colum in the pandas dataframe
            #  string : column name (as index) in the pandas dataframe
            ('open', -1),
            ('high', -1),
            ('low', -1),
            ('close', -1),
            ('volume', -1),
            ('openinterest', -1),
        )

    Ejemplos de estrategias de backtrader

    Ahora que Cerebro tiene datos, creemos algunas estrategias. Las estrategias generalmente siguen un proceso de cuatro pasos:

  • Iniciación
  • Preprocesamiento
  • Procesando
  • Postprocesamiento
  • El procesamiento previo ocurre porque necesitamos procesar 15 barras (período = 15) antes de que podamos usar nuestro indicador de promedio móvil simple. Una vez que se haya completado el procesamiento previo, comenzará el procesamiento y llamará a continuación.

    class MyStrategy(bt.Strategy):
        def __init__(self):  # Initiation
            self.sma = btind.SimpleMovingAverage(period=15)  # Processing
    
        def next(self):  # Processing
            if self.sma > self.data.close:
                # Do something
                pass
            elif self.sma < self.data.close: # Post-processing
                # Do something else
                pass

    Estrategia de doble media móvil (DMA)

    Si va a seguirme, deberá instalar el módulo de solicitudes para obtener datos de Yahoo Finance.

    La primera estrategia es una estrategia simple de doble media móvil (DMA) trading Apple (AAPL) para 2017. Comenzamos con $1,337.00 y terminamos con $1,354.77. Como puede ver, el código para crear una estrategia DMA en Backtrader es más simple que en la estrategia Zipline DMA, pero como se indicó anteriormente, el análisis de rendimiento también es más simple.

    from datetime import datetime
    import backtrader as bt
    
    class SmaCross(bt.SignalStrategy):
        def __init__(self):
            sma1, sma2 = bt.ind.SMA(period=10), bt.ind.SMA(period=20)
            crossover = bt.ind.CrossOver(sma1, sma2)
            self.signal_add(bt.SIGNAL_LONG, crossover)
    
    if __name__ == '__main__':
        cerebro = bt.Cerebro()
        cerebro.addstrategy(SmaCross)
        cerebro.broker.setcash(1337.0)
        cerebro.broker.setcommission(commission=0.001)
    
        data = bt.feeds.YahooFinanceData(dataname='AAPL',
                                         fromdate=datetime(2017, 1, 1),
                                         todate=datetime(2017, 12, 31))
        cerebro.adddata(data)
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
        cerebro.run()
        print('Ending Portfolio Value: %.2f' % cerebro.broker.getvalue())
        cerebro.plot()

    Estrategia de canales Donchain

    Los canales de Donchian no están implementados en Backtrader, por lo que debemos crear un indicador heredándolo de backtrader.Indicator. Canales Donchain llevan el nombre de Richard Donchain y se pueden usar de varias maneras, pero se usan con mayor frecuencia para comprar rupturas. Heredamos bt.Indicator para crear la clase DonchainChannels y codificar la lógica. Compramos cuando el precio supera el máximo de 20 períodos y vendemos cuando el precio cae por debajo del mínimo de 20 períodos. También podemos agregar gráficos a través de tramas como se ve a continuación.

    from datetime import datetime
    import backtrader as bt
    
    
    class DonchianChannels(bt.Indicator):
        '''
        Params Note:
          - `lookback` (default: -1)
            If `-1`, the bars to consider will start 1 bar in the past and the
            current high/low may break through the channel.
            If `0`, the current prices will be considered for the Donchian
            Channel. This means that the price will **NEVER** break through the
            upper/lower channel bands.
        '''
        alias = ('DCH', 'DonchianChannel',)
    
        lines = ('dcm', 'dch', 'dcl',)  # dc middle, dc high, dc low
        params = dict(
            period=20,
            lookback=-1,  # consider current bar or not
        )
    
        plotinfo = dict(subplot=False)  # plot along with data
        plotlines = dict(
            dcm=dict(ls='--'),  # dashed line
            dch=dict(_samecolor=True),  # use same color as prev line (dcm)
            dcl=dict(_samecolor=True),  # use same color as prev line (dch)
        )
    
        def __init__(self):
            hi, lo = self.data.high, self.data.low
            if self.p.lookback:  # move backwards as needed
                hi, lo = hi(self.p.lookback), lo(self.p.lookback)
    
            self.l.dch = bt.ind.Highest(hi, period=self.p.period)
            self.l.dcl = bt.ind.Lowest(lo, period=self.p.period)
            self.l.dcm = (self.l.dch + self.l.dcl) / 2.0  # avg of the above
    
    class MyStrategy(bt.Strategy):
        def __init__(self):
            self.myind = DonchianChannels()
    
        def next(self):
            if self.data[0] > self.myind.dch[0]:
                self.buy()
            elif self.data[0] < self.myind.dcl[0]:
                self.sell()
    
    if __name__ == '__main__':
        cerebro = bt.Cerebro()
        cerebro.addstrategy(MyStrategy)
        cerebro.broker.setcash(1337.0)
        cerebro.broker.setcommission(commission=0.001)
    
        data = bt.feeds.YahooFinanceData(dataname='AAPL',
                                         fromdate=datetime(2017, 1, 1),
                                         todate=datetime(2017, 12, 31))
        cerebro.adddata(data)
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
        cerebro.run()
        print('Ending Portfolio Value: %.2f' % cerebro.broker.getvalue())
        cerebro.plot()

    Estrategia de RSI de Connors usando pandas

    Creado por Larry Connors, Estrategia RSI de Connors tres indicadores diferentes juntos. Venderemos si el RSI de Connors llega a 90 y compraremos si llega a 10. Si desea seguir esta estrategia, deberá crear una base de datos de acciones e importar los datos de acciones mediante Python.

    from datetime import datetime
    import backtrader as bt
    import pandas as pd
    import sqlalchemy
    import setup_psql_environment
    from models import Security, SecurityPrice
    
    class Streak(bt.ind.PeriodN):
        '''
        Keeps a counter of the current upwards/downwards/neutral streak
        '''
        lines = ('streak',)
        params = dict(period=2)  # need prev/cur days (2) for comparisons
    
        curstreak = 0
    
        def next(self):
            d0, d1 = self.data[0], self.data[-1]
    
            if d0 > d1:
                self.l.streak[0] = self.curstreak = max(1, self.curstreak + 1)
            elif d0 < d1:
                self.l.streak[0] = self.curstreak = min(-1, self.curstreak - 1)
            else:
                self.l.streak[0] = self.curstreak = 0
    
    class ConnorsRSI(bt.Indicator):
        '''
        Calculates the ConnorsRSI as:
            - (RSI(per_rsi) + RSI(Streak, per_streak) + PctRank(per_rank)) / 3
        '''
        lines = ('crsi',)
        params = dict(prsi=3, pstreak=2, prank=100)
    
        def __init__(self):
            # Calculate the components
            rsi = bt.ind.RSI(self.data, period=self.p.prsi)
            streak = Streak(self.data)
            rsi_streak = bt.ind.RSI(streak.data, period=self.p.pstreak)
            prank = bt.ind.PercentRank(self.data, period=self.p.prank)
    
            # Apply the formula
            self.l.crsi = (rsi + rsi_streak + prank) / 3.0
    
    class MyStrategy(bt.Strategy):
        def __init__(self):
            self.myind = ConnorsRSI()
    
        def next(self):
            if self.myind.crsi[0] <= 10:
                self.buy()
            elif self.myind.crsi[0] >= 90:
                self.sell()
    
    if __name__ == '__main__':
        cerebro = bt.Cerebro()
        cerebro.broker.setcash(1337.0)
        cerebro.broker.setcommission(commission=0.001)
    
        db = setup_psql_environment.get_database()
        session = setup_psql_environment.get_session()
    
        query = session.query(SecurityPrice).join(Security). \
            filter(Security.ticker == 'AAPL'). \
            filter(SecurityPrice.date >= '2017-01-01'). \
            filter(SecurityPrice.date <= '2017-12-31').statement
        dataframe = pd.read_sql(query, db, index_col='date', parse_dates=['date'])
        dataframe = dataframe[['adj_open',
                               'adj_high',
                               'adj_low',
                               'adj_close',
                               'adj_volume']]
        dataframe.columns = columns = ['open', 'high', 'low', 'close', 'volume']
        dataframe['openinterest'] = 0
        dataframe.sort_index(inplace=True)
    
        data = bt.feeds.PandasData(dataname=dataframe)
    
        cerebro.adddata(data)
        cerebro.addstrategy(MyStrategy)
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
        cerebro.run()
        print('Ending Portfolio Value: %.2f' % cerebro.broker.getvalue())
        cerebro.plot()

    Backtrader permite el análisis de estrategia visual mediante el uso de matplotlib para visualizar los resultados. Es fácil diseñar una estrategia y trazarla rápidamente usando cerebro.plot() antes de someter la estrategia a un análisis adicional en Zipline. Hay varias opciones cuando trazado en Backtrader.

    Alternativas de backtrader

    Si bien no soy un experto en las siguientes herramientas, escuché cosas buenas sobre QuantConnect y QuantRocket de la comunidad.

    Puedes ver como los revisé y más en el mejor python trading herramientas.

    Deja un comentario