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 Overview

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.

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
Lesson Requirements

We recommend you complete the Opening Range Breakout lesson and previous beginner lessons before starting this lesson.

 

Setting Up a Coarse Universe Filter
  1. Liquid Universe Selection
  2. Setting Up a Coarse Universe Filter
Introduction to Universe Selection

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 Bias

Universe 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 Selection

We can add a universe with the method self.AddUniverse()

# Pass the filter function into AddUniverse in your Initializer def Initialize(self): self.AddUniverse(self.CoarseSelectionFilter) # Create a filter function def CoarseSelectionFilter(self, coarse): pass

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.

Universe Unchanged

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.

# Create a filter function that currently does not change the universe def CoarseSelectionFilter(self, coarse): return Universe.Unchanged

Task Objectives CompletedContinue

Set up the foundation for performing universe selection.

  1. Create a filter function CoarseSelectionFilter(self, coarse).
  2. In your filter function, save coarse to self.coarse, then return Universe.Unchanged.
  3. Pass the name of filter function into self.AddUniverse to use coarse fundamental data.
Hint: 

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
  1. Liquid Universe Selection
  2. Understanding Coarse Fundamental Objects
Coarse Fundamental Objects

The CoarseSelectionFilter takes a parameter coarse. Coarse is an array of CoarseFundamentalObjects:

def CoarseSelectionFilter(self, coarse): # 'coarse' is an array of CoarseFundamental data. # CoarseFundamental has properties Symbol, DollarVolume and Price. coarse[0].Symbol coarse[0].DollarVolume coarse[0].PriceUsing the Coarse Selection Filter

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.

# Use the sorted method to get keys in ascending order (greatest to least in DollarVolume) sortedByDollarVolume = sorted(coarse, key=lambda c: c.DollarVolume, reverse=True) # Get symbols from your sorted list with a price more than $5 per share symbols_by_price = [c.Symbol for c in sortedByDollarVolume if c.Price > 5] # Get the top 10 symbols symbols_by_price[:10]

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.

  1. Sort your symbols descending by dollar volume and save to sortedByDollarVolume
  2. Set self.filteredByPrice to symbols with a price of more than $10 per share
  3. Return the 8 most liquid symbols from the self.filteredByPrice list
Hint: 

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].
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)
        
    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
  1. Liquid Universe Selection
  2. Tracking Security Changes
Monitoring Universe 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.

def OnSecuritiesChanged(self, changes): passUniverse Selection Timing

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.

Using Log()

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

  1. Create a function OnSecuritiesChanged with parameters self and changes
  2. Store the changes variable to self.changes.
  3. 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
  1. Liquid Universe Selection
  2. Building Our Portfolio
Adding and Removing Securities

The OnSecuritiesChanged event fires whenever we have changes to our universe. It receives a SecurityChanges object containing references to the added and removed securities.

# List of securities entering the universe changes.AddedSecurities # List of securities leaving the universe changed.RemovedSecurities

To build our portfolio we can loop over the securities changed. The AddedSecurities and RemovedSecurities properties are lists of security objects.

def OnSecuritiesChanged(self, changes): # Liquidate securities for security in changes.RemovedSecurities: if security.Invested: self.Liquidate(security.Symbol) # SetHoldings for each new asset for security in changes.AddedSecurities: self.SetHoldings(security.Symbol, 0.1)

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.

  1. In the OnSecuritiesChanged event, loop over the removed securities, liquidating each one.
  2. In the OnSecuritiesChanged event, loop over the added securities, using SetHoldings 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 allocationself.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)

 

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

Customizing Universe Settings
  1. Liquid Universe Selection
  2. Customizing Universe Settings
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 SetHoldings

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

Keeping a Cash Buffer

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

  1. Set the universe leverage to 2 with UniverseSettings
  2. Set holdings to 18% allocation each to leave a cash buffer
 Hint: Did you remember to use self.UniverseSettingsUniverseSettings to set your Resolution and Leverage? Use SetHoldings of 0.18 per asset. 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)
        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)

 

Author