Two issues caused the agent to get stuck after the last log message
from a completed scanner (e.g. "✓ reddit_trending: 11 candidates"):
1. `as_completed()` had no global timeout. If a scanner thread blocked
in a non-interruptible I/O call, `as_completed()` waited forever
because it only yields a future once it has finished — the per-future
`future.result(timeout=N)` call was never even reached.
Fixed by passing `timeout=global_timeout` to `as_completed()` so
the outer iterator raises TimeoutError after a capped wall-clock
budget, then logs which scanners didn't complete and continues.
2. `SectorRotationScanner` called `get_ticker_info()` (one HTTP request
per ticker) in a serial loop for up to 100 tickers from a 592-ticker
file, easily exceeding the 30 s per-scanner budget.
Fixed by batch-downloading close prices for all tickers in a single
`download_history()` call, computing 5-day returns locally, and only
calling `get_ticker_info()` for the small subset of laggard tickers
(<2% 5d move) that actually need a sector label.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs were causing the signal card HTML to display as literal text:
1. CommonMark HTML block termination: Streamlit's markdown parser
(CommonMark-compliant) terminates a <div> block at the first blank
line. Empty optional fields (name_html, desc_html, risk_badge_html)
left whitespace-only lines in the f-string template, ending the HTML
block and causing the parser to render subsequent tags as text.
Fixed by building HTML from a parts list and only appending optional
elements when non-empty — no blank lines can appear in output.
2. Unescaped HTML chars in LLM-generated text: reason fields from the
ranker contained raw > and & characters (e.g. '>5% move',
'50 & 200 SMA') that corrupted the HTML structure. Fixed by running
all LLM-generated fields through html.escape() before interpolation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Call get_finviz_insider_buying with return_structured=True and deduplicate=False
to get all raw transaction dicts instead of parsing markdown
- Group transactions by ticker for cluster detection (2+ unique insiders = CRITICAL)
- Smart priority: CEO/CFO + >$100K = CRITICAL, director + >$50K = HIGH, etc.
- Preserve insider_name, insider_title, transaction_value, num_insiders_buying in output
- Rich context strings: "CEO John Smith purchased $250K of AAPL shares"
- Update finviz_scraper alias to pass through return_structured and deduplicate params
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents the approved plan to fix signal quality issues in all 9
existing scanners and add 3 new scanners (analyst upgrades, technical
breakout, sector rotation).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add daily price movement display with color coding (green/red)
- Add 1D (intraday) and 7D chart options with granular data:
- 1D: 5-minute interval for detailed intraday view
- 7D: hourly interval for smooth 7-day chart
- Fix discontinuous chart rendering by plotting against sequential index for intraday data
- Eliminate overnight/weekend gaps in hourly charts
- Add timezone normalization for consistent date handling between daily and intraday data
- Improve fallback logic when data is sparse
- Better handling of yfinance column names (Datetime vs Date)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add GitHub Actions workflow for daily discovery (8:30 AM ET, weekdays)
- Add headless run_daily_discovery.py script for scheduling
- Expand options_flow scanner to use tickers.txt with parallel execution
- Add recommendation history section to Performance page with filters and charts
- Fix strategy name normalization (momentum/Momentum/Momentum-Hype → momentum)
- Fix strategy metrics to count all recs, not just evaluated ones
- Add error handling to Streamlit page rendering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>