In this tutorial, we shall cover how to add different timeframes to an algorithm. You may have noticed that QuantConnect provides only Tick, Second, Minute, Hourly and Daily data feeds. So how are we supposed to trade a Weekly, 15-minute, 30-minute or a 4-hour timeframe? Stick around to find out! Note: This post continues on from our Plotting The First Script post and forms part of the Getting Started Series. If you are completely new to QuantConnect, it is recommended to start at the first tutorial in the series.
ScopeWe are going to create a custom “weekly’ timeframe and add it to our algorithm. The weekly timeframe will be used in the strategy for confirming entries with a second RSI Indicator. In addition to this, we shall also refresh our newly acquired plotting skills by adding the upper timeframe confirmation series to our RSI plot. However, the “how and why” of adding the plot will not be covered here as all the details were shared in the last tutorial. At first glance, the scope of this tutorial may sound relatively simple. However, there are quite a few steps we must take in order to create a custom timeframe and add an indicator to it. As such, the post will actually touch upon the following topics:
- Consolidators: What they are and how to use them.
- A look at the differences between tradebars and quotebars.
- Registering an Indicator with our custom timeframe.
- How to avoid a “Forward Only Indicator” runtime error.
ConsolidatorsConsolidators answer the question of “how can I use XYZ timeframe if QuantConnect doesn’t provide the data for it?“. Although “consolidator” is unlikely to be the first word that would pop into your head when looking for documentation on “how to access 30-minute data feeds”, the name is actually quite appropriate. A consolidator will consolidate smaller data into larger data. So, for example, we can take 1-minute data and consolidate 30 x 1-minute bars into a single 30-minute bar. Whilst using consolidators may take a bit longer to learn, using them to create custom timeframes has a couple of key advantages:
- We can create any time-frame we can think of! We don’t need to stick to the usual suspects of 1-minute, 5-minute, 15-minute etc bars. We can create a 25 tick, a 17 second or a 12-minute data feed!
- It means QuantConnect don’t need to store or process all this data on our behalf.
- Create a consolidator class.
- Create a handler. This is a method (function) that is called/executed every time a new bar of consolidated data is created.
- Add the consolidator via the
Trade Bars Vs Quote BarsTrade bars contain,
volumedata. These are “mid” prices and a mid-price is the average price between
askprices. Mid prices are what you are given when you see that Stock XYZ is at $200. Conversely, Quote Bars actually contain the
askinformation, we can actually see the spread (the difference between the bid and ask) and get more accurate backtesting results. For more information regarding
askprices see the following link: https://www.investopedia.com/terms/b/bid-and-ask.asp
Selecting the right consolidatorSo now we know the difference between the two groups, how do we know which one to use with our data? Luckily QuantConnect’s documentation comes to the rescue:
For each asset class and the data we provide please see the data library. It is a little confusing but here is the summary of QuantConnect provided data:Source: https://www.quantconnect.com/docs/algorithm-reference/consolidating-data#Consolidating-Data-TradeBars-vs-QuoteBars So if you are adding an asset that only has trade data available (such as equities), then we must use the
- Equities – trade data only. For Resolution.Tick use the TickConsolidator, for all other resolutions use the TradeBarConsolidator.
- Forex and CFD – quote data only. For Resolution.Tick use the TickQuoteBarConsolidator, for all other resolutions use the QuoteBarConsolidators.
- Options- trade and quote data, at Resolution.Minute only. For consolidating options data use the TradeBarConsolidator or the QuoteBarConsolidator (depending on if you’re consolidating trades or quotes).
- Futures – trade and quote data, at tick, second and minute resolutions.
TradeBarConsolidator. Similarly, for assets that only have quote data (like FX) we use the
The CodeNow we have the required background on consolidators, tradebars and quote bars, we can move onto the code.
### <summary> ### Simple RSI Strategy intended to provide a minimal algorithm example using ### one indicator with the most basic plotting ### </summary> from datetime import timedelta class RSIAlgorithm(QCAlgorithm): def Initialize(self): '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.''' # Set our main strategy parameters self.SetStartDate(2015,1,1) # Set Start Date self.SetEndDate(2018,1,1) # Set End Date self.SetCash(10000) # Set Strategy Cash RSI_Period = 14 # RSI Look back period self.RSI_OB = 60 # RSI Overbought level self.RSI_OS = 40 # RSI Oversold level self.Allocate = 0.25 # Percentage of captital to allocate # Find more symbols here: http://quantconnect.com/data self.AddEquity("AAPL", Resolution.Daily) # Create our consolidators W1_Con = TradeBarConsolidator(timedelta(days=5)) # Register our Handlers W1_Con.DataConsolidated += self.On_W1 # Create our Indicators self.RSI_Ind = self.RSI("AAPL", RSI_Period) self.W1_RSI = RelativeStrengthIndex("AAPL", RSI_Period) # Register the indicaors with our stock and consolidator self.RegisterIndicator("AAPL", self.W1_RSI, W1_Con) # Finally add our consolidators to the subscription # manager in order to receive updates from the engine self.SubscriptionManager.AddConsolidator("AAPL", W1_Con) # Ensure that the Indicator has enough data before trading,. # x5 to get enough weekly data self.SetWarmUp(RSI_Period*5) # Plot the RSI RSIChart = Chart("RSI", ChartType.Stacked) RSIChart.AddSeries(Series("D1", SeriesType.Line)) RSIChart.AddSeries(Series("W1", SeriesType.Line)) self.AddChart(RSIChart) # Create a custom volume chart VolChart = Chart("Volume", ChartType.Stacked) VolChart.AddSeries(Series('Buying Volume', SeriesType.Bar)) VolChart.AddSeries(Series('Selling Volume', SeriesType.Bar)) self.AddChart(VolChart) def On_W1(self,sender,bar): ''' This method will be called every time a new 30 minute bar is ready. bar = The incoming Tradebar. This is different to the data object in OnData() ''' self.Plot('RSI', 'W1', self.W1_RSI.Current.Value) def OnData(self, data): '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here. Arguments: data: Slice object keyed by symbol containing the stock data ''' self.Plot('RSI', 'D1', self.RSI_Ind.Current.Value) if data["AAPL"].Close >= data["AAPL"].Open: self.Plot('Volume', 'Buying Volume', data["AAPL"].Volume) else: self.Plot('Volume', 'Selling Volume', data["AAPL"].Volume) # Make sure we are not warming up if self.IsWarmingUp: return # Determine our entry and exit conditions # Do it here to avoid long lines later long_cond1 = self.RSI_Ind.Current.Value < self.RSI_OS long_cond2 = self.W1_RSI.Current.Value < self.RSI_OS exit_cond1 = self.RSI_Ind.Current.Value > self.RSI_OB exit_cond2 = self.W1_RSI.Current.Value > self.RSI_OB # Check if we are in the market if not self.Portfolio.Invested: # If not, we check the RSI Indicator if all([long_cond1, long_cond2]): # Buy Apple self.SetHoldings("AAPL", self.Allocate) else: if all([exit_cond1, exit_cond2]): # Sell Apple self.Liquidate("AAPL")
Code CommentaryCompared to our plotting tutorial, the
initialize()method has grown significantly. It is here where we do most of the work to add our custom timeframe and a second indicator. However, before any of that, we need to import a python module at the start of the script:
from datetime import timedelta. We then use the imported
timedeltafunction to set how much data our consolidator will consolidate with the line.
W1_Con = TradeBarConsolidator(timedelta(days=5))Here, we are creating the
W1_Conconsolidator using a
AAPLis an equity and the documentation above says that QuantConnect only has trade data available for equities). Following this, we then pass the
TradeBarConsolidatora time delta of 5 days (which is one week in the equity markets). As an example, if you wanted to create a 30-minute timeframe for FX you would need to do 2 things:
- Import Forex MINUTE data using
- Use a
Creating and Registering HandlersAs mentioned in the consolidators section above, we need to create a handler that will be called whenever a new bar of data is ready. Therefore we have added:
def On_W1(self,sender,bar): ''' This method will be called every time a new 30 minute bar is ready. bar = The incoming Tradebar. This is different to the data object in OnData() ''' self.Plot('RSI', 'W1', self.W1_RSI.Current.Value)You can give this handler any name you wish. In our example code, it is named
On_W1(). You will see that all we actually do in the method is plot our RSI value. Our core entry/exit logic will still be performed on our lower timeframe using
On_Data(). Once we have our handler we can register it in the
initialize()method with the line:
W1_Con.DataConsolidated += self.On_W1Those familiar with python may be familiar with using
+=to add a number to a current variable i.e
x += 1. However, as a side note it,
+=can do other things. See the great answer on stack overflow for some background reading and an example: https://stackoverflow.com/questions/4841436/what-exactly-does-do-in-python Finally to finish off adding the timeframe we add it to algorithm via the Subscription Manager with:
self.SubscriptionManager.AddConsolidator("AAPL", W1_Con)Note: If you didn’t want to add any indicators to the timeframe, these are all the steps you need to add a timeframe to the algorithm. You could stop here if that was the case.
Creating and Registering Our RSI IndicatorSince our objective is to use a second upper timeframe for confirmation, we will need to register an RSI indicator to the timeframe. This is done in a slightly different way to how we added the RSI indicator in our first script and if you do this wrong, you can end up scratching your head for some time.
# Create our Indicators self.RSI_Ind = self.RSI("AAPL", RSI_Period) self.W1_RSI = RelativeStrengthIndex("AAPL", RSI_Period) # Register the indicaors with our stock and consolidator self.RegisterIndicator("AAPL", self.W1_RSI, W1_Con)The first
self.RSI_Indindicator is created in exactly the same way as our first script. However, notice the difference with between
self.RSI_Ind? Why are they using different API calls? Well, if you try to create our weekly RSI indicator with
self.RSI()you will run into the following error…
Runtime Error: This is a forward only indicator:As a new Rookie to the platform, this had me stumped for a good hour or two. I was creating both indicators using
self.RSI(). Obviously, I know now that this is NOT the correct way to do it for consolidated data feeds. To ensure that my pain is your pleasure, let’s take a look at why the error appears. When we use
self.RSI(), we are actually using one of QuantConnect’s “helper” methods. It is a shortcut that does some stuff behind the scenes to save you lines of code. In this case, it registers the indicator “under the hood”. As such, If we then register it ourselves to the weekly timeframe, the same indicator will have then been registered twice leading to the error. So to summarize:
RSI("AAPL", RSI_Period): Is a helper method.
RelativeStrengthIndex("AAPL", RSI_Period): Is what we should use. QuantConnect refer to this as the “constructor”.
EMAand other indicators so be on the lookout for them!