fix: armed trigger names now get watcher scoring (blank reasoning panels)

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 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-07-31 21:41:11 -05:00
1 parent 72a9ecbd0b
commit 37757d3d47
2 files changed
+31 -15

No files matched your search

+20 -15
View File
@@ -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:
+11
View File
@@ -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 "")