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.