Hello everyone,
if i change my startdate from self.SetStartDate(2025, 9, 26) to self.SetStartDate(2025, 9, 30) then in the first case my algo calls onsecuritieschanged but not in the second case.
Why? is there no data for after 26th September 2025? I have tried to debug this for days but I can not solve it.
All dates after 26th of September 2025 dont work. I am on the free tier. I have tested this with just SPY as my universe but also a normal universe selection function.
This is my main.py:
# main.py
from AlgorithmImports import *
from universe.models import SmallCapAndAboveUniverse
from data.consolidators import attach_5m_bars
from alpha.patterns import TrendPullbackContinuationPattern
from alpha.signals import emit_signal
from data.indicators import VwapWithPrev
from alpha.detectors import VwapSideFilter
from alpha.filters import VwapSideGate
# ==========================
# CONFIG (single place)
# ==========================
CFG = {
"session": {
"extended_hours": False, # one switch for your whole codebase
},
"universe": {
"min_price": 5.0,
"min_avg_daily_volume": 1_000_000,
"min_market_cap": None,#300e6,
"filter_us_only": True,
"max_coarse": 200,
},
"ha": {
"run_len_default": 7,
},
"vwap_filter": {
"side_n": 2, # consecutive closes threshold
}
}
class TestStockSelectionAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2025, 9, 26)
self.SetEndDate(2025, 10, 20)
self.SetCash(10000)
# One flag used everywhere
self.use_extended_hours = bool(CFG["session"]["extended_hours"])
# Run length: parameter overrides config default
self.ha_run_len = int(self.GetParameter("ha_streak") or CFG["ha"]["run_len_default"])
# Minute data needed because we consolidate 1m -> 5m
self.UniverseSettings.Resolution = Resolution.Minute
Universe model uses config dict
self.universe_model = SmallCapAndAboveUniverse(
**CFG["universe"],
debug=True # Enable debug logging
)
self.universe_model._algo = self
self._universe = self.add_universe(self.universe_model.select_fine)
# Anchor symbol
self.spy = self.AddEquity(
"SPY",
Resolution.Minute,
extendedMarketHours=self.use_extended_hours
).Symbol
# Debug if SPY has data for given dates
start = self.StartDate
end = self.EndDate
hist = self.History(self.spy, 390, Resolution.Minute) # ~1 trading day
self.Debug(f"SPY minute history rows: {len(hist)} at algo time {self.Time}")
# Optional: show a few timestamps to confirm
if len(hist) > 0:
self.Debug(f"SPY minute first index: {hist.index[0]} last index: {hist.index[-1]}")
else:
self.Debug("SPY minute history is EMPTY (check dates, symbol, market, resolution).")
# Per-symbol plumbing
self._patterns = {} # Symbol -> TrendPullbackContinuationPattern
self._consolidators = {} # Symbol -> TradeBarConsolidator
# VWAP plumbing
self._vwap5 = {} # Symbol -> VwapWithPrev (QC IntradayVwap wrapped)
self._vwap_filter = {} # Symbol -> VwapSideFilter
self._signal_gates = [VwapSideGate()]
# Debug snapshots (latest 5m normal-bar context per symbol)
self._last_close_5m = {} # Symbol -> float
self._last_vwap = {} # Symbol -> float (current vwap after update)
self._last_prev_vwap = {} # Symbol -> float (prev vwap used for comparisons)
# Debug tracking
self._bar_count = {} # Symbol -> int (count of 5m bars received)
self._ha_bar_count = {} # Symbol -> int (count of HA bars received)
self.Debug(
f"Initialized (extended hours = {self.use_extended_hours}, ha_run_len = {self.ha_run_len})"
)
self.Debug(f"Subscriptions at init: {[x.Symbol.Value + ':' + str(x.Resolution) for x in self.SubscriptionManager.Subscriptions]}")
self.Debug(f"Universe count at init: {len(self.UniverseManager)}")
def OnSecuritiesChanged(self, changes: SecurityChanges):
self.Debug(f"!!! OnSecuritiesChanged CALLED at {self.Time} !!!")
added_symbols = [s.Symbol.Value for s in changes.AddedSecurities]
removed_symbols = [s.Symbol.Value for s in changes.RemovedSecurities]
self.Debug(f"OnSecuritiesChanged at {self.Time}: Added {len(changes.AddedSecurities)}, Removed {len(changes.RemovedSecurities)}")
# Log first 20 symbols being added
if added_symbols:
self.Debug(f" Adding: {', '.join(added_symbols[:20])}")
if len(added_symbols) > 20:
self.Debug(f" ... and {len(added_symbols) - 20} more")
for sec in changes.AddedSecurities:
symbol = sec.Symbol
# prevent double-attach
if symbol in self._consolidators:
self.Debug(f" WARNING: {symbol.Value} already has consolidator!")
continue
# Ensure minute subscription respects same RTH/Extended switch
self.AddEquity(
symbol.Value,
Resolution.Minute,
extendedMarketHours=self.use_extended_hours
)
# Pattern per symbol
self._patterns[symbol] = TrendPullbackContinuationPattern(run_len=self.ha_run_len)
# VWAP per symbol (QC indicator)
self._vwap5[symbol] = VwapWithPrev(self, symbol)
self._vwap_filter[symbol] = VwapSideFilter(n=CFG["vwap_filter"]["side_n"])
# 5m consolidator -> normal bars (VWAP feed) + HA bars (pattern feed)
cons = attach_5m_bars(
algo=self,
symbol=symbol,
on_tradebar_5m=self.OnTradeBar5m,
on_heikin_ashi_5m=self.OnHeikinAshi5m
)
self._consolidators[symbol] = cons
self.Debug(f" Total tracked symbols now: {len(self._consolidators)}")
for sec in changes.RemovedSecurities:
symbol = sec.Symbol
cons = self._consolidators.pop(symbol, None)
if cons is not None:
self.SubscriptionManager.RemoveConsolidator(symbol, cons)
self._patterns.pop(symbol, None)
self._vwap5.pop(symbol, None)
self._vwap_filter.pop(symbol, None)
self._last_close_5m.pop(symbol, None)
self._last_vwap.pop(symbol, None)
self._last_prev_vwap.pop(symbol, None)
self._bar_count.pop(symbol, None)
self._ha_bar_count.pop(symbol, None)
def OnTradeBar5m(self, symbol: Symbol, bar: TradeBar):
vwap = self._vwap5.get(symbol)
filt = self._vwap_filter.get(symbol)
if vwap is None or filt is None:
return
# Track bar count
self._bar_count[symbol] = self._bar_count.get(symbol, 0) + 1
# Update VWAP (stores PrevValue internally)
vwap.update(bar)
# Snapshot values for logging at HA-signal time
self._last_close_5m[symbol] = float(bar.Close)
self._last_vwap[symbol] = vwap.Value
self._last_prev_vwap[symbol] = vwap.PrevValue
# Need a prior VWAP to compare against (prevents bar influencing itself)
if vwap.PrevValue is None:
return
# Update side filter vs PREV VWAP
filt.update(float(bar.Close), float(vwap.PrevValue))
def OnHeikinAshi5m(self, symbol: Symbol, ha_bar):
pattern = self._patterns.get(symbol)
if pattern is None:
return
# Track HA bar count
self._ha_bar_count[symbol] = self._ha_bar_count.get(symbol, 0) + 1
sig = pattern.update(ha_bar)
if sig is None:
return
# Pull debug snapshot
last_close = self._last_close_5m.get(symbol)
last_vwap = self._last_vwap.get(symbol)
last_prev_vwap = self._last_prev_vwap.get(symbol)
filt = self._vwap_filter.get(symbol)
above = getattr(filt, "above", None)
below = getattr(filt, "below", None)
block_up = filt.block_bullish() if filt else None
block_down = filt.block_bearish() if filt else None
# Apply gates (VWAP side filter)
for gate in self._signal_gates:
if not gate.allow(self, symbol, sig):
self.Debug(
f"{self.Time} {symbol.Value}: BLOCKED "
f"sig={sig.get('direction')} "
f"close5m={last_close} "
f"vwap={last_vwap} prevVwap={last_prev_vwap} "
f"above={above} below={below} "
f"blockUP={block_up} blockDOWN={block_down}"
)
return
# Allowed -> log the same context + emit
self.Debug(
f"{self.Time} {symbol.Value}: ALLOWED "
f"sig={sig.get('direction')} "
f"close5m={last_close} "
f"vwap={last_vwap} prevVwap={last_prev_vwap} "
f"above={above} below={below} "
f"blockUP={block_up} blockDOWN={block_down}"
)
emit_signal(self, symbol, sig)
def OnData(self, data: Slice):
pass
def OnEndOfAlgorithm(self):
self.Debug(f"=== End of Algorithm Summary ===")
self.Debug(f"Total symbols with consolidators: {len(self._consolidators)}")
if self._bar_count:
symbols_with_bars = len(self._bar_count)
total_bars = sum(self._bar_count.values())
self.Debug(f"Symbols that received 5m bars: {symbols_with_bars}")
self.Debug(f"Total 5m bars received: {total_bars}")
# Show top 5 symbols by bar count
top_symbols = sorted(self._bar_count.items(), key=lambda x: x[1], reverse=True)[:5]
self.Debug(f"Top 5 symbols by bar count: {[(s.Value, c) for s, c in top_symbols]}")
else:
self.Debug(f"WARNING: No 5m bars received at all!")
if self._ha_bar_count:
symbols_with_ha = len(self._ha_bar_count)
total_ha = sum(self._ha_bar_count.values())
self.Debug(f"Symbols that received HA bars: {symbols_with_ha}")
self.Debug(f"Total HA bars received: {total_ha}")
else:
self.Debug(f"WARNING: No HA bars received at all!")This is the universe selection function:
from AlgorithmImports import *
from universe.pipeline import apply_filters
from universe import steps
class SmallCapAndAboveUniverse:
def __init__(
self,
min_price: float = 5.0,
min_avg_daily_volume: int = 1_000_000,
min_market_cap: float = 300e6,
filter_us_only: bool = True,
max_coarse: int = None,
debug: bool = False,
):
self.min_price = float(min_price)
self.min_avg_daily_volume = int(min_avg_daily_volume)
self.min_market_cap = None if min_market_cap is None else float(min_market_cap)
self.filter_us_only = bool(filter_us_only)
self.max_coarse = max_coarse
self.debug = debug
self.min_dollar_volume = self.min_price * self.min_avg_daily_volume
def select_fine(self, fine):
"""Modern unified universe selector - combines coarse + fine logic"""
fine_list = list(fine)
if self.debug and hasattr(self, '_algo'):
self._algo.Debug(f"Universe selection: {len(fine_list)} candidates")
# Apply all filters in one pass
selected = []
rejected_reasons = {}
for f in fine_list:
# Coarse-equivalent filters (using fundamental data)
if not f.HasFundamentalData:
rejected_reasons["No fundamental data"] = rejected_reasons.get("No fundamental data", 0) + 1
continue
if f.Price is None or f.Price < self.min_price:
rejected_reasons[f"Price < ${self.min_price}"] = rejected_reasons.get(f"Price < ${self.min_price}", 0) + 1
continue
if f.DollarVolume is None or f.DollarVolume < self.min_dollar_volume:
rejected_reasons["Low dollar volume"] = rejected_reasons.get("Low dollar volume", 0) + 1
continue
# Fine filters (market cap)
if self.min_market_cap is not None:
if f.MarketCap is None or f.MarketCap < self.min_market_cap:
rejected_reasons[f"MarketCap < ${self.min_market_cap:,.0f}"] = rejected_reasons.get(f"MarketCap < ${self.min_market_cap:,.0f}", 0) + 1
continue
# Fine filters (country)
if self.filter_us_only:
cr = f.CompanyReference
if cr is None or cr.CountryId != "USA":
rejected_reasons["Non-USA"] = rejected_reasons.get("Non-USA", 0) + 1
continue
selected.append(f)
# Optional performance cap
capped_count = 0
if self.max_coarse is not None and len(selected) > self.max_coarse:
capped_count = len(selected) - self.max_coarse
selected = sorted(selected, key=lambda f: f.DollarVolume, reverse=True)[:self.max_coarse]
if self.debug and hasattr(self, '_algo'):
total_rejected = sum(rejected_reasons.values())
self._algo.Debug(f"Universe results: {len(selected)} selected, {total_rejected} rejected")
# Show rejection breakdown
for reason, count in list(rejected_reasons.items())[:5]:
self._algo.Debug(f" {reason}: {count}")
if capped_count > 0:
self._algo.Debug(f" Capped: {capped_count} (kept top {self.max_coarse} by dollar volume)")
# Show first 10 selected
for i, f in enumerate(selected[:10]):
self._algo.Debug(f" Selected: {f.Symbol.Value}")
return [f.Symbol for f in selected]I hope someone can help me with this.
The NewQuant
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!