Navigation

    Backtrader Community

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

    Mutable variables not responding with Backtrader (replication of zipline strategy)

    General Code/Help
    1
    1
    17
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • C
      chuchu last edited by

      Hi everyone,

      First I wanted to thank everyone in this community. I've been reading the forum for a while and found help for many issues I had. However, I've been running into an issue in the last weeks and cannot seem to find the answer. I've already spent days on it but really cannot find what's wrong. I want to clarify that I'm not a programming super star either so it might be something stupid but I just can't get my hands on it.
      My problem is the following. I have been trying to replicate my zipline backtesting strategy in backtrader. It's a backtesting strategy on a pool of crypto currencies. However, I'm obtaining completely different results. There might be different issues with my code but I've identified at least one of them which is that some mutable variables I use do not seem to be responding or reacting well as they do not generate any signals or difference when I delete them from the strategy. Those variables are more specifically: entry_price, entry_date and topped.

      Please find my entire code below. Only thing you need to run it are Binance API keys. If you have any idea what's wrong please let me know.

      '''
      Thanks in advance!!
      '''
      
      import pandas as pd
      from binance.client import Client
      import binance
      from datetime import datetime as dt
      import pytz
      from strategy import *
      import pandas_market_calendars as mcal
      import backtrader as bt
      import backtrader.indicators as btind
      from datetime import datetime
      import matplotlib
      import numpy as np
      
      tickers = ['ETHUSDT','BTCUSDT','LTCUSDT','XLMUSDT','EOSUSDT','XTZUSDT',
                                   'BNBUSDT','IOTAUSDT']
      
      class maCross(bt.Strategy):
          '''
          For an official backtrader blog on this topic please take a look at:
      
          https://www.backtrader.com/blog/posts/2017-04-09-multi-example/multi-example.html
      
          oneplot = Force all datas to plot on the same master.
          '''
          params = (
          ('sma7', 7),
          ('sma10', 10),
          ('sma20', 20),
          ('rsi5', 5),
          ('rsi10', 10),
          ('natr', 10),
          ('sma_vol', 10),
          ('adosc_f', 7),
          ('adosc_s', 20),
          ('oneplot', True)
          )
      
          def __init__(self):
              '''
              Create an dictionary of indicators so that we can dynamically add the
              indicators to the strategy using a loop. This mean the strategy will
              work with any numner of data feeds. 
              '''
              self.inds = dict()
              self.mutables = dict()
              for i, d in enumerate(self.datas):
                  print()
                  self.mutables[d] = dict()
                  self.mutables[d]['entry_price'] = np.nan
                  self.mutables[d]['entry_date'] = 0
                  self.mutables[d]['topped'] = False            
                  self.mutables[d]['close'] = d.close
                  
                  self.inds[d] = dict()
                  self.inds[d]['sma7'] = bt.talib.SMA(d.close, timeperiod=self.params.sma7)
                  self.inds[d]['sma7'].csv = True
                  self.inds[d]['sma10'] = bt.talib.SMA(d.close, timeperiod=self.params.sma10)
                  self.inds[d]['sma10'].csv = True
                  self.inds[d]['sma20'] = bt.talib.SMA(d.close, timeperiod=self.params.sma20)
                  self.inds[d]['sma20'].csv = True
                  self.inds[d]['rsi5'] = bt.talib.RSI(d.close, timeperiod=self.params.rsi5)
                  self.inds[d]['rsi5'].csv = True
                  self.inds[d]['rsi10'] = bt.talib.SMA(d.close, timeperiod=self.params.rsi10)
                  self.inds[d]['rsi10'].csv = True
                  self.inds[d]['natr'] = bt.talib.NATR(d.high, d.low, d.close, timeperiod=self.params.natr)
                  self.inds[d]['sma_vol'] = bt.talib.SMA(self.inds[d]['natr'], timeperiod=self.params.sma_vol)
                  self.inds[d]['sma_vol'].csv = True
                  self.inds[d]['adosc'] = bt.talib.ADOSC(d.high, d.low, d.close, d.volume, fastperiod=self.params.adosc_f, slowperiod=self.params.adosc_s)
                  self.inds[d]['adosc'].csv = True
      
                  if i > 0: #Check we are not on the first loop of data feed:
                      if self.p.oneplot == True:
                          d.plotinfo.plotmaster = self.datas[0]
      
          def next(self):
              for i, d in enumerate(self.datas):
                  dt, dn = self.datetime.date(), d._name
                  pos = self.getposition(d).size
                  self.mutables[d]['entry_date'] = self.mutables[d]['entry_date'] + 1
                  
                  # Entry functions
                  ha1_reac = self.inds[d]['rsi5'][0] < 60 and self.inds[d]['rsi5'][0] > 45
                  ha1_cond = self.inds[d]['sma7'][-5] > self.inds[d]['sma10'][-5] and self.inds[d]['sma10'][-5] > self.inds[d]['sma20'][-5] and self.inds[d]['sma_vol'][0] < 20
                  vol_ind = self.inds[d]['adosc'][0] > 0
                  entry = (vol_ind and ha1_reac and ha1_cond and self.mutables[d]['close'][0] > self.mutables[d]['close'][-10]*0.98) or self.mutables[d]['topped']==1
                  
                  # Exit functions
                  v1 = self.inds[d]['rsi10'][0] > 80 
                  v2 = self.mutables[d]['entry_price']*0.93 > self.mutables[d]['close'][0]
                  v3 = self.mutables[d]['close'][0] > self.mutables[d]['entry_price']*1.1
                  v4 = self.mutables[d]['entry_date'] >= 30
                  
                  if not pos:  # no market / no orders
                      if entry:
                          self.mutables[d]['entry_price'] = self.mutables[d]['close'][0]
                          self.mutables[d]['entry_date'] = 0 
                          self.order = self.order_target_percent(data=d, target=1)
                  elif pos > 0:
                      if v1 or v2 or v3 or v4:
                          self.order = self.order_target_size(data=d, target=0)
                      if v1 or v2 or v4:
                          self.mutables[d]['topped'] = 0
                      if v3:
                          self.mutables[d]['topped'] = 1
      
          def notify_trade(self, trade):
              dt = self.data.datetime.date()
              if trade.isclosed:
                  print('{} {} Closed: PnL Gross {}, Net {}'.format(
                                                      dt,
                                                      trade.data._name,
                                                      round(trade.pnl,2),
                                                      round(trade.pnlcomm,2)))
                  
      
      #Variable for our starting cash
      startcash = 2000
      pct_per_stock = 1.0 / len(tickers)
      
      #Create an instance of cerebro
      cerebro = bt.Cerebro()
      
      #For annual returns
      cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
      cerebro.addobserver(bt.observers.Value)
      cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.0)
      cerebro.addanalyzer(bt.analyzers.Returns)
      cerebro.addanalyzer(bt.analyzers.AnnualReturn)
      cerebro.addanalyzer(bt.analyzers.DrawDown)
      
      #Add our strategy
      cerebro.addstrategy(maCross, oneplot=False)
      
      
      data = {}
      # Prepare dataframes with needed columns
      for ticker in tickers:
          klines = client.get_historical_klines(ticker, client.KLINE_INTERVAL_1DAY, "29 Sep, 2017", "28 Dec, 2020")
          df = pd.DataFrame.from_records(klines)
          df = df.rename(columns={0:"date", 1:"open", 2:"high", 3:"low", 4:"close",5:"volume"})
          df = df[['date','open','high','low','close','volume']]
          df['date'] = pd.to_datetime(df['date'], unit='ms')
          df['date'] = df['date'].dt.tz_localize('UTC')
          df = df.set_index('date')
          df["open"] = pd.to_numeric(df["open"], downcast="float")
          df["high"] = pd.to_numeric(df["high"], downcast="float")
          df["low"] = pd.to_numeric(df["low"], downcast="float")
          df["close"] = pd.to_numeric(df["close"], downcast="float")
          df["volume"] = pd.to_numeric(df["volume"], downcast="float")    
          data = bt.feeds.PandasData(dataname=df, calendar='NYSE')
          cerebro.adddata(data, name=ticker)
      
      # Set our desired cash start
      cerebro.broker.setcash(startcash)
      cerebro.addsizer(bt.sizers.PercentSizer, percents = 10)
      #cerebro.broker.set_checksubmit(False)
      cerebro.broker.setcommission(commission=0.0015)
      
      # Write data
      cerebro.addwriter(bt.WriterFile, csv=True, out="trade_history.csv")
      #cerebro.addwriter(bt.WriterFile, csv=False)
      
      # Run over everything
      #cerebro.run(runonce=False)
      results = cerebro.run(runonce=False)
      cerebro.plot()
      
      #Get final portfolio Value
      portvalue = cerebro.broker.getvalue()
      pnl = portvalue - startcash
      
      #Print out the final result
      print('Final Portfolio Value: ${}'.format(portvalue))
      print(f"Sharpe: {results[0].analyzers.sharperatio.get_analysis()['sharperatio']:.3f}")
      print(f"Norm. Annual Return: {results[0].analyzers.returns.get_analysis()['rnorm100']:.2f}%")
      print(f"Max Drawdown: {results[0].analyzers.drawdown.get_analysis()['max']['drawdown']:.2f}%")
      print(f"Annual Return:", results[0].analyzers.annualreturn.get_analysis())
      #cerebro.plot(iplot=False, volume=False, width=20)
      
      
      1 Reply Last reply Reply Quote 0
      • 1 / 1
      • First post
        Last post
      Copyright © 2016, 2017, 2018 NodeBB Forums | Contributors
      $(document).ready(function () { app.coldLoad(); }); }