Backtrader Community

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    1. Home
    2. Mango Loco
    For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/
    M
    • Profile
    • Following 0
    • Followers 1
    • Topics 6
    • Posts 23
    • Best 1
    • Controversial 0
    • Groups 0

    Mango Loco

    @Mango Loco

    1
    Reputation
    176
    Profile views
    23
    Posts
    1
    Followers
    0
    Following
    Joined Last Online

    Mango Loco Unfollow Follow

    Best posts made by Mango Loco

    • RE: Struggling to implement custom Pandas data feed

      @ab_trader said in Struggling to implement custom Pandas data feed:

      Looks like you've created your new CustomDataLoader data class, but still use bt built-in Pandas data class:

      Gah! Thank you so much for that!

      Big thanks to anyone trying to help so far, it really is much appreciated as I am somewhat of a new coder, with a discretionary strategy that I have been using for years that I would like to turn into an algo. I have taken some python classes at Uni, but alas, I am still learning.

      SO, even after pointing to the newly created class as you suggested, I still hit an error.

      I have greatly simplified the code into one to allow someone to hopefully help me get to the bottom of this. Here it is:

      from __future__ import (absolute_import, division, print_function,
                              unicode_literals)
      
      import argparse
      
      import backtrader as bt
      import backtrader.feeds as btfeeds
      
      from make_df import get_df
      
      
      class StratData(btfeeds.DataBase):
          lines = ('datetime', 'Open', 'High', 'Low', 'Close', 'TSL_2', 'TSL_2_L/S', 'Fast_ATR', 'Slow_ATR', 'Impulse')
          params = (
              ('datetime', None),
              ('open', 'Open'),
              ('high', 'High'),
              ('low', 'Low'),
              ('close', 'Close'),
              ('volume', None),
              ('openinterest', None),
              ('TSL_2', 4),
              ('TSL_2_L/S', 5),
              ('Fast_ATR', 6),
              ('Slow_ATR', 7),
              ('Impulse', 8)
          )
      
          # if False:
          #     # No longer needed with version 1.9.62.122
          #     datafields = btfeeds.PandasData.datafields + (
          #         ['optix_close', 'optix_pess', 'optix_opt'])
      
      
      class StrategyPrint(bt.Strategy):
      
          def next(self):
              print('%03d %f %f, %f' % (
                  len(self),
                  self.data.l.Close[0],
                  self.data.l.TSL_2[0],
                  self.data.l.Impulse[0],))
      
      
      def runstrat():
          args = parse_args()
      
          # Create a cerebro entity
          cerebro = bt.Cerebro(stdstats=False)
      
          # Add a strategy
          cerebro.addstrategy(StrategyPrint)
      
          dataframe = get_df()
      
          if not args.noprint:
              print('--------------------------------------------------')
              print(dataframe)
              print('--------------------------------------------------')
      
          # Pass it to the backtrader datafeed and add it to the cerebro
          data = StratData(dataname=dataframe)
      
          cerebro.adddata(data)
      
          # Run over everything
          cerebro.run()
      
          # Plot the result
          if not args.noplot:
              cerebro.plot(style='bar')
      
      
      def parse_args():
          parser = argparse.ArgumentParser(
              description='Pandas test script')
      
          parser.add_argument('--noheaders', action='store_true', default=False,
                              required=False,
                              help='Do not use header rows')
      
          parser.add_argument('--noprint', action='store_true', default=False,
                              help='Print the dataframe')
      
          parser.add_argument('--noplot', action='store_true', default=False,
                              help='Do not plot the chart')
      
          return parser.parse_args()
      
      
      if __name__ == '__main__':
          runstrat()
      

      Running this prints my Pandas df, here's a head():

      --------------------------------------------------
                                    Open     High  ...  Slow_ATR  Impulse
      2015-01-01 22:00:00+00:00  0.81827  0.81856  ...  0.006917    green
      2015-01-04 22:00:00+00:00  0.80734  0.81074  ...  0.006949    green
      2015-01-05 22:00:00+00:00  0.80855  0.81574  ...  0.007015    green
      2015-01-06 22:00:00+00:00  0.80828  0.80902  ...  0.006871     grey
      2015-01-07 22:00:00+00:00  0.80756  0.81308  ...  0.006773     grey
      2015-01-08 22:00:00+00:00  0.81198  0.82089  ...  0.007171    green
      

      But instead of plotting/logging, it ultimately results in this error:

      Traceback (most recent call last):
        File "/Users/kayne/Desktop/PyCharm Algos/pandas_test.py", line 92, in <module>
          runstrat()
        File "/Users/kayne/Desktop/PyCharm Algos/pandas_test.py", line 71, in runstrat
          cerebro.plot(style='bar')
        File "/anaconda3/lib/python3.7/site-packages/backtrader/cerebro.py", line 996, in plot
          plotter.show()
        File "/anaconda3/lib/python3.7/site-packages/backtrader/plot/plot.py", line 795, in show
          self.mpyplot.show()
      AttributeError: 'Plot_OldSync' object has no attribute 'mpyplot'
      

      I really do apologise if this is really basic stuff, I have looked at any backtrader Pandas related topic and docs, github etc. but still can't seem to figure this out - I know it is most likely very easy!

      Thanks in advance!

      posted in General Code/Help
      M
      Mango Loco

    Latest posts made by Mango Loco

    • Getting IB data source to work

      Hi,

      I have built all my indicators, analyzers, and observers, run my backtests, and now I'm ready to look at deploying a strategy live. I've been trying for the past few days now to integrate Interactive Brokers with Backtrader to just start receiving some historical data - I've tried following all the sample scripts, blog posts, medium posts, docs, everything. However, I just cannot get it to return data other than anything FX (e.g. EUR.USD-CASH-IDEALPRO). See below for the ibtest.py version I'm using, although as mentioned above, I have tried all of the different code samples.

      from __future__ import (absolute_import, division, print_function,
                              unicode_literals)
      
      import argparse
      import datetime
      
      # The above could be sent to an independent module
      import backtrader as bt
      from backtrader.utils import flushfile  # win32 quick stdout flushing
      
      
      class TestStrategy(bt.Strategy):
          params = dict(
              smaperiod=5,
              trade=False,
              stake=10,
              exectype=bt.Order.Market,
              stopafter=0,
              valid=None,
              cancel=0,
              donotsell=False,
          )
      
          def __init__(self):
              # To control operation entries
              self.orderid = list()
              self.order = None
      
              self.counttostop = 0
              self.datastatus = 0
      
              # Create SMA on 2nd data
              self.sma = bt.indicators.MovAv.SMA(self.data, period=self.p.smaperiod)
      
              print('--------------------------------------------------')
              print('Strategy Created')
              print('--------------------------------------------------')
      
          def notify_data(self, data, status, *args, **kwargs):
              print('*' * 5, 'DATA NOTIF:', data._getstatusname(status), *args)
              if status == data.LIVE:
                  self.counttostop = self.p.stopafter
                  self.datastatus = 1
      
          def notify_store(self, msg, *args, **kwargs):
              print('*' * 5, 'STORE NOTIF:', msg)
      
          def notify_order(self, order):
              if order.status in [order.Completed, order.Cancelled, order.Rejected]:
                  self.order = None
      
              print('-' * 50, 'ORDER BEGIN', datetime.datetime.now())
              print(order)
              print('-' * 50, 'ORDER END')
      
          def notify_trade(self, trade):
              print('-' * 50, 'TRADE BEGIN', datetime.datetime.now())
              print(trade)
              print('-' * 50, 'TRADE END')
      
          def prenext(self):
              self.next(frompre=True)
      
          def next(self, frompre=False):
              txt = list()
              txt.append('%04d' % len(self))
              dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
              txt.append('%s' % self.data.datetime.datetime(0).strftime(dtfmt))
              txt.append('O: {:.5f}'.format(self.data.open[0]))
              txt.append('H: {:.5f}'.format(self.data.high[0]))
              txt.append('L: {:.5f}'.format(self.data.low[0]))
              txt.append('C: {:.5f}'.format(self.data.close[0]))
              # txt.append('{}'.format(self.data.volume[0]))
              # txt.append('{}'.format(self.data.openinterest[0]))
              # txt.append('{}'.format(self.sma[0]))
              print(', '.join(txt))
      
              if len(self.datas) > 1:
                  txt = list()
                  txt.append('%04d' % len(self))
                  dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
                  txt.append('%s' % self.data1.datetime.datetime(0).strftime(dtfmt))
                  txt.append('{}'.format(self.data1.open[0]))
                  txt.append('{}'.format(self.data1.high[0]))
                  txt.append('{}'.format(self.data1.low[0]))
                  txt.append('{}'.format(self.data1.close[0]))
                  txt.append('{}'.format(self.data1.volume[0]))
                  txt.append('{}'.format(self.data1.openinterest[0]))
                  txt.append('{}'.format(float('NaN')))
                  print(', '.join(txt))
      
              if self.counttostop:  # stop after x live lines
                  self.counttostop -= 1
                  if not self.counttostop:
                      self.env.runstop()
                      return
      
              if not self.p.trade:
                  return
      
              if self.datastatus and not self.position and len(self.orderid) < 1:
                  self.order = self.buy(size=self.p.stake,
                                        exectype=self.p.exectype,
                                        price=round(self.data0.close[0] * 0.90, 2),
                                        valid=self.p.valid)
      
                  self.orderid.append(self.order)
              elif self.position.size > 0 and not self.p.donotsell:
                  if self.order is None:
                      self.order = self.sell(size=self.p.stake // 2,
                                             exectype=bt.Order.Market,
                                             price=self.data0.close[0])
      
              elif self.order is not None and self.p.cancel:
                  if self.datastatus > self.p.cancel:
                      self.cancel(self.order)
      
              if self.datastatus:
                  self.datastatus += 1
      
          def start(self):
              if self.data0.contractdetails is not None:
                  print('Timezone from ContractDetails: {}'.format(
                        self.data0.contractdetails.m_timeZoneId))
      
              header = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume',
                        'OpenInterest', 'SMA']
              print(', '.join(header))
      
              self.done = False
      
      
      def runstrategy():
          args = parse_args([
              '--port', '7497',
              '--data0', 'TWTR',
              # '--data0', 'EUR.USD-CASH-IDEALPRO',
              '--historical',
              '--fromdate', '2018-01-02',
              # '--no-backfill_start',
              # '--no-backfill',
              '--timeframe', bt.TimeFrame.Names[bt.TimeFrame.Days],
              '--compression', '1',
              '--timezone', 'NZ',
          ])
      
          # Create a cerebro
          cerebro = bt.Cerebro()
      
          storekwargs = dict(
              host=args.host, port=args.port,
              clientId=args.clientId, timeoffset=not args.no_timeoffset,
              reconnect=args.reconnect, timeout=args.timeout,
              notifyall=args.notifyall, _debug=args.debug
          )
      
          if args.usestore:
              ibstore = bt.stores.IBStore(**storekwargs)
      
          if args.broker:
              if args.usestore:
                  broker = ibstore.getbroker()
              else:
                  broker = bt.brokers.IBBroker(**storekwargs)
      
              cerebro.setbroker(broker)
      
          timeframe = bt.TimeFrame.TFrame(args.timeframe)
          if args.resample or args.replay:
              datatf = bt.TimeFrame.Ticks
              datacomp = 1
          else:
              datatf = timeframe
              datacomp = args.compression
      
          fromdate = None
          if args.fromdate:
              dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.fromdate))
              fromdate = datetime.datetime.strptime(args.fromdate, dtformat)
      
          IBDataFactory = ibstore.getdata if args.usestore else bt.feeds.IBData
      
          datakwargs = dict(
              timeframe=datatf, compression=datacomp,
              historical=args.historical, fromdate=fromdate,
              rtbar=args.rtbar,
              qcheck=args.qcheck,
              what=args.what,
              backfill_start=not args.no_backfill_start,
              backfill=not args.no_backfill,
              latethrough=args.latethrough,
              tz=args.timezone
          )
      
          if not args.usestore and not args.broker:   # neither store nor broker
              datakwargs.update(storekwargs)  # pass the store args over the data
      
          data0 = IBDataFactory(dataname=args.data0, **datakwargs)
      
          data1 = None
          if args.data1 is not None:
              data1 = IBDataFactory(dataname=args.data1, **datakwargs)
      
          rekwargs = dict(
              timeframe=timeframe, compression=args.compression,
              bar2edge=not args.no_bar2edge,
              adjbartime=not args.no_adjbartime,
              rightedge=not args.no_rightedge,
              takelate=not args.no_takelate,
          )
      
          if args.replay:
              cerebro.replaydata(dataname=data0, **rekwargs)
      
              if data1 is not None:
                  cerebro.replaydata(dataname=data1, **rekwargs)
      
          elif args.resample:
              cerebro.resampledata(dataname=data0, **rekwargs)
      
              if data1 is not None:
                  cerebro.resampledata(dataname=data1, **rekwargs)
      
          else:
              cerebro.adddata(data0)
              if data1 is not None:
                  cerebro.adddata(data1)
      
          if args.valid is None:
              valid = None
          else:
              datetime.timedelta(seconds=args.valid)
          # Add the strategy
          cerebro.addstrategy(TestStrategy,
                              smaperiod=args.smaperiod,
                              trade=args.trade,
                              exectype=bt.Order.ExecType(args.exectype),
                              stake=args.stake,
                              stopafter=args.stopafter,
                              valid=valid,
                              cancel=args.cancel,
                              donotsell=args.donotsell)
      
          # Live data ... avoid long data accumulation by switching to "exactbars"
          cerebro.run(exactbars=args.exactbars)
      
          if args.plot and args.exactbars < 1:  # plot if possible
              cerebro.plot()
      
      
      def parse_args(pargs=None):
          parser = argparse.ArgumentParser(
              formatter_class=argparse.ArgumentDefaultsHelpFormatter,
              description='Test Interactive Brokers integration')
      
          parser.add_argument('--exactbars', default=1, type=int,
                              required=False, action='store',
                              help='exactbars level, use 0/-1/-2 to enable plotting')
      
          parser.add_argument('--plot',
                              required=False, action='store_true',
                              help='Plot if possible')
      
          parser.add_argument('--stopafter', default=0, type=int,
                              required=False, action='store',
                              help='Stop after x lines of LIVE data')
      
          parser.add_argument('--usestore',
                              required=False, action='store_true',
                              help='Use the store pattern')
      
          parser.add_argument('--notifyall',
                              required=False, action='store_true',
                              help='Notify all messages to strategy as store notifs')
      
          parser.add_argument('--debug',
                              required=False, action='store_true',
                              help='Display all info received form IB')
      
          parser.add_argument('--host', default='127.0.0.1',
                              required=False, action='store',
                              help='Host for the Interactive Brokers TWS Connection')
      
          parser.add_argument('--qcheck', default=0.5, type=float,
                              required=False, action='store',
                              help=('Timeout for periodic '
                                    'notification/resampling/replaying check'))
      
          parser.add_argument('--port', default=7496, type=int,
                              required=False, action='store',
                              help='Port for the Interactive Brokers TWS Connection')
      
          parser.add_argument('--clientId', default=None, type=int,
                              required=False, action='store',
                              help='Client Id to connect to TWS (default: random)')
      
          parser.add_argument('--no-timeoffset',
                              required=False, action='store_true',
                              help=('Do not Use TWS/System time offset for non '
                                    'timestamped prices and to align resampling'))
      
          parser.add_argument('--reconnect', default=3, type=int,
                              required=False, action='store',
                              help='Number of recconnection attempts to TWS')
      
          parser.add_argument('--timeout', default=3.0, type=float,
                              required=False, action='store',
                              help='Timeout between reconnection attempts to TWS')
      
          parser.add_argument('--data0', default=None,
                              required=True, action='store',
                              help='data 0 into the system')
      
          parser.add_argument('--data1', default=None,
                              required=False, action='store',
                              help='data 1 into the system')
      
          parser.add_argument('--timezone', default=None,
                              required=False, action='store',
                              help='timezone to get time output into (pytz names)')
      
          parser.add_argument('--what', default=None,
                              required=False, action='store',
                              help='specific price type for historical requests')
      
          parser.add_argument('--no-backfill_start',
                              required=False, action='store_true',
                              help='Disable backfilling at the start')
      
          parser.add_argument('--latethrough',
                              required=False, action='store_true',
                              help=('if resampling replaying, adjusting time '
                                    'and disabling time offset, let late samples '
                                    'through'))
      
          parser.add_argument('--no-backfill',
                              required=False, action='store_true',
                              help='Disable backfilling after a disconnection')
      
          parser.add_argument('--rtbar', default=False,
                              required=False, action='store_true',
                              help='Use 5 seconds real time bar updates if possible')
      
          parser.add_argument('--historical',
                              required=False, action='store_true',
                              help='do only historical download')
      
          parser.add_argument('--fromdate',
                              required=False, action='store',
                              help=('Starting date for historical download '
                                    'with format: YYYY-MM-DD[THH:MM:SS]'))
      
          parser.add_argument('--smaperiod', default=5, type=int,
                              required=False, action='store',
                              help='Period to apply to the Simple Moving Average')
      
          pgroup = parser.add_mutually_exclusive_group(required=False)
      
          pgroup.add_argument('--replay',
                              required=False, action='store_true',
                              help='replay to chosen timeframe')
      
          pgroup.add_argument('--resample',
                              required=False, action='store_true',
                              help='resample to chosen timeframe')
      
          parser.add_argument('--timeframe', default=bt.TimeFrame.Names[0],
                              choices=bt.TimeFrame.Names,
                              required=False, action='store',
                              help='TimeFrame for Resample/Replay')
      
          parser.add_argument('--compression', default=1, type=int,
                              required=False, action='store',
                              help='Compression for Resample/Replay')
      
          parser.add_argument('--no-takelate',
                              required=False, action='store_true',
                              help=('resample/replay, do not accept late samples '
                                    'in new bar if the data source let them through '
                                    '(latethrough)'))
      
          parser.add_argument('--no-bar2edge',
                              required=False, action='store_true',
                              help='no bar2edge for resample/replay')
      
          parser.add_argument('--no-adjbartime',
                              required=False, action='store_true',
                              help='no adjbartime for resample/replay')
      
          parser.add_argument('--no-rightedge',
                              required=False, action='store_true',
                              help='no rightedge for resample/replay')
      
          parser.add_argument('--broker',
                              required=False, action='store_true',
                              help='Use IB as broker')
      
          parser.add_argument('--trade',
                              required=False, action='store_true',
                              help='Do Sample Buy/Sell operations')
      
          parser.add_argument('--donotsell',
                              required=False, action='store_true',
                              help='Do not sell after a buy')
      
          parser.add_argument('--exectype', default=bt.Order.ExecTypes[0],
                              choices=bt.Order.ExecTypes,
                              required=False, action='store',
                              help='Execution to Use when opening position')
      
          parser.add_argument('--stake', default=10, type=int,
                              required=False, action='store',
                              help='Stake to use in buy operations')
      
          parser.add_argument('--valid', default=None, type=int,
                              required=False, action='store',
                              help='Seconds to keep the order alive (0 means DAY)')
      
          parser.add_argument('--cancel', default=0, type=int,
                              required=False, action='store',
                              help=('Cancel a buy order after n bars in operation,'
                                    ' to be combined with orders like Limit'))
      
          if pargs is not None:
              return parser.parse_args(pargs)
      
          return parser.parse_args()
      
      
      if __name__ == '__main__':
          runstrategy()
      

      Running this script, and the others that have been "shown" to work, results in:

      Server Version: 76
      TWS Time at connection:20200205 16:02:32 NZST
      --------------------------------------------------
      Strategy Created
      --------------------------------------------------
      Timezone from ContractDetails: EST (Eastern Standard Time)
      Datetime, Open, High, Low, Close, Volume, OpenInterest, SMA
      ***** STORE NOTIF: <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:cashfarm>
      ***** STORE NOTIF: <error id=-1, errorCode=2104, errorMsg=Market data farm connection is OK:usfarm>
      ***** STORE NOTIF: <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:cashhmds>
      ***** STORE NOTIF: <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:hkhmds>
      ***** STORE NOTIF: <error id=-1, errorCode=2106, errorMsg=HMDS data farm connection is OK:ushmds>
      ***** STORE NOTIF: <error id=-1, errorCode=2158, errorMsg=Sec-def data farm connection is OK:secdefhk>
      ***** DATA NOTIF: DELAYED
      ***** DATA NOTIF: DISCONNECTED
      ***** STORE NOTIF: <error id=16777217, errorCode=162, errorMsg=Historical Market Data Service error message:No market data permissions for NYSE STK>
      
      Process finished with exit code 0
      

      I just cannot for the life of me figure out why I cannot get any historical data - inside TWS (and the STORE NOTIFs above), all the data connections are working. I've tried following all of the sample scripts (even uninstalling and reinstalling TWS to "perfectly" follow along with the examples), and while those are able to get the data coming through, mine never seem too.

      Does anybody have any piece of information that may help me solve this issue? I really don't know what to do from here...

      Many thanks in advance!

      posted in General Code/Help
      M
      Mango Loco
    • RE: Adding MA to Equity Curve

      Problem solved, using numpy to calculate and feed the average of account value.

      class AcctValue(bt.Observer):
          lines = ('value', 'ma')
          params = dict(ma_len=100)
          plotinfo = dict(plot=True, subplot=True)
      
          def next(self):
              self.l.value[0] = self._owner.broker.getvalue()
              self.l.ma[0] = np.average(self.l.value.get(size=self.p.ma_len))
      
      posted in General Code/Help
      M
      Mango Loco
    • RE: Adding MA to Equity Curve

      Thanks for your help. I have the following:

      class AcctValueSMA(bt.Indicator):
          lines = ('AcctValue', 'MA')
          params = dict(ma_len=20)
      
          def __init__(self, type):
              self.l.AcctValue = self._owner.broker.get_value()
      
              if type == 'SMA':
                  self.l.MA = bt.ind.MovingAverageSimple(self.l.AcctValue, period=self.p.ma_len)
              elif type == 'EMA':
                  self.l.MA = bt.ind.ExponentialMovingAverage(self.l.AcctValue, period=self.p.ma_len)
      

      But I get:
      TypeError: 'float' object is not callable

      Is there a specific way to store and subsequently use the value of the broker in this case?

      posted in General Code/Help
      M
      Mango Loco
    • Adding MA to Equity Curve

      Hi,

      I'm wanting to add a MA to the Equity Curve. I have tried:

      class AcctValue(bt.Observer):
          alias = ('Value',)
          lines = ('value', 'ma')
      
          plotinfo = {"plot": True, "subplot": True}
          plotlines = dict(ma=dict(ls='-', color='black', _plotvalue=False, _plotvaluetag=False))
      
          def __init__(self):
              self.l.ma = bt.indicators.MovingAverageSimple(self.l.value, period=20)
      
          def next(self):
              self.l.value[0] = self._owner.broker.getvalue()
      

      That results in:
      Screen Shot 2020-01-19 at 8.45.41 PM.png

      The "ma" tag shows up, but no MA.
      Any ideas for how I could achieve the desired outcome?

      Thanks in advance!

      posted in General Code/Help
      M
      Mango Loco
    • RE: Multi-Data Long & Short

      @backtrader
      My understanding is that self.close will close the entry order. From seeing the code at the link I attached, I assumed that attached bracket orders (SL and/or TP) had to still be cancelled manually (one of them, at least).

      Commenting out both the parts of code you highlighted, results in running, and accurate looking, backtest. Could you clarify whether self.close will cancel an attached child order or whether we need to do it manually? Have I set up my ordering correctly?

      posted in General Code/Help
      M
      Mango Loco
    • RE: Multi-Data Long & Short

      @run-out
      Hey, thank you for that. I plan on transferring one of my factor models over soon so that will be very useful as a start/reference point!

      To be honest, I think it's more trouble with opening/closing the bracket orders, and keeping track of those orders. I just want something that I can trust there is no "funny business" going on where it is entering where it shouldn't, orders/trades are being accounted for etc. The "realest" possible backtest. :)

      posted in General Code/Help
      M
      Mango Loco
    • RE: Multi-Data Long & Short

      @backtrader
      Ok, I see that now. That is the same way you have shown it to be done in the link I pointed out?

      Can you give me an example of how to cleanly handle ordering and orders for a multi-data, long and short strategy? Or at least point me in the direction of getting me closer to that.

      Thanks in advance.

      posted in General Code/Help
      M
      Mango Loco
    • Multi-Data Long & Short

      Hi there,

      For a few months now I have been piecing together different parts of a framework with backtrader to handle multiple datas and be able to trade both long and short. However, I keep running into an error. Firstly, I will summarise what the code should achieve, post the code, and then post the error. Any help in helping me get this strategy off the ground would be much appreciated!

      Target:
      For each data (1D timeframe) in the trading universe:
      -> Is it a long environment or a short environment?

      If long environment and not in trade already:
      Does it meet conditions to take a long?
      -> If yes, order with or without tp
      -> If no, do nothing

      If long environment and in a trade:
      Does it meet condition to close trade?
      -> If yes, close trade
      -> If no, leave trade open / do nothing

      Exactly vice versa for the short side logic.

      Here is the code I currently have to try to achieve the above:
      note: BackTrader is the folder where I keep my own creations, such as indicators.

      import backtrader as bt
      import math
      from BackTrader import indicators
      
      class NHL(bt.Strategy):
          params = (
              ('use_tp', True),
              ('tp_percent', 12),
              ('ma_fast_len', 10),
              ('ma_slow_len', 100),
              ('ind1_period', 80)
          )
      
          def log(self, txt, dt=None):
              ''' Logging function for this strategy'''
              dt = dt or self.datas[0].datetime.date(0)
              print('%s, %s' % (dt.isoformat(), txt))
      
          def __init__(self):
              self.name = 'Multi_Long_Short'
      
              self.inds = dict()
              self.trades = dict()
              self.orders = dict()
      
              self.cash = self.broker.get_value()
              self.risk = 0.01
              self.onePlot = False
      
              for i, d in enumerate(self.datas):
                  plot_ind1 = True
                  plot_vstop1 = True
                  plot_vstop2 = False
      
                  self.inds[d] = dict()
                  self.inds[d]['ind1'] = indicators.Ind1(d, plot=plot_ind1, n=self.p.ind1_period)
                  self.inds[d]['vstop1'] = indicators.Vol_Stop(d, mult=1, plot=plot_vstop1)
                  self.inds[d]['vstop2'] = indicators.Vol_Stop(d, mult=2, plot=plot_vstop2)
      
                  use_sma = False
                  if use_sma:
                      self.inds[d]['fast_ma'] = bt.indicators.SMA(d, period=self.p.ma_fast_len)
                      self.inds[d]['slow_ma'] = bt.indicators.SMA(d, period=self.p.ma_slow_len)
                  else:
                      self.inds[d]['fast_ma'] = bt.indicators.ExponentialMovingAverage(d, period=self.p.ma_fast_len)
                      self.inds[d]['slow_ma'] = bt.indicators.ExponentialMovingAverage(d, period=self.p.ma_slow_len)
      
                  if i > 0:
                      if self.onePlot:
                          d.plotinfo.plotmaster = self.datas[0]
      
          def notify_order(self, order):
              order_name = order.info.name
              d = order.data
              data_name = d._name
              order_price = order.executed.price
      
              if order.status in [order.Submitted, order.Accepted]:
                  return
      
              if order.status in [order.Completed]:
                  self.log('%s, %s, %s, Price: %.5f' % (data_name, self.name, order_name, order_price))
      
              elif order.status in [order.Canceled]:
                  # if not order.info.name in ['Long SL', 'Short SL', 'Long Entry', 'Short Entry', 'Long TP', 'Short TP']:
                      self.log('Order Canceled')
              elif order.status in [order.Margin, order.Rejected]:
                  self.log('Order Margin/Rejected')
      
              if not order.alive():
                  orders = self.orders[d]
                  idx = orders.index(order)
                  orders[idx] = None
                  if all(x is None for x in orders):
                      orders[:] = []
      
          def notify_trade(self, trade):
              if not trade.isclosed:
                  return
      
              self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f, PORTFOLIO %.2f' %
                       (trade.pnl, trade.pnlcomm, self.broker.get_value()))
      
          def next(self):
              for i, d in enumerate(self.datas):
                  name = d._name
                  close = d.close[0]
                  close1 = d.close[-1]
                  high = d.high[0]
                  low = d.low[0]
                  self.log('{}: {}'.format(name, close))
      
                  ind1_1 = self.inds[d]['ind1'].lines.ind1_1[0]
                  ind1_2 = self.inds[d]['ind1'].lines.ind1_2[0]
                  ma_fast = self.inds[d]['fast_ma'][0]
                  ma_slow = self.inds[d]['slow_ma'][0]
                  vstop1 = self.inds[d]['vstop1'][0]
                  vstop2 = self.inds[d]['vstop2'][0]
      
                  long_environment = ma_fast > ma_slow
                  short_environment = ma_fast < ma_slow
                  go_long = close >= ind1_1
                  go_short = close <= ind1_2
                  close_long = close < ma_fast
                  close_short = close > ma_fast
      
                  position = self.getposition(d).size
                  r_percent = self.p.tp_percent / 100
      
                  if not position and not self.orders.get(d, None):
                      if long_environment and go_long:
                          sl = vstop2 if (close > vstop2) else (low - (close*0.01))
                          qty = math.floor(self.cash * self.risk / abs(close - sl))
                          qty = qty if (qty > 0) else 1 # Just in case
                          tp = close + (close*r_percent)
      
                          if self.p.use_tp:
                              # Open Long with TP
                              o1 = self.buy(data=d, price=close, size=qty, exectype=bt.Order.Limit, transmit=False)
                              o2 = self.sell(data=d, price=sl, size=o1.size, exectype=bt.Order.Stop, parent=o1, transmit=False)
                              o3 = self.sell(data=d, price=tp, size=o1.size, exectype=bt.Order.Limit, parent=o1, transmit=True)
                              o1.addinfo(name='Long Entry')
                              o2.addinfo(name='Long SL')
                              o3.addinfo(name='Long TP')
                              self.orders[d] = [o1, o2, o3]
                          else:
                              # Open Long without TP
                              o1 = self.buy(data=d, price=close, size=qty, exectype=bt.Order.Limit, transmit=False)
                              o2 = self.sell(data=d, price=sl, size=o1.size, exectype=bt.Order.Stop, parent=o1, transmit=True)
                              o1.addinfo(name='Long Entry')
                              o2.addinfo(name='Long SL')
                              self.orders[d] = [o1, o2]
      
      
                      elif short_environment and go_short:
                          sl = vstop2 if (close < vstop2) else high + (close*0.01)
                          qty = math.floor(self.cash * self.risk / abs(close - sl))
                          qty = qty if (qty > 0) else 1 # Just in case
                          tp = close - (close*r_percent)
      
                          if self.p.use_tp:
                              # Open Short with TP
                              o1 = self.sell(data=d, price=close, size=qty, exectype=bt.Order.Limit, transmit=False)
                              o2 = self.buy(data=d, price=sl, size=o1.size, exectype=bt.Order.Stop, parent=o1, transmit=False)
                              o3 = self.buy(data=d, price=tp, size=o1.size, exectype=bt.Order.Limit, parent=o1, transmit=True)
                              o1.addinfo(name='Short Entry')
                              o2.addinfo(name='Short SL')
                              o3.addinfo(name='Short TP')
                              self.orders[d] = [o1, o2, o3]
                          else:
                              # Open Short without TP
                              o1 = self.sell(data=d, price=close, size=qty, exectype=bt.Order.Limit, transmit=False)
                              o2 = self.buy(data=d, price=sl, size=o1.size, exectype=bt.Order.Stop, parent=o1, transmit=True)
                              o1.addinfo(name='Short Entry')
                              o2.addinfo(name='Short SL')
                              self.orders[d] = [o1, o2]
      
                  elif position:
                      if (position > 0) and close_long:
                          # Close Long
                          long_close = self.close(data=d)
                          if long_close:
                              long_close.addinfo(name='Close Long')
                              self.orders[d].append(long_close)
                              self.cancel(self.orders[d][1])
      
                      elif (position < 0) and close_short:
                          # Close Short
                          short_close = self.close(data=d)
                          if short_close:
                              short_close.addinfo(name='Close Short')
                              self.orders[d].append(short_close)
                              self.cancel(self.orders[d][1])
      

      The above has been pieced together mainly through docs, examples, forum posts, etc.

      Here is an example of the error that I am hitting at the moment:

      Traceback (most recent call last):
        File "/Users/.../Desktop/Python Projects/AlgoTrading/.../backtester.py", line 170, in <module>
          run_strat()
        File "/Users/.../Desktop/Python Projects/AlgoTrading/.../backtester.py", line 88, in run_strat
          run = cerebro.run(stdstats=False, tradehistory=True)
        File "/anaconda3/lib/python3.7/site-packages/backtrader/cerebro.py", line 1127, in run
          runstrat = self.runstrategies(iterstrat)
        File "/anaconda3/lib/python3.7/site-packages/backtrader/cerebro.py", line 1293, in runstrategies
          self._runonce(runstrats)
        File "/anaconda3/lib/python3.7/site-packages/backtrader/cerebro.py", line 1695, in _runonce
          strat._oncepost(dt0)
        File "/anaconda3/lib/python3.7/site-packages/backtrader/strategy.py", line 309, in _oncepost
          self.next()
        File "/Users/.../Desktop/Python Projects/AlgoTrading/.../LT_2.py", line 120, in next
          self.cancel(self.orders[d][1])
      IndexError: list index out of range
      

      I am struggling to see where it is going wrong in achieving what I have in mind, the ordering and "orderbook" are implemented almost identically to:
      https://www.backtrader.com/blog/posts/2017-04-09-multi-example/multi-example/

      I believe what I have is very close to being fully functional for my purposes - can anyone help me get past this error and/or achieve what I would like in-full?

      Please let me know if I need to provide more code.

      Thanks in advance!

      posted in General Code/Help
      M
      Mango Loco
    • RE: Plotting only chosen data?

      @backtrader
      Sorry to bring up an old thread, but I'm still having issues with one aspect of the above.

      I would like to access the returns information of each data within the strategy. I don't think this is currently possible - could you help me with this?

      Thanks in advance!

      posted in General Code/Help
      M
      Mango Loco
    • RE: Plotting only chosen data?

      @run-out
      Thanks for your input! Unfortunately, I don't know ahead of time which assets I would like to plot.

      I would like to access something rank-able, such as returns of each asset, and make plotting decisions based on that (top-performing, mean, bottom-performing - for example).

      Any ideas or tips on achieving that?

      posted in General Code/Help
      M
      Mango Loco