# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. # Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import typing from AlgorithmImports import * constituentData = [] ### ### Alpha model for ETF constituents, where we generate insights based on the weighting ### of the ETF constituent ### class ETFConstituentAlphaModel(AlphaModel): def OnSecuritiesChanged(self, algorithm, changes): pass ### ### Creates new insights based on constituent data and their weighting ### in their respective ETF ### def Update(self, algorithm: QCAlgorithm, data: Slice): insights = [] for constituent in constituentData: if constituent.Symbol not in data.Bars and \ constituent.Symbol not in data.QuoteBars: continue insightDirection = InsightDirection.Up if constituent.Weight is not None and constituent.Weight >= 0.01 else InsightDirection.Down insights.append(Insight( algorithm.UtcTime, constituent.Symbol, timedelta(days=1), InsightType.Price, insightDirection, float(1 * int(insightDirection)), 1.0, weight=float(0 if constituent.Weight is None else constituent.Weight) )) return insights ### ### Generates targets for ETF constituents, which will be set to the weighting ### of the constituent in their respective ETF ### class ETFConstituentPortfolioModel(PortfolioConstructionModel): def __init__(self): self.hasAdded = False ### ### Securities changed, detects if we've got new additions to the universe ### so that we don't try to trade every loop ### def OnSecuritiesChanged(self, algorithm: QCAlgorithm, changes: SecurityChanges): self.hasAdded = len(changes.AddedSecurities) != 0 ### ### Creates portfolio targets based on the insights provided to us by the alpha model. ### Emits portfolio targets setting the quantity to the weight of the constituent ### in its respective ETF. ### def CreateTargets(self, algorithm: QCAlgorithm, insights: typing.List[Insight]): if not self.hasAdded: return [] finalInsights = [] for insight in insights: finalInsights.append(PortfolioTarget(insight.Symbol, float(0 if insight.Weight is None else insight.Weight))) self.hasAdded = False return finalInsights ### ### Executes based on ETF constituent weighting ### class ETFConstituentExecutionModel(ExecutionModel): ### ### Liquidates if constituents have been removed from the universe ### def OnSecuritiesChanged(self, algorithm: QCAlgorithm, changes: SecurityChanges): for change in changes.RemovedSecurities: algorithm.Liquidate(change.Symbol) ### ### Creates orders for constituents that attempts to add ### the weighting of the constituent in our portfolio. The ### resulting algorithm portfolio weight might not be equal ### to the leverage of the ETF (1x, 2x, 3x, etc.) ### def Execute(self, algorithm: QCAlgorithm, targets: typing.List[IPortfolioTarget]): for target in targets: algorithm.SetHoldings(target.Symbol, target.Quantity) ### ### Tests ETF constituents universe selection with the algorithm framework models (Alpha, PortfolioConstruction, Execution) ### class ETFConstituentUniverseFrameworkRegressionAlgorithm(QCAlgorithm): ### ### Initializes the algorithm, setting up the framework classes and ETF constituent universe settings ### def Initialize(self): self.SetStartDate(2020, 12, 1) self.SetEndDate(2021, 1, 31) self.SetCash(100000) self.SetAlpha(ETFConstituentAlphaModel()) self.SetPortfolioConstruction(ETFConstituentPortfolioModel()) self.SetExecution(ETFConstituentExecutionModel()) spy = Symbol.Create("SPY", SecurityType.Equity, Market.USA) self.UniverseSettings.Resolution = Resolution.Hour self.AddUniverse(self.Universe.ETF(spy, self.UniverseSettings, self.FilterETFConstituents)) ### ### Filters ETF constituents ### ### ETF constituents ### ETF constituent Symbols that we want to include in the algorithm def FilterETFConstituents(self, constituents): global constituentData constituentDataLocal = [i for i in constituents if i is not None and i.Weight >= 0.001] constituentData = list(constituentDataLocal) return [i.Symbol for i in constituentDataLocal] ### ### no-op for performance ### def OnData(self, data): pass