In this lesson we will select a universe of assets where the 50-EMA is greater than the 200 EMA, create a custom class to hold symbol specific data, and use the history API to prepare our indicators.
What You'll Learn- Laying down our universe foundation
- Grouping data with classes
- Creating properties and methods to tidy code
- Using a history call to prepare indicators
We recommend you complete all previous beginner lessons before starting this lesson.
Laying Our Universe Foundation- 200-50 EMA Momentum Universe
- Laying Our Universe Foundation
In this lesson we will using two indicators to perform selection on a universe of securities.
Creating Our UniverseRecall that we use coarse universe selection to create a set of stocks based on dollar-volume, and price. To create our universe of assets we need to pass a coarse selection filter function to the self.AddUniverse()
method in our initializer.
Our CoarseSelectionFilter must return a list of symbols, which LEAN automatically subscribes to and adds to our algorithm.
To filter and sort our stocks we can use lambda functions and list comprehension.
# Use the sorted method and lambda to get keys in ascending order (greatest to least in DollarVolume) sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True) # Use list comprehension to get symbols from your sorted list # with a price more than $5 per share and get the top 10 symbols [c.Symbol for c in sortedByDollarVolume if c.Price > 5][:10]Adding and Removing SecuritiesThe OnSecuritiesChanged
event fires whenever we have changes to our universe. It receives a SecurityChanges
object containing references to the added and removed securities. The AddedSecurities
and RemovedSecurities
properties are lists of security objects.
Task Objectives CompletedContinue
- Reverse sort coarse by dollar volume to get the highest volume stocks.
- Filter out the stocks less than $10, save the result to
self.selected
. - Liquidate securities leaving the universe in the
OnSecuritiesChanged
event. - Allocate 10% holdings to each asset added to the universe.
changes.AddedSecurities
and changes.RemovedSecurities
, collections with the security.Symbol
? If you need to review these topics you can go back to the lesson Liquid Universe Selection. Code: class EMAMomentumUniverse(QCAlgorithm):def Initialize(self):
self.SetStartDate(2019, 1, 7)
self.SetEndDate(2019, 4, 1)
self.SetCash(100000)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
self.selected = None
def CoarseSelectionFunction(self, coarse):
#1. Sort coarse by dollar volume
sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True)
#2. Filter out the stocks less than $10 and return selected
self.selected = [c.Symbol for c in sortedByDollarVolume if c.Price > 10][:10]
return self.selected
def OnSecuritiesChanged(self, changes):
#3. Liquidate securities leaving the universe
for security in changes.RemovedSecurities:
self.Liquidate(security.Symbol)
#4. Allocate 10% holdings to each asset added to the universe
for security in changes.AddedSecurities:
self.SetHoldings(security.Symbol, 0.10) ------------------------continued below....
Greg Kendall
- 200-50 EMA Momentum Universe
- Grouping Data with Classes
The 200-50 EMA Momentum StrategyAn exponential moving average (EMA) is a technical analysis tool. We can calculate averages of asset price for any time period.
The momentum strategy hypothesis is that stocks that have performed well in the past will continue to perform well. The EMA Momentum Universe selects stocks based on their fast and slow moving average values. An
Creating ClassesExponentialMovingAverage()
class is initialized with a number of bars. Its period is that bar count, multiplied by the period of the bars used.So far our algorithms have all been part of one class. We can create additional classes as a way to group together variables for our universe selection and update any indicators all in a few lines of code. We create another class so each class focuses on one problem, this is known as "Separation of Concerns".
Initializing Class ObjectsClasses can set a constructor to initialize members of the class.
# Create a class to group our indicators class SelectionData: # Set the constructer __init__ that takes the parameter self def __init__(self): # Save the indicator to self.ema self.slow = ExponentialMovingAverage(200)Methods can group code and provide a clean way to check our indicators are ready. For universes it is convenient to check if our indicators are ready with the
# Create a class method called is_ready class SelectionData: def is_ready(self): # Return the IsReady property return self.slow.IsReadyIsReady
property:We could also use a method to group updating indicators as demonstrated below. The benefits of this abstraction are more obvious with more complex examples.
# Create a class method called update that takes the parameters self, time, price class SelectionData: def update(self, time, price): #Use the Update property to get the time and price for our indicators self.fast.Update(time, price) self.slow.Update(time, price)Task Objectives CompletedContinue
We will create a new class to hold the 200 ("slow") and 50 ("fast") day moving average indicator objects. The new class is initialized and updated via methods.
- Create a class named
- Create a constructor which takes the
- Create a method in
- Create a method in
Hint: Remember to include theclass SelectionData()
.self
object. In the constructor, create theself.fast
andself.slow
class variables and initialize them with anExponentialMovingAverage
object.SelectionData
namedis_ready
with a self parameter which returns true if our slow and fast indicators are ready.SelectionData
namedupdate
withself
,time
, andprice
parameters which updates the indicators with the latest price.self
paramter first in your methods. Remember the indicator classes take an uppercase U for theirUpdate
methods, and uppercase, or pascal caseIsReady
for checking their state. Code:class EMAMomentumUniverse(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2019, 1, 7)
self.SetEndDate(2019, 4, 1)
self.SetCash(100000)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
def CoarseSelectionFunction(self, coarse):
sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True)
selected = [c.Symbol for c in sortedByDollarVolume if c.Price > 10][:10]
return selected
def OnSecuritiesChanged(self, changes):
for security in changes.RemovedSecurities:
self.Liquidate(security.Symbol)
for security in changes.AddedSecurities:
self.SetHoldings(security.Symbol, 0.10)
#1. Create a class SelectionData
class SelectionData():
#2. Create a constructor that takes self
def __init__(self):
#2. Save the fast and slow ExponentialMovingAverage
self.slow = ExponentialMovingAverage(200)
self.fast = ExponentialMovingAverage(50)
#3. Check if our indicators are ready
def is_ready(self):
return self.slow.IsReady and self.fast.IsReady
#4. Use the "indicator.Update" method to update the time and price of both indicators
def update(self, time, price):
self.fast.Update(time, price)
self.slow.Update(time, price)
Greg Kendall
- 200-50 EMA Momentum Universe
- Applying Class In Universe
Universes and DictionariesLet's create a
Initializing a Dictionary# Initialize a dictionary called averages self.averages = {}Adding to a Dictionary, Checking if an Item is in DictionarySelectionData
for every security in our universe, and store it in a dictionary to quickly access their methods.Dictionaries are a way of storing data which you can look up later with a "key". To perform EMA-universe selection on every security we'll need a
SelectionData
for every Symbol in our universe.We can check if an item exists in a dictionary with
# check if the object is already in the dictionary. if symbol not in self.averages: # Add the new SelectionData to the key self.averages[symbol] = SelectionData()Accessing Dictionary Itemsin
. We should check if it exists before creating a new copy:You can access and use dictionary items with braces/brackets. Using braces you can look up the specific Symbol and find the right SelectionData value.
# e.g. Accessing a method and pass in parameters self.averages[symbol].update(self.Time, coarse.AdjustedPrice) # e.g. Accessing property of class, as a dictionary item if self.averages[symbol].fast > self.averages[symbol].slow: # Access dictionary methods with braces using "dot notation" if self.averages[symbol].is_ready(): selected.append(symbol)Task Objectives CompletedContinue
- Create a dictionary class variable named
- In your universe selection, check if the dictionary contains symbol.
- If Symbol is not in the
- Access the
- Using the
Hint: Dictionaries can be a little tricky until you get used to them. Think of it like a book, where you're sorting data on different pages. You can look up the right page with the Symbol object Code: class EMAMomentumUniverse(QCAlgorithm):self.averages
.averages
dictionary, create a new instance ofSelectionData
; and add it to averages.self.averages[symbol]
and update it with the time and adjusted price data.SelectionData
object in averages, check if the indicators are ready, and the fast-EMA is greater than the slow-EMA. Then useappend
to add symbol to the selected list.def Initialize(self):
self.SetStartDate(2019, 1, 7)
self.SetEndDate(2019, 4, 1)
self.SetCash(100000)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
#1. Create our dictionary and save it to self.averages
self.averages = { }
def CoarseSelectionFunction(self, universe):
selected = []
universe = sorted(universe, key=lambda c: c.DollarVolume, reverse=True)
universe = [c for c in universe if c.Price > 10][:100]
# Create loop to use all the coarse data
for coarse in universe:
symbol = coarse.Symbol
#2. Check if we've created an instance of SelectionData for this symbol
if symbol not in self.averages:
#3. Create a new instance of SelectionData and save to averages[symbol]
self.averages[symbol] = SelectionData()
#4. Update the symbol with the latest coarse.AdjustedPrice data
self.averages[symbol].update(self.Time, coarse.AdjustedPrice)
#5. Check if 50-EMA > 200-EMA and if so append the symbol to selected list.
if self.averages[symbol].fast > self.averages[symbol].slow:
if self.averages[symbol].is_ready():
selected.append(symbol)
return selected[:10]
def OnSecuritiesChanged(self, changes):
for security in changes.RemovedSecurities:
self.Liquidate(security.Symbol)
for security in changes.AddedSecurities:
self.SetHoldings(security.Symbol, 0.10)
class SelectionData(object):
def __init__(self):
self.slow = ExponentialMovingAverage(200)
self.fast = ExponentialMovingAverage(50)
def is_ready(self):
return self.slow.IsReady and self.fast.IsReady
def update(self, time, price):
self.fast.Update(time, price)
self.slow.Update(time, price)
Greg Kendall
- 200-50 EMA Momentum Universe
- Preparing Indicators with History
Applying a History FunctionWe need to prepare our indicators with data. With the QuantConnect History API, we request data for symbols as a pandas dataframe.
The History API has many different helpers, the simplest of these gets data for a single symbol.
history = self.History(symbol, 200, Resolution.Daily)Using History DataFrameEach row of the history result represents the prices at a point of time. Each column of the DataFrame is a property of the price data (e.g. open, high, low, close).
History data is returned in ascending order from oldest to newest. If no resolution is chosen the default is Resolution.Minute. We can iterate over the results of a history call using
for bar in history.itertuples(): self.fast.Update(bar.Index[1], bar.close) self.slow.Update(bar.Index[1], bar.close)itertuples()
. The history DataFrame has two indexes; Symbol(0) and Time(1). You can access the time of a row by usingrow.Index[1]
. The column names of the history result are lowercase.Task Objectives CompletedContinue
In our previous backtest we didn't see trades because our indicators were waiting for 200 days to pass before they were ready to use. Here we will use the History API to speed up getting ready for trading.
- Call
- Adjust the creation of the
- Update the
- Iterate over the history data, passing rows into the
Hint: You can pass in the history result like thisHistory
to get 200 days of history data for each symbol in our universeSelectionData
object to pass in the history result__init__
constructor to accept a history array.update
method to prepare the indicators.SelectionData(history)
. CODE:class EMAMomentumUniverse(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2019, 1, 7)
self.SetEndDate(2019, 4, 1)
self.SetCash(100000)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction)
self.averages = { }
def CoarseSelectionFunction(self, universe):
selected = []
universe = sorted(universe, key=lambda c: c.DollarVolume, reverse=True)
universe = [c for c in universe if c.Price > 10][:100]
for coarse in universe:
symbol = coarse.Symbol
if symbol not in self.averages:
# 1. Call history to get an array of 200 days of history data
history = self.History(symbol, 200, Resolution.Daily)
#2. Adjust SelectionData to pass in the history result
self.averages[symbol] = SelectionData(history)
self.averages[symbol].update(self.Time, coarse.AdjustedPrice)
if self.averages[symbol].is_ready() and self.averages[symbol].fast > self.averages[symbol].slow:
selected.append(symbol)
return selected[:10]
def OnSecuritiesChanged(self, changes):
for security in changes.RemovedSecurities:
self.Liquidate(security.Symbol)
for security in changes.AddedSecurities:
self.SetHoldings(security.Symbol, 0.10)
class SelectionData():
#3. Update the constructor to accept a history array
def __init__(self, history):
self.slow = ExponentialMovingAverage(200)
self.fast = ExponentialMovingAverage(50)
#4. Loop over the history data and update the indicators
for bar in history.itertuples():
self.fast.Update(bar.Index[1], bar.close)
self.slow.Update(bar.Index[1], bar.close)
def is_ready(self):
return self.slow.IsReady and self.fast.IsReady
def update(self, time, price):
self.fast.Update(time, price)
self.slow.Update(time, price)
Greg Kendall
Here is the backtest and code:
Angelo Galanti
I cannot complete Lesson 7 Task 03 Grouping Data with Classes in the boot camp.
I copied the solution.py in my main.py but I do receive the error 'QCAlgorithm' is not defined.
This is the first task when I need to create a Class and somehow I think this is related to the problem
Could someone help me?
Thanks
Error Message:
Algorithm.Initialize() Error: During the algorithm initialization, the following exception has occurred: Loader.TryCreatePythonAlgorithm(): Unable to import python module ./cache/algorithm/project/main.pyc. AlgorithmPythonWrapper(): name 'QCAlgorithm' is not defined
at <module>
class EMAMomentumUniverse(QCAlgorithm):
at Python.Runtime.PythonException.ThrowLastAsClrException()
at Python.Runtime.NewReferenceExtensions.BorrowOrThrow(NewReference& reference)
at Python.Runtime.PyModule.Import(String name)
at Python.Runtime.Py.Import(String name)
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper..ctor(String moduleName) at AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs:line 74 in main.py: line 0
name 'QCAlgorithm' is not defined Stack Trace: During the algorithm initialization, the following exception has occurred: Loader.TryCreatePythonAlgorithm(): Unable to import python module ./cache/algorithm/project/main.pyc. AlgorithmPythonWrapper(): name 'QCAlgorithm' is not defined
at <module>
class EMAMomentumUniverse(QCAlgorithm):
at Python.Runtime.PythonException.ThrowLastAsClrException()
at Python.Runtime.NewReferenceExtensions.BorrowOrThrow(NewReference& reference)
at Python.Runtime.PyModule.Import(String name)
at Python.Runtime.Py.Import(String name)
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper..ctor(String moduleName) at AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs:line 74 in main.py: line 0
name 'QCAlgorithm' is not defined
Stacktrace:
During the algorithm initialization, the following exception has occurred: Loader.TryCreatePythonAlgorithm(): Unable to import python module ./cache/algorithm/project/main.pyc. AlgorithmPythonWrapper(): name 'QCAlgorithm' is not defined at class EMAMomentumUniverse(QCAlgorithm): at Python.Runtime.PythonException.ThrowLastAsClrException() at Python.Runtime.NewReferenceExtensions.BorrowOrThrow(NewReference& reference) at Python.Runtime.PyModule.Import(String name) at Python.Runtime.Py.Import(String name) at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper..ctor(String moduleName) at AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs:line 74 in main.py: line 0 name 'QCAlgorithm' is not defined
Alexandre Catarino
Hi Angelo Galanti ,
Thank you for the report.
We have fixed the lesson by adding
at the beginning of the script. The AlgorithmImports contains the QCAlgorithm definition.
Best regards,
Alex
Greg Kendall
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!