Equity
Fundamental Universes
Introduction
A fundamental universe lets you select stocks based on corporate fundamental data. This data is powered by Morningstar® and includes approximately 8,100 tickers (including delisted companies) with 900 properties each.
Create Universes
To add a fundamental universe, in the Initialize
initialize
method, pass a filter function to the AddUniverse
add_universe
method. The filter function receives a list of Fundamental
objects and must return a list of Symbol
objects. The Symbol
objects you return from the function are the constituents of the fundamental universe and LEAN automatically creates subscriptions for them. Don't call AddEquity
add_equity
in the filter function.
public class MyUniverseAlgorithm : QCAlgorithm { private Universe _universe; public override void Initialize() { UniverseSettings.Asynchronous = true; // Add a fundamental universe with a custom filter function. _universe = AddUniverse(FundamentalFilterFunction); } private IEnumerable<Symbol> FundamentalFilterFunction(IEnumerable<Fundamental> fundamental) { // Select US Equities that have fundamental data. return (from f in fundamental where f.HasFundamentalData select f.Symbol); } }
class MyUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.universe_settings.asynchronous = True # Add a fundamental universe with a custom filter function. self._universe = self.add_universe(self._fundamental_function) def _fundamental_function(self, fundamental: List[Fundamental]) -> List[Symbol]: # Select US Equities that have fundamental data. return [c.symbol for c in fundamental if c.has_fundamental_data]
Fundamental
objects have the following attributes:
Example
The simplest example of accessing the fundamental object would be harnessing the iconic PE ratio for a stock. This is a ratio of the price it commands to the earnings of a stock. The lower the PE ratio for a stock, the more affordable it appears.
// Select the top 50 most liquid US Equities and then select the 10 with // lowest PE ratios. UniverseSettings.Asynchronous = true; _universe = AddUniverse( fundamental => (from f in fundamental where f.Price > 10 && f.HasFundamentalData && !Double.IsNaN(f.ValuationRatios.PERatio) orderby f.DollarVolume descending select f).Take(100) .OrderBy(f => f.ValuationRatios.PERatio).Take(10) .Select(f => f.Symbol));
# In the initialize method: self.universe_settings.asynchronous = True # Add a fundamental universe with a custom filter function. self._universe = self.add_universe(self._fundamental_selection_function) def _fundamental_selection_function(self, fundamental: List[Fundamental]) -> List[Symbol]: # Select assets that have a price > $10, fundamental data, and a price_to_earnings ratio. filtered = [f for f in fundamental if f.price > 10 and f.has_fundamental_data and not np.isnan(f.valuation_ratios.pe_ratio)] # Select the 100 most liquid assets. sorted_by_dollar_volume = sorted(filtered, key=lambda f: f.dollar_volume, reverse=True)[:100] # Select the 10 assets with the lowest P/E ratios. sorted_by_pe_ratio = sorted(sorted_by_dollar_volume, key=lambda f: f.valuation_ratios.pe_ratio)[:10] # Return the selected securities. return [f.symbol for f in sorted_by_pe_ratio]
Asset Categories
In addition to valuation ratios, the US Fundamental Data from Morningstar has many other data point attributes, including over 200 different categorization fields for each US stock. Morningstar groups these fields into sectors, industry groups, and industries.
Sectors are large super categories of data. To get the sector of a stock, use the MorningstarSectorCode
morningstar_sector_code
property.
// Select the US Equities in the technology sector. var tech = fundamental.Where(x => x.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.Technology);
# Select the US Equities in the technology sector. tech = [x for x in fundamental if x.asset_classification.morningstar_sector_code == MorningstarSectorCode.TECHNOLOGY]
Industry groups are clusters of related industries that tie together. To get the industry group of a stock, use the MorningstarIndustryGroupCode
morningstar_industry_group_code
property.
// Select the US Equities in the agricluture industry group. var ag = fundamental.Where(x => x.AssetClassification.MorningstarIndustryGroupCode == MorningstarIndustryGroupCode.Agriculture);
# Select the US Equities in the agricluture industry group. ag = [x for x in fundamental if x.asset_classification.morningstar_industry_group_code == MorningstarIndustryGroupCode.AGRICULTURE]
Industries are the finest level of classification available. They are the individual industries according to the Morningstar classification system. To get the industry of a stock, use the MorningstarIndustryCode
morningstar_industry_code
property.
// Select the US Equities in the coal industry. var coal = fundamental.Where(x => x.AssetClassification.MorningstarIndustryCode == MorningstarIndustryCode.Coal);
# Select the US Equities in the coal industry. coal = [x for x in fundamental if x.asset_classification.morningstar_industry_code == MorningstarIndustryCode.COAL]
Practical Limitations
Fundamental universes allow you to select an unlimited universe of assets to analyze. Each asset in the universe consumes approximately 5MB of RAM, so you may quickly run out of memory if your universe filter selects many assets. If you backtest your algorithms in the Algorithm Lab, familiarize yourself with the RAM capacity of your backtesting and live trading nodes. To keep your algorithm fast and efficient, only subscribe to the assets you need.
Data Availability
Fundamental
objects can have NaN values for some of their properties. Before you sort the Fundamental
objects by one of the properties, filter out the objects that have a NaN value for the property.
private IEnumerable<Symbol> FundamentalFilterFunction(IEnumerable<Fundamental> fundamentals) { return fundamentals // Select objects that have a value for the fundamental property. .Where(f => f.HasFundamentalData && !Double.IsNaN(f.ValuationRatios.PERatio)) // Sort the objects by the fundamental property. .OrderBy(f => f.ValuationRatios.PERatio) .Take(10) .Select(x => x.Symbol); }
def _fundamental_selection_function(self, fundamental: List[Fundamental]) -> List[Symbol]: # Select objects that have a value for the fundamental property. filtered = [f for f in fundamental if f.has_fundamental_data and not np.isnan(f.valuation_ratios.pe_ratio)] # Sort the objects by the fundamental property. sorted_by_pe_ratio = sorted(filtered, key=lambda f: f.valuation_ratios.pe_ratio) return [f.symbol for f in sortedByPeRatio[:10] ]
Direct Access
To get fundamental data for Equities in your algorithm, use the Fundamentals
fundamentals
property of the Equity
objects. The fundamental data represent the company's fundamentals for the current algorithm time.
var fundamentals = Securities[_symbol].Fundamentals;
fundamentals = self.securities[self._symbol].fundamentals
To get fundamental data for Equities, regardless of whether or not you have subscribed to them in your algorithm, call the Fundamentals
fundamentals
method. If you pass one Symbol
, the method returns a Fundamental
object. If you pass a list of Symbol
objects, the method returns a list of Fundamental
objects. The fundamental data represents the corporate fundamentals for the current algorithm time.
// Single asset var ibm = QuantConnect.Symbol.Create("IBM", SecurityType.Equity, Market.USA); var ibmFundamental = Fundamentals(ibm); // Multiple assets var nb = QuantConnect.Symbol.Create("NB", SecurityType.Equity, Market.USA); var fundamentals = Fundamentals(new List<Symbol>{ nb, ibm }).ToList();
# Single asset ibm = QuantConnect.symbol.create("IBM", SecurityType.EQUITY, Market.USA) ibm_fundamental = self.fundamentals(ibm) # Multiple assets nb = QuantConnect.symbol.create("NB", SecurityType.EQUITY, Market.USA) fundamentals = self.fundamentals([ nb, ibm ])
Data Availability
Some assets don't have fundamentals (for example, ETFs) and the Morningstar dataset doesn't provide fundamentals for all US Equities. To check if fundamental data is available for an asset, use the HasFundamentalData
has_fundamental_data
property.
var hasFundamentalData = Securities[_symbol].Fundamentals.HasFundamentalData;
has_fundamental_data = self.securities[self._symbol].fundamentals.has_fundamental_data
Object References
If you save a reference to the Fundamentals
fundamentals
object or its properties, you can access the fundamental properties as they change over time.
_fundamentals = Securities[_symbol].Fundamentals; var earningRatios = _fundamentals.EarningRatios;
self._fundamentals = self.securities[self._symbol].fundamentals earning_ratios = self.fundamentals.earning_ratios
Historical Data
To get historical fundamental data, call the History
history
method. The return type depends on how you call the method.
var ibm = QuantConnect.Symbol.Create("IBM", SecurityType.Equity, Market.USA); // Fundamental objects var fundamentalHistory = History<Fundamental>(ibm, TimeSpan.FromDays(30)); // Fundamentals objects for all US Equities (including delisted companies) var fundamentalsHistory = History<Fundamentals>(TimeSpan.FromDays(30)); // Collection of Fundamental objects for all US Equities (including delisted companies) var collectionHistory = History(_universe, 30, Resolution.Daily); foreach (var fundamental in collectionHistory) { // Cast to Fundamental is required var highestMarketCap = fundamental.OfType<Fundamental>().OrderByDescending(x => x.MarketCap).Take(5); }
ibm = Symbol.create("IBM", SecurityType.EQUITY, Market.USA) # Multi-index DataFrame objects df_history = self.history(Fundamental, ibm, timedelta(30)) # Fundamental objects fundamental_history = self.history[Fundamental](ibm, timedelta(30)) # Fundamentals objects for all US Equities (including delisted companies) fundamentals_history = self.history[Fundamentals](timedelta(30)) # Multi-index Series objects of list of Fundamental objects series_history = self.history(self._universe, 30, Resolution.DAILY) for (universe_symbol, time), fundamental in series_history.items(): highest_market_cap = sorted(fundamental, key=lambda x: x.market_cap)[-5:]
For more information about historical fundamental data, see Equity Fundamental Data.
Selection Frequency
Equity universes run on a daily basis by default. To adjust the selection schedule, see Schedule.
Live Trading Considerations
The live data for fundamental universe selection arrives at 6/7 AM Eastern Time (ET), so fundamental universe selection runs for live algorithms between 7 and 8 AM ET. This timing allows you to place trades before the market opens. Don't schedule anything for midnight because the universe selection data isn't ready yet.
Examples
The following examples demonstrate some common practices for fundamental universes.
Example 1: 500 Stocks >$10/Share and >$10M in Daily Trading Volume
The following algorithm selects the 500 most liquid US Equities above $10/share and $10M in daily volume.
public class LiquidNonPennyStocksUniverseAlgorithm : QCAlgorithm { public override void Initialize() { // Configure the universe to update at the start of each month. Most of the top 500 doesn't change very // frequently. UniverseSettings.Schedule.On(DateRules.MonthStart()); // Add a universe with custom selection rules for filtering. AddUniverse(fundamental => (from f in fundamental where f.Price > 10 && f.DollarVolume > 10000000 orderby f.DollarVolume descending select f.Symbol).Take(500)); } }
class LiquidNonPennyStocksUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: # Configure the universe to update at the start of each month. Most of the top 500 doesn't change very # frequently. self.universe_settings.schedule.on(self.date_rules.month_start()) # Add a universe with custom selection rules for filtering. self.add_universe( lambda fundamental: [ x.symbol for x in sorted( [f for f in fundamental if f.price > 10 and f.dollar_volume > 10_000_000], key=lambda f: f.dollar_volume )[-500:] ] )
Example 2: 10 Stocks Above Their 200-Day EMA With >$1B of Daily Trading Volume
Another common request is to filter the universe by a technical indicator, such as only picking stocks above their 200-day Exponential Moving Average (EMA).
The Fundamental
object has adjusted price and volume information, so you can do any price-related analysis.
The following algorithm defines a separate class to contain the indicator of each asset.
using System.Collections.Concurrent; public class UpTrendLiquidUniverseAlgorithm : QCAlgorithm { // Create a concurrent dictionary to store the EMA data for universe selection. private ConcurrentDictionary<Symbol, SelectionData> _selectionDataBySymbol = new(); public override void Initialize() { // Add the custom universe. AddUniverse(SelectAssets); } private IEnumerable<Symbol> SelectAssets(IEnumerable<Fundamental> fundamental) { return (from f in fundamental // Create/Update the EMA indicators of each stock. let avg = _selectionDataBySymbol.GetOrAdd(f.Symbol, sym => new SelectionData(200)) where avg.Update(f.EndTime, f.AdjustedPrice) // Select the Equities that are above their EMA and have a daily volume of $1B. // These assets are in an uptrend and are very liquid. where avg.Ema.IsReady && f.Price > avg.Ema.Current.Value && f.DollarVolume > 1000000000 // Select the 10 most liquid Equities to avoid extra slippage. orderby f.DollarVolume descending select f.Symbol).Take(10); } } // Create a separate class to contain the EMA information of each asset. class SelectionData { public readonly ExponentialMovingAverage Ema; // Create an EMA indicator for trend estimation and filtering. public SelectionData(int period) { Ema = new ExponentialMovingAverage(period); } // Update your variables and indicators with the latest data. // You may also want to use the History API here to warm-up the indicator. public bool Update(DateTime time, decimal value) { return Ema.Update(time, value); } }
class UpTrendLiquidUniverseAlgorithm(QCAlgorithm): # Create a dictionary to store the EMA data for universe selection. _selection_data_by_symbol = {} def initialize(self) -> None: # Add the custom universe. self.add_universe(self._select_assets) def _select_assets(self, fundamental: List[Fundamental]) -> List[Symbol]: for f in fundamental: # Create/Update the EMA indicators of each stock. if f.symbol not in self._selection_data_by_symbol: self._selection_data_by_symbol[f.symbol] = SelectionData(f.symbol, 200) self._selection_data_by_symbol[f.symbol].update(f.end_time, f.adjusted_price, f.dollar_volume) # Select the Equities that are above their EMA and have a daily volume of $1B. # These assets are in an uptrend and are very liquid. selected = [x for x in self._selection_data_by_symbol.values() if x.is_above_ema and x.volume > 1_000_000_000] # Select the 10 most liquid Equities to avoid extra slippage. return [ x.symbol for x in sorted(selected, key=lambda x: x.volume)[-10:] ] # Create a separate class to contain the EMA information of each asset. class SelectionData(object): def __init__(self, symbol, period): # Create an EMA indicator for trend estimation and filtering. self.symbol = symbol self._ema = ExponentialMovingAverage(period) self.is_above_ema = False self.volume = 0 # Update your variables and indicators with the latest data. # You may also want to use the History API here to warm-up the indicator. def update(self, time, price, volume): self.volume = volume if self._ema.update(time, price): self.is_above_ema = price > self._ema.current.value
In this example, the SelectionData
class group variables for the universe selection and updates the indicator of each asset.
We highly recommend you follow this pattern to keep your algorithm tidy and bug free.
The following snippet shows an example implementation of the SelectionData
class, but you can make this whatever you need to store your custom universe filters.
Note that the preceding SelectionData
class uses a manual EMA indicator instead of the automatic version.
For more information about universes that select assets based on indicators, see Indicator Universes.
You need to use a SelectionData
class instead of assigning the EMA to the Fundamental
object because you can't create custom propertiesattributes on Fundamental
objects like you can with Security
objects.
Example 3: 10 Stocks Furthest Above their 10-day SMA of Volume
The process to get the 10-day Simple Moving Average (SMA) stock volume is the same process as in Example 2.
First, define a SelectionData
class that performs the averaging.
This class tracks the ratio of today's volume relative to historical volumes.
You can use this ratio to select assets that are above their 10-day SMA and sort the results by the Equities that have had the biggest jump since yesterday.
using System.Collections.Concurrent; public class HighRelativeVolumeUniverseAlgorithm : QCAlgorithm { // Create a dictionary to store the EMA data for universe selection. private ConcurrentDictionary<Symbol, SelectionData> _selectionDataBySymbol = new(); public override void Initialize() { // Add a universe with custom selection rules for filtering. AddUniverse(SelectAssets); } private IEnumerable<Symbol> SelectAssets(IEnumerable<Fundamental> fundamental) { return (from f in fundamental // Create/Update the volume SMA indicator of each stock. let avg = _selectionDataBySymbol.GetOrAdd(f.Symbol, sym => new SelectionData(f.Symbol, 10)) where avg.Update(f.EndTime, f.Volume) // Select the Equities with higher trading volume than their SMA, indicating higher capital flow. where avg.VolumeRatio > 1 // Select the 10 Equities with the highest relative volume, since they have the highest capactity // for scalp-trading or intra-day movement. orderby avg.VolumeRatio descending select f.Symbol).Take(10); } } // Define a separate class to contain and calculate the SMA of each Equity. class SelectionData { public readonly Symbol Symbol; public readonly SimpleMovingAverage VolumeSma; public decimal VolumeRatio; public SelectionData(Symbol symbol, int period) { // Create an SMA of volume to track the popularity of the stock. Symbol = symbol; VolumeSma = new SimpleMovingAverage(period); } public bool Update(DateTime time, decimal value) { // Update the SMA with today's data and calculate the relative volume position for filtering. var ready = VolumeSma.Update(time, value); VolumeRatio = value / VolumeSma.Current.Value; return ready; } }
class HighRelativeVolumeUniverseAlgorithm(QCAlgorithm): # Create a dictionary to store the EMA data for universe selection. _selection_data_by_symbol = {} def initialize(self) -> None: # Add a universe with custom selection rules for filtering. self.add_universe(self._select_assets) def _select_assets(self, fundamental: List[Fundamental]) -> List[Symbol]: # Create/Update the volume SMA indicator of each stock. for f in fundamental: if f.symbol not in self._selection_data_by_symbol: self._selection_data_by_symbol[f.symbol] = SelectionData(f.symbol, 10) self._selection_data_by_symbol[f.symbol].update(f.end_time, f.adjusted_price, f.dollar_volume) # Select the Equities with higher trading volume than their SMA, indicating higher capital flow. selected = [sd for sd in self._selection_data_by_symbol.values() if sd.volume_ratio > 1] # Select the 10 Equities with the highest relative volume, since they have the highest capactity # for scalp-trading or intra-day movement. return [ x.symbol for x in sorted(selected, key=lambda x: x.volume_ratio)[-10:] ] # Define a separate class to contain and calculate the SMA of each Equity. class SelectionData(object): def __init__(self, symbol, period): self.symbol = symbol self.volume_ratio = 0 # Create an SMA of volume to track the popularity of the stock. self._sma = SimpleMovingAverage(period) def update(self, time, price, volume): # Update the SMA with today's data and calculate the relative volume position for filtering. if self._sma.update(time, volume): self.volume_ratio = volume / self._sma.current.value
Example 4: 10 "Fastest Moving" Stocks With a 50-Day EMA > 200 Day EMA
You can construct complex universe filters with the SelectionData
helper class pattern.
To view a full example of this algorithm, see the EmaCrossUniverseSelectionAlgorithmEmaCrossUniverseSelectionAlgorithm in the LEAN GitHub repository or take the related Boot Camp lesson.
Example 5: Piotroski F-Score
To view this example, see the Piotroski F-Score Investing Research post.
Other Examples
For more examples, see the following algorithms: