Backtrader Community

    • 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/

    Live IBKR - multi symbol, different way + plot issue

    General Code/Help
    1
    1
    113
    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.
    • M
      mmbot last edited by

      Trying to do multi symbol in live trade. Not adding data to a strat but trading multiple symbols. I have seen enumerating in the strategy as the default way to do this. I am doing it this waym wonder if anyone tried it this way?

      from __future__ import (absolute_import, division, print_function, unicode_literals)
      import backtrader as bt
      from MyStratLive import BaseStrategy
      from helper import *
      import datetime
      import pytz
      from backtrader_plotting import Bokeh
      from backtrader_plotting.schemes import Tradimo
      
      
      def saveplots(cerebro, numfigs=1, iplot=True, start=None, end=None, width=16, height=9, dpi=300, tight=False, use=None, file_path='', **kwargs):
          from backtrader import plot
          if cerebro.p.oldsync:
              plotter = plot.Plot_OldSync(**kwargs)
          else:
              plotter = plot.Plot(**kwargs)
      
          figs = []
          for stratlist in cerebro.runstrats:
              for si, strat in enumerate(stratlist):
                  rfig = plotter.plot(strat, figid=si * 100,
                                      numfigs=numfigs, iplot=iplot,
                                      start=start, end=end, use=use)
                  figs.append(rfig)
      
          for fig in figs:
              for f in fig:
                  f.savefig(file_path, bbox_inches='tight')
          return figs
      
      
      def main():
       symbols = ['AMD', 'TSLA']
      
          backfill_date = get_backfill_date()   # date & time does not work. Only Date works
          print('backfill from: {}'.format(backfill_date))
          store = bt.stores.IBStore(host='127.0.0.1', clientId=10, port=7497, notifyall=False, reconnect=-1, timeout=3.0, timeoffset=True, timerefresh=60.0)
      
          cerebro = bt.Cerebro(stdstats=False)
          data = []
          for x, each_symbol in enumerate(symbols):
      
              data.append(store.getdata(dataname=each_symbol, sectype='STK', exchange='SMART', currency='USD', timeframe=bt.TimeFrame.Minutes, compression=5,
                                      historical=False, backfill=True, fromdate=backfill_date, rtbar=True, qcheck=0.5, backfill_start=True,
                                      useRTH=False, latethrough=False, tz='US/Eastern')
                          )
      
              cerebro.resampledata(data[x], timeframe=bt.TimeFrame.Minutes, compression=5, name=each_symbol)
              cerebro.addstrategy(BaseStrategy, data_id=data[x]._id-1)  # data_id=0 1 2 etc
      
          cerebro.broker = store.getbroker()
          cerebro.run()
          print('ended, come out to main last line')
      
          now = datetime.datetime.now(pytz.timezone('US/Eastern'))
      
          b = Bokeh(style='bar', barup='green', bardown='grey', fmt_x_ticks='%d-%b-%y %H:%M', fmt_x_data='%d-%b-%y %H:%M', valuetags=False, volume=True, plot_mode='multiple',
                    scheme=Tradimo(), tabs='multi', output_mode='save', filename='charts\\' + '{} {}-{}-{}.html'.format(each_symbol, now.day, now.month, now.year))
          cerebro.plot(b)
          cerebro.plot(style='candlestick', barup='green', bardown='grey', fmt_x_ticks='%d-%b-%y %H:%M', fmt_x_data='%d-%b-%y %H:%M', valuetags=False, volume=True)
      

      and strategy file, MyStratLive.py only trying to get it to run properly no strat yet:

      import backtrader as bt
      from datetime import datetime
      from pytz import timezone
      from helper import *
      
      
      class BaseStrategy(bt.Strategy):  # inherit MyStrat
          params = dict(data_id=None, atrperiod=14, emaperiod=200, rsiperiod=30, vix_threshold=25,
                        risk=2, slmultiple=1.75, trailmultiple=1, multiplenonvolatile=1, multiplevolatile=1.75)
      
          def __init__(self):
              self.notoptimize = True
              self.data0 = self.datas[self.p.data_id]
              symbol = self.data0._name
              self.early_close_days = get_early_close_days()
              self.long_parent_order, self.long_stoploss, self.long_partial, self.long_trailstop, self.close_posn = None, None, None, None, None
              self.short_parent_order, self.short_stoploss, self.short_partial, self.short_trailstop, self.early_exit = None, None, None, None, None
      
              self.data_live = False
              self.entered = -1
              self.maxdd = 0
              self.numtrades = 0
              self.tradepnl = 0
              self.lasttradewinloss = 0
              self.lastclosedlen = 0
      
              self.tws_parent_exe_px = None
              self.move_to_position_px = False
              self.trail_stop_started = False
              self.ATR_at_exe = -1
              self.action = 'nothing'
              self.atr = bt.ind.AverageTrueRange(self.data0, period=self.p.atrperiod)
              self.ema200 = bt.ind.EMA(self.data0, period=self.p.emaperiod)
      
          def notify_data(self, data, status, *args, **kwargs):
              print('*' * 5, 'DATA NOTIFY:', data._getstatusname(status), *args)
              if status == data.LIVE:
                  self.data_live = True
      
          def notify_store(self, msg, *args, **kwargs):
              print('*' * 5, 'STORE NOTIFY:', msg)
      
          def notify_order(self, order):
              if order.status in [order.Cancelled]:
                  if self.notoptimize: print('Order Cancelled...ref: {}, name: {}'.format(order.ref, order.info['name']))
      
              if order.status in [order.Margin, order.Rejected]:
                  if self.notoptimize: print('Order Margin/Rejected', order.ref)
                  return
      
              if order.status in [order.Submitted]:  # explain status 1
                  # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                  if self.notoptimize: print('Order Submitted...', order.ref, datetime.datetime.now(timezone('US/Eastern')))
                  return
      
              if order.status in [order.Accepted]:  # explain status 2
                  # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                  if self.notoptimize: print('Order Accepted...', order.ref)
                  return
      
              if order.status in [order.Completed]:
                  value = order.executed.value + order.executed.comm
                  entry_price = round(abs(value/order.executed.size), 2)
                  if self.notoptimize:
                      print('-'*90, 'EXECUTED ORDER', '-'*90)
                      print('{} EXE-D, REF {}, AT {}: {:d} shares of {}, TWS Px: {:.2f}, Entry Px incl Comm: {:.2f}, Value: {:.2f}, Comm: {:.2f}, PNL Aft Comm: {:.2f}'.format(
                            order.info['name'], order.ref, self.data0.datetime.datetime(0), order.executed.size, self.data0._name,
                            order.executed.price, entry_price, order.executed.value, order.executed.comm, order.executed.pnl))
                      print('-' * 194)
      
                  if 'Main-cond' in order.info['name']:  # main order opened
                      self.tws_parent_exe_px = round(order.executed.price, 2)
                      self.entered = len(self.data0)
                      self.ATR_at_exe = self.atr[0]
      
                  if not self.position and ('Main-cond' not in order.info['name']):  # todo -> added this 9 mar. bcos if order open and close on same bar
                      if self.notoptimize: print('came in to reset everything')
                      if self.long_parent_order is not None: self.cancel(self.long_parent_order)
                      if self.long_stoploss is not None: self.cancel(self.long_stoploss)
                      if self.long_partial is not None: self.cancel(self.long_partial)
                      if self.long_trailstop is not None: self.cancel(self.long_trailstop)
                      if self.close_posn is not None: self.cancel(self.close_posn)
                      if self.short_parent_order is not None: self.cancel(self.short_parent_order)
                      if self.short_stoploss is not None: self.cancel(self.short_stoploss)
                      if self.short_partial is not None: self.cancel(self.short_partial)
                      if self.short_trailstop is not None: self.cancel(self.short_trailstop)
                      if self.early_exit is not None: self.cancel(self.early_exit)
      
                      self.long_parent_order, self.long_stoploss, self.long_partial, self.long_trailstop, self.close_posn = None, None, None, None, None
                      self.short_parent_order, self.short_stoploss, self.short_partial, self.short_trailstop, self.early_exit = None, None, None, None, None
                      self.move_to_position_px = False
                      self.trail_stop_started = False
      
          def notify_trade(self, trade):
              if trade.isclosed:
                  if self.notoptimize:
                      print('*' * 50, 'TRADE COMPLETE', '*' * 50)
                      print('DT:{}, Symbol: {}, Held: {}, PNL: {:.2f}, PNL after Comm: {:.2f}, Total comm both sides: {:.2f}'
                            .format(datetime.datetime.now(timezone('US/Eastern')), trade.data._name, len(self.data0) - self.entered, trade.pnl, trade.pnlcomm, abs(trade.pnlcomm)-abs(trade.pnl)))
                      print('*' * 116)
                  self.tradepnl = trade.pnl
                  self.numtrades += 1
                  self.lasttradewinloss = 1 if trade.pnl > 0 else -1
                  self.lastclosedlen = len(self.data0)
      
          # ************TURN OFF FOR PANDAS DATA**************************************************************************************
          def start(self):
              pass
              if self.data0.contractdetails is not None:
                  print('Timezone from ContractDetails: {}'.format(self.data0.contractdetails.m_timeZoneId))
      
              header = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume']
              print(', '.join(header))
          # ***************************************************************************************************************************
      
          def logdata(self, trigger):
              txt = list()
              dtfmt = '%Y-%m-%dT%H:%M:%S'
              txt.append('{}'.format(self.data0._name))
              txt.append('{:4d}'.format(len(self.data0)))
              txt.append('{}'.format(self.data0.datetime.datetime(0).strftime(dtfmt)))
              txt.append('  {:.2f}'.format(self.data0.open[0]))
              txt.append('{:.2f}'.format(self.data0.high[0]))
              txt.append('{:.2f}'.format(self.data0.low[0]))
              txt.append('{:.2f}'.format(self.data0.close[0]))
      
              txt.append('  {:.2f}'.format(self.ema200.l.ema[0]))
              txt.append('{:.2f}'.format(self.atr[0]))
      
              txt.append('  {}'.format(self.position.size))
              txt.append('{:.2f}'.format(self.tradepnl))
      
              if self.notoptimize: print(','.join(txt))
      
          def prenext(self):
              self.next(frompre=True)
      
          def next(self, frompre=False):
      
              # ************TURN OFF FOR PANDAS DATA**************************************************************************************
      
      
              if datetime.datetime.now(timezone('US/Eastern')).time() > datetime.time(16, 30):
                  self.env.runstop()  # stop prog at 1940 NY time
              else:
                  pass
              # *************************************************************************************************************************
      
              self.logdata(trigger)
      
              if not self.position:
                  self.action = 'nothing'
                  sl_px, exe_size = None, None
                  # **************************************************************My Strategy here later***************************************************************
                
                  if self.action != 'nothing':
                      if self.long_parent_order is not None: self.cancel(self.long_parent_order)
                      if self.long_stoploss is not None: self.cancel(self.long_stoploss)
                      if self.short_parent_order is not None: self.cancel(self.short_parent_order)
                      if self.short_stoploss is not None: self.cancel(self.short_stoploss)
      
                      self.long_parent_order, self.short_parent_order = None, None
                      self.long_stoploss, self.short_stoploss = None, None
      
                      if self.action == 'long' and exe_size is not None:
                          if self.notoptimize:
                              print('main stoploss to use: ', sl_px)
                              print('price going long decide on: {:.2f} '.format(self.data0.close[0]), self.data0.datetime.datetime(0))
      
                          self.long_parent_order = self.buy(exectype=bt.Order.Market, size=exe_size, transmit=False, valid=datetime.datetime.now() + datetime.timedelta(seconds=120))
                          self.long_stoploss = self.sell(price=sl_px, exectype=bt.Order.Stop, size=exe_size, transmit=True, parent=self.long_parent_order)
                          self.long_parent_order.addinfo(name='Parent Long Main-cond')
                          self.long_stoploss.addinfo(name='Parent Long Main Stoploss Triggered')  # explain this stop loss is not the most accurate due to slippage
      
                      elif self.action == 'short' and exe_size is not None:
                          if self.notoptimize:
                              print('price going short decide on: {:.2f} '.format(self.data0.close[0]), self.data0.datetime.datetime(0))
      
                          self.short_parent_order = self.sell(exectype=bt.Order.Market, size=exe_size, transmit=False, valid=datetime.datetime.now() + datetime.timedelta(seconds=120))
                          self.short_stoploss = self.buy(price=sl_px, exectype=bt.Order.Stop, size=exe_size, transmit=True, parent=self.short_parent_order)
                          self.short_parent_order.addinfo(name='Parent Short Main-cond')
                          self.short_stoploss.addinfo(name='Parent Short Main Stoploss Triggered')  # explain this stop loss is not the most accurate due to slippage
      
                      self.action = 'nothing'
              # **************************************************************Explain Order Management*****************************************************************************
              elif self.position:
                  # current time > 1245+10min or 1545+10min
                  if self.data0.datetime.time(0) >= get_tradeable_time(self.data0.datetime.date(0), self.early_close_days, 2) + datetime.timedelta(minutes=10):
                      self.early_exit = self.close()
                      self.early_exit.addinfo(name='Early exit - Times up')
                      return
                 
      
                  if self.position.size > 0:  # explain this is a long order
                      # explain Have not partial closed and price hit 1 ATR -> partial close and move stop to position price
                      if not self.trail_stop_started and not self.move_to_position_px and (self.tws_parent_exe_px + (self.ATR_at_exe * 1) <= self.data0.close[0] < self.tws_parent_exe_px + (self.ATR_at_exe * 2)):
      
                          half_size = int(self.position.size / 2)
                          size_left = abs(self.position.size - half_size)
                          stop_px = round(self.tws_parent_exe_px, 2)
                          if self.notoptimize:
                              print('Target1ATR: {:.2f} <= curr close[0]: {:.2f} < 2ATR: {:.2f}'.format(self.tws_parent_exe_px + (self.ATR_at_exe * 1), self.data0.close[0], self.tws_parent_exe_px + (self.ATR_at_exe * 2)))
                              print('Close half size {}, size left {}, new stop px {:.2f}'.format(half_size, size_left, stop_px))
                          if self.long_stoploss is not None: self.cancel(self.long_stoploss)
                          if half_size != 0:
                              self.long_partial = self.sell(exectype=bt.Order.Market, size=half_size, transmit=False)
                              self.long_partial.addinfo(name='Long Partial Closed, size left {}'.format(size_left))
                              self.long_stoploss = self.sell(price=stop_px, exectype=bt.Order.Stop, size=size_left, parent=self.long_partial, transmit=True)
                              self.long_stoploss.addinfo(name='Long Partial Stoploss at Position Price, {:.2f} Triggered'.format(stop_px))
                              self.move_to_position_px = True
      
                          elif half_size == 0:
                              self.early_exit = self.close()
                              self.early_exit.addinfo(name='Long Half size is 0, just close')
      
                      # explain 2 scenario:
                      # explain a. hit betw 1 - 2 ATR, sell 1/2, move stop to posn px, then hit 2 ATR change to trail stop
                      # explain b. hit above 2 ATR, go straight to trail stop
                      elif not self.trail_stop_started and (self.data0.close[0] >= self.tws_parent_exe_px + (self.ATR_at_exe * 2)):
                          if self.notoptimize: print('Came into Long Trailstop at: {}, size: {}'.format(self.data0.datetime.datetime(0), self.position.size))
                          stop_size = abs(self.position.size)
                          trail_stop_px = self.data0.close[0]
                          if self.long_stoploss is not None: self.cancel(self.long_stoploss)
      
                          self.long_trailstop = self.sell(exectype=bt.Order.StopTrail, price=trail_stop_px, size=stop_size, trailamount=round(self.ATR_at_exe * 1, 2))
                          self.long_trailstop.addinfo(name='Long TrailStop Stopped out, initially started trail at: {:.2f}, Trail amt was: {:.2f}'.format(trail_stop_px, round(self.ATR_at_exe * 1, 2)))
                          self.trail_stop_started = True
      
                  elif self.position.size < 0:  # explain this is short order
      
                      if not self.trail_stop_started and not self.move_to_position_px and (self.tws_parent_exe_px - (self.ATR_at_exe * 1) >= self.data0.close[0] > self.tws_parent_exe_px - (self.ATR_at_exe * 2)):
      
                          half_size = int(self.position.size / 2)
                          size_left = abs(self.position.size - half_size)
                          stop_px = round(self.tws_parent_exe_px, 2)
                          if self.notoptimize:
                              print('Target1ATR: {:.2f} >= curr close[0]: {:.2f} > 2ATR: {:.2f}'.format(self.tws_parent_exe_px - (self.ATR_at_exe * 1), self.data0.close[0], self.tws_parent_exe_px - (self.ATR_at_exe * 2)))
                              print('Close half size: {}, size left {}, new stop px {:.2f}'.format(half_size, size_left, stop_px))
                          if self.short_stoploss is not None: self.cancel(self.short_stoploss)
                          if half_size != 0:
                              self.short_partial = self.buy(exectype=bt.Order.Market, size=half_size, transmit=False)
                              self.short_partial.addinfo(name='Short Partial Closed, size left {}'.format(size_left))
                              self.short_stoploss = self.buy(price=stop_px, exectype=bt.Order.Stop, size=size_left, parent=self.short_partial, transmit=True)
                              self.short_stoploss.addinfo(name='Short Partial Stoploss at Position Price, {:.2f} Triggered'.format(stop_px))
                              self.move_to_position_px = True
      
                          elif half_size == 0:
                              self.early_exit = self.close()
                              self.early_exit.addinfo(name='Short Half size is 0, just close')
      
                      # explain 2 scenario:
                      # explain a. hit betw 1 - 2 ATR, sell 1/2, move stop to posn px then, hit 2 ATR change to trail stop
                      # explain b. hit above 2 ATR, go straight to trail stop
                      elif not self.trail_stop_started and (self.tws_parent_exe_px - (self.ATR_at_exe * 2) >= self.data0.close[0]):
                          if self.notoptimize: print('Came into Short Trailstop at: {}, size: {}'.format(self.data0.datetime.datetime(0), self.position.size))
                          stop_size = abs(self.position.size)  # 13 Mar from self.short_stoploss.size
                          trail_stop_px = self.data0.close[0]
                          if self.short_stoploss is not None: self.cancel(self.short_stoploss)
      
                          self.short_trailstop = self.buy(exectype=bt.Order.StopTrail, price=trail_stop_px, size=stop_size, trailamount=round(self.ATR_at_exe * 1, 2))
                          self.short_trailstop.addinfo(name='Short TrailStop Stopped out, initially started trail at: {:.2f}, Trail amt was: {:.2f}'.format(trail_stop_px, round(self.ATR_at_exe * 1, 2)))
                          self.trail_stop_started = True
      
      

      helper.py:

      import datetime
      from pandas_market_calendars import get_calendar
      
      def get_backfill_date():  # I want to fill from last trading day
          today = datetime.date.today()
          offset = max(1, (today.weekday() + 6) % 7 - 3)
          timedelta = datetime.timedelta(offset)
          last_biz_date = today - timedelta
      
          return last_biz_date  # datetime.date object
      
      def get_tradeable_time(trade_dt, early_close_days, last_buy):
          """
          returns the last buy time, or the sell all time depending on
          whether its an early close day or not.
          :param trade_dt: self.data.datetime.date(0)
          :param early_close_days: get from get_early_close_days function below
          :param last_buy: boolean to choose whether you want last buy time or sell all time
          :return: a time obj for comparison
          """
          if trade_dt in early_close_days:
              if last_buy == 0:  # last buy time
                  return datetime.time(12, 0)  # datetime.time object
              elif last_buy == 1:  # close all time
                  return datetime.time(12, 45)
              else:  # market close time
                  return datetime.time(12, 59)
          else:
              if last_buy == 0:
                  return datetime.time(15, 0)  # datetime.time object
              elif last_buy == 1:
                  return datetime.time(15, 45)
              else:
                  return datetime.time(15, 59)
      
      def get_early_close_days():
          calendar = get_calendar('NYSE')
          now = datetime.datetime.now()
          schedule = calendar.schedule(start_date=now - datetime.timedelta(weeks=780),
                                       end_date=now + datetime.timedelta(weeks=100))
          return calendar.early_closes(schedule).index
      
      

      some output for ref:

      ....
      AMD, 211,2021-05-07T05:30:00,  78.19,78.19,78.19,78.19,  77.69,0.03,  76.47,77.16,76.47,  77.69,  0,0.00
      TSLA, 211,2021-05-07T05:30:00,  666.02,666.02,666.02,666.02,  666.76,0.53,  650.00,663.01,658.00,  650.00,  0,0.00
      AMD, 212,2021-05-07T05:35:00,  78.19,78.19,78.15,78.15,  77.70,0.03,  76.47,77.16,76.47,  77.70,  0,0.00
      TSLA, 212,2021-05-07T05:35:00,  666.02,666.02,666.02,666.02,  666.76,0.49,  650.00,663.01,658.00,  650.00,  0,0.00
      AMD, 213,2021-05-07T05:40:00,  78.17,78.18,78.17,78.18,  77.70,0.03,  76.47,77.16,76.47,  77.70,  0,0.00
      TSLA, 213,2021-05-07T05:40:00,  667.00,667.00,667.00,667.00,  666.76,0.53,  650.00,663.01,658.00,  650.00,  0,0.00
      AMD, 214,2021-05-07T05:45:00,  78.18,78.18,78.18,78.18,  77.71,0.03,  76.47,77.16,76.47,  77.71,  0,0.00
      TSLA, 214,2021-05-07T05:45:00,  667.00,667.00,667.00,667.00,  666.76,0.49,  650.00,663.01,658.00,  650.00,  0,0.00
      AMD, 215,2021-05-07T05:50:00,  78.15,78.15,78.15,78.15,  77.71,0.03,  76.47,77.16,76.47,  77.71,  0,0.00
      TSLA, 215,2021-05-07T05:50:00,  666.60,666.60,666.18,666.18,  666.76,0.51,  650.00,663.01,658.00,  650.00,  0,0.00
      AMD, 216,2021-05-07T05:55:00,  78.15,78.15,78.15,78.15,  77.72,0.02,  76.47,77.16,76.47,  77.72,  0,0.00
      TSLA, 216,2021-05-07T05:55:00,  666.18,666.18,666.18,666.18,  666.75,0.48,  650.00,663.01,658.00,  650.00,  0,0.00
      AMD, 217,2021-05-07T06:00:00,  78.15,78.15,78.15,78.15,  77.72,0.02,  76.47,77.16,76.47,  77.72,  0,0.00
      TSLA, 217,2021-05-07T06:00:00,  667.30,668.69,667.00,668.69,  666.77,0.62,  650.00,663.01,658.00,  650.00,  0,0.00
      AMD, 218,2021-05-07T06:05:00,  78.15,78.15,78.15,78.15,  77.72,0.02,  76.47,77.16,76.47,  77.72,  0,0.00
      TSLA, 218,2021-05-07T06:05:00,  668.88,668.88,668.49,668.49,  666.79,0.61,  650.00,663.01,658.00,  650.00,  0,0.00
      ....
      

      Questions:
      a. Like to ask the more experienced here, do you foresee any issues with running this way for multi data live? I did not execute any trades yet. but I seem to be getting live data ok. Indicators here seem also to be calculated fine. As a noob I really get confused with enumerating within strategy.

      b. The problem I have using this is the plot. Be it using matplotlib or bokeh, it plots both TSLA and AMD on the same figure... I am out of ideas to get rid or 1. Imagine if I need to trade 20 symbols. The plot will be atrocious.

      c. any comments on my general code? always looking to improve.

      1 Reply Last reply Reply Quote 0
      • 1 / 1
      • First post
        Last post
      Copyright © 2016, 2017, 2018, 2019, 2020, 2021 NodeBB Forums | Contributors