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/

    opening range breakout strategy for multiple stocks

    General Code/Help
    backtrader opening range error
    4
    6
    1384
    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.
    • A
      abhishek.anand 1 last edited by

      Hi,

      I am trying to backtest opening range breakout strategy for a list of 50 stocks together. With an idea that the trades across stocks are executed till the cash is there in the system for the day and all positions closed at the end of the day. I am placing the strategy code below -

      --
      class OpeningRangeBreakout(bt.Strategy):
      params = dict(
      num_opening_bars=3,
      fast_length= 5126,
      slow_length= 25126
      )

      def __init__(self):
          self.opening_range_low = 0
          self.opening_range_high = 0
          self.opening_range = 0
          self.bought_today = False
          self.order = None
          self.crossovers = []
          
          # for d in self.datas: 
          #     ma_fast = bt.ind.SMA(d, period = self.params.fast_length)
          #     ma_slow = bt.ind.SMA(d, period = self.params.slow_length)
              
          #     self.crossovers.append(bt.ind.CrossOver(ma_fast, ma_slow))
             
      def log(self, txt, dt=None):
          if dt is None:
              dt = self.datas[0].datetime.datetime()
      
          # print('%s, %s' % (dt, txt))
      
      def notify_order(self, order):
          if order.status in [order.Submitted, order.Accepted]:
              # Buy/Sell order submitted/accepted to/by broker - Nothing to do
              return
      
          # Check if an order has been completed
          if order.status in [order.Completed]:
              order_details = f"{order.executed.price}, Cost: {order.executed.value}, Comm {order.executed.comm}"
      
              if order.isbuy():
                  self.log(f"BUY EXECUTED, Price: {order_details}")
              else:  # Sell
                  self.log(f"SELL EXECUTED, Price: {order_details}")
      
          elif order.status in [order.Canceled, order.Margin, order.Rejected]:
              self.log('Order Canceled/Margin/Rejected')
      
          self.order = None
      
      def next(self):
          self.opening_range_low = {}
          self.opening_range_high = {}
          self.bought_today = {}
          self.opening_range = {}
          for i, d in enumerate(self.datas):
              current_bar_datetime = d.num2date(d.datetime[0])
              previous_bar_datetime = d.num2date(d.datetime[-1])
              print("current_bar_datetime",current_bar_datetime, 
                    "previous_bar_datetime", previous_bar_datetime)
      
              if (current_bar_datetime.date() != previous_bar_datetime.date()) :
                  self.opening_range_low[i] = d.low[0]
                  self.opening_range_high[i] = d.high[0]
                  self.bought_today[i] = False
      
              opening_range_start_time = time(9, 30, 0)
              dt = datetime.combine(date.today(), opening_range_start_time) + \
                  timedelta(minutes=self.p.num_opening_bars * 5)
              opening_range_end_time = dt.time()
      
              print("self.opening_range_high",self.opening_range_high)
              
              if (current_bar_datetime.time() >= opening_range_start_time) \
                      and (current_bar_datetime.time() < opening_range_end_time):
                  self.opening_range_high[i] = max(d.high[0], self.opening_range_high[i])
                  self.opening_range_low[i] = min(d.low[0], self.opening_range_low[i])
                  self.opening_range[i] = self.opening_range_high[i] - self.opening_range_low[i]
              else:
      
                  if self.order:
                      return
      
                  if self.getposition(d).size and (d.close[0] > (self.opening_range_high[i] + self.opening_range[i])):
                      self.close()
      
                  if d.close[0] > self.opening_range_high[i] and not self.getposition(d).size and not self.bought_today[i]:
                      self.order = self.buy()
                      self.bought_today[i] = True
      
                  if self.getposition(d).size and (self.data.close[0] < (self.opening_range_high[i] - self.opening_range[i])):
                      self.order = self.close()
      
                  if self.getposition(d).size and current_bar_datetime.time() >= time(15, 45, 0):
                      self.log("RUNNING OUT OF TIME - LIQUIDATING POSITION")
                      self.close()
      
      def stop(self):
          self.roi = (self.broker.get_value() / 100000) - 1.0
      

      This however results in an error after the first run where the opening range is stored in this dict - self.opening_range_high, but when it moves to the next time indicator the value of self.opening_range_high fro the previous run is not retained.

      Below is the last bit of error log. Can post the entire code if needed -

      current_bar_datetime 2021-01-15 09:30:00 previous_bar_datetime 2021-03-09 15:25:00
      self.opening_range_high {0: 536.25, 1: 2631.1, 2: 676.8, 3: 3563.6, 4: 8775.0, 5: 4889.9, 6: 603.6, 7: 425.45, 8: 3649.45, 9: 838.6, 10:
      147.2, 11: 5257.9, 12: 2885.35, 13: 145.6, 14: 1011.6, 15: 1025.85, 16: 2680.0, 17: 1462.55, 18: 3207.0, 19: 262.95, 20: 2386.35, 21: 550.4, 22: 965.0, 23: 1359.8, 24: 103.7, 25: 214.1, 26: 401.65, 27: 1886.55, 28: 1378.0, 29: 828.15, 30: 8131.0, 31: 18243.2, 32: 102.7, 33: 106.1, 34: 204.0, 35: 1964.5, 36: 310.65, 37: 24532.0, 38: 604.5, 39: 244.75, 40: 721.5, 41: 3224.95, 42: 1045.0, 43: 1505.0, 44: 5527.95, 45: 524.0, 46: 178.85, 47: 454.2, 48: 232.95}
      1,ADANIPORTS,1,2021-01-15 09:30:00,536.25,536.25,534.3,535.0,70646.0,,ASIANPAINT,1,2021-01-15 09:30:00,2627.4,2631.1,2626.0,2628.8,34444.0,,AXISBANK,1,2021-01-15 09:30:00,676.7,676.8,675.1,675.8,211308.0,,BAJAJ-AUTO,1,2021-01-15 09:30:00,3560.0,3563.6,3552.8,3559.8,6547.0,,BAJAJFINSV,1,2021-01-15 09:30:00,8767.35,8775.0,8751.1,8757.45,10775.0,,BAJFINANCE,1,2021-01-15 09:30:00,4889.0,4889.9,4865.5,4873.9,52116.0,,BHARTIARTL,1,2021-01-15 09:30:00,603.5,603.6,600.2,602.2,2011855.0,,BPCL,1,2021-01-15 09:30:00,425.3,425.45,423.25,423.4,162279.0,,BRITANNIA,1,2021-01-15 09:30:00,3646.25,3649.45,3640.05,3641.95,5971.0,,CIPLA,1,2021-01-15 09:30:00,837.7,838.6,835.7,837.5,38658.0,,COALINDIA,1,2021-01-15 09:30:00,147.1,147.2,146.75,146.9,130012.0,,DRREDDY,1,2021-01-15 09:30:00,5255.0,5257.9,5243.0,5254.4,16992.0,,EICHERMOT,1,2021-01-15 09:30:00,2885.35,2885.35,2871.0,2875.0,23501.0,,GAIL,1,2021-01-15 09:30:00,145.35,145.6,145.0,145.3,621437.0,,GRASIM,1,2021-01-15 09:30:00,1011.05,1011.6,1009.55,1010.0,29951.0,,HCLTECH,1,2021-01-15 09:30:00,1022.0,1025.85,1020.65,1024.35,495943.0,,HDFC,1,2021-01-15 09:30:00,2679.6,2680.0,2676.7,2678.55,20000.0,,HDFCBANK,1,2021-01-15 09:30:00,1460.7,1462.55,1459.25,1460.25,122419.0,,HEROMOTOCO,1,2021-01-15 09:30:00,3194.75,3207.0,3182.45,3187.7,63657.0,,HINDALCO,1,2021-01-15 09:30:00,262.55,262.95,261.35,262.2,157124.0,,HINDUNILVR,1,2021-01-15 09:30:00,2384.65,2386.35,2383.2,2383.55,17541.0,,ICICIBANK,1,2021-01-15 09:30:00,550.2,550.4,549.05,549.15,250540.0,,INDUSINDBK,1,2021-01-15 09:30:00,965.0,965.0,957.2,958.95,329196.0,,INFY,1,2021-01-15 09:30:00,1357.0,1359.8,1355.3,1355.8,307078.0,,IOC,1,2021-01-15 09:30:00,103.7,103.7,102.95,103.0,920844.0,,ITC,1,2021-01-15 09:30:00,214.0,214.1,213.55,213.8,583160.0,,JSWSTEEL,1,2021-01-15
      09:30:00,401.0,401.65,399.35,401.65,169314.0,,KOTAKBANK,1,2021-01-15 09:30:00,1886.0,1886.55,1878.6,1880.75,57796.0,,LT,1,2021-01-15 09:30:00,1377.8,1378.0,1374.0,1374.15,51607.0,,M&M,1,2021-01-15 09:30:00,828.15,828.15,825.5,826.35,76797.0,,MARUTI,1,2021-01-15 09:30:00,8126.05,8131.0,8106.0,8120.0,21054.0,,NESTLEIND,1,2021-01-15 09:30:00,18243.2,18243.2,18200.3,18200.4,1607.0,,NTPC,1,2021-01-15 09:30:00,102.7,102.7,102.4,102.55,295404.0,,ONGC,1,2021-01-15 09:30:00,105.8,106.1,105.5,105.8,524289.0,,POWERGRID,1,2021-01-15 09:30:00,204.0,204.0,203.1,203.3,161929.0,,RELIANCE,1,2021-01-15 09:30:00,1964.4,1964.5,1961.9,1961.9,153914.0,,SBIN,1,2021-01-15 09:30:00,310.65,310.65,308.25,308.45,1273422.0,,SHREECEM,1,2021-01-15 09:30:00,24520.0,24532.0,24500.0,24526.7,679.0,,SUNPHARMA,1,2021-01-15 09:30:00,602.9,604.5,600.8,603.55,138630.0,,TATAMOTORS,1,2021-01-15 09:30:00,244.75,244.75,243.65,244.0,1420424.0,,TATASTEEL,1,2021-01-15 09:30:00,718.0,721.5,716.65,721.25,1274300.0,,TCS,1,2021-01-15 09:30:00,3219.6,3224.95,3210.5,3219.95,89570.0,,TECHM,1,2021-01-15 09:30:00,1044.85,1045.0,1036.2,1036.4,137201.0,,TITAN,1,2021-01-15 09:30:00,1504.35,1505.0,1501.2,1504.9,28613.0,,ULTRACEMCO,1,2021-01-15 09:30:00,5517.95,5527.95,5501.95,5515.05,12135.0,,UPL,1,2021-01-15 09:30:00,523.5,524.0,521.2,521.5,381937.0,,VEDL,1,2021-01-15 09:30:00,178.85,178.85,178.0,178.35,265411.0,,WIPRO,1,2021-01-15 09:30:00,453.8,454.2,452.8,453.6,367378.0,,ZEEL,1,2021-01-15 09:30:00,232.7,232.95,231.45,232.5,487195.0,,OpeningRangeBreakout,1,737805.3958333334,Broker,1,100000.0,100000.0,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,BuySell,1,,,DataTrades,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
      current_bar_datetime 2021-01-15 09:35:00 previous_bar_datetime 2021-01-15 09:30:00
      self.opening_range_high {}
      Traceback (most recent call last):
      File "c:\Users\40100147\Abhishek\Projects\fullstack-trading-app\backtest.py", line 185, in <module>
      strategies = cerebro.run()
      File "c:\ProgramData\Anaconda3\envs\backtrader\lib\site-packages\backtrader\cerebro.py", line 1127, in run
      runstrat = self.runstrategies(iterstrat)
      File "c:\ProgramData\Anaconda3\envs\backtrader\lib\site-packages\backtrader\cerebro.py", line 1293, in runstrategies
      self._runonce(runstrats)
      File "c:\ProgramData\Anaconda3\envs\backtrader\lib\site-packages\backtrader\cerebro.py", line 1695, in _runonce
      strat._oncepost(dt0)
      File "c:\ProgramData\Anaconda3\envs\backtrader\lib\site-packages\backtrader\strategy.py", line 309, in _oncepost
      self.next()
      File "c:\Users\40100147\Abhishek\Projects\fullstack-trading-app\backtest.py", line 84, in next
      self.opening_range_high[i] = max(d.high[0], self.opening_range_high[i])
      KeyError: 0

      run-out 1 Reply Last reply Reply Quote 0
      • run-out
        run-out @abhishek.anand 1 last edited by

        @abhishek-anand-1 said in opening range breakout strategy for multiple stocks:

        self.opening_range_high[i] = max(d.high[0], self.opening_range_high[i])

        You are trying to compare maximum with d.high[0] and self.opening_range_high[i]. In reading your code, it is possible that self.opening_range_high[i] is not created yet at this point in the code. If you look for this key in the dictionary and it's not there, you get KeyError: 0.

        Two options. You could set the value of self.opening_range_high[i] to zero at the beginning of the enumeration, or you can check to see if it is in the keys before proceeding, like this:

        if i in self.opening_range_high:
            self.opening_range_high[i] = max(d.high[0], self.opening_range_high[i])
        else: 
            self.opening_range_high[i] = d.high[0]
        

        RunBacktest.com

        1 Reply Last reply Reply Quote 1
        • A
          abhishek.anand 1 last edited by

          @run-out Thanks for the response. I was able to find out the erro. Just posting the corrected code here -

          import con_config as conf
          import backtrader as bt
          import pandas as pd
          import sqlite3
          from datetime import date, datetime, time, timedelta
          import matplotlib
          import psycopg2
          import os
          import rootpath
          
          
          class OpeningRangeBreakout(bt.Strategy):
              params = dict(
                  num_opening_bars=3,
                  fast_length= 5*12*6,
                  slow_length= 25*12*6
              )
          
              def __init__(self):
                  self.bought_today = False
                  self.order = None
                  self.crossovers = []
                  self.opening_range_low = {}
                  self.opening_range_high = {}
                  self.bought_today = {}
                  self.opening_range = {}
                  
                     
              def log(self, txt, dt=None):
                  if dt is None:
                      dt = self.datas[0].datetime.datetime()
          
                  # print('%s, %s' % (dt, txt))
          
              def notify_order(self, order):
                  if order.status in [order.Submitted, order.Accepted]:
                      # Buy/Sell order submitted/accepted to/by broker - Nothing to do
                      return
          
                  # Check if an order has been completed
                  if order.status in [order.Completed]:
                      order_details = f"{order.executed.price}, Cost: {order.executed.value}, Comm {order.executed.comm}"
          
                      if order.isbuy():
                          self.log(f"BUY EXECUTED, Price: {order_details}")
                      else:  # Sell
                          self.log(f"SELL EXECUTED, Price: {order_details}")
          
                  elif order.status in [order.Canceled, order.Margin, order.Rejected]:
                      self.log('Order Canceled/Margin/Rejected')
          
                  self.order = None
          
              def next(self):
                  for i, d in enumerate(self.datas):
                      current_bar_datetime = d.num2date(d.datetime[0])
                      previous_bar_datetime = d.num2date(d.datetime[-1])
                      print("current_bar_datetime",current_bar_datetime, 
                            "previous_bar_datetime", previous_bar_datetime)
          
                      if (current_bar_datetime.date() != previous_bar_datetime.date()) :
                          self.opening_range_low[i] = d.low[0]
                          self.opening_range_high[i] = d.high[0]
                          self.bought_today[i] = False
          
                      opening_range_start_time = time(9, 30, 0)
                      dt = datetime.combine(date.today(), opening_range_start_time) + \
                          timedelta(minutes=self.p.num_opening_bars * 5)
                      opening_range_end_time = dt.time()
          
                      print("self.opening_range_high",self.opening_range_high)
                      
                      if (current_bar_datetime.time() >= opening_range_start_time) \
                              and (current_bar_datetime.time() < opening_range_end_time):
                          self.opening_range_high[i] = max(d.high[0], self.opening_range_high[i])
                          self.opening_range_low[i] = min(d.low[0], self.opening_range_low[i])
                          self.opening_range[i] = self.opening_range_high[i] - self.opening_range_low[i]
                      else:
          
                          if self.order:
                              return
          
                          if self.getposition(d).size and (d.close[0] > (self.opening_range_high[i] + self.opening_range[i])):
                              self.close()
          
                          if d.close[0] > self.opening_range_high[i] and not self.getposition(d).size and not self.bought_today[i]:
                              self.order = self.buy()
                              self.bought_today[i] = True
          
                          if self.getposition(d).size and (self.data.close[0] < (self.opening_range_high[i] - self.opening_range[i])):
                              self.order = self.close()
          
                          if self.getposition(d).size and current_bar_datetime.time() >= time(15, 45, 0):
                              self.log("RUNNING OUT OF TIME - LIQUIDATING POSITION")
                              self.close()
          
              def stop(self):
                  self.roi = (self.broker.get_value() / 100000) - 1.0
          
          
          if __name__ == '__main__':
          
              # Reading in the csv
              # Getting the rootpath of the working directory
              working_dir = rootpath.detect(__file__)
              # working_dir = "C:\\Users\\40100147\\Abhishek\\Projects\\fullstack-trading-app"
              nifty50_path = os.path.join(working_dir, "data\\nifty50.csv")
              outputpath = os.path.join(working_dir, "data\\result_df_2.csv")
              nifty50 = pd.read_csv(nifty50_path, header=0, sep='\t')
              nifty50_list = tuple(nifty50['Symbol'].tolist())
              conn = psycopg2.connect(**conf.con_dict)
          
              query = f"""--sql
                      select  a.tradingsymbol
                      from ( 
                      select tradingsymbol, avg(Volume) as volume
                      from equities.candlestick
                      where candle_date_time >= '01-15-2021'
                      and cast(candle_date_time as time(0)) > '09:30:00'
                      and cast(candle_date_time as time(0)) < '15:30:00'
                      and candle_length = '5minute'
                      and tradingsymbol in {nifty50_list}
                      group by tradingsymbol) as a;
                      """
              stocks = pd.read_sql(query, con=conn)
              stocks_list = stocks['tradingsymbol']
              result_df = []
              cerebro = bt.Cerebro()
          
              for stock in stocks_list:
                  query = f"""--sql
                          select *
                          from equities.candlestick
                          where candle_date_time >= '01-15-2021'
                          and cast(candle_date_time as time(0)) >= '09:30:00'
                          and cast(candle_date_time as time(0)) <= '15:30:00'
                          and tradingsymbol = '{stock}'
                          and candle_length = '5minute';
                          """
                  dataframe = pd.read_sql(query,
                                          con=conn,
                                          index_col='candle_date_time',
                                          parse_dates=['datetime'])
                  data = bt.feeds.PandasData(dataname=dataframe)
          
                  cerebro.adddata(data, name = stock)
          
              temp_list = []
              initial_cash = 100000.0
              cerebro.broker.setcash(initial_cash)
              # 0.01% of the operation value
              cerebro.broker.setcommission(commission=0.0001)
              cerebro.addsizer(bt.sizers.PercentSizer, percents=95)
              cerebro.addwriter(bt.WriterFile, csv=True)
              cerebro.addstrategy(OpeningRangeBreakout)
          
              strats = cerebro.optstrategy(OpeningRangeBreakout, num_opening_bars=\
              [15, 30, 60])
              cerebro.run()
          
              # Get final portfolio Value
              portvalue = cerebro.broker.getvalue()
          
              # Print out the final result
              print('Final Portfolio Value: ${}'.format(portvalue))
          K 1 Reply Last reply Reply Quote 1
          • K
            krispy @abhishek.anand 1 last edited by

            @abhishek-anand-1 This appears to be the code from the Part Time Larry youtube channel video titled "Opening Range Breakout Backtest with Python and Backtrader". Just be warned that his original code has some issues. I could be wrong but it appears that you have not made the corrections to the code.

            • The code would define the first 15 minutes of the opening range to generate the high. There is no problem with this.

            • The code would trigger an order outside of the 15 minute opening range. This is no longer an opening range breakout if the purchase is at 1 pm. You can validate that his code is not functioning correctly at the 37:14 of the video that shows purchases outside of the 15 minute opening range.

            • If the purchase occurs after the 15:45, it is held overnight. This is now a swing trade strategy.

            K 1 Reply Last reply Reply Quote 0
            • K
              krispy @krispy last edited by

              @krispy @ abhishek.anand 1
              Come to think about it. This code can't trigger an order in the opening range. This is why.

              The data.high is placed into variable opening_range_high. Then data.close (the same bar) is compared against opening_range_high to trigger an order. How can data.close (of the same bar) be > data.high (of the same bar)? They can be equal, but not greater.

              To summarize, within the opening range the same bar is compared to itself to see if it is greater than itself. This is why it can't trigger an order within the opening range.

              How do you go and create a YouTube video about this strategy when it is clearly not tested?

              Federico Bld 1 Reply Last reply Reply Quote 0
              • Federico Bld
                Federico Bld @krispy last edited by

                @abhishek.anand @krispy have you tried to specify which asset should be sold / closed? In the code i see:

                • self.buy() instead self.buy(data=d)
                • self.close() instead self.close(data=d)

                Let me know if works better.

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