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:

NameData 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 OptionsMedium (500 GB)
CryptoLight (1 GB)
Alternative / GeneralLight (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.

#region imports
    using System.Collections.Generic;
    using System.Linq;
    using QuantConnect.Util;
    using QuantConnect.Data;
    using QuantConnect.DataSource;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Securities;
#endregion
    
namespace QuantConnect.Algorithm.CSharp
{
    public class ChainedUniverseAlgorithm : QCAlgorithm
    {
        private List<Symbol> _fundamental = new();

        public override void Initialize()
        {
            SetStartDate(2023, 1, 2);
            SetCash(100000);
            UniverseSettings.Asynchronous = true;
            AddUniverse(fundamental =>
            {
                _fundamental = (from c in fundamental
                    orderby c.DollarVolume descending
                    select c.Symbol).Take(100).ToList();
                return Universe.Unchanged;
            });
            AddUniverse<QuiverCNBCsUniverse>(altCoarse =>
            {
                var followers = from d in altCoarse.OfType<QuiverCNBCsUniverse>()
                    where d.Traders.ToLower().Contains("cramer")
                    select d.Symbol;
                return _fundamental.Intersect(followers);
            });
        }

        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            foreach (var added in changes.AddedSecurities)
            {
                AddData<QuiverCNBCs>(added.Symbol);
            }
        }
    
        public override void OnData(Slice data)
        {
            foreach (var dataPoint in data.Get<QuiverCNBCs>().SelectMany(x=> x.Value.OfType()))
            {
                Debug($"{dataPoint.Symbol} traders at {data.Time}: {dataPoint.Traders}");
            }
        }
    }
}
# region imports
    from AlgorithmImports import * 
#endregion

from AlgorithmImports import *

class ChainedUniverseAlgorithm(QCAlgorithm):

    fundamental = []

    def initialize(self):
        self.set_start_date(2023, 1, 2)
        self.set_cash(100000)
        self.universe_settings.asynchronous = True
        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]:
        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]:
        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):
        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 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.

using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Util;
using System.Collections.Generic;
using System.Linq;

namespace QuantConnect.Algorithm.CSharp
{
    public class ETFUniverseOptions : QCAlgorithm
    {
        public override void Initialize()
        {
            SetStartDate(2023, 2, 2);
            SetCash(100000);
            UniverseSettings.Asynchronous = true;
            UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
            var universe = AddUniverse(FundamentalFunction);
            AddUniverseOptions(universe, OptionFilterFunction);
        }

        private IEnumerable FundamentalFunction(IEnumerable fundamental)
        {
            return fundamental
                .Where(f => !double.IsNaN(f.ValuationRatios.PERatio))
                .OrderBy(f => f.ValuationRatios.PERatio)
                .Take(10)
                .Select(x => x.Symbol);
        }
    
        private OptionFilterUniverse OptionFilterFunction(OptionFilterUniverse optionFilterUniverse)
        {
            return optionFilterUniverse.Strikes(0, 2).FrontMonth().CallsOnly();
        }
    
        public override void OnData(Slice data)
        {
            foreach (var (symbol, chain) in data.OptionChains)
            {
                foreach (var contract in chain)
                {
                    Debug($"Found {contract.Symbol} option contract for {symbol}");
                }
            }
        }
    }
}
from AlgorithmImports import *

class ChainedUniverseAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2023, 2, 2)
        self.universe_settings.asynchronous = True
        self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW
        universe = self.add_universe(self.fundamental_function)
        self.add_universe_options(universe, self.option_filter_function)

    def fundamental_function(self, fundamental: List[Fundamental]) -> List[Symbol]:
        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, optionFilterUniverse: OptionFilterUniverse) -> OptionFilterUniverse:
        return optionFilterUniverse.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}")

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.

using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;

namespace QuantConnect.Algorithm.CSharp
{
    public class ChainedUniverseAlgorithm : QCAlgorithm
    {
        public override void Initialize()
        {
            SetStartDate(2023, 2, 2);
            SetCash(100000);
            UniverseSettings.Asynchronous = true;
            AddUniverse(Universe.ETF("QQQ", Market.USA, UniverseSettings, ETFConstituentsFilter), FundamentalSelection);
        }

        private IEnumerable<Symbol> ETFConstituentsFilter(IEnumerable<ETFConstituentUniverse> constituents)
        {
            return constituents.Select(c => c.Symbol);
        }

        private IEnumerable<Symbol> FundamentalSelection(IEnumerable<Fundamental> fundamental)
        {
            return fundamental
                .Where(f => !double.IsNaN(f.ValuationRatios.PERatio))
                .OrderBy(f => f.ValuationRatios.PERatio)
                .Take(10)
                .Select(x => x.Symbol);
        }

        public override void OnData(Slice data)
        {
            foreach (var symbol in data.Keys)
            {
                Debug($"{symbol} PE Ratio: {Securities[symbol].Fundamentals.ValuationRatios.PERatio}");
            }
        }
    }
}
from AlgorithmImports import * 

class ChainedUniverseAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2023, 2, 2)
        self.set_cash(100000)
        self.universe_settings.asynchronous = True
        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]:
        return [c.symbol for c in constituents]

    def fundamental_selection(self, fundamental: List[Fundamental]) -> List[Symbol]:
        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.

#region imports
    using System.Collections.Generic;
    using System.Linq;
    using QuantConnect.Util;
    using QuantConnect.Data;
    using QuantConnect.DataSource;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Securities;
#endregion
    
namespace QuantConnect.Algorithm.CSharp
{
    public class ChainedUniverseAlgorithm : QCAlgorithm
    {
        private List<Symbol> _etf = new();
    
        public override void Initialize()
        {
            SetStartDate(2023, 1, 2);
            SetCash(100000);
            UniverseSettings.Asynchronous = true;
            AddUniverse(Universe.ETF("SPY", Market.USA, UniverseSettings, constituents =>
            {
                _etf = constituents.Select(c => c.Symbol).ToList();
                return Universe.Unchanged;
            }));
            AddUniverse<QuiverCNBCsUniverse>(altCoarse =>
            {
                var followers = from d in altCoarse.OfType<QuiverCNBCsUniverse>()
                    where d.Traders.ToLower().Contains("cramer")
                    select d.Symbol;
                return _etf.Intersect(followers);
            });
        }
    
        public override void OnSecuritiesChanged(SecurityChanges changes)
        {
            foreach (var added in changes.AddedSecurities)
            {
                AddData<QuiverCNBCs>(added.Symbol);
            }
        }
    
        public override void OnData(Slice data)
        {
            foreach (var dataPoint in data.Get<QuiverCNBCs>().SelectMany(x=> x.Value.OfType()))
            {
                Debug($"{dataPoint.Symbol} traders at {data.Time}: {dataPoint.Traders}");
            }
        }
    }
}
# region imports
    from AlgorithmImports import *
# endregion

# region imports
    from AlgorithmImports import *
# endregion

class ChainedUniverseAlgorithm(QCAlgorithm):

    etf = []

    def initialize(self):
        self.set_start_date(2023, 1, 2)
        self.set_cash(100000)
        self.universe_settings.asynchronous = True
        self.add_universe(self.universe.etf("SPY", Market.USA, self.universe_settings, self.etf_constituents_filter))
        self.add_universe(QuiverCNBCsUniverse, self.mad_money_selection)

    def etf_constituents_filter(self, fundamental: List[Fundamental]) -> List[Symbol]:
        self.etf = [c.symbol for c in constituents]
        return Universe.UNCHANGED

    def mad_money_selection(self, alt_coarse: List[QuiverCNBCsUniverse]) -> List[Symbol]:
        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):
        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 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.

using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
using QuantConnect.Util;
using System.Collections.Generic;
using System.Linq;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
    public class ETFUniverseOptions : QCAlgorithm
    {
        public override void Initialize()
        {
            SetStartDate(2023, 2, 2);
            SetCash(100000);
            UniverseSettings.Asynchronous = true;
            UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw;
            var etfUniverse = Universe.ETF("QQQ", Market.USA, UniverseSettings, ETFConstituentsFilter);
            AddUniverse(etfUniverse);
            AddUniverseOptions(etfUniverse, OptionFilterFunction);
        }
    
        private IEnumerable<Symbol> ETFConstituentsFilter(IEnumerable<ETFConstituentUniverse> constituents)
        {
            return constituents.OrderByDescending(c => c.Weight).Take(10).Select(c => c.Symbol);
        }
    
        private OptionFilterUniverse OptionFilterFunction(OptionFilterUniverse optionFilterUniverse)
        {
            return optionFilterUniverse.Strikes(0, 2).FrontMonth().CallsOnly();
        }
    
        public override void OnData(Slice data)
        {
            foreach (var (symbol, chain) in data.OptionChains)
            {
                foreach (var contract in chain)
                {
                    Debug($"Found {contract.Symbol} option contract for {symbol}");
                }
            }
        }
    }
}
from AlgorithmImports import *

class ChainedUniverseAlgorithm(QCAlgorithm):
    def initialize(self):
        self.set_start_date(2023, 2, 2)
        self.universe_settings.asynchronous = True
        self.universe_settings.data_normalization_mode = DataNormalizationMode.RAW
        etf_universe = self.universe.etf("QQQ", Market.USA, self.universe_settings, self.etf_constituents_filter)
        self.add_universe(etf_universe)
        self.add_universe_options(etf_universe, self.option_filter_function)

    def etf_constituents_filter(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]:
        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, optionFilterUniverse: OptionFilterUniverse) -> OptionFilterUniverse:
        return optionFilterUniverse.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}")

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: