diff --git a/project-templates/python/alternative-data-universe-brainsentimentindicatoruniverse/main.py b/project-templates/python/alternative-data-universe-brainsentimentindicatoruniverse/main.py index ed25fbe71f..7a136986e4 100644 --- a/project-templates/python/alternative-data-universe-brainsentimentindicatoruniverse/main.py +++ b/project-templates/python/alternative-data-universe-brainsentimentindicatoruniverse/main.py @@ -3,6 +3,7 @@ # endregion class BrainSentimentIndicatorUniverseAlgorithm(QCAlgorithm): + _fundamental: list[Symbol] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -10,23 +11,35 @@ def initialize(self) -> None: self.set_cash(100000) self.universe_settings.resolution = Resolution.DAILY - # Universe of US Equities with positive 7-day media sentiment and active mention coverage. + # Add a fundamental universe to track the most liquid US Equities by dollar volume. + self.add_universe(self._fundamental_filter) + # Add a Brain Sentiment universe, restricted to high-sentiment names within the fundamental list. self._universe = self.add_universe(BrainSentimentIndicatorUniverse, self._select_assets) + # Rebalance every day at 9am. + self.schedule.on( + self.date_rules.every_day("SPY"), + self.time_rules.at(9, 0), + self._rebalance + ) - # Rebalance shortly after the open so today's universe is locked in. - self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) + def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: + self._fundamental = [c.symbol for c in sorted(fundamental, key=lambda x: x.dollar_volume)[-100:]] + return Universe.UNCHANGED def _select_assets(self, data: List[BrainSentimentIndicatorUniverse]) -> List[Symbol]: # Keep names with both active mention coverage and positive 7-day sentiment. - return [d.symbol for d in data - if d.total_article_mentions_7_days and d.total_article_mentions_7_days > 0 - and d.sentiment_7_days and d.sentiment_7_days > 0] + alt = [d.symbol for d in data + if d.total_article_mentions_7_days and d.total_article_mentions_7_days > 0 + and d.sentiment_7_days and d.sentiment_7_days > 0] + return [s for s in self._fundamental if s in alt] def _rebalance(self) -> None: if not self._universe.selected: return - - weight = 1 / len(self._universe.selected) - targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] - - self.set_holdings(targets, liquidate_existing_holdings=True) + # Filter to only securities with valid prices. + tradeable = [s for s in self._universe.selected if self.securities[s].price > 0] + if not tradeable: + return + weight = 1 / len(tradeable) + targets = [PortfolioTarget(symbol, weight) for symbol in tradeable] + self.set_holdings(targets, True)