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. 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(2024, 9, 1);
SetEndDate(2024, 12, 31);
// 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(),
liquidateExistingHoldings: true
);
}
} class BrainSentimentUniverseAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
# 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 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()],
liquidate_existing_holdings=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 invests 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 Universe _universe;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
// Seed the price of each asset with its last known price to
// avoid trading errors.
SetSecurityInitializer(
new BrokerageModelSecurityInitializer(BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices))
);
// Keep each security in the universe for a minimum of 30 days to
// digest the insiders purchase sentiment.
UniverseSettings.MinimumTimeInUniverse = TimeSpan.FromDays(30);
// Add the QuiverInsiderTrading dataset for insider trade
// detection and filtering.
_universe = AddUniverse<QuiverInsiderTradingUniverse>(SelectAssets);
// Add a Scheduled Event to rebalance the portfolio each day.
var spy = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
Schedule.On(DateRules.EveryDay(spy), TimeRules.AfterMarketOpen(spy, 1), Rebalance);
}
private IEnumerable<Symbol> SelectAssets(IEnumerable<BaseData> altData)
{
// Calculate the net dollar volume of insider trades for each
// asset.
var dollarVolumeBySymbol = new Dictionary<Symbol, decimal?>();
foreach (QuiverInsiderTradingUniverse data in altData)
{
if (data.PricePerShare == 0m || data.PricePerShare == null)
{
continue;
}
if (!dollarVolumeBySymbol.ContainsKey(data.Symbol))
{
dollarVolumeBySymbol[data.Symbol] = 0;
}
dollarVolumeBySymbol[data.Symbol] += data.Shares * data.PricePerShare;
}
// Select the 10 assets with the largest dollar volume of insider
// trades.
return dollarVolumeBySymbol
.OrderByDescending(kvp => kvp.Value)
.Take(10)
.Select(kvp => kvp.Key);
}
public void Rebalance()
{
// Equally invest in insider buying companies to evenly
// dissipate the capital risk.
var symbols = _universe.Selected.Where(symbol => Securities[symbol].Price != 0);
SetHoldings(symbols.Select(s => new PortfolioTarget(s, 1m / symbols.Count())).ToList(), true);
}
} class InsiderTradingUniverseAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
# Seed the price of each asset with its last known price to
# avoid trading errors.
self.set_security_initializer(
BrokerageModelSecurityInitializer(
self.brokerage_model,
FuncSecuritySeeder(self.get_last_known_prices)
)
)
# 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)
# Add the QuiverInsiderTrading dataset for insider trade
# detection and filtering.
self._universe = self.add_universe(
QuiverInsiderTradingUniverse, self._select_assets
)
# Add a Scheduled Event to rebalance the portfolio each day.
spy = Symbol.create('SPY', SecurityType.EQUITY, Market.USA)
self.schedule.on(
self.date_rules.every_day(spy),
self.time_rules.after_market_open(spy, 1),
self._rebalance
)
def _select_assets(
self, alt_data: List[QuiverInsiderTradingUniverse]) -> List[Symbol]:
# Calculate the net dollar volume of insider trades for each
# asset.
dollar_volume_by_symbol = {}
for data in alt_data:
symbol = data.symbol
if not data.price_per_share:
continue
if symbol not in dollar_volume_by_symbol:
dollar_volume_by_symbol[symbol] = 0
dollar_volume_by_symbol[symbol] += data.shares * data.price_per_share
# Select the 10 assets with the largest dollar volume of insider
# trades.
return [
symbol for symbol, _ in sorted(
dollar_volume_by_symbol.items(), key=lambda kvp: kvp[1]
)[-10:]
]
def _rebalance(self):
# Equally invest in insider buying companies to evenly
# dissipate the capital risk.
symbols = [s for s in self._universe.selected if self.securities[s].price]
self.set_holdings(
[PortfolioTarget(symbol, 1/len(symbols)) for symbol in symbols],
True
)
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 buyback size for position sizing.
public Dictionary<Symbol, decimal> _buybackSize = new();
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
// Seed the price of each asset with its last known price to
// avoid trading errors.
SetSecurityInitializer(
new BrokerageModelSecurityInitializer(
BrokerageModel, new FuncSecuritySeeder(GetLastKnownPrices)
)
);
// Add a universe that selects assets at the start of each week.
// The universe should select Equities that have announced a
// material buyback plan, since they have confidence in their
// future prospect and the reduction in supply can drive their
// price up.
UniverseSettings.Schedule.On(DateRules.WeekStart());
AddUniverse<SmartInsiderIntentionUniverse>(altData => {
_buybackSize.Clear();
return altData.OfType<SmartInsiderIntentionUniverse>()
// Select assets that have a material buyback size to have
// strong confidence.
.Where(d => d.AmountValue.HasValue && d.AmountValue.Value > 10000000m)
.Select(d => {
// Record the buyback size in the dictionary.
_buybackSize[d.Symbol] = d.AmountValue.Value;
return d.Symbol;
});
});
// Add a Scheduled Event to rebalance the portfolio at the start
// of each week. The weekly schedule allows time to capitalize
// the market swings from the positive sentiment.
var spy = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
Schedule.On(DateRules.WeekStart(spy), TimeRules.AfterMarketOpen(spy, 1), Rebalance);
}
private void Rebalance()
{
// Ignore illiquid assets that have no price yet.
_buybackSize = _buybackSize.Where(kvp => Securities[kvp.Key].Price != 0).ToDictionary();
// Get the sum of all buyback size so we can scale the portfolio
// weights based on the buyback size.
var buybackSum = _buybackSize.Sum(x => x.Value);
// Place the trades to rebalance the portfolio.
SetHoldings(_buybackSize.Select(x => new PortfolioTarget(x.Key, x.Value / buybackSum)).ToList());
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
// Liquidate assets when they leave the universe.
foreach (var removed in changes.RemovedSecurities)
{
_buybackSize.Remove(removed.Symbol);
Liquidate(removed.Symbol);
}
}
} class SmartInsiderIntentionUniverseAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
# Seed the price of each asset with its last known price to
# avoid trading errors.
self.set_security_initializer(
BrokerageModelSecurityInitializer(
self.brokerage_model,
FuncSecuritySeeder(self.get_last_known_prices)
)
)
# Add a universe that selects assets at the start of each week.
# The universe should select Equities that have announced a
# material buyback plan, since they have confidence in their
# future prospect and the reduction in supply can drive their
# price up.
self.universe_settings.schedule.on(self.date_rules.week_start())
self.add_universe(SmartInsiderIntentionUniverse, self._select_assets)
# Add a Scheduled Event to rebalance the portfolio at the start
# of each week. The weekly schedule allows time to capitalize
# the market swings from the positive sentiment.
spy = Symbol.create('SPY', SecurityType.EQUITY, Market.USA)
self.schedule.on(
self.date_rules.week_start(spy),
self.time_rules.after_market_open(spy, 1),
self._rebalance
)
def _select_assets(
self,
alt_coarse: List[SmartInsiderIntentionUniverse]) -> List[Symbol]:
self._buyback_size = {}
selected = []
for d in alt_coarse:
# Select assets that have a material buyback size to have
# strong confidence.
if d.amount_value and d.amount_value > 10_000_000:
# Record the buyback size in the dictionary.
self._buyback_size[d.symbol] = d.amount_value
selected.append(d.symbol)
return selected
def _rebalance(self) -> None:
# Ignore illiquid assets that have no price yet.
self._buyback_size = {
symbol: amount
for symbol, amount in self._buyback_size.items()
if self.securities[symbol].price
}
# Get the sum of all buyback size so we can scale the portfolio
# weights based on the buyback size.
buyback_sum = sum(self._buyback_size.values())
# Place the trades to rebalance the portfolio.
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 assets when they leave the universe.
for removed in changes.removed_securities:
self.liquidate(removed.symbol)
self._buyback_size.pop(removed.symbol, None)