feat: add Polygon grouped daily endpoint for broad market data

Two tiers of market data:
1. Per-ticker prev bars (existing 50 sources, 15-min cadence) for
   watchlist detail — trading decisions, stop-loss, position sizing
2. Grouped daily (new single source, once per day) for broad market
   context — correlation analysis, sector rotation, competitive intel

Changes:
- Add grouped_daily endpoint to PolygonMarketAdapter with auto date
  calculation (previous trading day, skip weekends)
- Add fetch_global_market_sources() to scheduler for sources without
  company_id, scheduled once daily (86400s cadence)
- Update _persist_market_items to use item-level ticker from T field
  and look up company_id dynamically for grouped daily bars
- Migration 020: make company_id nullable on sources and
  market_snapshots tables, add grouped daily source row
- Fix backtest replay to query market_snapshots data->>'c' for prices
This commit is contained in:
Celes Renata
2026-04-15 22:38:18 +00:00
parent ea6c2b3f54
commit 4501bbebd4
4 changed files with 133 additions and 4 deletions
+16
View File
@@ -43,6 +43,7 @@ class PolygonMarketAdapter(MarketDataAdapter):
PREV_BARS = "/v2/aggs/ticker/{ticker}/prev"
RANGE_BARS = "/v2/aggs/ticker/{ticker}/range/{multiplier}/{timespan}/{from_date}/{to_date}"
GROUPED_DAILY = "/v2/aggs/grouped/locale/us/market/stocks/{date}"
TICKER_DETAILS = "/v3/reference/tickers/{ticker}"
def __init__(self, api_key: str, base_url: str = "https://api.polygon.io") -> None:
@@ -132,6 +133,20 @@ class PolygonMarketAdapter(MarketDataAdapter):
params["sort"] = config["sort"]
if config.get("limit"):
params["limit"] = str(config["limit"])
elif endpoint_key == "grouped_daily":
# Grouped daily: returns bars for ALL tickers for a given date
target_date = config.get("date", "")
if not target_date:
# Default to previous trading day
from datetime import date, timedelta
today = date.today()
prev = today - timedelta(days=1)
# Skip weekends
while prev.weekday() > 4:
prev -= timedelta(days=1)
target_date = prev.isoformat()
path = self.GROUPED_DAILY.format(date=target_date)
params["adjusted"] = str(config.get("adjusted", True)).lower()
elif endpoint_key == "ticker_details":
path = self.TICKER_DETAILS.format(ticker=ticker)
else:
@@ -149,6 +164,7 @@ class PolygonMarketAdapter(MarketDataAdapter):
return [results] if isinstance(results, dict) and results else []
# Aggregate endpoints return results as a list
# For grouped_daily, each item has a "T" field with the ticker
results = data.get("results", [])
if isinstance(results, list):
return results