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 trading strategy with different start-dates (IPOs)

    Indicators/Strategies/Analyzers
    1
    1
    176
    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.
    • V
      vignesh last edited by

      I just got started with backtrader about a week back and was looking to implement a particular strategy described on this blog (https://www.backtrader.com/blog/2019-07-19-rebalancing-conservative/rebalancing-conservative/) using a similar multi-asset trading strategy. However, my data had data for stocks with different start dates - some due to IPOs and some due to data availability. Obviously this meant that the platform implemented the strategy only from when all the buffers could be guaranteed. The prenext and nextstart methods obviously were going to be the way to tackle this. While just implementing self.next() within the prenext method would be a simple straightforward fix to most trading strategies, this wasn't going to be in this case. The __init__ method initialised the ranking lines and there was no way to control it from the prenext method. So I implemented a a small workaround as this below.

      class rebalancing(bt.Strategy):
              params = dict(
                  selcperc=0.10,  # percentage of stocks to select from the universe
                  rperiod=1,  # period for the returns calculation, default 1 period
                  vperiod=36,  # lookback period for volatility - default 36 periods
                  mperiod=12,  # lookback period for momentum - default 12 periods
                  reserve=0.05  # 5% reserve capital
              )
          
              def log(self, arg):
                  print('{} {}'.format(self.datetime.date(), arg))
                  # return
          
              def prenext(self):
                  self.d_with_len = [d for d in self.datas if len(d)>self.p.vperiod]
                  if len(self.d_with_len)>self.selnum:
                      self.next()
          
              def nextstart(self):
                  self.d_with_len = self.datas
                  self.next()
          
      

      The above fix came straight from https://www.backtrader.com/blog/2019-05-20-momentum-strategy/momentum-strategy/

      However, further tweaking required inside the self.next() method still had to be addressed - which I did so.

      def next(self):
      
              comps_in_d_with_len = []
              for d in self.d_with_len:
                  comps_in_d_with_len.append(d._name)
      
              sub_ranks = self.ranks.copy()
              for key in list(sub_ranks):
                  if key._name not in comps_in_d_with_len:
                      del sub_ranks[key]
      
              ranks = sorted(
                  sub_ranks.items(),  # get the (d, rank), pair
                  key=lambda x: x[1][0],  # use rank (elem 1) and current time "0"
                  reverse=True,  # highest ranked 1st ... please
              )
      
              # put top ranked in dict with data as key to test for presence
              rtop = dict(ranks[:self.selnum])
      
              # For logging purposes of stocks leaving the portfolio
              rbot = dict(ranks[self.selnum:])
      
              # prepare quick lookup list of stocks currently holding a position
              posdata = [d for d, pos in self.getpositions().items() if pos]
      
              # remove those no longer top ranked
              # do this first to issue sell orders and free cash
              for d in (d for d in posdata if d not in rtop):
                  self.log('Leave {} - Rank {:.2f}'.format(d._name, rbot[d][0]))
                  self.order_target_percent(d, target=0.0)
      
              # rebalance those already top ranked and still there
              for d in (d for d in posdata if d in rtop):
                  self.log('Rebal {} - Rank {:.2f}'.format(d._name, rtop[d][0]))
                  self.order_target_percent(d, target=self.perctarget)
                  del rtop[d]  # remove it, to simplify next iteration
      
              # issue a target order for the newly top ranked stocks
              # do this last, as this will generate buy orders consuming cash
              for d in rtop:
                  self.log('Enter {} - Rank {:.2f}'.format(d._name, rtop[d][0]))
                  self.order_target_percent(d, target=self.perctarget)
      

      Much of the code remains the same, but it was quite tricky to figure out how to get the platform to go as far back as possible with the available dataset and the minimum period buffer constraint in order to start implementation. A saw a lot of discussion about this multi-period multi-start date trading strategy in the forum, but nothing quite concrete. So perhaps this is helpful.

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