Rejected thesis
News-sentiment layer (removed from the model)
Thesis (rejected): A lexicon-scored sentiment read over cached yfinance headlines carries enough signal to justify 16 % of the composite score.
Code: research/news/ (deleted — see git history at a86bb919~1)
Report: none, and that is the finding.
Why it failed: it was never tested, in either direction.
The layer reached DEFAULT_HORIZON_WEIGHTS values of 0.20 / 0.15 / 0.10
(short / medium / long) — second-largest weight at the short horizon,
behind catalyst — without ever facing walk-forward. It could not: the
backtest config pins news to zero because yfinance cannot backdate
headlines, so every weight sweep recorded in this file skipped it by
construction. The coordinate-descent run says so explicitly ("21
non-news cells"), and the marginal sweep's "news shows zero impact"
line is an artifact of exclusion, not a measurement. Every other layer
in the model earned its weight in those sweeps. This one was handed a
weight and exempted from the exam.
research/news_gap_validate.py was the only validator ever written, and
its own docstring opened with SKELETON ONLY — it tested a different
thesis (news gaps continuing vs technical gaps fading) and was blocked
on a catalyst classifier that was never built. No report JSON was ever
committed, which is the tell.
Blast radius before removal (50 tickers with per-layer scores.json,
re-running judgment.judge() with and without the layer):
| Measure | Result |
|---|---|
| Ratings that change | 1 / 50 (2 %) |
| Mean |Δ composite| | 0.053 |
| Spearman rank correlation | 0.977 |
| Top-10 turnover | 0 / 10 |
| Top-20 turnover | 2 / 20 |
Notably the raw layer was the loudest input in the panel — mean |score| 0.629 against fundamental 0.343, technical 0.195, sector 0.092, sentiment 0.040 — and non-zero for 100 % of names. Large magnitude, no effect on ranking: after standardisation and orthogonalisation it was contributing variance, not information.
Verdict: removed 2026-08-06 from LAYERS, both weight presets, the
orchestrator, the nightly refresh phase and the store's artifact kinds.
The remaining weights renormalise in judge(), so the other layers keep
their relative proportions.
Be precise about what this measured: it bounds the cost of removing the layer, not the value of keeping it. Nobody has shown news sentiment fails to predict returns. What was shown is that this implementation of it changed almost nothing, had never been validated, and could not be validated on the current data. If it comes back it needs a dated news source the harness can replay — otherwise it is unfalsifiable by design.
The API endpoint /companies/{symbol}/news stays in RETIRED_ENDPOINTS
and keeps returning 410 with its explanation; it was already retired on
licensing grounds, so removal broke no consumer.
| Validator | research/news/ |
|---|---|
| Verdict | Rejected |
Every result here is reproducible from the script named above. Reports are in the repository.
- Volatility squeeze
- Pre-FOMC drift
- Uptrend pullback
- Momentum continuation
- Industry-relative momentum (at 4-week lookback)
- Earnings-reaction reversal (anti-PEAD)
- Beat-persistence pre-earnings drift
- Sector ETF mean-reversion
- 52-week-high breakout
- Volume accumulation (above-200DMA +...
- Cross-sector momentum
- Analyst-upgrade cluster