From 21d99b7463cedc87a0a970cee97e97a5a030063d Mon Sep 17 00:00:00 2001 From: Alexandre Catarino Date: Wed, 13 May 2026 23:01:52 +0100 Subject: [PATCH 1/2] Fix non-chain alt-data templates - alternative-data-universe-eodhdupcomingipos: drop invalid `from QuantConnect.DataSource.EODHD import DealType` Py import (EODHD is a class, not a module); guard C# `Min().Value` against an all-null price band so the algorithm no longer throws "Nullable object must have a value". - alternative-data-universe-quivercnbcsuniverse, alternative-data-universe-quiverquantcongressuniverse, alternative-data-universe-smartinsiderintentionuniverse, alternative-data-universe-smartinsidertransactionuniverse: set `seed_initial_prices = True` so newly-joined equities have a price at the 9:00 ET rebalance, guard rebalance with a `price > 0` filter, and cap individual weights at `min(1/N, 0.1)` so sparse-day universes don't trigger insufficient-buying-power rejections. - alternative-data-universe-smartinsiderintentionuniverse: drop the `USDMarketCap > 100_000_000` filter (the field isn't populated for intention records in the dataset, so the filter rejected every row). - custom-indicator Py: set `data_normalization_mode = RAW` to match the C# template so both implementations consume the same SPY series. --- .../Main.cs | 9 +++++--- .../Main.cs | 11 +++++++--- .../Main.cs | 1 + .../Main.cs | 22 ++++++++++++------- .../Main.cs | 11 +++++++--- .../main.py | 3 +-- .../main.py | 9 +++++--- .../main.py | 1 + .../main.py | 15 ++++++++----- .../main.py | 9 +++++--- .../python/custom-indicator/main.py | 2 +- 11 files changed, 61 insertions(+), 32 deletions(-) diff --git a/project-templates/csharp/alternative-data-universe-eodhdupcomingipos/Main.cs b/project-templates/csharp/alternative-data-universe-eodhdupcomingipos/Main.cs index 502076b611..59906874e1 100644 --- a/project-templates/csharp/alternative-data-universe-eodhdupcomingipos/Main.cs +++ b/project-templates/csharp/alternative-data-universe-eodhdupcomingipos/Main.cs @@ -24,11 +24,14 @@ public override void Initialize() { // Keep expected/priced IPOs with a confirmed date and an above-$1 price band. return from d in data.OfType() + let prices = new[] { d.LowestPrice, d.HighestPrice, d.OfferPrice } + .Where(x => x.HasValue) + .Select(x => x.Value) + .ToList() where _dealTypesWanted.Contains(d.DealType) && d.IpoDate.HasValue - && new[] { d.LowestPrice, d.HighestPrice, d.OfferPrice } - .Where(x => x.HasValue) - .Min().Value > 1m + && prices.Count > 0 + && prices.Min() > 1m select d.Symbol; }); diff --git a/project-templates/csharp/alternative-data-universe-quivercnbcsuniverse/Main.cs b/project-templates/csharp/alternative-data-universe-quivercnbcsuniverse/Main.cs index e489b1fbf8..0a256ee141 100644 --- a/project-templates/csharp/alternative-data-universe-quivercnbcsuniverse/Main.cs +++ b/project-templates/csharp/alternative-data-universe-quivercnbcsuniverse/Main.cs @@ -15,6 +15,7 @@ public override void Initialize() SetStartDate(2024, 9, 1); SetEndDate(2024, 12, 31); SetCash(100000); + Settings.SeedInitialPrices = true; // Trade daily on CNBC opinion updates. UniverseSettings.Resolution = Resolution.Daily; @@ -39,9 +40,13 @@ private void Rebalance() { return; } - - var weight = 1m / _universe.Selected.Count; - var targets = _universe.Selected + var securities = _universe.Selected.Where(s => Securities[s].Price > 0).ToList(); + if (securities.Count == 0) + { + return; + } + var weight = securities.Count >= 10 ? 1m / securities.Count : 0.1m; + var targets = securities .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-universe-quiverquantcongressuniverse/Main.cs b/project-templates/csharp/alternative-data-universe-quiverquantcongressuniverse/Main.cs index 3792ef252b..cb96403f29 100644 --- a/project-templates/csharp/alternative-data-universe-quiverquantcongressuniverse/Main.cs +++ b/project-templates/csharp/alternative-data-universe-quiverquantcongressuniverse/Main.cs @@ -15,6 +15,7 @@ public override void Initialize() SetStartDate(2024, 9, 1); SetEndDate(2024, 12, 31); SetCash(100000); + Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Daily; // Universe of US Equities recently bought by US Congress members in trades over $200K. diff --git a/project-templates/csharp/alternative-data-universe-smartinsiderintentionuniverse/Main.cs b/project-templates/csharp/alternative-data-universe-smartinsiderintentionuniverse/Main.cs index c5959cbb69..6edd6caeb1 100644 --- a/project-templates/csharp/alternative-data-universe-smartinsiderintentionuniverse/Main.cs +++ b/project-templates/csharp/alternative-data-universe-smartinsiderintentionuniverse/Main.cs @@ -14,15 +14,17 @@ public override void Initialize() SetStartDate(2024, 9, 1); SetEndDate(2024, 12, 31); SetCash(100000); + Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Daily; - // Universe of large-cap US Equities announcing meaningful buyback intentions. + // Universe of US Equities announcing meaningful buyback intentions. _universe = AddUniverse(data => { - // Keep $100M+ market-cap names announcing a buyback over 0.5% of shares. - return from d in data.OfType() - where d.Percentage > 0.005m && d.USDMarketCap > 100000000m - select d.Symbol; + // Keep names announcing a buyback over 0.5% of shares. (USDMarketCap is not + // populated for intention records, unlike SmartInsiderTransactionUniverse.) + return data.OfType() + .Where(d => d.Percentage > 0.005m) + .Select(d => d.Symbol); }); // Rebalance shortly after the open so today's universe is locked in. @@ -35,9 +37,13 @@ private void Rebalance() { return; } - - var weight = 1m / _universe.Selected.Count; - var targets = _universe.Selected + var securities = _universe.Selected.Where(s => Securities[s].Price > 0).ToList(); + if (securities.Count == 0) + { + return; + } + var weight = securities.Count >= 10 ? 1m / securities.Count : 0.1m; + var targets = securities .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-universe-smartinsidertransactionuniverse/Main.cs b/project-templates/csharp/alternative-data-universe-smartinsidertransactionuniverse/Main.cs index a29e1337f7..a743dffdb7 100644 --- a/project-templates/csharp/alternative-data-universe-smartinsidertransactionuniverse/Main.cs +++ b/project-templates/csharp/alternative-data-universe-smartinsidertransactionuniverse/Main.cs @@ -14,6 +14,7 @@ public override void Initialize() SetStartDate(2024, 9, 1); SetEndDate(2024, 12, 31); SetCash(100000); + Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Daily; // Universe of large-cap US Equities executing meaningful share buybacks. @@ -35,9 +36,13 @@ private void Rebalance() { return; } - - var weight = 1m / _universe.Selected.Count; - var targets = _universe.Selected + var securities = _universe.Selected.Where(s => Securities[s].Price > 0).ToList(); + if (securities.Count == 0) + { + return; + } + var weight = securities.Count >= 10 ? 1m / securities.Count : 0.1m; + var targets = securities .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/python/alternative-data-universe-eodhdupcomingipos/main.py b/project-templates/python/alternative-data-universe-eodhdupcomingipos/main.py index 51613dd290..38bf9a8061 100644 --- a/project-templates/python/alternative-data-universe-eodhdupcomingipos/main.py +++ b/project-templates/python/alternative-data-universe-eodhdupcomingipos/main.py @@ -1,6 +1,5 @@ # region imports from AlgorithmImports import * -from QuantConnect.DataSource.EODHD import DealType # endregion class EODHDUpcomingIPOsUniverseAlgorithm(QCAlgorithm): @@ -20,7 +19,7 @@ def initialize(self) -> None: def _select_assets(self, data: List[EODHDUpcomingIPOs]) -> List[Symbol]: # Keep expected/priced IPOs with a confirmed date and a >$1 minimum across the price band. return [d.symbol for d in data - if d.ipo_date and d.deal_type in [DealType.EXPECTED, DealType.PRICED] + if d.ipo_date and d.deal_type in [EODHD.DealType.EXPECTED, EODHD.DealType.PRICED] and (prices := [x for x in [d.lowest_price, d.highest_price, d.offer_price] if x]) and min(prices) > 1] diff --git a/project-templates/python/alternative-data-universe-quivercnbcsuniverse/main.py b/project-templates/python/alternative-data-universe-quivercnbcsuniverse/main.py index a3dadebadd..eb5f8d1a43 100644 --- a/project-templates/python/alternative-data-universe-quivercnbcsuniverse/main.py +++ b/project-templates/python/alternative-data-universe-quivercnbcsuniverse/main.py @@ -8,6 +8,7 @@ def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) self.set_cash(100000) + self.settings.seed_initial_prices = True # Trade daily on CNBC opinion updates. self.universe_settings.resolution = Resolution.DAILY @@ -29,8 +30,10 @@ def _select_assets(self, data: List[QuiverCNBCsUniverse]) -> List[Symbol]: 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] + securities = [s for s in self._universe.selected if self.securities[s].price] + if not securities: + return + weight = min(1 / len(securities), 0.1) + targets = [PortfolioTarget(symbol, weight) for symbol in securities] self.set_holdings(targets, liquidate_existing_holdings=True) diff --git a/project-templates/python/alternative-data-universe-quiverquantcongressuniverse/main.py b/project-templates/python/alternative-data-universe-quiverquantcongressuniverse/main.py index 90109f0be1..9bd9142150 100644 --- a/project-templates/python/alternative-data-universe-quiverquantcongressuniverse/main.py +++ b/project-templates/python/alternative-data-universe-quiverquantcongressuniverse/main.py @@ -8,6 +8,7 @@ def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) self.set_cash(100000) + self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.DAILY # Universe of US Equities recently bought by US Congress members in trades over $200K. diff --git a/project-templates/python/alternative-data-universe-smartinsiderintentionuniverse/main.py b/project-templates/python/alternative-data-universe-smartinsiderintentionuniverse/main.py index 0a8a4c585f..ca23551ca1 100644 --- a/project-templates/python/alternative-data-universe-smartinsiderintentionuniverse/main.py +++ b/project-templates/python/alternative-data-universe-smartinsiderintentionuniverse/main.py @@ -8,6 +8,7 @@ def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) self.set_cash(100000) + self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.DAILY # Universe of large-cap US Equities announcing meaningful buyback intentions. @@ -17,16 +18,18 @@ def initialize(self) -> None: self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance) def _select_assets(self, data: List[SmartInsiderIntentionUniverse]) -> List[Symbol]: - # Keep $100M+ market-cap names announcing a buyback over 0.5% of shares. + # Keep names announcing a buyback over 0.5% of shares. (USDMarketCap is not + # populated for intention records, unlike SmartInsiderTransactionUniverse.) return [d.symbol for d in data - if d.percentage and d.usd_market_cap - and d.percentage > 0.005 and d.usd_market_cap > 100000000] + if d.percentage and d.percentage > 0.005] 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] + securities = [s for s in self._universe.selected if self.securities[s].price] + if not securities: + return + weight = min(1 / len(securities), 0.1) + targets = [PortfolioTarget(symbol, weight) for symbol in securities] self.set_holdings(targets, liquidate_existing_holdings=True) diff --git a/project-templates/python/alternative-data-universe-smartinsidertransactionuniverse/main.py b/project-templates/python/alternative-data-universe-smartinsidertransactionuniverse/main.py index 62b73f2383..d1e82a4a33 100644 --- a/project-templates/python/alternative-data-universe-smartinsidertransactionuniverse/main.py +++ b/project-templates/python/alternative-data-universe-smartinsidertransactionuniverse/main.py @@ -8,6 +8,7 @@ def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) self.set_cash(100000) + self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.DAILY # Universe of large-cap US Equities executing meaningful share buybacks. @@ -25,8 +26,10 @@ def _select_assets(self, data: List[SmartInsiderTransactionUniverse]) -> List[Sy 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] + securities = [s for s in self._universe.selected if self.securities[s].price] + if not securities: + return + weight = min(1 / len(securities), 0.1) + targets = [PortfolioTarget(symbol, weight) for symbol in securities] self.set_holdings(targets, liquidate_existing_holdings=True) diff --git a/project-templates/python/custom-indicator/main.py b/project-templates/python/custom-indicator/main.py index fac808ba2f..140da03788 100644 --- a/project-templates/python/custom-indicator/main.py +++ b/project-templates/python/custom-indicator/main.py @@ -9,7 +9,7 @@ def initialize(self) -> None: self.set_start_date(2024, 9, 1) self.set_end_date(2024, 12, 31) # Request daily SPY data to feed the indicators to generate trade signals and trade. - self._spy = self.add_equity("SPY") + self._spy = self.add_equity("SPY", data_normalization_mode=DataNormalizationMode.RAW) # Create a custom money flow index to generate a trade signal. self._custom_mfi = CustomMoneyFlowIndex(20) # Warm up for immediate usage of indicators. From 9c92a384c139a5b3fda9b8ab546c690b9e64ede4 Mon Sep 17 00:00:00 2001 From: Alexandre Catarino Date: Wed, 13 May 2026 23:02:38 +0100 Subject: [PATCH 2/2] Unify alternative-data chain templates around fundamental-intersect pattern All 14 alt-data chain templates now share one shape, with only the alt-data filter expression differing between them: - First universe stores every US Equity fundamental and emits Universe.Unchanged. - Second universe filters alt-data into a `alt` set, plots its size as Universe/Raw, then intersects with the cached fundamentals and keeps the 100 most liquid by dollar volume. - Scheduled rebalance at 9:00 ET with a `min(1/N, 0.1)` weight cap so sparse-day universes don't trigger insufficient-buying-power rejections. - C# uses method-syntax LINQ throughout. Other changes folded in: - alternative-data-chain-universe-eodhdupcomingipos: maintain an `_ipo_dates` map and trade an IPO seven days after the listing so Morningstar fundamentals have caught up. Previously the chain produced zero orders because IPO names aren't yet in the fundamental universe. - alternative-data-chain-universe-smartinsiderintentionuniverse: drop the unused `USDMarketCap > 100_000_000` filter (the field isn't populated for intention records). - alternative-data-chain-universe-quiverquantcongressuniverse: fix C# `OfType` to `OfType` so the same rows that Python iterates are visible to C#. Doc examples in `03 Writing Algorithms/12 Universes/03 Equity/04 Chained Universes/` updated to match: the fundamental + alt-data example now actually trades (1458 orders, +14.35%, Sharpe 1.65), and the ETF + alt-data example ranks by `Weight` and drops alt names that aren't in SPY (889 orders, +12.15%, Sharpe 1.78). --- ...hain Fundamental and Alternative Data.html | 110 +++++++++--------- .../08 Chain ETF and Alternative Data.html | 110 ++++++++++-------- .../Main.cs | 31 ++--- .../Main.cs | 30 ++--- .../Main.cs | 30 ++--- .../Main.cs | 30 ++--- .../Main.cs | 28 +++-- .../Main.cs | 56 ++++++--- .../Main.cs | 28 +++-- .../Main.cs | 27 +++-- .../Main.cs | 27 +++-- .../Main.cs | 42 +++---- .../Main.cs | 29 +++-- .../Main.cs | 26 +++-- .../Main.cs | 30 +++-- .../Main.cs | 28 +++-- .../main.py | 28 +++-- .../main.py | 30 ++--- .../main.py | 25 ++-- .../main.py | 23 ++-- .../main.py | 22 ++-- .../main.py | 35 ++++-- .../main.py | 22 ++-- .../main.py | 24 ++-- .../main.py | 20 ++-- .../main.py | 18 +-- .../main.py | 20 ++-- .../main.py | 20 ++-- .../main.py | 26 +++-- .../main.py | 22 ++-- 30 files changed, 573 insertions(+), 424 deletions(-) diff --git a/03 Writing Algorithms/12 Universes/03 Equity/04 Chained Universes/05 Chain Fundamental and Alternative Data.html b/03 Writing Algorithms/12 Universes/03 Equity/04 Chained Universes/05 Chain Fundamental and Alternative Data.html index 13211630a6..c6a7a405a7 100644 --- a/03 Writing Algorithms/12 Universes/03 Equity/04 Chained Universes/05 Chain Fundamental and Alternative Data.html +++ b/03 Writing Algorithms/12 Universes/03 Equity/04 Chained Universes/05 Chain Fundamental and Alternative Data.html @@ -7,90 +7,96 @@ 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. + . It stores every US Equity fundamental, intersects them with the names CNBC commentator Jim Cramer mentions, and trades the 100 most liquid intersection members each morning.

public class ChainedUniverseAlgorithm : QCAlgorithm
 {
-    private List<Symbol> _fundamental = new();
+    private List<Fundamental> _fundamental = [];
+    private Universe _universe;
 
     public override void Initialize()
     {
         SetStartDate(2024, 9, 1);
         SetEndDate(2024, 12, 31);
         SetCash(100000);
-
-        // Filter the top 100 liquid equities of the last trading day, and save the symbols for the next filtering.
+        Settings.SeedInitialPrices = true;
+        UniverseSettings.Resolution = Resolution.Minute;
+        // First universe: store all US Equity fundamentals; emits Universe.Unchanged.
         AddUniverse(fundamental =>
         {
-            _fundamental = (from c in fundamental
-                orderby c.DollarVolume descending
-                select c.Symbol).Take(100).ToList();
+            _fundamental = [..fundamental];
             return Universe.Unchanged;
         });
-        // Filter the equities being commented on by CNBC analyst Cramer, then select the ones that intersect with the fundamental universe.
-        AddUniverse<QuiverCNBCsUniverse>(altCoarse =>
+        // Second universe: equities mentioned by Cramer, ranked by dollar volume.
+        _universe = AddUniverse<QuiverCNBCsUniverse>(altCoarse =>
         {
-            var followers = from d in altCoarse.OfType<QuiverCNBCsUniverse>()
-                where d.Traders.ToLower().Contains("cramer")
-                select d.Symbol;
-            return _fundamental.Intersect(followers);
+            var alt = altCoarse.OfType<QuiverCNBCsUniverse>()
+                .Where(d => d.Traders.ToLower().Contains("cramer"))
+                .Select(d => d.Symbol)
+                .ToHashSet();
+            Plot("Universe", "Raw", alt.Count);
+            return _fundamental
+                .Where(c => alt.Contains(c.Symbol))
+                .OrderByDescending(c => c.DollarVolume)
+                .Select(c => c.Symbol)
+                .Take(100);
         });
+        // Rebalance before market open to trade today's intersection.
+        Schedule.On(DateRules.EveryDay("SPY"), TimeRules.At(9, 0, 0), Rebalance);
     }
 
-    public override void OnSecuritiesChanged(SecurityChanges changes)
-    {
-        // Request CNBC data for the selected stocks.
-        foreach (var added in changes.AddedSecurities)
-        {
-            AddData<QuiverCNBCs>(added.Symbol);
-        }
-    }
-
-    public override void OnData(Slice data)
+    private void Rebalance()
     {
-        foreach (var dataPoint in data.Get<QuiverCNBCs>().SelectMany(x=> x.Value.OfType<QuiverCNBC>()))
+        if (_universe.Selected.Count == 0)
         {
-            Debug($"{dataPoint.Symbol} traders at {data.Time}: {dataPoint.Traders}");
+            return;
         }
+        var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m;
+        var targets = _universe.Selected
+            .Select(symbol => new PortfolioTarget(symbol, weight))
+            .ToList();
+        SetHoldings(targets, true);
     }
 }
from AlgorithmImports import *
 
+
 class ChainedUniverseAlgorithm(QCAlgorithm):
 
-    _fundamental = []
+    _fundamental: List[Fundamental] = []
 
     def initialize(self) -> None:
         self.set_start_date(2024, 9, 1)
         self.set_end_date(2024, 12, 31)
         self.set_cash(100000)
-        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]:
-        # Filter the top 100 liquid equities of the last trading day, and save the symbols for the next filtering.
-        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]]
+        self.settings.seed_initial_prices = True
+        self.universe_settings.resolution = Resolution.MINUTE
+        # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED.
+        self.add_universe(self._fundamental_filter)
+        # Second universe: equities mentioned by Cramer, ranked by dollar volume.
+        self._universe = self.add_universe(QuiverCNBCsUniverse, self._select_assets)
+        # Rebalance before market open to trade today's intersection.
+        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 = fundamental
         return Universe.UNCHANGED
-    
-    def _mad_money_selection(self, alt_coarse: List[QuiverCNBCsUniverse]) -> List[Symbol]:
-        # Filter the equities being commented on by CNBC analyst Cramer, then select the ones that intersect with the fundamental universe.
-        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: SecurityChanges) -> None:
-        # Request CNBC data for the selected stocks.
-        for added in changes.added_securities:
-            self.add_data(QuiverCNBCs, added.symbol)
-    
-    def on_data(self, data: Slice) -> None:
-        # Prices in the slice from the universe selection
-        # Alternative data in a 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}")
+
+    def _select_assets(self, alt_coarse: List[QuiverCNBCsUniverse]) -> List[Symbol]:
+        # Keep symbols mentioned by Cramer.
+        alt = {d.symbol for d in alt_coarse if 'cramer' in d.traders.lower()}
+        self.plot('Universe', 'Raw', len(alt))
+        return [c.symbol for c in sorted(
+            [c for c in self._fundamental if c.symbol in alt],
+            key=lambda c: c.dollar_volume, reverse=True
+        )[:100]]
+
+    def _rebalance(self) -> None:
+        if not self._universe.selected:
+            return
+        weight = min(1 / len(self._universe.selected), 0.1)
+        targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected]
+        self.set_holdings(targets, True)
 
diff --git a/03 Writing Algorithms/12 Universes/03 Equity/04 Chained Universes/08 Chain ETF and Alternative Data.html b/03 Writing Algorithms/12 Universes/03 Equity/04 Chained Universes/08 Chain ETF and Alternative Data.html index 0db2580561..85d50261b6 100644 --- a/03 Writing Algorithms/12 Universes/03 Equity/04 Chained Universes/08 Chain ETF and Alternative Data.html +++ b/03 Writing Algorithms/12 Universes/03 Equity/04 Chained Universes/08 Chain ETF and Alternative Data.html @@ -7,90 +7,98 @@ 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. + . It stores every SPY constituent, intersects them with the names CNBC commentator Jim Cramer mentions, and trades the 100 heaviest-weighted intersection members each morning. Names CNBC mentions but that aren't in SPY are dropped.

public class ChainedUniverseAlgorithm : QCAlgorithm
 {
-    private List<Symbol> _etf = new();
+    private List<ETFConstituentUniverse> _etf = [];
+    private Universe _universe;
 
     public override void Initialize()
     {
         SetStartDate(2024, 9, 1);
         SetEndDate(2024, 12, 31);
         SetCash(100000);
-        UniverseSettings.Asynchronous = true;
-
-        // Save all SPY constituents for the next filtering.
+        Settings.SeedInitialPrices = true;
+        UniverseSettings.Resolution = Resolution.Minute;
+        // First universe: store all SPY constituents; emits Universe.Unchanged.
         AddUniverse(Universe.ETF("SPY", Market.USA, UniverseSettings, constituents =>
         {
-            _etf = constituents.Select(c => c.Symbol).ToList();
+            _etf = [..constituents];
             return Universe.Unchanged;
         }));
-        // Filter the equities being commented on by CNBC analyst Cramer, then select the ones in SPY constituents.
-        AddUniverse<QuiverCNBCsUniverse>(altCoarse =>
+        // Second universe: equities mentioned by Cramer that are in SPY, ranked by ETF weight.
+        _universe = AddUniverse<QuiverCNBCsUniverse>(altCoarse =>
         {
-            var followers = from d in altCoarse.OfType<QuiverCNBCsUniverse>()
-                where d.Traders.ToLower().Contains("cramer")
-                select d.Symbol;
-            return _etf.Intersect(followers);
+            var alt = altCoarse.OfType<QuiverCNBCsUniverse>()
+                .Where(d => d.Traders.ToLower().Contains("cramer"))
+                .Select(d => d.Symbol)
+                .ToHashSet();
+            Plot("Universe", "Raw", alt.Count);
+            // Names not in SPY cannot be traded, so intersect with the ETF list.
+            return _etf
+                .Where(c => alt.Contains(c.Symbol))
+                .OrderByDescending(c => c.Weight)
+                .Select(c => c.Symbol)
+                .Take(100);
         });
+        // Rebalance before market open to trade today's intersection.
+        Schedule.On(DateRules.EveryDay("SPY"), TimeRules.At(9, 0, 0), Rebalance);
     }
 
-    public override void OnSecuritiesChanged(SecurityChanges changes)
-    {
-        // Request CNBC data for the selected stocks.
-        foreach (var added in changes.AddedSecurities)
-        {
-            AddData<QuiverCNBCs>(added.Symbol);
-        }
-    }
-
-    public override void OnData(Slice data)
+    private void Rebalance()
     {
-        foreach (var dataPoint in data.Get<QuiverCNBCs>().SelectMany(x=> x.Value.OfType<QuiverCNBC>()))
+        if (_universe.Selected.Count == 0)
         {
-            Debug($"{dataPoint.Symbol} traders at {data.Time}: {dataPoint.Traders}");
+            return;
         }
+        var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m;
+        var targets = _universe.Selected
+            .Select(symbol => new PortfolioTarget(symbol, weight))
+            .ToList();
+        SetHoldings(targets, true);
     }
 }
from AlgorithmImports import *
 
+
 class ChainedUniverseAlgorithm(QCAlgorithm):
 
-    _etf = []
+    _etf: List[ETFConstituentUniverse] = []
 
-    def initialize(self):
+    def initialize(self) -> None:
         self.set_start_date(2024, 9, 1)
         self.set_end_date(2024, 12, 31)
         self.set_cash(100000)
-        self.universe_settings.asynchronous = True
-        # Save all SPY constituents for the next filtering.
-        self.add_universe(self.universe.etf("SPY", Market.USA, self.universe_settings, self._etf_constituents_filter))
-        # Next filtering based on CNBC data.
-        self.add_universe(QuiverCNBCsUniverse, self._mad_money_selection)
+        self.settings.seed_initial_prices = True
+        self.universe_settings.resolution = Resolution.MINUTE
+        # First universe: store all SPY constituents; emits Universe.UNCHANGED.
+        self.add_universe(self.universe.etf("SPY", Market.USA, self.universe_settings, self._etf_filter))
+        # Second universe: equities mentioned by Cramer that are in SPY, ranked by ETF weight.
+        self._universe = self.add_universe(QuiverCNBCsUniverse, self._select_assets)
+        # Rebalance before market open to trade today's intersection.
+        self.schedule.on(self.date_rules.every_day("SPY"), self.time_rules.at(9, 0, 0), self._rebalance)
 
-    def _etf_constituents_filter(self, fundamental: List[Fundamental]) -> List[Symbol]:
-        # Save all SPY constituents for the next filtering.
-        self._etf = [c.symbol for c in constituents]
+    def _etf_filter(self, constituents: List[ETFConstituentUniverse]) -> Universe.UnchangedUniverse:
+        self._etf = constituents
         return Universe.UNCHANGED
 
-    def _mad_money_selection(self, alt_coarse: List[QuiverCNBCsUniverse]) -> List[Symbol]:
-        # Filter the equities being commented on by CNBC analyst Cramer, then select the ones in SPY constituents.
-        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):
-        # Request CNBC data for the selected stocks.
-        for added in changes.added_securities:
-            self.add_data(QuiverCNBCs, added.symbol)
+    def _select_assets(self, alt_coarse: List[QuiverCNBCsUniverse]) -> List[Symbol]:
+        # Keep symbols mentioned by Cramer.
+        alt = {d.symbol for d in alt_coarse if 'cramer' in d.traders.lower()}
+        self.plot('Universe', 'Raw', len(alt))
+        # Names not in SPY cannot be traded, so intersect with the ETF list.
+        return [c.symbol for c in sorted(
+            [c for c in self._etf if c.symbol in alt],
+            key=lambda c: c.weight, reverse=True
+        )[:100]]
 
-    def on_data(self, data):
-        # Prices in the slice from the universe selection
-        # Alternative data in a 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}")
+ def _rebalance(self) -> None: + if not self._universe.selected: + return + weight = min(1 / len(self._universe.selected), 0.1) + targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] + self.set_holdings(targets, True) +
diff --git a/project-templates/csharp/alternative-data-chain-universe-braincompanyfilinglanguagemetricsuniverseall/Main.cs b/project-templates/csharp/alternative-data-chain-universe-braincompanyfilinglanguagemetricsuniverseall/Main.cs index 9fc8ef096b..d5308057ca 100644 --- a/project-templates/csharp/alternative-data-chain-universe-braincompanyfilinglanguagemetricsuniverseall/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-braincompanyfilinglanguagemetricsuniverseall/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class BrainCompanyFilingLanguageMetricsChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = []; + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -19,23 +19,28 @@ public override void Initialize() SetCash(100000); Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: positive sentiment in latest SEC filings, intersected with the fundamental list. + // Second universe: positive sentiment in latest SEC filings, ranked by dollar volume. _universe = AddUniverse(altCoarse => { // Keep names with positive sentiment in both the report and MD&A sections. - var alt = from d in altCoarse.OfType() - where d.ReportSentiment.Sentiment > 0m - && d.ManagementDiscussionAnalyasisOfFinancialConditionAndResultsOfOperations.Sentiment > 0m - select d.Symbol; - return _fundamental.Intersect(alt); + var alt = altCoarse.OfType() + .Where(d => d.ReportSentiment != null && d.ReportSentiment.Sentiment > 0m + && d.ManagementDiscussionAnalyasisOfFinancialConditionAndResultsOfOperations != null + && d.ManagementDiscussionAnalyasisOfFinancialConditionAndResultsOfOperations.Sentiment > 0m) + .Select(d => d.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. Schedule.On(DateRules.EveryDay("SPY"), TimeRules.At(9, 0, 0), Rebalance); @@ -43,11 +48,11 @@ where d.ReportSentiment.Sentiment > 0m private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-chain-universe-brainsentimentindicatoruniverse/Main.cs b/project-templates/csharp/alternative-data-chain-universe-brainsentimentindicatoruniverse/Main.cs index b2fc06a8c5..d34df220d9 100644 --- a/project-templates/csharp/alternative-data-chain-universe-brainsentimentindicatoruniverse/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-brainsentimentindicatoruniverse/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class BrainSentimentIndicatorChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = []; + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -19,22 +19,26 @@ public override void Initialize() SetCash(100000); Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: positive 7-day media sentiment with active mention coverage, intersected with the fundamental list. + // Second universe: positive 7-day Brain sentiment, ranked by dollar volume. _universe = AddUniverse(altCoarse => { - // Keep names with both active mention coverage and positive 7-day sentiment. - var alt = from d in altCoarse.OfType() - where d.TotalArticleMentions7Days > 0m && d.Sentiment7Days > 0m - select d.Symbol; - return _fundamental.Intersect(alt); + // Keep names with both active 7-day mention coverage and positive 7-day sentiment. + var alt = altCoarse.OfType() + .Where(d => d.TotalArticleMentions7Days > 0m && d.Sentiment7Days > 0m) + .Select(d => d.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. Schedule.On(DateRules.EveryDay("SPY"), TimeRules.At(9, 0, 0), Rebalance); @@ -42,11 +46,11 @@ where d.TotalArticleMentions7Days > 0m && d.Sentiment7Days > 0m private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-chain-universe-brainstockrankinguniverse/Main.cs b/project-templates/csharp/alternative-data-chain-universe-brainstockrankinguniverse/Main.cs index db9bb753c1..f06348b50d 100644 --- a/project-templates/csharp/alternative-data-chain-universe-brainstockrankinguniverse/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-brainstockrankinguniverse/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class BrainStockRankingChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = []; + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -19,22 +19,26 @@ public override void Initialize() SetCash(100000); Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe selects top 100 US Equities by dollar volume. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe filters for positive Brain ML rankings across all horizons. + // Second universe: positive Brain ML rankings across 2-, 3-, and 5-day horizons, ranked by dollar volume. _universe = AddUniverse(altCoarse => { - // Keep symbols with positive rankings across 2-day, 3-day, and 5-day horizons. - var alt = from d in altCoarse.OfType() - where d.Rank2Days > 0m && d.Rank3Days > 0m && d.Rank5Days > 0m - select d.Symbol; - return _fundamental.Intersect(alt); + // Keep names with consistent positive momentum across all three horizons. + var alt = altCoarse.OfType() + .Where(d => d.Rank2Days > 0m && d.Rank3Days > 0m && d.Rank5Days > 0m) + .Select(d => d.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. Schedule.On(DateRules.EveryDay("SPY"), TimeRules.At(9, 0, 0), Rebalance); @@ -42,11 +46,11 @@ where d.Rank2Days > 0m && d.Rank3Days > 0m && d.Rank5Days > 0m private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingdividends/Main.cs b/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingdividends/Main.cs index 1c11533f6b..7a6bc2767e 100644 --- a/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingdividends/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingdividends/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class EODHDUpcomingDividendsChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = []; + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -19,22 +19,26 @@ public override void Initialize() SetCash(100000); Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: ex-dividend in the next day with a $0.05+ payout, intersected with the fundamental list. + // Second universe: ex-dividend in the next day with a $0.05+ payout, ranked by dollar volume. _universe = AddUniverse(altCoarse => { - // Filter symbols with dividends over $0.05 paying within one day. - var alt = from d in altCoarse.OfType() - where d.DividendDate <= Time.AddDays(1) && d.Dividend > 0.05m - select d.Symbol; - return _fundamental.Intersect(alt); + // Keep names with a dividend over $0.05 paying within one day. + var alt = altCoarse.OfType() + .Where(d => d.DividendDate <= Time.AddDays(1) && d.Dividend > 0.05m) + .Select(d => d.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. Schedule.On(DateRules.EveryDay("SPY"), TimeRules.At(9, 0, 0), Rebalance); @@ -42,11 +46,11 @@ orderby c.DollarVolume descending private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingearnings/Main.cs b/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingearnings/Main.cs index dec76e6e88..a2e2e08461 100644 --- a/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingearnings/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingearnings/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class EODHDUpcomingEarningsChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = new(); + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -20,22 +20,26 @@ public override void Initialize() Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: earnings in the next 3 days with a positive estimate, intersected with the fundamental list. + // Second universe: upcoming earnings with positive estimates, ranked by dollar volume. _universe = AddUniverse(altCoarse => { // Keep names with a positive analyst estimate ahead of the report. - var alt = from d in altCoarse.OfType() - where d.ReportDate <= Time.AddDays(3) && d.Estimate > 0m - select d.Symbol; - return _fundamental.Intersect(alt); + var alt = altCoarse.OfType() + .Where(d => d.ReportDate <= Time.AddDays(3) && d.Estimate > 0m) + .Select(d => d.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -44,12 +48,12 @@ orderby c.DollarVolume descending private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingipos/Main.cs b/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingipos/Main.cs index c736826028..3a451fd669 100644 --- a/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingipos/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingipos/Main.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Algorithm.Framework.Portfolio; @@ -11,7 +12,10 @@ public class EODHDUpcomingIPOsChainedUniverseAlgorithm : QCAlgorithm { private static readonly HashSet _dealTypesWanted = new() { EODHD.DealType.Expected, EODHD.DealType.Priced }; - private List _fundamental = new(); + private List _fundamental = []; + // Map of IPO symbol -> IPO date, captured while the event is upcoming so we can + // trade the name once Morningstar has a few days of fundamentals on it. + private Dictionary _ipoDates = new(); private Universe _universe; public override void Initialize() @@ -22,25 +26,47 @@ public override void Initialize() Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: confirmed non-penny upcoming IPOs, intersected with the fundamental list. + // Second universe: trade IPOs one week after listing so fundamentals are populated. _universe = AddUniverse(altCoarse => { - // Keep expected/priced IPOs with a confirmed date and an above-$1 price band. - var alt = from d in altCoarse.OfType() - where _dealTypesWanted.Contains(d.DealType) - && d.IpoDate.HasValue - && (new[] { d.LowestPrice, d.HighestPrice, d.OfferPrice } - .Where(x => x.HasValue).Min() ?? 0m) > 1m - select d.Symbol; - return _fundamental.Intersect(alt); + // Capture expected/priced IPOs with a confirmed date and a >$1 minimum price band. + foreach (var d in altCoarse.OfType()) + { + if (!_dealTypesWanted.Contains(d.DealType) || !d.IpoDate.HasValue) + { + continue; + } + var prices = new[] { d.LowestPrice, d.HighestPrice, d.OfferPrice } + .Where(x => x.HasValue) + .Select(x => x.Value) + .ToList(); + if (prices.Count == 0 || prices.Min() <= 1m) + { + continue; + } + _ipoDates[d.Symbol] = d.IpoDate.Value; + } + // Drop entries whose IPO was more than 30 days ago to keep the dict bounded. + _ipoDates = _ipoDates + .Where(kv => kv.Value > Time.AddDays(-30)) + .ToDictionary(kv => kv.Key, kv => kv.Value); + // Trade IPOs that listed at least 7 days ago, ranked by dollar volume. + var alt = _ipoDates + .Where(kv => kv.Value <= Time.AddDays(-7)) + .Select(kv => kv.Key) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -49,7 +75,7 @@ where _dealTypesWanted.Contains(d.DealType) private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } diff --git a/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingsplits/Main.cs b/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingsplits/Main.cs index 37756bd368..4f6ee36c18 100644 --- a/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingsplits/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-eodhdupcomingsplits/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class EODHDUpcomingSplitsChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = new(); + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -20,22 +20,26 @@ public override void Initialize() Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: forward stock split in the next 3 days, intersected with the fundamental list. + // Second universe: forward stock split in the next 3 days, ranked by dollar volume. _universe = AddUniverse(altCoarse => { // Keep names with a forward split (factor > 1) within 3 days. - var alt = from d in altCoarse.OfType() - where d.SplitDate <= Time.AddDays(3) && d.SplitFactor > 1m - select d.Symbol; - return _fundamental.Intersect(alt); + var alt = altCoarse.OfType() + .Where(d => d.SplitDate <= Time.AddDays(3) && d.SplitFactor > 1m) + .Select(d => d.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -44,12 +48,12 @@ orderby c.DollarVolume descending private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-chain-universe-quivercnbcsuniverse/Main.cs b/project-templates/csharp/alternative-data-chain-universe-quivercnbcsuniverse/Main.cs index 1af7eaeadf..0284efd98f 100644 --- a/project-templates/csharp/alternative-data-chain-universe-quivercnbcsuniverse/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-quivercnbcsuniverse/Main.cs @@ -10,7 +10,7 @@ namespace QuantConnect.Algorithm.CSharp { public class QuiverCNBCsChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = new(); + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -24,19 +24,24 @@ public override void Initialize() // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); // Second universe: 3+ BUY CNBC opinions, intersected with the fundamental list. _universe = AddUniverse(altCoarse => { - // Group raw CNBC opinions by ticker and keep names with 3+ BUY recommendations. - var alt = from g in altCoarse.OfType().GroupBy(x => x.Symbol) - where g.Count(x => x.Direction == OrderDirection.Buy) >= 3 - select g.Key; - return _fundamental.Intersect(alt); + // Group raw CNBC opinions by ticker and keep names with 2+ BUY recommendations. + var alt = altCoarse.OfType() + .GroupBy(x => x.Symbol) + .Where(g => g.Count(x => x.Direction == OrderDirection.Buy) >= 2) + .Select(g => g.Key) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -45,12 +50,12 @@ where g.Count(x => x.Direction == OrderDirection.Buy) >= 3 private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-chain-universe-quivergovernmentcontractuniverse/Main.cs b/project-templates/csharp/alternative-data-chain-universe-quivergovernmentcontractuniverse/Main.cs index 97f349f94f..f85c7600c3 100644 --- a/project-templates/csharp/alternative-data-chain-universe-quivergovernmentcontractuniverse/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-quivergovernmentcontractuniverse/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class QuiverGovernmentContractChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = new(); + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -20,22 +20,27 @@ public override void Initialize() Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: 3+ government contracts totalling over $50K, intersected with the fundamental list. + // Second universe: 3+ government contracts totalling over $50K, ranked by dollar volume. _universe = AddUniverse(altCoarse => { // Group by ticker and keep names with 3+ contracts totalling over $50K. - var alt = from g in altCoarse.OfType().GroupBy(x => x.Symbol) - where g.Count() >= 3 && g.Sum(x => x.Amount) > 50000m - select g.Key; - return _fundamental.Intersect(alt); + var alt = altCoarse.OfType() + .GroupBy(x => x.Symbol) + .Where(g => g.Count() >= 3 && g.Sum(x => x.Amount) > 50000m) + .Select(g => g.Key) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -44,7 +49,7 @@ where g.Count() >= 3 && g.Sum(x => x.Amount) > 50000m private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } diff --git a/project-templates/csharp/alternative-data-chain-universe-quiverinsidertradinguniverse/Main.cs b/project-templates/csharp/alternative-data-chain-universe-quiverinsidertradinguniverse/Main.cs index a0e2657f52..e639871dd1 100644 --- a/project-templates/csharp/alternative-data-chain-universe-quiverinsidertradinguniverse/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-quiverinsidertradinguniverse/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class QuiverInsiderTradingChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = new(); + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -20,36 +20,30 @@ public override void Initialize() Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: 10 largest insider-trading dollar volumes, intersected with the fundamental list. + // Second universe: 10 largest insider-trading dollar volumes, ranked by dollar volume. _universe = AddUniverse(altCoarse => { // Aggregate insider dollar volume per ticker and keep the 10 largest. - var dollarVolume = new Dictionary(); - foreach (var d in altCoarse.OfType()) - { - if (d.PricePerShare == null || d.PricePerShare == 0m) - { - continue; - } - if (!dollarVolume.ContainsKey(d.Symbol)) - { - dollarVolume[d.Symbol] = 0m; - } - dollarVolume[d.Symbol] += (d.Shares ?? 0m) * d.PricePerShare.Value; - } - var alt = dollarVolume - .OrderByDescending(kvp => kvp.Value) + var alt = altCoarse.OfType() + .Where(d => d.PricePerShare.HasValue && d.PricePerShare.Value != 0m) + .GroupBy(d => d.Symbol) + .Select(g => new { Symbol = g.Key, DollarVolume = g.Sum(d => (d.Shares ?? 0m) * d.PricePerShare.Value) }) + .OrderByDescending(x => x.DollarVolume) .Take(10) - .Select(kvp => kvp.Key); - return _fundamental.Intersect(alt); + .Select(x => x.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -58,7 +52,7 @@ orderby c.DollarVolume descending private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } diff --git a/project-templates/csharp/alternative-data-chain-universe-quiverlobbyinguniverse/Main.cs b/project-templates/csharp/alternative-data-chain-universe-quiverlobbyinguniverse/Main.cs index 900b7dd171..bdfb2f6431 100644 --- a/project-templates/csharp/alternative-data-chain-universe-quiverlobbyinguniverse/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-quiverlobbyinguniverse/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class QuiverLobbyingChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = new(); + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -20,22 +20,27 @@ public override void Initialize() Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: $100K+ corporate lobbying spend, intersected with the fundamental list. + // Second universe: $100K+ corporate lobbying spend, ranked by dollar volume. _universe = AddUniverse("QuiverLobbyingUniverse", Resolution.Daily, altCoarse => { // Aggregate lobbying spend per ticker and keep names spending $100K+. - var alt = from g in altCoarse.OfType().GroupBy(x => x.Symbol) - where g.Sum(x => x.Amount) >= 100000m - select g.Key; - return _fundamental.Intersect(alt); + var alt = altCoarse.OfType() + .GroupBy(x => x.Symbol) + .Where(g => g.Sum(x => x.Amount) >= 100000m) + .Select(g => g.Key) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -44,12 +49,12 @@ where g.Sum(x => x.Amount) >= 100000m private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-chain-universe-quiverquantcongressuniverse/Main.cs b/project-templates/csharp/alternative-data-chain-universe-quiverquantcongressuniverse/Main.cs index 50ca06d58f..113f922e9c 100644 --- a/project-templates/csharp/alternative-data-chain-universe-quiverquantcongressuniverse/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-quiverquantcongressuniverse/Main.cs @@ -10,7 +10,7 @@ namespace QuantConnect.Algorithm.CSharp { public class QuiverQuantCongressChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = new(); + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -21,22 +21,26 @@ public override void Initialize() Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: US Congress BUY disclosures over $200K, intersected with the fundamental list. + // Second universe: US Congress BUY disclosures over $200K, ranked by dollar volume. _universe = AddUniverse(altCoarse => { // Keep buy disclosures over $200K to filter out small reports. - var alt = from d in altCoarse.OfType() - where d.Amount > 200000m && d.Transaction == OrderDirection.Buy - select d.Symbol; - return _fundamental.Intersect(alt); + var alt = altCoarse.OfType() + .Where(d => d.Amount > 200000m && d.Transaction == OrderDirection.Buy) + .Select(d => d.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -45,7 +49,7 @@ orderby c.DollarVolume descending private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } diff --git a/project-templates/csharp/alternative-data-chain-universe-smartinsiderintentionuniverse/Main.cs b/project-templates/csharp/alternative-data-chain-universe-smartinsiderintentionuniverse/Main.cs index fb8eb2e6f7..c84e57f745 100644 --- a/project-templates/csharp/alternative-data-chain-universe-smartinsiderintentionuniverse/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-smartinsiderintentionuniverse/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class SmartInsiderIntentionChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = new(); + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -20,22 +20,28 @@ public override void Initialize() Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: $100M+ market-cap buyback intentions over 0.5%, intersected with the fundamental list. + // Second universe: $100M+ market-cap buyback intentions over 0.5%, ranked by dollar volume. _universe = AddUniverse(altCoarse => { // Keep $100M+ market-cap names announcing a buyback over 0.5% of shares. - var alt = from d in altCoarse.OfType() - where d.Percentage > 0.005m && d.USDMarketCap > 100000000m - select d.Symbol; - return _fundamental.Intersect(alt); + // Keep names announcing a buyback over 0.5% of shares. (USDMarketCap is not + // populated for intention records, unlike SmartInsiderTransactionUniverse.) + var alt = altCoarse.OfType() + .Where(d => d.Percentage > 0.005m) + .Select(d => d.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -44,12 +50,12 @@ where d.Percentage > 0.005m && d.USDMarketCap > 100000000m private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/csharp/alternative-data-chain-universe-smartinsidertransactionuniverse/Main.cs b/project-templates/csharp/alternative-data-chain-universe-smartinsidertransactionuniverse/Main.cs index c2ed324ccd..fc29d70c57 100644 --- a/project-templates/csharp/alternative-data-chain-universe-smartinsidertransactionuniverse/Main.cs +++ b/project-templates/csharp/alternative-data-chain-universe-smartinsidertransactionuniverse/Main.cs @@ -9,7 +9,7 @@ namespace QuantConnect.Algorithm.CSharp { public class SmartInsiderTransactionChainedUniverseAlgorithm : QCAlgorithm { - private List _fundamental = new(); + private List _fundamental = []; private Universe _universe; public override void Initialize() @@ -20,22 +20,26 @@ public override void Initialize() Settings.SeedInitialPrices = true; UniverseSettings.Resolution = Resolution.Minute; - // First universe: top 100 US Equities by dollar volume; emits Universe.Unchanged. + // First universe: store all US Equity fundamentals; emits Universe.Unchanged. AddUniverse(fundamental => { - _fundamental = (from c in fundamental - orderby c.DollarVolume descending - select c.Symbol).Take(100).ToList(); + _fundamental = [..fundamental]; return Universe.Unchanged; }); - // Second universe: $100M+ market-cap buybacks over 0.5%, intersected with the fundamental list. + // Second universe: $100M+ market-cap buybacks over 0.5%, ranked by dollar volume. _universe = AddUniverse(altCoarse => { // Keep $100M+ market-cap names buying back over 0.5% of shares. - var alt = from d in altCoarse.OfType() - where d.BuybackPercentage > 0.005m && d.USDMarketCap > 100000000m - select d.Symbol; - return _fundamental.Intersect(alt); + var alt = altCoarse.OfType() + .Where(d => d.BuybackPercentage > 0.005m && d.USDMarketCap > 100000000m) + .Select(d => d.Symbol) + .ToHashSet(); + Plot("Universe", "Raw", alt.Count); + return _fundamental + .Where(c => alt.Contains(c.Symbol)) + .OrderByDescending(c => c.DollarVolume) + .Select(c => c.Symbol) + .Take(100); }); // Rebalance before market open to trade today's intersection. @@ -44,12 +48,12 @@ where d.BuybackPercentage > 0.005m && d.USDMarketCap > 100000000m private void Rebalance() { - if (_universe.Selected == null || _universe.Selected.Count == 0) + if (_universe.Selected.Count == 0) { return; } - var weight = 1m / _universe.Selected.Count; + var weight = _universe.Selected.Count >= 10 ? 1m / _universe.Selected.Count : 0.1m; var targets = _universe.Selected .Select(symbol => new PortfolioTarget(symbol, weight)) .ToList(); diff --git a/project-templates/python/alternative-data-chain-universe-braincompanyfilinglanguagemetricsuniverseall/main.py b/project-templates/python/alternative-data-chain-universe-braincompanyfilinglanguagemetricsuniverseall/main.py index 5e7299e160..1758ae1e84 100644 --- a/project-templates/python/alternative-data-chain-universe-braincompanyfilinglanguagemetricsuniverseall/main.py +++ b/project-templates/python/alternative-data-chain-universe-braincompanyfilinglanguagemetricsuniverseall/main.py @@ -4,7 +4,8 @@ class BrainCompanyFilingLanguageMetricsChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -12,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: positive sentiment in latest SEC filings, intersected with the fundamental list. + # Second universe: positive sentiment in latest SEC filings, ranked by dollar volume. self._universe = self.add_universe(BrainCompanyFilingLanguageMetricsUniverseAll, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -24,22 +25,25 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[BrainCompanyFilingLanguageMetricsUniverseAll]) -> List[Symbol]: # Keep names with positive sentiment in both the report and MD&A sections. - alt = [d.symbol for d in alt_coarse - if d.report_sentiment and d.report_sentiment.sentiment and d.report_sentiment.sentiment > 0 and - d.management_discussion_analyasis_of_financial_condition_and_results_of_operations and - d.management_discussion_analyasis_of_financial_condition_and_results_of_operations.sentiment and - d.management_discussion_analyasis_of_financial_condition_and_results_of_operations.sentiment > 0] - return [s for s in self._fundamental if s in alt] + alt = {d.symbol for d in alt_coarse + if d.report_sentiment and d.report_sentiment.sentiment and d.report_sentiment.sentiment > 0 + and d.management_discussion_analyasis_of_financial_condition_and_results_of_operations + and d.management_discussion_analyasis_of_financial_condition_and_results_of_operations.sentiment + and d.management_discussion_analyasis_of_financial_condition_and_results_of_operations.sentiment > 0} + self.plot('Universe', 'Raw', len(alt)) + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-chain-universe-brainsentimentindicatoruniverse/main.py b/project-templates/python/alternative-data-chain-universe-brainsentimentindicatoruniverse/main.py index 0e1d188e7d..e9fdb230eb 100644 --- a/project-templates/python/alternative-data-chain-universe-brainsentimentindicatoruniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-brainsentimentindicatoruniverse/main.py @@ -4,7 +4,8 @@ class BrainSentimentIndicatorChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -12,11 +13,11 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # Add a fundamental universe to track the most liquid US Equities by dollar volume. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Add a Brain Sentiment universe, restricted to high-sentiment names within the fundamental list. + # Second universe: positive 7-day Brain sentiment, ranked by dollar volume. self._universe = self.add_universe(BrainSentimentIndicatorUniverse, self._select_assets) - # Rebalance before market open. + # Rebalance before market open to trade today's intersection. self.schedule.on( self.date_rules.every_day("SPY"), self.time_rules.at(9, 0), @@ -24,22 +25,23 @@ def initialize(self) -> None: ) 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:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[BrainSentimentIndicatorUniverse]) -> List[Symbol]: - # Keep only names with active mention coverage and positive sentiment. - alt = [d.symbol for d in alt_coarse - 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] + # Keep names with both active 7-day mention coverage and positive 7-day sentiment. + alt = {d.symbol for d in alt_coarse + 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} + self.plot('Universe', 'Raw', len(alt)) + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - # Enter the universe equally weighted across all selected assets. - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-chain-universe-brainstockrankinguniverse/main.py b/project-templates/python/alternative-data-chain-universe-brainstockrankinguniverse/main.py index afc7c1982a..ef47895a53 100644 --- a/project-templates/python/alternative-data-chain-universe-brainstockrankinguniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-brainstockrankinguniverse/main.py @@ -5,7 +5,7 @@ class BrainStockRankingChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: positive Brain ML rankings across 2-, 3-, and 5-day horizons, intersected with the fundamental list. + # Second universe: positive Brain ML rankings across 2-, 3-, and 5-day horizons, ranked by dollar volume. self._universe = self.add_universe(BrainStockRankingUniverse, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,21 +25,24 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[BrainStockRankingUniverse]) -> List[Symbol]: # Keep names with consistent positive momentum across all three horizons. - alt = [d.symbol for d in alt_coarse - if d.rank_2_days and d.rank_2_days > 0 and - d.rank_3_days and d.rank_3_days > 0 and - d.rank_5_days and d.rank_5_days > 0] - return [s for s in self._fundamental if s in alt] + alt = {d.symbol for d in alt_coarse + if d.rank_2_days and d.rank_2_days > 0 + and d.rank_3_days and d.rank_3_days > 0 + and d.rank_5_days and d.rank_5_days > 0} + self.plot('Universe', 'Raw', len(alt)) + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-chain-universe-eodhdupcomingdividends/main.py b/project-templates/python/alternative-data-chain-universe-eodhdupcomingdividends/main.py index 218fb5a1a8..dfc173e3d2 100644 --- a/project-templates/python/alternative-data-chain-universe-eodhdupcomingdividends/main.py +++ b/project-templates/python/alternative-data-chain-universe-eodhdupcomingdividends/main.py @@ -5,7 +5,7 @@ class EODHDUpcomingDividendsChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: ex-dividend in the next day with a $0.05+ payout, intersected with the fundamental list. + # Second universe: ex-dividend in the next day with a $0.05+ payout, ranked by dollar volume. self._universe = self.add_universe(EODHDUpcomingDividends, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,20 +25,23 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[EODHDUpcomingDividends]) -> List[Symbol]: # Keep names with a dividend over $0.05 paying within one day. - alt = [d.symbol for d in alt_coarse - if d.dividend_date and d.dividend and - d.dividend_date <= self.time + timedelta(1) and d.dividend > 0.05] - return [s for s in self._fundamental if s in alt] + alt = {d.symbol for d in alt_coarse + if d.dividend_date and d.dividend + and d.dividend_date <= self.time + timedelta(1) and d.dividend > 0.05} + self.plot('Universe', 'Raw', len(alt)) + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-chain-universe-eodhdupcomingearnings/main.py b/project-templates/python/alternative-data-chain-universe-eodhdupcomingearnings/main.py index fead4fc361..87502a4689 100644 --- a/project-templates/python/alternative-data-chain-universe-eodhdupcomingearnings/main.py +++ b/project-templates/python/alternative-data-chain-universe-eodhdupcomingearnings/main.py @@ -5,7 +5,7 @@ class EODHDUpcomingEarningsChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: earnings in the next 3 days with a positive estimate, intersected with the fundamental list. + # Second universe: upcoming earnings with positive estimates, ranked by dollar volume. self._universe = self.add_universe(EODHDUpcomingEarnings, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,20 +25,24 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[EODHDUpcomingEarnings]) -> List[Symbol]: # Keep names with a positive analyst estimate ahead of the report. - alt = [d.symbol for d in alt_coarse + alt = {d.symbol for d in alt_coarse if d.report_date and d.estimate and - d.report_date <= self.time + timedelta(3) and d.estimate > 0] - return [s for s in self._fundamental if s in alt] + d.report_date <= self.time + timedelta(3) and d.estimate > 0} + self.plot('Universe', 'Raw', len(alt)) + # Among the matches, keep the 100 most liquid by dollar volume. + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-chain-universe-eodhdupcomingipos/main.py b/project-templates/python/alternative-data-chain-universe-eodhdupcomingipos/main.py index c12d8e58e4..439f3e43be 100644 --- a/project-templates/python/alternative-data-chain-universe-eodhdupcomingipos/main.py +++ b/project-templates/python/alternative-data-chain-universe-eodhdupcomingipos/main.py @@ -5,7 +5,10 @@ class EODHDUpcomingIPOsChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] + # Map of IPO symbol -> IPO date, captured while the event is upcoming so we can + # trade the name once Morningstar has a few days of fundamentals on it. + _ipo_dates: Dict[Symbol, datetime] = {} def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +16,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: confirmed non-penny upcoming IPOs, intersected with the fundamental list. + # Second universe: trade IPOs one week after listing so fundamentals are populated. self._universe = self.add_universe(EODHDUpcomingIPOs, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,17 +28,27 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[EODHDUpcomingIPOs]) -> List[Symbol]: - # Keep expected/priced IPOs with a confirmed date and a >$1 minimum across the price band. - alt = [d.symbol for d in alt_coarse - if d.ipo_date and d.deal_type in [EODHD.DealType.EXPECTED, EODHD.DealType.PRICED] and - (prices := [x for x in [d.lowest_price, d.highest_price, d.offer_price] if x]) and - min(prices) > 1] - return [s for s in self._fundamental if s in alt] + # Capture expected/priced IPOs with a confirmed date and a >$1 minimum price band. + for d in alt_coarse: + if (d.ipo_date and d.deal_type in [EODHD.DealType.EXPECTED, EODHD.DealType.PRICED] and + (prices := [x for x in [d.lowest_price, d.highest_price, d.offer_price] if x]) and + min(prices) > 1): + self._ipo_dates[d.symbol] = d.ipo_date + # Drop entries whose IPO was more than 30 days ago to keep the dict bounded. + self._ipo_dates = {s: d for s, d in self._ipo_dates.items() + if d > self.time - timedelta(30)} + # Trade IPOs that listed at least 7 days ago, ranked by dollar volume. + alt = {s for s, d in self._ipo_dates.items() + if d <= self.time - timedelta(7)} + self.plot('Universe', 'Raw', len(alt)) + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: diff --git a/project-templates/python/alternative-data-chain-universe-eodhdupcomingsplits/main.py b/project-templates/python/alternative-data-chain-universe-eodhdupcomingsplits/main.py index 38050f38a3..f3cc18ed58 100644 --- a/project-templates/python/alternative-data-chain-universe-eodhdupcomingsplits/main.py +++ b/project-templates/python/alternative-data-chain-universe-eodhdupcomingsplits/main.py @@ -5,7 +5,7 @@ class EODHDUpcomingSplitsChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: forward stock split in the next 3 days, intersected with the fundamental list. + # Second universe: forward stock split in the next 3 days, ranked by dollar volume. self._universe = self.add_universe(EODHDUpcomingSplits, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,20 +25,24 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[EODHDUpcomingSplits]) -> List[Symbol]: # Keep names with a forward split (factor > 1) within 3 days. - alt = [d.symbol for d in alt_coarse + alt = {d.symbol for d in alt_coarse if d.split_date and d.split_factor and - d.split_date <= self.time + timedelta(3) and d.split_factor > 1] - return [s for s in self._fundamental if s in alt] + d.split_date <= self.time + timedelta(3) and d.split_factor > 1} + self.plot('Universe', 'Raw', len(alt)) + # Among the matches, keep the 100 most liquid by dollar volume. + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-chain-universe-quivercnbcsuniverse/main.py b/project-templates/python/alternative-data-chain-universe-quivercnbcsuniverse/main.py index e5cc991dd2..e38c9de9f0 100644 --- a/project-templates/python/alternative-data-chain-universe-quivercnbcsuniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-quivercnbcsuniverse/main.py @@ -5,7 +5,7 @@ class QuiverCNBCsChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: 3+ BUY CNBC opinions, intersected with the fundamental list. + # Second universe: CNBC opinions intersected with fundamentals, ranked by dollar volume. self._universe = self.add_universe(QuiverCNBCsUniverse, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,22 +25,26 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[QuiverCNBCsUniverse]) -> List[Symbol]: - # Group raw CNBC opinions by ticker and keep names with 3+ BUY recommendations. + # Group raw CNBC opinions by ticker and keep names with 2+ BUY recommendations. cnbc_by_symbol: dict[Symbol, list[QuiverCNBCsUniverse]] = {} for d in alt_coarse: cnbc_by_symbol.setdefault(d.symbol, []).append(d) - alt = [s for s, ds in cnbc_by_symbol.items() - if sum(1 for d in ds if d.direction == OrderDirection.BUY) >= 3] - return [s for s in self._fundamental if s in alt] + alt = {s for s, ds in cnbc_by_symbol.items() + if sum(1 for d in ds if d.direction == OrderDirection.BUY) >= 2} + self.plot('Universe', 'Raw', len(alt)) + # Among the matches, keep the 100 most liquid by dollar volume. + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-chain-universe-quivergovernmentcontractuniverse/main.py b/project-templates/python/alternative-data-chain-universe-quivergovernmentcontractuniverse/main.py index 67ea29bbdd..5c9ef63e59 100644 --- a/project-templates/python/alternative-data-chain-universe-quivergovernmentcontractuniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-quivergovernmentcontractuniverse/main.py @@ -5,7 +5,7 @@ class QuiverGovernmentContractChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: 3+ government contracts totalling over $50K, intersected with the fundamental list. + # Second universe: 3+ government contracts totalling over $50K, ranked by dollar volume. self._universe = self.add_universe(QuiverGovernmentContractUniverse, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,8 +25,7 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[QuiverGovernmentContractUniverse]) -> List[Symbol]: @@ -34,9 +33,14 @@ def _select_assets(self, alt_coarse: List[QuiverGovernmentContractUniverse]) -> contracts_by_symbol: dict[Symbol, list[QuiverGovernmentContractUniverse]] = {} for d in alt_coarse: contracts_by_symbol.setdefault(d.symbol, []).append(d) - alt = [s for s, ds in contracts_by_symbol.items() - if len(ds) >= 3 and sum(x.amount or 0 for x in ds) > 50000] - return [s for s in self._fundamental if s in alt] + alt = {s for s, ds in contracts_by_symbol.items() + if len(ds) >= 3 and sum(x.amount or 0 for x in ds) > 50000} + self.plot('Universe', 'Raw', len(alt)) + # Among the matches, keep the 100 most liquid by dollar volume. + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: diff --git a/project-templates/python/alternative-data-chain-universe-quiverinsidertradinguniverse/main.py b/project-templates/python/alternative-data-chain-universe-quiverinsidertradinguniverse/main.py index 165e71f012..bdcc7c453c 100644 --- a/project-templates/python/alternative-data-chain-universe-quiverinsidertradinguniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-quiverinsidertradinguniverse/main.py @@ -5,7 +5,7 @@ class QuiverInsiderTradingChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: 10 largest insider-trading dollar volumes, intersected with the fundamental list. + # Second universe: 10 largest insider-trading dollar volumes, ranked by dollar volume. self._universe = self.add_universe(QuiverInsiderTradingUniverse, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,8 +25,7 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[QuiverInsiderTradingUniverse]) -> List[Symbol]: @@ -36,8 +35,13 @@ def _select_assets(self, alt_coarse: List[QuiverInsiderTradingUniverse]) -> List if not d.price_per_share: continue dollar_volume[d.symbol] = dollar_volume.get(d.symbol, 0) + (d.shares or 0) * d.price_per_share - alt = [s for s, _ in sorted(dollar_volume.items(), key=lambda kv: kv[1])[-10:]] - return [s for s in self._fundamental if s in alt] + alt = {s for s, _ in sorted(dollar_volume.items(), key=lambda kv: kv[1])[-10:]} + self.plot('Universe', 'Raw', len(alt)) + # Among the matches, keep the 100 most liquid by dollar volume. + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: diff --git a/project-templates/python/alternative-data-chain-universe-quiverlobbyinguniverse/main.py b/project-templates/python/alternative-data-chain-universe-quiverlobbyinguniverse/main.py index c441c99aa9..9c6291ccd3 100644 --- a/project-templates/python/alternative-data-chain-universe-quiverlobbyinguniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-quiverlobbyinguniverse/main.py @@ -5,7 +5,7 @@ class QuiverLobbyingChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: $100K+ corporate lobbying spend, intersected with the fundamental list. + # Second universe: $100K+ corporate lobbying spend, ranked by dollar volume. self._universe = self.add_universe(QuiverLobbyingUniverse, "QuiverLobbyingUniverse", Resolution.DAILY, self._select_assets) # Rebalance before market open to trade today's intersection. @@ -26,8 +26,7 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[QuiverLobbyingUniverse]) -> List[Symbol]: @@ -35,12 +34,17 @@ def _select_assets(self, alt_coarse: List[QuiverLobbyingUniverse]) -> List[Symbo spend_by_symbol: dict[Symbol, float] = {} for d in alt_coarse: spend_by_symbol[d.symbol] = spend_by_symbol.get(d.symbol, 0) + (d.amount or 0) - alt = [s for s, v in spend_by_symbol.items() if v >= 100000] - return [s for s in self._fundamental if s in alt] + alt = {s for s, v in spend_by_symbol.items() if v >= 100000} + self.plot('Universe', 'Raw', len(alt)) + # Among the matches, keep the 100 most liquid by dollar volume. + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-chain-universe-quiverquantcongressuniverse/main.py b/project-templates/python/alternative-data-chain-universe-quiverquantcongressuniverse/main.py index ab8f41b25f..95f00b6678 100644 --- a/project-templates/python/alternative-data-chain-universe-quiverquantcongressuniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-quiverquantcongressuniverse/main.py @@ -5,7 +5,7 @@ class QuiverQuantCongressChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: US Congress BUY disclosures over $200K, intersected with the fundamental list. + # Second universe: US Congress BUY disclosures over $200K, ranked by dollar volume. self._universe = self.add_universe(QuiverQuantCongressUniverse, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,16 +25,20 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[QuiverQuantCongressUniverse]) -> List[Symbol]: # Keep buy disclosures over $200K to filter out small reports. - alt = [d.symbol for d in alt_coarse + alt = {d.symbol for d in alt_coarse if d.amount and d.amount > 200000 and - d.transaction == OrderDirection.BUY] - return [s for s in self._fundamental if s in alt] + d.transaction == OrderDirection.BUY} + self.plot('Universe', 'Raw', len(alt)) + # Among the matches, keep the 100 most liquid by dollar volume. + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: diff --git a/project-templates/python/alternative-data-chain-universe-smartinsiderintentionuniverse/main.py b/project-templates/python/alternative-data-chain-universe-smartinsiderintentionuniverse/main.py index d2dbfb13ae..f981041b68 100644 --- a/project-templates/python/alternative-data-chain-universe-smartinsiderintentionuniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-smartinsiderintentionuniverse/main.py @@ -5,7 +5,7 @@ class SmartInsiderIntentionChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: $100M+ market-cap buyback intentions over 0.5%, intersected with the fundamental list. + # Second universe: $100M+ market-cap buyback intentions over 0.5%, ranked by dollar volume. self._universe = self.add_universe(SmartInsiderIntentionUniverse, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,20 +25,24 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[SmartInsiderIntentionUniverse]) -> List[Symbol]: - # Keep $100M+ market-cap names announcing a buyback over 0.5% of shares. - alt = [d.symbol for d in alt_coarse - if d.percentage and d.usd_market_cap and - d.percentage > 0.005 and d.usd_market_cap > 100000000] - return [s for s in self._fundamental if s in alt] + # Keep names announcing a buyback over 0.5% of shares. (USDMarketCap is not + # populated for intention records, unlike SmartInsiderTransactionUniverse.) + alt = {d.symbol for d in alt_coarse + if d.percentage and d.percentage > 0.005} + self.plot('Universe', 'Raw', len(alt)) + # Among the matches, keep the 100 most liquid by dollar volume. + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True) diff --git a/project-templates/python/alternative-data-chain-universe-smartinsidertransactionuniverse/main.py b/project-templates/python/alternative-data-chain-universe-smartinsidertransactionuniverse/main.py index 8c70626968..e2575d9623 100644 --- a/project-templates/python/alternative-data-chain-universe-smartinsidertransactionuniverse/main.py +++ b/project-templates/python/alternative-data-chain-universe-smartinsidertransactionuniverse/main.py @@ -5,7 +5,7 @@ class SmartInsiderTransactionChainedUniverseAlgorithm(QCAlgorithm): - _fundamental: list[Symbol] = [] + _fundamental: List[Fundamental] = [] def initialize(self) -> None: self.set_start_date(2024, 9, 1) @@ -13,9 +13,9 @@ def initialize(self) -> None: self.set_cash(100_000) self.settings.seed_initial_prices = True self.universe_settings.resolution = Resolution.MINUTE - # First universe: top 100 US Equities by dollar volume; emits Universe.UNCHANGED. + # First universe: store all US Equity fundamentals; emits Universe.UNCHANGED. self.add_universe(self._fundamental_filter) - # Second universe: $100M+ market-cap buybacks over 0.5%, intersected with the fundamental list. + # Second universe: $100M+ market-cap buybacks over 0.5%, ranked by dollar volume. self._universe = self.add_universe(SmartInsiderTransactionUniverse, self._select_assets) # Rebalance before market open to trade today's intersection. self.schedule.on( @@ -25,20 +25,24 @@ def initialize(self) -> None: ) def _fundamental_filter(self, fundamental: List[Fundamental]) -> Universe.UnchangedUniverse: - sorted_by_dollar_volume = sorted(fundamental, key=lambda x: x.dollar_volume) - self._fundamental = [c.symbol for c in sorted_by_dollar_volume[-100:]] + self._fundamental = fundamental return Universe.UNCHANGED def _select_assets(self, alt_coarse: List[SmartInsiderTransactionUniverse]) -> List[Symbol]: # Keep $100M+ market-cap names buying back over 0.5% of shares. - alt = [d.symbol for d in alt_coarse + alt = {d.symbol for d in alt_coarse if d.buyback_percentage and d.usd_market_cap and - d.buyback_percentage > 0.005 and d.usd_market_cap > 100000000] - return [s for s in self._fundamental if s in alt] + d.buyback_percentage > 0.005 and d.usd_market_cap > 100000000} + self.plot('Universe', 'Raw', len(alt)) + # Among the matches, keep the 100 most liquid by dollar volume. + return [c.symbol for c in sorted( + [c for c in self._fundamental if c.symbol in alt], + key=lambda c: c.dollar_volume, reverse=True + )[:100]] def _rebalance(self) -> None: if not self._universe.selected: return - weight = 1 / len(self._universe.selected) + weight = min(1 / len(self._universe.selected), 0.1) targets = [PortfolioTarget(symbol, weight) for symbol in self._universe.selected] self.set_holdings(targets, True)