feat: intraday hourly price bars via Polygon range endpoint
- New 'intraday_bars' endpoint in PolygonMarketAdapter: fetches hourly bars for today using range_bars URL with timespan=hour, sort=asc - Scheduler expands intraday_bars global source into per-ticker jobs for all active companies (every 15 minutes via polling_interval) - Migration 025 inserts the intraday source with 900s cadence - Frontend price matching uses closest-timestamp instead of date-string matching, with 2h tolerance for intraday and 36h for daily windows - Bumped market price fetch limit to 200 for intraday granularity
This commit is contained in:
@@ -44,6 +44,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}"
|
||||
INTRADAY_BARS = "/v2/aggs/ticker/{ticker}/range/{multiplier}/{timespan}/{from_date}/{to_date}"
|
||||
TICKER_DETAILS = "/v3/reference/tickers/{ticker}"
|
||||
|
||||
def __init__(self, api_key: str, base_url: str = "https://api.polygon.io") -> None:
|
||||
@@ -133,6 +134,22 @@ class PolygonMarketAdapter(MarketDataAdapter):
|
||||
params["sort"] = config["sort"]
|
||||
if config.get("limit"):
|
||||
params["limit"] = str(config["limit"])
|
||||
elif endpoint_key == "intraday_bars":
|
||||
# Intraday: fetch hourly bars for today
|
||||
from datetime import date as date_cls
|
||||
today = date_cls.today().isoformat()
|
||||
multiplier = str(config.get("multiplier", 1))
|
||||
timespan = config.get("timespan", "hour")
|
||||
path = self.INTRADAY_BARS.format(
|
||||
ticker=ticker,
|
||||
multiplier=multiplier,
|
||||
timespan=timespan,
|
||||
from_date=today,
|
||||
to_date=today,
|
||||
)
|
||||
params["adjusted"] = str(config.get("adjusted", True)).lower()
|
||||
params["sort"] = "asc"
|
||||
params["limit"] = str(config.get("limit", 50))
|
||||
elif endpoint_key == "grouped_daily":
|
||||
# Grouped daily: returns bars for ALL tickers for a given date
|
||||
target_date = config.get("date", "")
|
||||
|
||||
@@ -453,11 +453,22 @@ async def schedule_cycle(pool: asyncpg.Pool, rds: aioredis.Redis) -> int:
|
||||
|
||||
# Build job with ticker="_MARKET" for global sources
|
||||
job = build_job_payload(src, [], now)
|
||||
job["ticker"] = "_MARKET"
|
||||
await rds.rpush(queue_key(QUEUE_INGESTION), json.dumps(job))
|
||||
enqueued += 1
|
||||
|
||||
logger.info("Enqueued grouped daily market data job")
|
||||
if endpoint == "intraday_bars":
|
||||
# Expand intraday source into per-ticker jobs for all active companies
|
||||
tickers = await pool.fetch(
|
||||
"SELECT ticker FROM companies WHERE active = TRUE"
|
||||
)
|
||||
for t_row in tickers:
|
||||
ticker_job = dict(job)
|
||||
ticker_job["ticker"] = t_row["ticker"]
|
||||
await rds.rpush(queue_key(QUEUE_INGESTION), json.dumps(ticker_job))
|
||||
enqueued += 1
|
||||
logger.info("Enqueued %d intraday bar jobs", len(tickers))
|
||||
else:
|
||||
job["ticker"] = "_MARKET"
|
||||
await rds.rpush(queue_key(QUEUE_INGESTION), json.dumps(job))
|
||||
enqueued += 1
|
||||
logger.info("Enqueued grouped daily market data job")
|
||||
|
||||
logger.info(
|
||||
"Cycle complete: enqueued=%d skipped_not_due=%d skipped_rate_limit=%d total_sources=%d",
|
||||
|
||||
Reference in New Issue
Block a user