Alpaca News API ingestion -> raw_news_items (feeds catalysts + Ollama)

The only news source was RSS (slow, regex ticker-scraping). Alpaca exposes
Benzinga news with a stable id + authoritative symbols list. Add it as a second
source landing into the SAME raw_news_items table, so recent_news_map (catalyst
prioritisation) and the Ollama enrichment (PR #2) pick it up with no other changes.

- AlpacaPaperClient.get_news(symbols, limit): GET /v1beta1/news (works on the
  existing data keys; verified live).
- research/ingestion/alpaca_news.py: ingest_alpaca_news lands new items, deduped
  by the Alpaca news id; never raises (news is an enhancement, not a dependency).
- run_live_paper step_news on the scheduler (ALPACA_NEWS_* env, default on, 120s),
  scoped to the tracked universe.

Verified live: passes landed ~34 then deduped; recent_news_map went from RSS-only
to 370 ticker->headline catalysts. tests/unit: 165 passed (+5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Opus 4.8 committed 2026-06-20 11:10:37 -04:00
1 parent ea799f975c
commit e601973de2
5 files changed
+251

No files matched your search

+22
View File
@@ -337,6 +337,28 @@ class AlpacaPaperClient:
)
return payload.get("most_actives") or []
def get_news(
self,
symbols: list[str] | None = None,
limit: int = 50,
start_iso: str | None = None,
sort: str = "desc",
) -> list[dict]:
"""Latest market news (Benzinga). Each item carries a stable ``id`` and an
authoritative ``symbols`` list, so no regex ticker-scraping is needed.
Optional symbol filter (capped at 50 symbols/req); Alpaca caps ``limit`` at 50."""
params: dict = {
"limit": max(1, min(int(limit), 50)),
"sort": sort,
"exclude_contentless": "true",
}
if symbols:
params["symbols"] = ",".join(symbols[:50])
if start_iso:
params["start"] = start_iso
payload = self._data("GET", "/v1beta1/news", params=params)
return payload.get("news") or []
def get_daily_bars(
self, symbols: list[str], start_iso: str, end_iso: str | None = None
) -> dict[str, list[dict]]: