I am constantly referring back to the Boot Camp, but it is difficult to access the information in its interactive format. I have copied the text of the lesson here for quick access and review. (Do the boot camp before using this for review.) -GK    ......

Opening Range Breakout

In this lesson we will implement an opening range breakout strategy. This strategy predicts a breakout if the price leaves the range of the first 30 minutes of trading.

What You'll Learn
  • Using a consolidator to create a time bar
  • Creating an event handler
  • Taking a position depending on the high and low of the opening range
  • Using a scheduled event to close a position
Lesson Requirements

We suggest you complete Momentum Based Tactical Allocation before starting this lesson.

-----------------------------------

Creating a Consolidator
  1. Opening Range Breakout
  2. Creating a Consolidator
Introduction

Opening range breakout uses a defined period of time to set a price-range, and trades on leaving that range. To achieve this we will start by consolidating the first 30 minutes of data.

Consolidators

Consolidators are used to combine smaller data points into larger bars. We commonly use consolidators to create 5, 10 or 15-minute bars from minute data.

The self.Consolidate() method takes a tickertimedelta, and event handler. It can also take a CalendarType or a Resolution parameter.

# Receive consolidated data with a timedelta # parameter and OnDataConsolidated event handler self.Consolidate("SPY", timedelta(minutes=45), self.OnDataConsolidated) # Receive consolidated data with a CalendarType # parameter and OnDataConsolidated event handler self.Consolidate("SPY", CalendarType.Weekly, self.OnDataConsolidated) # Receive consolidated data with a Resolution # parameter and OnDataConsolidated event handler self.Consolidate("SPY", Resolution.Hour, TickType.Trade, self.OnDataConsolidated)Initializing Consolidators

Consolidate should be called in your Initialize() method so it is initialized only once. You must request data smaller than the bar you aim to consolidate. For example, it is not possible to consolidate daily data into minute bars.

Event Handler Functions

Consolidators require an accompanying event handler to receive the output data. The consolidator event handlers are functions which are called when a new bar is created. We can name the event handler anything as long as it is passed into our Consolidate method.

# Consolidators require an event handler function to recieve data def OnDataConsolidated(self, bar): # We can save the first bar as the currentBar self.currentBar = ...

Task Objectives

  1. Create a 30 minute consolidator, which calls an event handler self.OnDataConsolidated.
  2. Create a function OnDataConsolidated() and save the bar to self.currentBar.
Hint:Did you save the bar to currentBar in your OnDataConsolidated function? Code:

class OpenRangeBreakout(QCAlgorithm):
    
    openingBar = None
    currentBar = None

    def Initialize(self):
        self.SetStartDate(2018, 7, 10) # Set Start Date  
        self.SetEndDate(2019, 6, 30) # Set End Date 
        self.SetCash(100000)  # Set Strategy Cash 
        
        # Subscribe to TSLA with Minute Resolution
        self.symbol = self.AddEquity("TSLA", Resolution.Minute)
        #1. Create our consolidator with a timedelta of 30 min
        self.Consolidate("TSLA", timedelta(minutes=30), self.OnDataConsolidated)
        
    def OnData(self, data):
        pass
    
    #2. Create a function OnDataConsolidator which saves the currentBar as bar 
    def OnDataConsolidated(self, bar):
        self.currentBar = bar

--------------------------------Bar Data and Bar Time
  1. Opening Range Breakout
  2. Bar Data and Bar Time
Consolidator Types

The type of data created by a consolidator depends on the type of underlying data of your requested data. QuantConnect has specific data for each asset type:

Asset TypeData AvailableConsolidated OutputEquityTrades (Tick, TradeBar)TradeBarForex/CfdQuotes (Tick, QuoteBar)QuoteBarCryptoTrades and Quotes (Tick, TradeBar, and QuoteBar)TradeBar, QuoteBarFuturesTrades and Quotes (Tick, TradeBar, and QuoteBar)TradeBar, QuoteBarOptionsTrades and Quotes (Tick, TradeBar, and QuoteBar)TradeBar, QuoteBar Checking Our Time

Regardless of the resulting bar type, all data contains a few common properties: bar.Time, and bar.Price. You can use bar.Time to get the current moment the bar was created and bar.EndTime to get the time at the end of the bar.

# Check the bar data using Time if bar.Time.hour == 9 and bar.Time.minute == 30: # Save the first bar of the trading day self.openingBar = bar # Check the bar data using EndTime # The EndTime of 10am ET is the bar from 9:30am to 10am using a 30 min consolidator if bar.EndTime.hour == 10 and bar.EndTime.minute == 0: self.openingBar = bar

Keep in mind bar is a variable that can go by any name. It is defined in the parameters of our event handler function.

Task Objectives

Let's save the first bar of the day to use it's range later for trading sigals.

  1. Save the first bar of the trading day to the class variable: self.openingBar. The first bar starts at 9:30am ET and ends at 10:00am ET.
Hint:Keep in mind bar.Time.hour is the time at the start of your bar and bar.EndTime.hour is the time at the end of your bar. The two are not interchangeable. We only need to check for one of the two. Code:class OpeningRangeBreakout(QCAlgorithm):
    
    openingBar = None 
  
    def Initialize(self):
        self.SetStartDate(2018, 7, 10)  
        self.SetEndDate(2019, 6, 30)  
        self.SetCash(100000)  
        self.AddEquity("TSLA", Resolution.Minute)
        self.Consolidate("TSLA", timedelta(minutes=30), self.OnDataConsolidated)
        
    def OnData(self, data):
        pass
        
    def OnDataConsolidated(self, bar):
        #1. Check the time, we only want to work with the first 30min after Market Open
        if bar.Time.hour == 9 and bar.Time.minute == 30:
            #2. Save one bar as openingBar 
            self.openingBar = bar -----------------------------Using the Output of the Consolidator
  1. Opening Range Breakout
  2. Using the Output of the Consolidator
Using Our Consolidated Data

As we have requested equities data, the consolidated bar's type is TradeBar. It has the properties OpenHighLowClose and Volume for a given period of time.

def OnDataConsolidated(self, bar): # bar.Time # bar.Open # bar.High # bar.Low # bar.Close Opening Short Positions

Short positions are when you are betting the market will fall in value. When you place a "short trade" you are borrowing someone else's stocks for a while to sell them. When you "close" a short position you are buying back the stocks you borrowed at the new market price.

Going Short In QuantConnect

In QuantConnect, we don't need to place a separate order type to enter a short position. We can use a scale of 1 to -1 in self.SetHoldings to place a long or short order. Going long is denoted by ordering a positive number, and short a negative one. QC does not support hedging (long and short at the same time).

# Go 100% short on SPY if data["SPY"].Close < self.openingBar.Low: self.SetHoldings("SPY", -1)

Task Objectives

The breakout point is when the price breaks past the high or low of the market opening range.

opening-range-breakout_rev0.png

  1. If we have already invested, or if the openingBar is None, exit OnData.
  2. If the close price of TSLA is greater than the openingBar.High price, go 100% long on TSLA.
  3. If the close price of TSLA is less than the openingBar.Low price, go 100% short on TSLA.
Hint:Did you check if the close price is above the high, and below the low price in two separate if statements? Code:class OpeningRangeBreakout(QCAlgorithm):
    
    openingBar = None 
    
    def Initialize(self):
        self.SetStartDate(2018, 7, 10)  
        self.SetEndDate(2019, 6, 30)  
        self.SetCash(100000)
        self.AddEquity("TSLA", Resolution.Minute)
        self.Consolidate("TSLA", timedelta(minutes=30), self.OnDataConsolidated)
        
    def OnData(self, data):
        
        #1. If self.Portfolio.Invested is true, or if the openingBar is None, return
        if self.Portfolio.Invested or self.openingBar is None:
            return
        
        #2. Check if the close price is above the high price, if so go 100% long on TSLA 
        if data["TSLA"].Close > self.openingBar.High:
            self.SetHoldings("TSLA", 1)
        
        #3. Check if the close price is below the low price, if so go 100% short on TSLA
        elif data["TSLA"].Close < self.openingBar.Low:
            self.SetHoldings("TSLA", -1)
        
    def OnDataConsolidated(self, bar):
        if bar.Time.hour == 9 and bar.Time.minute == 30:
            self.openingBar = bar  --------------------------------Scheduling Events
  1. Opening Range Breakout
  2. Scheduling Events
Scheduling Events

Scheduled events allow you to trigger code blocks for execution at specific times according to rules you set. We initialize scheduled events in the Initialize method so they are only executed once.

We use self.Schedule.On() to coordinate your algorithm activities and perform analysis at regular intervals while letting the trading engine take care of market holidays.

# Coordinate algorithm activities with self.Schedule.On() self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.At(13,30), self.ClosePositions)Using Scheduling Helpers

Scheduled events need a DateRules and TimeRules pair to set a specific time, and an action that you want to complete. When the event is triggered the action block (or function) is executed. We include our asset ticker in our EveryDay("ticker") object to specifify that we are calling days there is data for the ticker. You can learn more in the documentation.

# Trigger an event that occurs every day there is data for SPY self.DateRules.EveryDay("SPY") Liquidate

Liquidate("ticker") closes any open positions with a market order, and closes any open limit orders.

def ClosePositions(self): self.openingBar = None self.Liquidate("TSLA")

Task Objectives

The breakout signal is strongest in the morning. Let's use a scheduled event to close our position each day at 13:30 ET.

  1. Create an event handler for our scheduled event named def ClosePositions(self):
  2. Inside your ClosePositions function set self.openingBar to None and liquidate TSLA.
  3. Create a scheduled event triggered at 13:30 ET calling the ClosePositions function.
Hint:

You can use TimeRules.At(int hour, in min) helper to trigger at a specific time each day. Keep in mind we are operating on a 24-hour clock so 4pm ET is hour 16.

For the Schedule.On() call, make sure you are using self.ClosePositions not self.ClosePositions(), the parentheses execute the function.

Code:

class OpeningRangeBreakout(QCAlgorithm):
    
    openingBar = None 
    
    def Initialize(self):
        self.SetStartDate(2018, 7, 10)  
        self.SetEndDate(2019, 6, 30)  
        self.SetCash(100000)
        self.AddEquity("TSLA", Resolution.Minute)
        self.Consolidate("TSLA", timedelta(minutes=30), self.OnDataConsolidated)
        
        #3. Create a scheduled event triggered at 13:30 calling the ClosePositions function
        self.Schedule.On(self.DateRules.EveryDay("TSLA"), self.TimeRules.At(13, 30), self.ClosePositions)
        
    def OnData(self, data):
        
        if self.Portfolio.Invested or self.openingBar is None:
            return
        
        if data["TSLA"].Close > self.openingBar.High:
            self.SetHoldings("TSLA", 1)

        elif data["TSLA"].Close < self.openingBar.Low:
            self.SetHoldings("TSLA", -1)  
         
    def OnDataConsolidated(self, bar):
        if bar.Time.hour == 9 and bar.Time.minute == 30:
            self.openingBar = bar
    
    #1. Create a function named ClosePositions(self)
    def ClosePositions(self):
        #2. Set self.openingBar to None, and liquidate TSLA 
        self.openingBar = None
        self.Liquidate("TSLA")