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 ......
Liquid Universe Selection
Using a universe selection filter, we will invest in the top 8 stocks which are liquid and cost more than $10 per share. To do this we will use coarse universe selection features.
Lesson OverviewUsing a universe selection filter, we will invest in the top 8 stocks which are liquid and cost more than $10 per share. To do this we will use coarse universe selection features.
What You'll Learn- Using universe selection data (“coarse fundamental data”)
- Sorting an array of coarse fundamental objects for its 8 most liquid symbols
- Monitoring how your universe of securities change
- Allocating and liquidating securities in your universe
- Configuring properties of the universe data requested
We recommend you complete the Opening Range Breakout lesson and previous beginner lessons before starting this lesson.
Setting Up a Coarse Universe Filter
- Liquid Universe Selection
- Setting Up a Coarse Universe Filter
We use universe selection to algorithmically reduce the number of assets added to our algorithm (the investment “universe”). We want to choose our assets with formulas, not by manually selecting our favorite assets.
Selection BiasUniverse selection helps us avoid selection bias by algorithmically choosing our assets for trading. Selection bias is introduced when the asset selection is influenced by personal or non-random decision making, and often results in selecting winning stocks based on future knowledge.
Coarse SelectionWe can add a universe with the method self.AddUniverse()
The method requires a filter function CoarseSelectionFilter
which is passed an array of coarse data representing all stocks active on that trading day. Assets selected by our filter are automatically added to your algorithm in minute resolution.
If we don't have specific filtering instructions for our universe, we can use Universe.Unchanged
which specifies that universe selection should not make changes on this iteration.
Task Objectives CompletedContinue
Set up the foundation for performing universe selection.
- Create a filter function
CoarseSelectionFilter(self, coarse)
. - In your filter function, save
coarse
toself.coarse
, then returnUniverse.Unchanged
. - Pass the name of filter function into
self.AddUniverse
to use coarse fundamental data.
Did you make the CoarseSelectionFilter empty with pass
, and include two parameters, self
and coarse
?
Make sure to not accidentally execute the filter function with brackets (), simply pass the name of the function.
Code:
class LiquidUniverseSelection(QCAlgorithm):
filteredByPrice = None
coarse = None
def Initialize(self):
self.SetStartDate(2019, 1, 11)
self.SetEndDate(2019, 7, 1)
self.SetCash(100000)
#3. Add a Universe model using Coarse Fundamental Data and set the filter function
self.AddUniverse(self.CoarseSelectionFilter)
#1. Add an empty filter function
def CoarseSelectionFilter(self, coarse):
#2. Save coarse as self.coarse and return an Unchanged Universe
self.coarse = coarse
return Universe.Unchanged
-------------------------------------
Understanding Coarse Fundamental Objects
- Liquid Universe Selection
- Understanding Coarse Fundamental Objects
The CoarseSelectionFilter
takes a parameter coarse
. Coarse is an array of CoarseFundamentalObjects:
The CoarseSelectionFilter
function narrows the list of companies according to properties like price and volume. The filter needs to return a list of symbols. For example, we may want to narrow down our universe to liquid assets, or assets that pass a technical indicator filter. We can do all of this in the coarse selection function.
You can use the coarse fundamental data to create your own criteria ("factors") to perform your selection. Once you have your target criteria there are two key tools: sorting and filtering. Sorting lets you take the top and/or bottom ranked symbols according to your criteria, filtering allows you to narrow your selection range to eliminate some assets. In python this is accomplished by sort
and list selection methods. You can read more about it here.
When you return a symbol from the CoarseSelectionFilter
, LEAN automatically subscribes to these symbols and adds them to our algorithm.
Task Objectives CompletedContinue
We want to trade the 8 most liquid symbols, worth more than $10 per share. To get this list of symbols we will need to sort our list of coarse objects ("coarse
") by dollar volume. Then we want to use a list comprehension filter to exclude penny stocks, and get the symbol objects.
- Sort your symbols descending by dollar volume and save to
sortedByDollarVolume
- Set
self.filteredByPrice
to symbols with a price of more than $10 per share - Return the 8 most liquid symbols from the
self.filteredByPrice
list
This one can be a little tricky.
- Make sure you are sorting with the reverse descending=True like in the example code.
- Remember we want to iterate through the top 8 symbols by returning filteredByPrice[:8].
class LiquidUniverseSelection(QCAlgorithm):
filteredByPrice = None
def Initialize(self):
self.SetStartDate(2019, 1, 11)
self.SetEndDate(2019, 7, 1)
self.SetCash(100000)
self.AddUniverse(self.CoarseSelectionFilter)
def CoarseSelectionFilter(self, coarse):
#1. Sort descending by daily dollar volume
sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
#2. Select only Symbols with a price of more than $10 per share
self.filteredByPrice = [x.Symbol for x in sortedByDollarVolume if x.Price > 10]
#3. Return the 8 most liquid Symbols from the filteredByPrice list
self.filteredByPrice = self.filteredByPrice[:8]
return self.filteredByPrice
----------------------------------
Tracking Security Changes
- Liquid Universe Selection
- Tracking Security Changes
When the universe constituents change (securities are added or removed from the algorithm) the algorithm generates an OnSecuritiesChanged
event which is passed information about the asset changes.
You can monitor and act on these events in the OnSecuritiesChanged()
event handler. The universe selection is performed at midnight each day, to choose the assets for the next trading day. These assets are added in minute resolution by default.
Algorithms can write log files for later analysis using self.Log(string message)
. At the end of the backtest a link will be presented in your console to view your results.
self.Log(f"OnSecuritiesChanged({self.Time}):: {changes}")
Task Objectives CompletedContinue
- Create a function
OnSecuritiesChanged
with parameters self and changes - Store the changes variable to
self.changes
. - Log the changes in your
OnSecuritiesChanged
event handler.
Fun Tip: You might want to inspect the logs link in the console after the algorithm runs before moving to the next task.
Hint:
You will want to use a format string: prefixed with "f" to log the changes i.e. f"OnSecuritiesChanged({self.Time}):: {changes}"
. .
Code:
class LiquidUniverseSelection(QCAlgorithm):
filteredByPrice = None
changes = None
def Initialize(self):
self.SetStartDate(2019, 1, 11)
self.SetEndDate(2019, 7, 1)
self.SetCash(100000)
self.AddUniverse(self.CoarseSelectionFilter)
def CoarseSelectionFilter(self, coarse):
sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
filteredByPrice = [x.Symbol for x in sortedByDollarVolume if x.Price > 10]
return filteredByPrice[:8]
#1. Create a function OnSecuritiesChanged
def OnSecuritiesChanged(self, changes):
#2. Save securities changed as self.changes
self.changes = changes
#3. Log the changes in the function
self.Log(f"OnSecuritiesChanged({self.Time}):: {changes}")
------------------------------
Building Our Portfolio
- Liquid Universe Selection
- Building Our Portfolio
The OnSecuritiesChanged
event fires whenever we have changes to our universe. It receives a SecurityChanges
object containing references to the added and removed securities.
To build our portfolio we can loop over the securities changed. The AddedSecurities
and RemovedSecurities
properties are lists of security objects.
Task Objectives CompletedContinue
For this strategy, we want to always have 10% of our cash allocated to each of the securities in the universe. When a security leaves the universe liquidate any holdings we have in that asset. Once all the securities are liquidated, purchase the new universe securities.
- In the
OnSecuritiesChanged
event, loop over the removed securities, liquidating each one. - In the
OnSecuritiesChanged
event, loop over the added securities, usingSetHoldings
to allocate 10% to each.
Note: We changed the data resolution to daily. In next task will discuss configuring more universe settings.
Hint:
Remember when we set holdings we can pass in a symbol
and a percentage allocation
. self.SetHoldings(security.Symbol, 0.1)
Code:
class LiquidUniverseSelection(QCAlgorithm):
filteredByPrice = None
def Initialize(self):
self.SetStartDate(2019, 1, 11)
self.SetEndDate(2019, 7, 1)
self.SetCash(100000)
self.AddUniverse(self.CoarseSelectionFilter)
# Ignore this for now, we'll cover it in the next task.
self.UniverseSettings.Resolution = Resolution.Daily
def CoarseSelectionFilter(self, coarse):
sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
filteredByPrice = [x.Symbol for x in sortedByDollarVolume if x.Price > 10]
return filteredByPrice[:8]
def OnSecuritiesChanged(self, changes):
self.changes = changes
self.Log(f"OnSecuritiesChanged({self.UtcTime}):: {changes}")
#1. Liquidate removed securities
for security in changes.RemovedSecurities:
if security.Invested:
self.Liquidate(security.Symbol)
#2. We want 10% allocation in each security in our universe
for security in changes.AddedSecurities:
self.SetHoldings(security.Symbol, 0.1)
---------------------------
- Liquid Universe Selection
- Customizing Universe Settings
You can control the settings of assets added by universe selection with the UniverseSettings helper. This mostly used to control the Resolution, Leverage and Data Normalization Mode of assets in your universe.
self.UniverseSettings.Resolution self.UniverseSettings.Leverage self.UniverseSettings.FillForward self.UniverseSettings.MinimumTimeInUniverse self.UniverseSettings.ExtendedMarketHoursLeverage with SetHoldingsBrokerages sometimes let clients borrow money to invest, this is known as leverage. When leverage is available you can purchase up to "Cash x Leverage" worth of stock.
SetHoldings
calculates the number of shares to purchase, based on a fraction of your equity, and then places a market order for them. For example, if you have $10,000 cash, SetHoldings("SPY", 1.0)
will attempt to purchase $10,000 of SPY. With a leverage of 2.0, you could use SetHoldings("SPY", 2.0)
and purchase $20,000 of SPY, with $10,000 cash.
The market can quickly change price from the time you place the SetHoldings request to when the order is filled. If your portfolio allocations sum to exactly 100% orders may be rejected due to insufficient buying power. You should leave a buffer to account for market movements and fees. This is especially important with daily data where orders are placed overnight.
# Set holdings to a percentage with a cash buffer # Leverage 1.0: 10 Assets x 0.10 each => 100%, 0% buffer, fail. # Leverage 2.0: 10 Assets x 0.18 each => 180%, 20% buffer self.SetHoldings(security.Symbol, 0.18)Task Objectives CompletedContinue
- Set the universe leverage to 2 with
UniverseSettings
- Set holdings to 18% allocation each to leave a cash buffer
class LiquidUniverseSelection(QCAlgorithm):
filteredByPrice = None
def Initialize(self):
self.SetStartDate(2019, 1, 11)
self.SetEndDate(2019, 7, 1)
self.SetCash(100000)
self.AddUniverse(self.CoarseSelectionFilter)
self.UniverseSettings.Resolution = Resolution.Daily
#1. Set the leverage to 2
self.UniverseSettings.Leverage = 2
def CoarseSelectionFilter(self, coarse):
sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True)
filteredByPrice = [c.Symbol for c in sortedByDollarVolume if c.Price > 10]
return filteredByPrice[:10]
def OnSecuritiesChanged(self, changes):
self.changes = changes
self.Log(f"OnSecuritiesChanged({self.Time}):: {changes}")
for security in self.changes.RemovedSecurities:
if security.Invested:
self.Liquidate(security.Symbol)
for security in self.changes.AddedSecurities:
#2. Leave a cash buffer by setting the allocation to 0.18 instead of 0.2
# self.SetHoldings(security.Symbol, ...)
self.SetHoldings(security.Symbol, 0.18)
Greg Kendall
The Backtest with code…
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!