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/

    Multi asset bracket orders

    General Code/Help
    2
    6
    124
    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.
    • toinou222
      toinou222 last edited by

      Hello there,

      I'm trying to backtest on multiple assets that have 'prediction' and 'signal' columns.
      I'm having some troubles to keep track of orders as i try to use manual bracket orders (1 market buy, 1 limit take profit, 1 limit stop loss).

      class custom_data_loader(btfeeds.PandasData):
          lines = ("signal","prediction",)
          params = (("signal", -1),("prediction", -1))
          datafields = btfeeds.PandasData.datafields + (["prediction", "signal",])
      
      class custom_Sizer(bt.Sizer):
            
          def __init__(self):
              self.size_per_asset = {
                      'bnbusd': 0.05,
                      'vetusd': 0.05,
                      'iotusd': 0.05,
                      'xlmusd': 0.05,
                      'ontusd': 0.05,
                      'trxusd': 0.05,
                      'xrpusd': 0.05,
                      'adausd': 0.05,
                      'ltcusd': 0.05,
                      'eosusd': 0.05,
                      'ethusd': 0.10,
                      'btcusd': 0.20
              }
      
          def _getsizing(self, comminfo, cash, data, isbuy):
              if isbuy == True:
                  size = math.floor((cash * self.size_per_asset[data._name]) / data[0])
              else:
                  size = self.broker.getposition(d).size
              return size
      
      class custom_Strategy(bt.Strategy):
          
          def log(self, txt, dt=None, vlm=None):
              dt = dt or self.datas[0].datetime.datetime(0)
              print('%s, %s' % (dt.isoformat(), txt))
      
          def __init__(self):
      
              self.inds = dict()
              self.o = dict()
              
              for i, d in enumerate(self.datas):
                  self.o[d] = None
                  self.inds[d] = dict()
                  self.inds[d]['close'] = d.close
                  self.inds[d]['prediction'] = d.prediction
                  self.inds[d]['signal'] = d.signal
      
          def notify_order(self, order):
              if order.status in [order.Submitted, order.Accepted]:
                  return    
              self.last_executed_price = order.executed.price
              
              if order.status in [order.Completed]:
                  if order.isbuy():
                      self.log(
                          'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                          (order.executed.price,
                           order.executed.value,
                           order.executed.comm))
      
                      self.buyprice = order.executed.price
                      self.buycomm = order.executed.comm
                      self.last_executed_price = order.executed.price
                      
                      
                  else:  # Sell
                      self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                               (order.executed.price,
                                order.executed.value,
                                order.executed.comm))
                      
                      self.last_executed_price = order.executed.price
                                 
                      
                  self.bar_executed = len(self)
      
              elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                  self.log('Order Canceled/Margin/Rejected')
      
              # Write down: no pending order
              self.order = None
      
      
          def notify_trade(self, trade):
              if not trade.isclosed:
                  return
      
              self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                       (trade.pnl, trade.pnlcomm))
      
          def next(self):
              for i, d in enumerate(self.datas):      
      
                  if not self.getposition(d).size: #and not self.order[d]: 
                      
                      if self.inds[d]['signal'][0]>0:
        
                          print('%s : close: %.2f, prediction: %.2f, signal: %.2f'% (d._name, self.inds[d]['close'][0], self.inds[d]['prediction'][0], self.inds[d]['signal'][0]))
                          self.log('%s: BUY MARKET CREATE, at %.2f' % (d._name, d.close[0]))
                          o1 = self.buy(data=d, exectype=bt.Order.Market, transmit=False)
                          print('%s : close: %.2f, prediction: %.2f, signal: %.2f'% (d._name, self.inds[d]['close'][0], self.inds[d]['prediction'][0], self.inds[d]['signal'][0]))
                          self.log('%s: SELL STOP CREATE, at %.2f' % (d._name, d.close[0]*0.90))
                          o2 = self.buy(data=d, price=d.close[0]*0.90, exectype=bt.Order.Stop, transmit=False, parent=o1)
                          print('%s : close: %.2f, prediction: %.2f, signal: %.2f'% (d._name, self.inds[d]['close'][0], self.inds[d]['prediction'][0], self.inds[d]['signal'][0]))
                          self.log('%s: SELL LIMIT CREATE, at %.2f' % (d._name, d.prediction[0]))
                          o3 = self.buy(data=d, price=d.prediction[0], exectype=bt.Order.Limit, transmit=True, parent=o1)
                          self.o[d] = [o1, o2, o3]
                          
      # Create cerebro entity
      cerebro = bt.Cerebro()
      
      # Add data
      #pairs = ['bnbusd','vetusd','iotusd','xlmusd','ontusd','trxusd','xrpusd','adausd','ltcusd','btcusd','eosusd','ethusd']
      pairs = ['bnbusd','ethusd', 'ltcusd']
      
      for pair in pairs:
          pathname = 'data/' + pair + '.csv'
          df = pd.read_csv(pathname, infer_datetime_format=True,
              parse_dates=['datetime'],
              dtype={
                  "open"  : "float",
                  "high"  : "float",
                  "low"   : "float",
                  "close" : "float",
                  "volume": "float"
                  , "prediction": "float"
                  , "signal": "float"
              },
              index_col=0)
          df.index = pd.to_datetime(df.datetime, format='%Y-%m-%dT%H:%M:%S.%fZ')
          df = df[['open', 'high', 'low', 'close', 'volume', 'prediction', 'signal']]
          
          bt_data = custom_data_loader(dataname=df)
      
          cerebro.adddata(bt_data, name=pair)
      
      # Add sizer
      cerebro.addsizer(custom_Sizer)
      
      # Add analyzer
      cerebro.addanalyzer(bt.analyzers.PyFolio)
      
      # Add a strategy
      cerebro.addstrategy(custom_Strategy3)
      
      # Add broker fees
      cerebro.broker.setcommission(commission=0.00075)
      
      # Set our desired cash start
      cerebro.broker.setcash(10000.0)
      
      # Print out the starting conditions
      print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
      
      # Run over everything
      strat = cerebro.run()
      
      # Print out the final result
      print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
      
      

      It seems i'm printing some stuff twice... but the main problem is that only the first market buy is trigger...

      Starting Portfolio Value: 10000.00
      bnbusd : close: 32.94, prediction: 33.10, signal: 1.00
      2020-12-26T09:26:00, bnbusd: BUY MARKET CREATE, at 32.94
      bnbusd : close: 32.94, prediction: 33.10, signal: 1.00
      2020-12-26T09:26:00, bnbusd: SELL STOP CREATE, at 29.65
      bnbusd : close: 32.94, prediction: 33.10, signal: 1.00
      2020-12-26T09:26:00, bnbusd: SELL LIMIT CREATE, at 33.10
      2020-12-26T09:27:00, BUY EXECUTED, Price: 32.94, Cost: 494.09, Comm 0.37
      2020-12-26T09:28:00, BUY EXECUTED, Price: 32.92, Cost: 493.87, Comm 0.37
      2020-12-26T09:28:00, Order Canceled/Margin/Rejected
      ethusd : close: 622.26, prediction: 624.22, signal: 1.00
      2020-12-26T10:13:00, ethusd: BUY MARKET CREATE, at 622.26
      ethusd : close: 622.26, prediction: 624.22, signal: 1.00
      2020-12-26T10:13:00, ethusd: SELL STOP CREATE, at 560.03
      ethusd : close: 622.26, prediction: 624.22, signal: 1.00
      2020-12-26T10:13:00, ethusd: SELL LIMIT CREATE, at 624.22
      2020-12-26T10:14:00, BUY EXECUTED, Price: 622.25, Cost: 622.25, Comm 0.47
      2020-12-26T10:15:00, BUY EXECUTED, Price: 621.40, Cost: 621.40, Comm 0.47
      2020-12-26T10:15:00, Order Canceled/Margin/Rejected
      ltcusd : close: 124.39, prediction: 124.98, signal: 1.00
      2020-12-30T12:27:00, ltcusd: BUY MARKET CREATE, at 124.39
      ltcusd : close: 124.39, prediction: 124.98, signal: 1.00
      2020-12-30T12:27:00, ltcusd: SELL STOP CREATE, at 111.95
      ltcusd : close: 124.39, prediction: 124.98, signal: 1.00
      2020-12-30T12:27:00, ltcusd: SELL LIMIT CREATE, at 124.98
      2020-12-30T12:28:00, BUY EXECUTED, Price: 124.38, Cost: 373.14, Comm 0.28
      2020-12-30T12:29:00, BUY EXECUTED, Price: 124.23, Cost: 372.69, Comm 0.28
      2020-12-30T12:29:00, Order Canceled/Margin/Rejected
      Final Portfolio Value: 11154.97
      

      If someone could help me figure out what i'm doing wrong... I tried to adapt the script from the multi-exemple on the blog but with no success so far.

      Thanks!!

      1 Reply Last reply Reply Quote 0
      • run-out
        run-out last edited by

        Couple of things jump out. The first is that you are creating:

        self.o[d] = [o1, o2, o3]
        

        But you don't use it anywhere. When entering next and your loop of datas you would normally check to make sure this list of live orders doesn't exists for that specific data otherwise you run the risk of filling orders twice, if your position is not filled right away.

        In this case you are using market orders so you are probably only filling once before your checking of positions stops new orders. However, you should check for any live orders in the above mentioned dictionary before allowing orders to be created. I can see you had some code commented out for this.

        def next(self):
                for i, d in enumerate(self.datas):      
                    if not self.getposition(d).size and not self.order[d]: 
        

        I would put this condition back in and manage my list better after this.

        Remove that list for the data once there are no live orders left. You can check this in the completed notify orders section:

        if len([o for o in self.o[order.data] if o.status < 4]) == 0:
            self.o[order.data] = None
        

        One other minor point, if you datas are loaded already with:

        lines = ("signal","prediction",)
        

        Then this code is not necessary.

        self.inds[d] = dict()
                    self.inds[d]['close'] = d.close
                    self.inds[d]['prediction'] = d.prediction
                    self.inds[d]['signal'] = d.signal
        

        You can simply refer to your data lines directly:

        def next(self):
            for d in self.datas:
                d.close[0]
                d.prediction[0]
                d.signal[0]
        
        1 Reply Last reply Reply Quote 2
        • toinou222
          toinou222 last edited by

          Thanks a lot for your answer!!

          I made some changes following your advices but it seems I'm still missing something...

          class custom_Strategy(bt.Strategy):
              
              def log(self, txt, dt=None):
                  dt = dt or self.datas[0].datetime.datetime(0)
                  print('%s, %s' % (dt.isoformat(), txt))
          
              def __init__(self):
                  self.inds = dict()
                  self.o = dict()
                  for i, d in enumerate(self.datas):
                      self.o[d] = None
          
              def notify_order(self, order):
                  if order.status in [order.Submitted, order.Accepted]:
                      return    
                  if order.status in [order.Completed]:
                      if order.isbuy():
                          self.log(
                              'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                              (order.executed.price,
                               order.executed.value,
                               order.executed.comm))
                      else:  # Sell
                          self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                                   (order.executed.price,
                                    order.executed.value,
                                    order.executed.comm))
                  elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                      self.log('Order Canceled/Margin/Rejected')
                  if len([o for o in self.o[order.data] if o.status < 4]) == 0:
                      self.o[order.data].clear()
          
              def notify_trade(self, trade):
                  if not trade.isclosed:
                      return
                  self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm))
          
              def next(self):
                  for i, d in enumerate(self.datas):      
                      if not self.getposition(d).size and not self.o[d]: 
                          if d.signal[0]>0:
                              self.log('-----------------------------------------------')
                              self.log('%s : close: %.2f, prediction: %.2f, signal: %.2f'% (d._name, d.close[0], d.prediction[0], d.signal[0]))
                              self.log('%s: BUY MARKET CREATED, at %.2f' % (d._name, d.close[0]))
                              o1 = self.buy(data=d, exectype=bt.Order.Market, transmit=False)
                              self.log('%s: SELL STOP CREATED, at %.2f' % (d._name, d.close[0]*0.90))
                              o2 = self.buy(data=d, price=d.close[0]*0.90, exectype=bt.Order.Stop, transmit=False, parent=o1)
                              self.log('%s: SELL LIMIT CREATED, at %.2f' % (d._name, d.prediction[0]))
                              o3 = self.buy(data=d, price=d.prediction[0], exectype=bt.Order.Limit, transmit=True, parent=o1)
                              self.o[d] = [o1, o2, o3]
                      else:
                          self.log(' %s ################## Orders already opened ##################' % d._name)
          

          I used self.o[order.data].clear() instead of = None as I got 'NoneType is not iterable' error.

          I'm not sure to understand the meaning of 'o.status < 4' because in the docs, status is Submitted, Accepted, etc... but not an int ?

          I guess the main thing I don't understand is why orders aren't all submitted as I send them all at the same time...

          Starting Portfolio Value: 10000.00
          2020-12-26T09:26:00, -----------------------------------------------
          2020-12-26T09:26:00, bnbusd : close: 32.94, prediction: 33.10, signal: 1.00
          2020-12-26T09:26:00, bnbusd: BUY MARKET CREATED, at 32.94
          2020-12-26T09:26:00, bnbusd: SELL STOP CREATED, at 29.65
          2020-12-26T09:26:00, bnbusd: SELL LIMIT CREATED, at 33.10
          2020-12-26T09:27:00, BUY EXECUTED, Price: 32.94, Cost: 500.00, Comm 0.37
          2020-12-26T09:27:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:28:00, BUY EXECUTED, Price: 32.92, Cost: 499.78, Comm 0.37
          2020-12-26T09:28:00, Order Canceled/Margin/Rejected
          2020-12-26T09:28:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:29:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:30:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:31:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:32:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:33:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:34:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:35:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:36:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:37:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:38:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:39:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:40:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:41:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:42:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:43:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:44:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:45:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:46:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:47:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:48:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:49:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:50:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:51:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T09:59:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:00:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:01:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:02:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:03:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:04:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:05:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:06:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:07:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:08:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:09:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:10:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:11:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:12:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:13:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:13:00, -----------------------------------------------
          2020-12-26T10:13:00, ethusd : close: 622.26, prediction: 624.22, signal: 1.00
          2020-12-26T10:13:00, ethusd: BUY MARKET CREATED, at 622.26
          2020-12-26T10:13:00, ethusd: SELL STOP CREATED, at 560.03
          2020-12-26T10:13:00, ethusd: SELL LIMIT CREATED, at 624.22
          2020-12-26T10:14:00, BUY EXECUTED, Price: 622.25, Cost: 899.93, Comm 0.67
          2020-12-26T10:14:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:14:00,  ethusd ################## Orders already opened ##################
          2020-12-26T10:15:00, BUY EXECUTED, Price: 621.40, Cost: 898.70, Comm 0.67
          2020-12-26T10:15:00, Order Canceled/Margin/Rejected
          2020-12-26T10:15:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:15:00,  ethusd ################## Orders already opened ##################
          2020-12-26T10:16:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:16:00,  ethusd ################## Orders already opened ##################
          2020-12-26T10:17:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:17:00,  ethusd ################## Orders already opened ##################
          2020-12-26T10:18:00,  bnbusd ################## Orders already opened ##################
          2020-12-26T10:18:00,  ethusd ################## Orders already opened ##################
          

          and it continues like this til the end of backtest without doing anything...

          Sorry for those noob questions... and thanks again for your help!

          run-out 1 Reply Last reply Reply Quote 0
          • toinou222
            toinou222 last edited by

            Hello,

            I've been trying to make it works for days but can't figure out what i'm doing wrong...
            If you can have a quick look @run-out I would really appreciate :-)

            Thanks

            1 Reply Last reply Reply Quote 0
            • run-out
              run-out @toinou222 last edited by

              @toinou222 said in Multi asset bracket orders:

              self.log('%s: SELL STOP CREATED, at %.2f' % (d._name, d.close[0]*0.90))
              o2 = self.buy(data=d, price=d.close[0]*0.90, exectype=bt.Order.Stop, transmit=False, parent=o1)
              self.log('%s: SELL LIMIT CREATED, at %.2f' % (d._name, d.prediction[0]))

              Your sell orders are actually buy orders.

              1 Reply Last reply Reply Quote 2
              • toinou222
                toinou222 last edited by

                HAHAHA so sorry I wasted your time on this... thanks a lot mate!!

                1 Reply Last reply Reply Quote 1
                • 1 / 1
                • First post
                  Last post
                Copyright © 2016, 2017, 2018 NodeBB Forums | Contributors
                $(document).ready(function () { app.coldLoad(); }); }