From 37757d3d4707c1bd7331dd9e4d4059fc63c82e20 Mon Sep 17 00:00:00 2001 From: momentum dev Date: Fri, 31 Jul 2026 21:41:11 -0500 Subject: [PATCH] fix: armed trigger names now get watcher scoring (blank reasoning panels) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four of ten armed triggers had NO criteria events ever — the trigger book arms from the live gapper scan while the watcher scores only the discovery watchlist, so scanner-only names (TCX/ZTG class) showed an empty reasoning panel with nothing behind it. - arm pass injects every complete armed candidate into the watchlist provider's extra_symbols (its standing manual-add hook) - provider returns injected symbols FIRST: the watcher truncates at max_symbols, so extras appended after a full research list were silently dropped — 'always include manually injected symbols' was untrue exactly when the list was full 60 watcher/watchlist/trigger tests pass. Co-Authored-By: Claude Fable 5 --- research/ingestion/watcher_task.py | 35 +++++++++++++++++------------- run_live_paper.py | 11 ++++++++++ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/research/ingestion/watcher_task.py b/research/ingestion/watcher_task.py index 47d179f..cff6931 100644 --- a/research/ingestion/watcher_task.py +++ b/research/ingestion/watcher_task.py @@ -55,10 +55,29 @@ class ResearchWatchlistProvider: price_max=self.price_max, limit=self.limit, ) - seen = set() + # Injected symbols FIRST: the watcher truncates at max_symbols, so + # extras appended after a full research list were silently dropped — + # exactly the armed-trigger names the reasoning panel needs scored. candidates = [] + seen = set() + for symbol in sorted(self.extra_symbols): + seen.add(symbol) + candidates.append( + WatchCandidate( + symbol=symbol, + previous_close=research_query.query_previous_close( + self.con, symbol, session_date + ), + avg_daily_volume=research_query.query_avg_daily_volume( + self.con, symbol, session_date + ), + source="manual", + ) + ) for row in rows: symbol = row["symbol"] + if symbol in seen: + continue seen.add(symbol) candidates.append( WatchCandidate( @@ -73,20 +92,6 @@ class ResearchWatchlistProvider: source="research", ) ) - # always include manually injected symbols - for symbol in sorted(self.extra_symbols - seen): - candidates.append( - WatchCandidate( - symbol=symbol, - previous_close=research_query.query_previous_close( - self.con, symbol, session_date - ), - avg_daily_volume=research_query.query_avg_daily_volume( - self.con, symbol, session_date - ), - source="manual", - ) - ) return candidates def get_bars(self, symbol: str, session_date: date) -> pd.DataFrame: diff --git a/run_live_paper.py b/run_live_paper.py index c4a097b..a020ef4 100644 --- a/run_live_paper.py +++ b/run_live_paper.py @@ -1016,6 +1016,17 @@ def main(argv: list[str] | None = None) -> int: # catalyst-backed gappers float to the top of the armed set candidates.sort(key=lambda c: c.get("_score", 0.0), reverse=True) book.arm(candidates) + # Armed names must also get watcher scoring: the trigger book arms from + # the live gapper scan, so names discovery never surfaced carried NO + # criteria trail — 2026-08-01 four of ten armed rows had a blank + # reasoning panel. extra_symbols is the provider's standing hook. + try: + _prov = rt["watcher"].provider + for c in candidates: + if c.get("symbol") and c.get("complete"): + _prov.add_symbol(c["symbol"]) + except Exception: # noqa: BLE001 — observability aid, never blocks arming + pass armed = sum(1 for t in book.triggers.values() if t.state == "armed") n_news = sum(1 for c in candidates if c.get("catalyst")) return f"tracking={len(book.triggers)} armed={armed}" + (f" news={n_news}" if n_news else "")