Equity

Alternative Data Universes

Introduction

An alternative data universe lets you select a basket of Equities based on an alternative dataset that's linked to them. If you use an alternative data universe, you limit your universe to only the securities in the dataset, which avoids unnecessary subscriptions.

Examples

The following examples demonstrate some common alternative data universes.

Example 1: Brain Sentiment Universe

The following algorithm uses the Brain Sentiment Indicator dataset to create a universe of US Equities that have some article mentions and the most positive sentiment. It then forms a sentiment-weighted portfolio at the start of each month.

public class BrainSentimentUniverseAlgorithm : QCAlgorithm
{
    private Dictionary<Symbol, decimal> _sentimentBySymbol;

    public override void Initialize()
    {
        SetStartDate(2020, 6, 1);
        SetEndDate(2021, 1, 1);
        // Add an Equity universe that updates at the start of each month.
        var spy = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
        UniverseSettings.Schedule.On(DateRules.MonthStart(spy));
        AddUniverse<BrainSentimentIndicatorUniverse>(altData =>
        {
            _sentimentBySymbol = altData
                .Select(x => x as BrainSentimentIndicatorUniverse)
                // Select the assets with recent mentions and a sentiment score.
                .Where(x => x.TotalArticleMentions30Days > 0 && x.Sentiment30Days.HasValue)
                // Select the 10 assets with the greatest sentiment score.
                .OrderByDescending(x => x.Sentiment30Days.Value)
                .Take(10)
                .ToDictionary(x => x.Symbol, x => x.Sentiment30Days.Value);
            // Return the Symbol objects of the selected assets.
            return _sentimentBySymbol.Keys;
        });
        // Create a Scheduled Event to rebalance the portfolio at the start of each month.
        Schedule.On(DateRules.MonthStart(spy), TimeRules.At(9, 45), Rebalance);
    }

    public void Rebalance()
    {
        // To avoid trading errors, filter out the assets that don't have a price yet.
        _sentimentBySymbol = _sentimentBySymbol
            .Where(kvp => Securities[kvp.Key].Price > 0)
            .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
        // Form a sentiment-weighted portfolio.
        var sentimentScoreSum = _sentimentBySymbol.Values.Sum();
        SetHoldings(
            _sentimentBySymbol.Select(kvp => new PortfolioTarget(kvp.Key, kvp.Value / sentimentScoreSum)).ToList(), 
            true
        );
    }
}
class BrainSentimentUniverseAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2020, 6, 1)
        self.set_end_date(2021, 1, 1)
        # Add an Equity universe that updates at the start of each month.
        spy = Symbol.create('SPY', SecurityType.EQUITY, Market.USA)
        self.universe_settings.schedule.on(self.date_rules.month_start(spy))
        self.add_universe(BrainSentimentIndicatorUniverse, self._select_assets)
        # Create a Scheduled Event to rebalance the portfolio at the start of each month.
        self.schedule.on(self.date_rules.month_start(spy), self.time_rules.at(9, 45), self._rebalance)
    
    def _select_assets(self, alt_data: List[BrainSentimentIndicatorUniverse]) -> List[Symbol]:
        # Select the assets with recent mentions and a sentiment score.
        alt_data = [x for x in alt_data if x.total_article_mentions_30_days > 0 and x.sentiment_30_days]
        # Select the 10 assets with the greatest sentiment score.
        selected = sorted(alt_data, key=lambda x: x.sentiment_30_days)[-10:]
        # Save the sentiment scores for rebalancing later.
        self._sentiment_by_symbol = {x.symbol: x.sentiment_30_days for x in selected}
        # Return the Symbol objects of the selected assets.
        return list(self._sentiment_by_symbol.keys())
    
    def _rebalance(self) -> None:
        # To avoid trading errors, filter out the assets that don't have a price yet.
        self._sentiment_by_symbol = {
            symbol: sentiment for symbol, sentiment in self._sentiment_by_symbol.items() 
            if self.securities[symbol].price
        }
        # Form a sentiment-weighted portfolio.
        sentiment_sum = sum(list(self._sentiment_by_symbol.values()))
        self.set_holdings(
            [PortfolioTarget(symbol, sentiment/sentiment_sum) for symbol, sentiment in self._sentiment_by_symbol.items()], 
            True
        )

Example 2: Insiders Trading Universe

Insiders have more information to evaluate the overall prospect of the company, so following their trades can be useful. The following algorithm uses the Insider Trading to create a universe of US Equities that insiders have recently purchased. It then invest invest equally into the companies with positive insider trades, which may provide extra confidence in their expected return, and hold for 3 months.

public class InsiderTradingUniverseAlgorithm : QCAlgorithm
{
    private List<Symbol> _universe = new();

    public override void Initialize()
    {
        SetStartDate(2016, 1, 1);
        SetEndDate(2018, 1, 1);

        // Keep each security in the universe for a minimum of 30 days to digest the insiders purchase sentiment.
        UniverseSettings.MinimumTimeInUniverse = TimeSpan.FromDays(30);
        // Using QuiverInsiderTrading dataset for insider trade detection and filtering.
        AddUniverse<QuiverInsiderTradingUniverse>(altData =>
        {
            // Select assets that insiders have the most purchase, which may provide extra confidence in their expected return.
            return (from d in altData.OfType<QuiverInsiderTradingUniverse>()
                   where d.Shares.HasValue && d.Shares.Value > 0m
                   orderby d.Shares.Value descending
                   select d.Symbol).Take(10);
        });
    }

    public override void OnData(Slice slice)
    {
        // Equally invest in insider buying companies to evenly dissipate the capital risk.
        SetHoldings(_universe.Select(x => new PortfolioTarget(x, 1m / _universe.Count)).ToList());
    }
    
    public override void OnSecuritiesChanged(SecurityChanges changes)
    {
        // Update universe to trade.
        _universe.AddRange(changes.AddedSecurities.Select(x => x.Symbol).ToList());

        // Liquidate and remove from universe for the ones not being recently-purchase by insiders.
        foreach (var removed in changes.RemovedSecurities)
        {
            Liquidate(removed.Symbol);
            _universe.Remove(removed.Symbol);
        }
    }
}
class InsiderTradingUniverseAlgorithm(QCAlgorithm):
    _universe = []

    def initialize(self) -> None:
        self.set_start_date(2016, 1, 1)
        # Keep each security in the universe for a minimum of 30 days to digest the insiders purchase sentiment.
        self.universe_settings.minimum_time_in_universe = timedelta(30)
        # Using QuiverInsiderTrading dataset for insider trade detection and filtering.
        self.add_universe(QuiverInsiderTradingUniverse, self.selection)

    def selection(self, alt_data: List[QuiverInsiderTradingUniverse]) -> None:
        # Select assets that insiders have the most purchase, which may provide extra confidence in their expected return.
        filtered = sorted([x for x in alt_data if x.shares and x.shares > 0],
                        key=lambda x: x.shares,
                        reverse=True)[:10]
        return [x.symbol for x in filtered]
        
    def on_data(self, slice: Slice) -> None:
        # Equally invest in insider buying companies to evenly dissipate the capital risk.
        self.set_holdings([PortfolioTarget(x, 1. / len(self._universe)) for x in self._universe])

    def on_securities_changed(self, changes: SecurityChanges) -> None:
        # Update universe to trade.
        self._universe.extend([x.symbol for x in changes.added_securities])

        # Liquidate and remove from universe for the ones not being recently-purchase by insiders.
        for removed in changes.removed_securities:
            self.liquidate(removed.symbol)
            if removed.symbol in self._universe:
                self._universe.remove(removed.symbol)

Example 3: Share Buyback Universe

The following algorithm uses the Corporate Buybacks dataset to create a universe of US Equities that have announced an upcoming share buyback program:

public class SmartInsiderIntentionUniverseAlgorithm : QCAlgorithm
{
    // A dictionary to hold the updated buyback size for position sizing.
    public Dictionary<Symbol, decimal> _buybackSize = new();

    public override void Initialize()
    {
        SetStartDate(2021, 1, 1);
        SetEndDate(2022, 1, 1);
        SetSecurityInitializer(
            new BrokerageModelSecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices))
        );

        // Allow a week to capitalize the sentiment.
        Schedule.On(
            DateRules.WeekStart(),
            TimeRules.At(9, 30),
            Rebalance
        );
        UniverseSettings.Schedule.On(DateRules.WeekStart());
        // Filter for any coporate announced a material buyback plan, since they have confidence in their future prospect and the reduction in supply can drive their price up.
        AddUniverse<SmartInsiderIntentionUniverse>(
            altData => altData.OfType<SmartInsiderIntentionUniverse>()
                // A material buyback size to have better confidence in its prospect.
                .Where(d => d.AmountValue.HasValue && d.AmountValue.Value >= 5000000m)
                .Select(d => {
                    // Update buyback size dictionary for sizing.
                    _buybackSize[d.Symbol] = d.AmountValue.Value;
                    return d.Symbol;
                })
        );
    }

    private void Rebalance()
    {
        // Get the size of all buyback size to normalize the weightings.
        var buybackSum = _buybackSize.Sum(x => x.Value);
        // Equally invest in insider buying companies to evenly dissipate the capital risk.
        SetHoldings(_buybackSize.Select(x => new PortfolioTarget(x.Key, x.Value / buybackSum)).ToList());
    }
    
    public override void OnSecuritiesChanged(SecurityChanges changes)
    {
        // Liquidate and remove from universe for the ones not being recently-purchase by insiders.
        foreach (var removed in changes.RemovedSecurities)
        {
            _buybackSize.Remove(removed.Symbol);
            Liquidate(removed.Symbol);
        }
    }
}
class SmartInsiderIntentionUniverseAlgorithm(QCAlgorithm):
    _buyback_size = {}

    def initialize(self) -> None:
        self.set_start_date(2021, 1, 1)
        self.set_end_date(2022, 1, 1)
        self.set_security_initializer(
            BrokerageModelSecurityInitializer(self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices))
        )

        # Allow a week to capitalize the sentiment.
        self.schedule.on(
            self.date_rules.week_start(),
            self.time_rules.at(9, 30),
            self.rebalance
        )
        self.universe_settings.schedule.on(self.date_rules.week_start())
        # Filter for any coporate announced a material buyback plan, since they have confidence in their future prospect and the reduction in supply can drive their price up.
        self.add_universe(SmartInsiderIntentionUniverse, self.intention_selection)
    
    def intention_selection(self, alt_coarse: List[SmartInsiderIntentionUniverse]) -> List[Symbol]:
        selected = []
        
        for d in alt_coarse:
            # A material buyback percentage size to have better confidence in its prospect.
            if d.amount_value and d.amount_value > 5000000:
                # Update buyback size dictionary for sizing.
                self._buyback_size[d.symbol] = d.amount_value
                selected.append(d.symbol)

        return selected

    def rebalance(self) -> None:
        # Get the size of all buyback size to normalize the weightings.
        buyback_sum = sum(self._buyback_size.values())
        # Equally invest in insider buying companies to evenly dissipate the capital risk.
        self.set_holdings([PortfolioTarget(symbol, size / buyback_sum) for symbol, size in self._buyback_size.items()])
        
    def on_securities_changed(self, changes: SecurityChanges) -> None:
        # Liquidate and remove from universe for the ones not being recently-purchase by insiders.
        for removed in changes.removed_securities:
            self.liquidate(removed.symbol)
            if removed.symbol in self._buyback_size:
                self._buyback_size.pop(removed.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: