Free yfinance Ticker.news (keyword lexicon proxy for FinBERT) on a 20-name S&P pilot. Signal = average headline sentiment per symbol per day (title+summary), cross-sectional top decile long, hold 5 trading days, equal-weight, net 10bps next_open (validation/framework.py:118, lag>=1).
Fetched 200 headlines (2026-06-19 .. 2026-08-29) across 20 symbols; signal panel 8 symbols, 23 daily cells.
| Variant | Window | Ret (cum) | Sharpe(252) | MaxDD | p | CorrMkt | CorrEqMom | ResidSharpe |
|---|---|---|---|---|---|---|---|---|
| News top decile (hold5, keyword) | recent 90d | +0.00% | 0.00 | 0.00% | 1.000 | nan | nan | 0.00 |
| News top decile (hold5, keyword) | full | +0.00% | 0.00 | 0.00% | 1.000 | nan | nan | 0.00 |
| EqMom baseline (hold21) | full | +3165.97% | 0.86 | -54.71% | 0.000 | 1.00 | 1.00 | 0.00 |
Verdict (pilot, n is tiny — not significant by construction)
- News top decile (hold5, keyword) recent 90d: REJECT (p=1.000, Sharpe 0.00, corrEqMom nan, resid 0.00) — pilot sample too short for free yfinance history; pipeline works but edge is unverified.
Limitations & upgrade path
-
Depth: yfinance returns ~10 headlines per ticker, covering days-weeks, not years. So a long IS/OOS (2016) backtest is impossible free — the 90d recent window above is the honest pilot window. A real test needs a paid archive (RavenPack, Benzinga, Alpaca News
newshistory withas_oflag). -
Sentiment: keyword lexicon is a noisy FinBERT proxy. Production upgrade:
pip install transformersthenpipeline('sentiment-analysis', model='ProsusAI/finbert', tokenizer='ProsusAI/finbert')— sametitle+summaryinput, outputlabelin {positive,negative,neutral} with score; map to +score/-score. The rest of the pipeline (daily panel ->cross_section_portfolio_net) is unchanged. -
Timing: headlines are intraday (pubDate has hour). This pilot buckets by calendar date and uses
lag1(hold5withshift(1)), so a headline at 10am on D is tradable at D+1 open — no look-ahead. A true intraday news bot would need a minutely loop likeproduction/live_runner_alt.py:30(00:15 UTC daily is too slow for headline alpha which decays in minutes). -
Mic check: see
news['title']sample below for sanity (first 5 scored headlines):- 2026-08-28 MMM: score +0.00 — Carlisle (CSL) Down 2.5% Since Last Earnings Report: Can It Rebound?
- 2026-08-28 MMM: score +0.00 — 2 Cash-Producing Stocks for Long-Term Investors and 1 We Question
- 2026-08-27 MMM: score +1.00 — Will 3M Be Able to Sustain Margin Momentum Amid Cost Pressures?
- 2026-08-27 MMM: score +0.00 — What’s Going On With 3M (MMM)?
- 2026-08-26 MMM: score +0.00 — Can Honeywell Technologies Boost Margin Performance Amid Cost Pressures?
Next step if pilot shows promise
- Subscribe to Alpaca News API (or Benzinga) for historical headlines with
as_oflag, 2) swap keyword scorer for FinBERT, 3) promote to an intradayproduction/news_sleeve.py(minutely loop, own.env.newsaccount, fail-closed likepead_sleeve.py:1).