Equity
Chained Universes
Introduction
You can combine ("chain") universes together to fetch fundamental and alternative data on a specific subset of assets. Universes filter input data and return Symbol
objects. The only requirement is that Symbol
objects the filter returns are a subset of the input data. The source of the Symbol
objects is unrestricted, so you can feed the output of one universe into another.
Filter Pattern
Universes filter a large set of Symbol objects by a coarse filter to quickly reduce the data processing requirement. This is often a first step before applying a second filter or requesting alternative data. For example, a strategy might only be interested in easily tradable liquid assets so quickly eliminates all stocks with less than $1M USD / day in trading volume.
The order of your filters can improve the speed of your research. By applying filters that narrow the universe the most, or are the lightest weight first, you can significantly reduce the amount of data your algorithm processes. Unless necessary, you can also not return any selections from earlier filters to further improve research speed, keeping only the universe data for later filters.
Universe Data Weights
To speed up your algorithm, request the lightest weight data first before chaining heavier filters or adding alternative data. The following table shows the size each dataset:
Name | Data Size / Weight |
---|---|
US Equities (Fundamental - Dollar Volume only) | Light (100 MB) |
US Equities (Fundamental) | Heavy (up to 20 GB) |
US Equity Options | Huge (200 TB) |
US Index Options | Medium (500 GB) |
US Futures | Medium (500 GB) |
US Futures Options | Medium (500 GB) |
Crypto | Light (1 GB) |
Alternative / General | Light (100 MB - 2 GB) |
Alternative / Tiingo News | Medium (200 GB) |
Chain Fundamental and Alternative Data
The following example chains a fundamental universe and a QuiverCNBCsUniverse alternative universe . It first selects the 100 most liquid US Equities and then filters them down to those mentioned by CNBC commentator/trader Jim Cramer. The output of the alternative universe selection method is the output of the chained universe.
from AlgorithmImports import * class ChainedUniverseAlgorithm(QCAlgorithm): _fundamental = [] def initialize(self) -> None: self.set_start_date(2023, 1, 2) self.set_cash(100000) self.add_universe(self._fundamental_filter_function) self.add_universe(QuiverCNBCsUniverse, self._mad_money_selection) def _fundamental_filter_function(self, fundamental: List[Fundamental]) -> List[Symbol]: # Filter the top 100 liquid equities of the last trading day, and save the symbols for the next filtering. sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume, reverse=True) self.fundamental = [c.symbol for c in sorted_by_dollar_volume[:100]] return Universe.UNCHANGED def _mad_money_selection(self, alt_coarse: List[QuiverCNBCsUniverse]) -> List[Symbol]: # Filter the equities being commented on by CNBC analyst Cramer, then select the ones that intersect with the fundamental universe. madmoney = [d.symbol for d in alt_coarse if 'Cramer' in d.traders] return list(set(self._fundamental) & set(madmoney)) def on_securities_changed(self, changes: SecurityChanges) -> None: # Request CNBC data for the selected stocks. for added in changes.added_securities: self.add_data(QuiverCNBCs, added.symbol) def on_data(self, data: Slice) -> None: # Prices in the slice from the universe selection # Alternative data in a slice from OnSecuritiesChanged Addition # for ticker,bar in data.bars.items(): # pass for dataset_symbol, data_points in data.get(QuiverCNBCs).items(): for data_point in data_points: self.debug(f"{dataset_symbol} traders at {data.time}: {data_point.traders}")
Chain Fundamental and US Equity Options
The following example chains a fundamental universe and an Equity Options universe . It first selects 10 stocks with the lowest PE ratio and then selects their front-month call Option contracts. The output of both universes is the output of the chained universe.
from AlgorithmImports import * class ChainedUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2023, 2, 2) self.universe_settings.asynchronous = True # Need to set data normalization mode to raw for options to compare the strike price fairly. self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW # Filter for equities with fundamental data first. universe = self.add_universe(self._fundamental_function) # Based on the filtered equities, request an option universe with them as underlying. self.add_universe_options(universe, self._option_filter_function) def _fundamental_function(self, fundamental: List[Fundamental]) -> List[Symbol]: # Filter for equities with the lowest PE Ratio first. filtered = [f for f in fundamental if not np.isnan(f.valuation_ratios.pe_ratio)] sorted_by_pe_ratio = sorted(filtered, key=lambda f: f.valuation_ratios.pe_ratio) return [f.symbol for f in sorted_by_pe_ratio[:10]] def _option_filter_function(self, option_filter_universe: OptionFilterUniverse) -> OptionFilterUniverse: # Select the front month ATM calls. return option_filter_universe.strikes(0, 2).front_month().calls_only() def on_data(self, data: Slice) -> None: # Iterate each option chain to assert the contracts being selected. for symbol, chain in data.option_chains.items(): for contract in chain: self.debug(f"Found {contract.symbol} option contract for {symbol}")
Chain ETF and Fundamental
The following example chains a fundamental universe and an ETF constituents universe . It first selects all the constituents of the QQQ ETF and then filters them down to select the 10 assets with the lowest PE ratio. The output of the fundamental universe selection method is the output of the chained universe.
from AlgorithmImports import * class ChainedUniverseAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2023, 2, 2) self.set_cash(100000) self.universe_settings.asynchronous = True # Select QQQ constituents first, then by fundamental data. self.add_universe( self.universe.etf("QQQ", Market.USA, self.universe_settings, self._etf_constituents_filter), self._fundamental_selection ) def _etf_constituents_filter(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]: # Select all QQQ constituents. return [c.symbol for c in constituents] def _fundamental_selection(self, fundamental: List[Fundamental]) -> List[Symbol]: # Filter for the lowest PE Ratio stocks among QQQ constituents for undervalued stocks. filtered = [f for f in fundamental if not np.isnan(f.valuation_ratios.pe_ratio)] sorted_by_pe_ratio = sorted(filtered, key=lambda f: f.valuation_ratios.pe_ratio) return [f.symbol for f in sorted_by_pe_ratio[:10]] def on_data(self, data): for symbol in data.keys(): self.debug(f"{symbol} PE Ratio: {self.securities[symbol].fundamentals.valuation_ratios.pe_ratio}")
Chain ETF and Alternative Data
The following example chains an ETF universe and a QuiverCNBCsUniverse alternative universe . It first selects all constituents of SPY and then filters them down to those mentioned by CNBC commentator/trader Jim Cramer. The output of the alternative universe selection method is the output of the chained universe.
from AlgorithmImports import * class ChainedUniverseAlgorithm(QCAlgorithm): _etf = [] def initialize(self): self.set_start_date(2023, 1, 2) self.set_cash(100000) self.universe_settings.asynchronous = True # Save all SPY constituents for the next filtering. self.add_universe(self.universe.etf("SPY", Market.USA, self.universe_settings, self._etf_constituents_filter)) # Next filtering based on CNBC data. self.add_universe(QuiverCNBCsUniverse, self._mad_money_selection) def _etf_constituents_filter(self, fundamental: List[Fundamental]) -> List[Symbol]: # Save all SPY constituents for the next filtering. self._etf = [c.symbol for c in constituents] return Universe.UNCHANGED def _mad_money_selection(self, alt_coarse: List[QuiverCNBCsUniverse]) -> List[Symbol]: # Filter the equities being commented on by CNBC analyst Cramer, then select the ones in SPY constituents. madmoney = [d.symbol for d in alt_coarse if 'Cramer' in d.traders] return list(set(self._etf) & set(madmoney)) def on_securities_changed(self, changes): # Request CNBC data for the selected stocks. for added in changes.added_securities: self.add_data(QuiverCNBCs, added.symbol) def on_data(self, data): # Prices in the slice from the universe selection # Alternative data in a slice from OnSecuritiesChanged Addition # for ticker,bar in data.bars.items(): # pass for dataset_symbol, data_points in data.get(QuiverCNBCs).items(): for data_point in data_points: self.debug(f"{dataset_symbol} traders at {data.time}: {data_point.traders}")
Chain ETF and US Equity Options
The following example chains an ETF constituents universe and an Equity Options universe . It first selects the 10 largest-weighted constituents of QQQ and then selects their front-month call Option contracts. The output of both universes is the output of the chained universe.
from AlgorithmImports import * class ChainedUniverseAlgorithm(QCAlgorithm): def initialize(self): self.set_start_date(2023, 2, 2) self.universe_settings.asynchronous = True # Set the data normalization mode to raw for fair strike price comparison. self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW # First-pass filtering from QQQ constituents. etf_universe = self.universe.etf("QQQ", Market.USA, self.universe_settings, self._etf_constituents_filter) self.add_universe(etf_universe) # Filter for equity options from the selected QQQ constituents next. self.add_universe_options(etf_universe, self._option_filter_function) def _etf_constituents_filter(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]: # Select the top 10 weighted QQQ constituents for most liquid options. sorted_by_weight = sorted(constituents, key=lambda x: x.weight, reverse=True) return [c.symbol for c in sorted_by_weight[:10]] def _option_filter_function(self, option_filter_universe: OptionFilterUniverse) -> OptionFilterUniverse: # Select the front month ATM standard calls. return option_filter_universe.strikes(0, 2).front_month().calls_only() def on_data(self, data: Slice) -> None: for symbol, chain in data.option_chains.items(): for contract in chain: self.debug(f"Found {contract.symbol} option contract for {symbol}")