fix: multi-ticker intraday — DB storage + DB-first fetch logic

Same get_or_fetch_multi_bars() approach as daily bars:
- stores intraday rows in AlpacaPriceData (full timestamp as PK component)
- subsequent requests for historical periods served from DB
- same-day requests during market hours always hit Alpaca (max_date < end_dt)
- force_refresh=true bypasses DB check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
I Luk Kim 4 months ago
parent 2081611884
commit 4edb0a244a

@ -223,14 +223,14 @@ async def get_alpaca_price_data(
@router.get(
"/intraday",
response_model=AlpacaMultiBarsResponse,
summary="Get intraday bars for multiple tickers via Alpaca",
summary="Get intraday bars for multiple tickers via Alpaca (DB-backed)",
description=(
"Fetch intraday OHLCV bars for up to ~500 tickers in one call.\n\n"
"Fetch intraday OHLCV bars for up to ~500 tickers. Results are stored in DB "
"so subsequent calls for the same period skip Alpaca.\n\n"
"- `tickers`: comma-separated list, e.g. `AAPL,MSFT,BF-B`\n"
"- `interval`: `1m`, `5m`, `15m`, `30m`, `1h` (also accepts `5min`, `15min`, etc.)\n"
"- Ticker normalization: `BF-B` → `BF.B` handled automatically; "
"response keys use the original symbol names.\n"
"- **No cache** — always fetches from Alpaca.\n"
"- Ticker normalization: `BF-B` → `BF.B` handled automatically.\n"
"- During market hours, same-day requests always fetch from Alpaca (new bars forming).\n"
"- Requires `ALPACA_API_KEY` / `ALPACA_SECRET_KEY`."
),
)
@ -239,57 +239,47 @@ async def get_alpaca_intraday_multi(
interval: str = Query("5m", description="Interval: 1m, 5m, 15m, 30m, 1h (or 5min, 15min, etc.)"),
start_date: Optional[date] = Query(None, description="Start date (YYYY-MM-DD)"),
end_date: Optional[date] = Query(None, description="End date (YYYY-MM-DD)"),
limit: int = Query(10000, ge=1, le=50000, description="Max bars per symbol"),
force_refresh: bool = Query(False, description="Re-fetch from Alpaca even if DB has data"),
db: AsyncSession = Depends(get_db),
):
"""Multi-ticker intraday bars via Alpaca (ORB engine interface)."""
"""Multi-ticker intraday bars via Alpaca with DB storage (ORB engine interface)."""
symbols = [s.strip().upper() for s in tickers.split(",") if s.strip()]
if not symbols:
raise HTTPException(status_code=400, detail="No tickers provided.")
if len(symbols) > 1000:
raise HTTPException(status_code=400, detail="Maximum 1000 tickers per request.")
client = AlpacaClient()
if not client.is_configured():
raise HTTPException(status_code=503, detail="Alpaca API keys not configured.")
# Build reverse-map: normalized_alpaca_symbol → original_input_symbol
reverse_map = {normalize_ticker(s): s for s in symbols}
svc = _require_alpaca()
start_str = start_date.isoformat() if start_date else None
end_str = end_date.isoformat() if end_date else None
# Default date range: today
_start = start_date or date.today()
_end = end_date or date.today()
start_dt = datetime.combine(_start, datetime.min.time()).replace(tzinfo=timezone.utc)
end_dt = datetime.combine(_end, datetime.max.time()).replace(tzinfo=timezone.utc)
try:
raw = await client.get_multi_bars(
symbols=symbols,
timeframe=interval,
start=start_str,
end=end_str,
limit=min(limit, 10000),
data = await svc.get_or_fetch_multi_bars(
db, symbols, start_dt, end_dt, interval, force_refresh
)
except Exception as e:
raise HTTPException(status_code=502, detail=f"Alpaca API error: {e}")
finally:
await client.close()
await svc.client.close()
# Re-key results back to original input symbols and reshape bar dicts
bars: dict = {}
for alpaca_sym, bar_list in raw.items():
original_sym = reverse_map.get(alpaca_sym, alpaca_sym)
bars[original_sym] = [
bars = {
ticker: [
{
"timestamp": b["t"],
"open": b.get("o"),
"high": b.get("h"),
"low": b.get("l"),
"close": b.get("c"),
"volume": b.get("v"),
"timestamp": row.date.isoformat(),
"open": row.open,
"high": row.high,
"low": row.low,
"close": row.close,
"volume": row.volume,
}
for b in bar_list
for row in rows
]
# Ensure every requested symbol appears in the response (empty list if no data)
for sym in symbols:
bars.setdefault(sym, [])
for ticker, rows in data.items()
}
return AlpacaMultiBarsResponse(
interval=interval,

Loading…
Cancel
Save