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 ......

 

Fading The Gap

In this lesson we will use scheduled events to monitor for overnight price gaps in the market and trade on abnormal activity. Additionally, we will use a standard deviation indicator to reduce a parameter.

What You'll Learn
  • Creating Scheduled Events
  • Creating a Rolling Window
  • Accessing a Rolling Window
  • Reducing a Parameter
Lesson Requirements

We recommend completing the Liquid Universe Selection lesson and Opening Range Breakout lesson before starting this lesson.

Creating Scheduled Events
  1. Fading The Gap
  2. Creating Scheduled Events
Fading The Gap

The difference between the close price of the previous day and the opening price of the current day is referred to as a gap. Fading the gap is a strategy that monitors for a large gap down and buys stock assuming it will rebound.

Using Scheduled Events

Scheduled events trigger a method at a specific date and time. Scheduled events have three parameters: a DateRule, a TimeRule, and an action parameter. Our action parameter should be set to the name of a method.

Setting Event Time

Our time rules AfterMarketOpen and BeforeMarketClose take a symbol, and minute period. Both trigger an event a for a specific symbol's market hours.

# Trigger event 1 min after SPY market open (9.31am). self.TimeRules.AfterMarketOpen("SPY", 1) # Trigger event at SPY market close (4pm). self.TimeRules.BeforeMarketClose("SPY", 0)

Setting Event Action

def Initialize(self): # Schedule an event to fire every trading day for a security. # Remember to schedule events in initialize of your algorithm. # The time rule here tells the algorithm to fire 1 minute after TSLA's market open self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("TSLA", 1), self.FadeTheGap) def FadeTheGap(self): pass

You must schedule events in your algorithm Initialize method.

Task Objectives CompletedContinue

Let's set up our data schedule.

  1. Create an empty method ClosingBar. Create a scheduled event to run every day at TSLA market close that calls ClosingBar.
  2. Create an empty method OpeningBar. Create a scheduled event to run every day at 1 minute after TSLA market open that calls OpeningBar.
  3. Create a method ClosePositions and liquidate our position to limit our exposure and keep our holding period short. Create a scheduled event to run every day at 45 minutes after TSLA market open that calls ClosePositions.
 Hint: 

Are you using AfterMarketOpen with the TimeRules class? If you're rusty brush up on the documentation here.

Scheduled Events was introduced in the Opening Range Bootcamp.

 

Code: 

 

class FadingTheGap(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 11, 1)  
        self.SetEndDate(2018, 7, 1)  
        self.SetCash(100000)  
        self.AddEquity("TSLA", Resolution.Minute)
        
        #1. Create a scheduled event to run every day at "0 minutes" before 
        # TSLA market close that calls the method ClosingBar
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose("TSLA", 0), self.ClosingBar) 
        
        #2. Create a scheduled event to run every day at 1 minute after 
        # TSLA market open that calls the method OpeningBar
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("TSLA", 1), self.OpeningBar)
        
        #3. Create a scheduled event to run every day at 45 minutes after 
        # TSLA market open that calls the method ClosePositions
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("TSLA", 45), self.ClosePositions)
    
    #1. Create an empty method ClosingBar
    def ClosingBar(self):
        pass
    
    #2. Create an empty method OpeningBar
    def OpeningBar(self):
        pass
    
    #3. Create a method ClosePositions
        # Liquidate our position to limit our exposure and keep our holding period short 
    def ClosePositions(self):
        self.Liquidate()

 

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

 

Creating a Rolling Window
  1. Fading The Gap
  2. Creating a Rolling Window
Creating A Rolling Window

RollingWindow holds a set of the most recent entries of data. As we move from time t=0 forward, our rolling window will shuffle data further along to a different index until it leaves the window completely.

<see chart in lesson - chart/graphic of a rollling window>

Setting A Window Type and Length

When we create a rolling window we need to set the type of data it will use. See more about the data types available in QuantConnect such as float, TradeBar, and QuoteBar here.

The length of the window is specified in parentheses after the data type. Keep in mind the window is indexed from 0 up to Count-1.

# Rolling window of 4 TradeBars (indexed from 0: 0, 1, 2, 3) self.window = RollingWindow[TradeBar](4) Adding Entries To A Window

We update our rolling array with the Add() method, which adds a new element at the beginning of the window. The objects inserted must match the type of the RollingWindow.

# Add the SPY trade bar to the window. self.window.Add(self.CurrentSlice["SPY"])Accessing Price Data

The latest data point is accessible with the algorithm self.CurrentSlice property. This is the same Slice object from OnData. See more about slices in the documentation.

def OnData(self): self.Debug("Called Event: " + str(self.CurrentSlice["SPY"].Price))

Task Objectives CompletedContinue

Let's set up our rolling window to get the final bar of yesterday, and the first bar of today into our window.

  1. In Initialize(), create a RollingWindow with type TradeBar and length of 2 as self.window
  2. In the self.ClosingBar method, add the current TradeBar of TSLA to our rolling window.
  3. In the self.OpeningBar method, add the current TradeBar of TSLA to our rolling window.
 Hint:  You can use the self.CurrentSlice["TSLA"] to get the latest TradeBar for TSLA. Code: 

class FadingTheGap(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 11, 1) 
        self.SetEndDate(2018, 7, 1) 
        self.SetCash(100000)
        self.AddEquity("TSLA", Resolution.Minute)
        
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose("TSLA", 0), self.ClosingBar)
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("TSLA", 1), self.OpeningBar)
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("TSLA", 45), self.ClosePositions) 
        
        #1. Save a RollingWindow with type TradeBar and length of 2 as self.window
        self.window = RollingWindow[TradeBar](2)
    
    def ClosingBar(self):
        #2. Add the final bar of TSLA to our rolling window
        self.window.Add(self.CurrentSlice["TSLA"])
 
    def OpeningBar(self):
        #3. If "TSLA" is in the current slice, add the current slice to the window
        if "TSLA" in self.CurrentSlice.Bars:
            self.window.Add(self.CurrentSlice["TSLA"])
        
    def ClosePositions(self):
        self.Liquidate()

 

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

 

Accessing a Rolling Window
  1. Fading The Gap
  2. Accessing a Rolling Window
Checking if the Window Is Ready

Our window is not full when we first create it. QuantConnect provides a shortcut to check the if the rolling window is full("ready"): window.IsReady which will return true when all slots of the window have data.

# If the window is not ready # return and wait if not self.window.IsReady: returnAccessing Rolling Window Data

The object in the window with index [0] refers to the most recent item. The length-1 in the window is the oldest object.

# In a rolling window with len=2 # access the latest object # from the rolling window with index 0 self.window[0] # In a rolling window with len=2 # access the last object # from the rolling window with index 1 self.window[1]

Task Objectives CompletedContinue

We will use our rolling window to decide when to place an order in the OpeningBar method.

  1. If our window is not full yet use return to wait for tomorrow.
  2. Calculate the change in overnight price using index values from our rolling window (ie. today's open - yesterday's close). Save the value to delta.
  3. If delta is less than -$2.50, SetHoldings() to 100% TSLA.
 Hint: 

Remember our RollingWindow is saved to the variable window. Our rolling windows are reverse indexed so 0 is the most recent item.

# Save the delta between today's open price # and yesterday's close price delta = self.window[0].Open - self.window[1].Close

Remember you need to put an "m" on the end of decimal type numbers. "-2.5m"

 

Code:

 

class FadingTheGap(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 11, 1)
        self.SetEndDate(2018, 7, 1)
        self.SetCash(100000) 
        self.AddEquity("TSLA", Resolution.Minute)
        
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose("TSLA", 0), self.ClosingBar) 
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("TSLA", 1), self.OpeningBar)
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("TSLA", 45), self.ClosePositions) 
        
        self.window = RollingWindow[TradeBar](2)
        
    def ClosingBar(self):
        self.window.Add(self.CurrentSlice["TSLA"])
    
    def OpeningBar(self):
        if "TSLA" in self.CurrentSlice.Bars:
            self.window.Add(self.CurrentSlice["TSLA"])
        
        #1. If our window is not full use return to wait for tomorrow
        if not self.window.IsReady:
            return
        
        #2. Calculate the change in overnight price
        delta = self.window[0].Open - self.window[1].Close
        
        #3. If delta is less than -$2.5, SetHoldings() to 100% TSLA
        if delta < -2.5:
            self.SetHoldings("TSLA", 1)
        
    def ClosePositions(self):
        self.Liquidate()

 

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

 

Reducing a Parameter
  1. Fading The Gap
  2. Reducing a Parameter
Reducing Parameters Using Standard Deviation

Modern quant research suggests we should use as few parameters as possible. In this task we'll replace the arbitary -$10 move with a measurement of the standard deviation to replace a parameter with a statistical "confidence".

Assuming asset prices follow a normal distribution, we can use standard deviation to measure an asset's volatility. In a normal distribution, 68% of values are within 1 standard deviation of the mean, 95% of values are within 2 standard deviations of the mean, and 99% of values are within 3 standard deviations of the mean.

Creating an Indicator Manually

QuantConnect provides the ability to add indicators manually. When we create an indicator manually, we control what data goes into it. Each manual indicator is slightly different, but the standard deviation takes a name and period. More on this topic can be found in the documentation here.

# Create an indicator StandardDeviation # for the symbol TSLA with a 30 "value" period self.volatilty = StandardDeviation("TSLA", 30) self.rsi = RelativeStrengthIndex("MyRSI", 200) self.ema = ExponentialMovingAverage("SlowEMA", 200)Updating Our Indicator

When we create a manual indicator object, we need to update it in order to create valid indicator values. All manual indicators have an Update method which takes a time and a value.

# Update the state of our indicator with TSLA's close price def OnData(self, data): if data["SPY"] is not None: self.volatility.Update(self.Time, data["SPY"].Close)

When you're using the helper methods the updates are done automatically (e.g. RSI(), EMA()). When using a combination of OnData and Scheduled Events keep in mind that scheduled events trigger before our OnData events, so your indicator might not be updated yet.

Accessing Indicator Values

Manually created indicators values are accessed just like their automatic counterparts.

# Accessing current value of standard deviation x = self.volatility.Current.Value

Task Objectives CompletedContinue

In the previous task, we traded based on an arbitrary price setting. In this task, we will be replacing the setting with a manually created standard deviation indicator.

  1. In Initialize() create a manual StandardDeviation() indicator to calculate TSLA volatility for a 60 minute period. Save it to the variable self.volatility.
  2. In OnData(), update our StandardDeviation() indicator manually with algorithm time using self.Time and TSLA's close price.
  3. In OpeningBar, use IsReady to check if both volatility and the window are ready, or use return to wait for tomorrow.
  4. Save the number of standard deviations of the delta to the variable deviations for use when we set holdings.# Gap as a number of standard deviations self.deviations = delta / self.volatility.Current.Value
  5. SetHoldings() to 100% TSLA if deviations is less than -3 standard deviations (i.e. when we are 99% confident this gap is an anomaly).
 Hint: Did you use a 60 minute period for your indicator? Try saving StandardDeviation("TSLA", 60) to self.volatility . Code: 

class FadingTheGap(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 11, 1)
        self.SetEndDate(2018, 7, 1)
        self.SetCash(100000) 
        self.AddEquity("TSLA", Resolution.Minute)
        
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.BeforeMarketClose("TSLA", 0), self.ClosingBar) 
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("TSLA", 1), self.OpeningBar)
        self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("TSLA", 45), self.ClosePositions) 
        
        self.window = RollingWindow[TradeBar](2)
                
        #1. Create a manual Standard Deviation indicator to track recent volatility
        self.volatility = StandardDeviation("TSLA", 60)
        
    def OnData(self, data):
        if data["TSLA"] is not None: 
            #2. Update our standard deviation indicator manually with algorithm time and TSLA's close price
            self.volatility.Update(self.Time, data["TSLA"].Close)
    
    def OpeningBar(self):
        if "TSLA" in self.CurrentSlice.Bars:
            self.window.Add(self.CurrentSlice["TSLA"])
        
        #3. Use IsReady to check if both volatility and the window are ready, if not ready 'return'
        if not self.window.IsReady or not self.volatility.IsReady:
            return
        
        delta = self.window[0].Open - self.window[1].Close
        
        #4. Save an approximation of standard deviations to our deviations variable by dividing delta by the current volatility value:
        #   Normally this is delta from the mean, but we'll approximate it with current value for this lesson. 
        deviations = delta / self.volatility.Current.Value 
        
        #5. SetHoldings to 100% TSLA if deviations is less than -3 standard deviations from the mean:
        if deviations < -3:
            self.SetHoldings("TSLA", 1)
        
    def ClosePositions(self):
        self.Liquidate()
        
    def ClosingBar(self):
        self.window.Add(self.CurrentSlice["TSLA"])