Equity
Alternative Data Universes
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. The selected equities will be invested according to their sentiment score. The more positive the sentiment, the higher the weighting.
public class BrainSentimentUniverseAlgorithm : QCAlgorithm { // A dictionary to hold the updated sentiment score from the stocks with top sentiment score for position sizing. private Dictionary<Symbol, decimal> _sentimentScores = new(); public override void Initialize() { SetStartDate(2020, 1, 1); // Monthly universe update since the signal is for the next 30 days. UniverseSettings.Schedule.On(DateRules.MonthStart()); // Select from Brain Sentiment dataset by sentiment factor. AddUniverse<BrainSentimentIndicatorUniverse>( altData => { // Update sentiment score dictionary to only holds the most updated sentiment score for sizing. _sentimentScores.Clear(); return altData.OfType<BrainSentimentIndicatorUniverse>() // Make sure to select from the ones with recent mentions and sentiment score available for filtering and sizing. .Where(x => x.TotalArticleMentions30Days.HasValue && x.Sentiment30Days.HasValue) // Select assets with some mentions and the greatest sentiment to maximize the expected return from sentiment factor. .Where(x => x.TotalArticleMentions30Days > 0) .OrderByDescending(x => x.Sentiment30Days) .Take(10) // Return the symbols for the selected assets. .Select(x => { _sentimentScores[x.Symbol] = x.Sentiment30Days.Value; return x.Symbol; }); } ); // Monthly rebalance to allow time to digest the 30-day signal. Schedule.On( DateRules.MonthStart(), TimeRules.At(9, 30), Rebalance ); } private void Rebalance() { // Calculate the sentiment score sum to normalize them for sizing. var sentimentScoreSum = _sentimentScores.Sum(kvp => kvp.Value); foreach (var (symbol, holding) in Portfolio) { // If it is not the stock with high positive sentiment, liquidate for better opportunities with higher expected return. if (holding.Invested && !_sentimentScores.ContainsKey(symbol)) { Liquidate(symbol); } // Invest in the top sentiment stocks with sentiment score as size to maximize expected return. else if (_sentimentScores.TryGetValue(symbol, out var score)) { SetHoldings(symbol, score / sentimentScoreSum); } } } }
class BrainSentimentUniverseAlgorithm(QCAlgorithm): # A dictionary to hold the updated sentiment score from the stocks with top sentiment score for position sizing. _sentiment_scores = {} def initialize(self) -> None: self.set_start_date(2020, 1, 1) # Monthly universe update since the signal is for the next 30 days. self.universe_settings.schedule.on(self.date_rules.month_start()) # Select from Brain Sentiment dataset by sentiment factor. self.add_universe(BrainSentimentIndicatorUniverse, self._select_assets) # Monthly rebalance to allow time to digest the 30-day signal. self.schedule.on( self.date_rules.month_start(), self.time_rules.at(9, 30), self.rebalance ) def _select_assets(self, alt_data: List[BrainSentimentIndicatorUniverse]) -> List[Symbol]: # Make sure to select from the ones with recent mentions and sentiment score available for filtering and sizing. alt_data = [x for x in alt_data if x.total_article_mentions_30_days and x.total_article_mentions_30_days > 0 and x.sentiment_30_days] # Select assets with some mentions and the greatest sentiment to maximize the expected return from sentiment factor. selected = sorted(alt_data, key=lambda x: x.sentiment_30_days)[-10:] # Update sentiment score dictionary to only holds the most updated sentiment score for sizing. self._sentiment_scores.clear() universe = [] for datum in selected: self._sentiment_scores[datum.symbol] = datum.sentiment_30_days universe.append(datum.symbol) # Return the symbols for the selected assets. return universe def rebalance(self) -> None: # Calculate the sentiment score sum to normalize them for sizing. sentiment_score_sum = sum(list(self._sentiment_scores.values())) for symbol, holding in self.portfolio.items(): # If it is not the stock with high positive sentiment, liquidate for better opportunities with higher expected return. if holding.invested and symbol not in self._sentiment_scores: self.liquidate(symbol) # Invest in the top sentiment stocks with sentiment score as size to maximize expected return. elif symbol in self._sentiment_scores: self.set_holdings(symbol, self._sentiment_scores[symbol] / sentiment_score_sum)
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. We 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); // 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 OnSecuritiesChanged(SecurityChanges changes) { // Update universe to trade. _universe.AddRange(changes.AddedSecurities.Select(x => x.Symbol).ToList()); // Equally invest in insider buying companies to evenly dissipate the capital risk. SetHoldings(_universe.Select(x => new PortfolioTarget(x, 1m / _universe.Count)).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_securities_changed(self, changes: SecurityChanges) -> None: # Update universe to trade. self._universe.extend([x.symbol for x in changes.added_securities]) # 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]) # 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); 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_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)