Files
quantconnect--lean/Algorithm.Python/ETFConstituentUniverseCompositeDelistingRegressionAlgorithm.py
T
Martin-Molinero de10a1d669
Benchmarks / build (push) Has been cancelled
Build & Test Lean / build (push) Has been cancelled
Regression Tests / build (push) Has been cancelled
Research Regression Tests / build (push) Has been cancelled
Python Virtual Environments / build (push) Has been cancelled
Remove securities from algorithm collection (#7573)
- Once a security has been delisted or no longer selected by any
  universe it will be remove from the main algorithm securities
  collection but kept in the new Complete collection, this improves
  performance in long running backtests where security collection can
  get enumerated in different cases
- Minor refactor for security exchange local time to avoid having to set
  it explicitly
2023-11-15 17:09:43 -03:00

69 lines
3.3 KiB
Python

# 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.
from AlgorithmImports import *
### <summary>
### Tests the delisting of the composite Symbol (ETF symbol) and the removal of
### the universe and the symbol from the algorithm.
### </summary>
class ETFConstituentUniverseCompositeDelistingRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2020, 12, 1)
self.SetEndDate(2021, 1, 31)
self.SetCash(100000)
self.universeSymbolCount = 0
self.universeAdded = False
self.universeRemoved = False
self.UniverseSettings.Resolution = Resolution.Hour
self.delistingDate = date(2021, 1, 21)
self.aapl = self.AddEquity("AAPL", Resolution.Hour).Symbol
self.gdvd = self.AddEquity("GDVD", Resolution.Hour).Symbol
self.AddUniverse(self.Universe.ETF(self.gdvd, self.UniverseSettings, self.FilterETFs))
def FilterETFs(self, constituents):
if self.UtcTime.date() > self.delistingDate:
raise Exception(f"Performing constituent universe selection on {self.UtcTime.strftime('%Y-%m-%d %H:%M:%S.%f')} after composite ETF has been delisted")
constituentSymbols = [i.Symbol for i in constituents]
self.universeSymbolCount = len(constituentSymbols)
return constituentSymbols
def OnData(self, data):
if self.UtcTime.date() > self.delistingDate and any([i != self.aapl for i in data.Keys]):
raise Exception("Received unexpected slice in OnData(...) after universe was deselected")
if not self.Portfolio.Invested:
self.SetHoldings(self.aapl, 0.5)
def OnSecuritiesChanged(self, changes):
if len(changes.AddedSecurities) != 0 and self.UtcTime.date() > self.delistingDate:
raise Exception("New securities added after ETF constituents were delisted")
self.universeAdded = self.universeAdded or len(changes.AddedSecurities) >= self.universeSymbolCount
# Subtract 1 from universe Symbol count for AAPL, since it was manually added to the algorithm
self.universeRemoved = self.universeRemoved or (len(changes.RemovedSecurities) == self.universeSymbolCount and self.UtcTime.date() >= self.delistingDate and self.UtcTime.date() < self.EndDate.date())
def OnEndOfAlgorithm(self):
if not self.universeAdded:
raise Exception("ETF constituent universe was never added to the algorithm")
if not self.universeRemoved:
raise Exception("ETF constituent universe was not removed from the algorithm after delisting")
if len(self.ActiveSecurities) > 2:
raise Exception(f"Expected less than 2 securities after algorithm ended, found {len(self.Securities)}")