trader.news.merged_news module

One NewsProvider over two feeds.

The two feeds are disjoint corpora, not substitutes. Measured 2026-08-14: Alpaca’s news is 100% Benzinga and shared 0 of 10 headlines with yfinance on every symbol probed over the same 30-day window, and inside the 72-hour window that actually reaches the prompt Alpaca was thinner than yfinance for every small cap this app trades (ANNX 3 vs 4, ITRG 1 vs 3, QUBT 0 vs 2, IONQ 0 vs 10). A straight swap to Alpaca would therefore have cut fresh news for exactly the names where news drives the decision, pushing them into the strategy’s no-fresh-news skip — a change to the trading path disguised as a data-source change. Merging is what keeps both corpora.

Three properties are load-bearing:

  • A per-feed quota, or the chattier feed takes every slot. yfinance returns far more items per hour for large caps, so Benzinga’s would never appear.

  • A total order with a tiebreak. news_fingerprint digests the ordered set, so an unstable sort would change the fingerprint every cycle and defeat the reuse gate whose entire job is not re-asking the model a question it has already answered.

  • Failure isolation. One feed down must cost half the corpus, not the cycle. Both down must NOT read as “no news”: the strategy’s no-fresh-news skip would then quietly stop entering on an outage.

class trader.news.merged_news.MergedNewsProvider(primary, secondary, *, freshness_hours=72.0)[source]

Bases: object

Interleaves two feeds under the one-method NewsProvider Protocol.

primary is the feed whose items are preferred when the same story reaches both — in production yfinance, which carried a summary on 10 of 10 items for all eight symbols probed where Alpaca carried none on 24.

Parameters:
get_recent_news(symbol, limit=10)[source]

Most recent items first, capped at limit, drawn from both feeds.

Raises:

MarketDataErrorboth feeds failed. One failing is logged and its half dropped.

Parameters:
  • symbol (str)

  • limit (int)

Return type:

list[NewsItem]