feat: /alpaca/intraday — 장 마감(16:00 ET) 후 당일 SIP 조회 허용

기존: end_date >= today 이면 무조건 400
변경: 장 마감(오후 4시 ET) 후에는 당일 날짜도 SIP로 조회 가능

- zoneinfo.ZoneInfo("America/New_York") 기반 ET 시각 체크
- 장 중 당일 요청 시 /intraday/today 안내 메시지

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

@ -4,10 +4,21 @@ Alpaca Market Data endpoints — standalone price data via Alpaca API
from datetime import date, datetime, timezone, timedelta from datetime import date, datetime, timezone, timedelta
from typing import Optional from typing import Optional
from zoneinfo import ZoneInfo
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
_ET = ZoneInfo("America/New_York")
_MARKET_CLOSE_HOUR = 16 # 4:00 PM ET
def _market_closed_for(d: date) -> bool:
"""Return True if the US equity market session for date d has ended."""
now_et = datetime.now(_ET)
market_close_et = datetime(d.year, d.month, d.day, _MARKET_CLOSE_HOUR, 0, tzinfo=_ET)
return now_et >= market_close_et
from app.core.database import get_db from app.core.database import get_db
from app.schemas.financial import ( from app.schemas.financial import (
AlpacaMultiBarsResponse, AlpacaMultiBarsResponse,
@ -62,12 +73,12 @@ async def alpaca_status():
description=( description=(
"멀티 종목 과거 분봉 데이터를 Alpaca **SIP 피드**로 가져옵니다. " "멀티 종목 과거 분봉 데이터를 Alpaca **SIP 피드**로 가져옵니다. "
"DB에 저장되며 재요청 시 Alpaca 미호출.\n\n" "DB에 저장되며 재요청 시 Alpaca 미호출.\n\n"
"**⚠️ 어제(yesterday)까지만 조회 가능** — 당일 데이터는 `/intraday/today` 사용\n\n" "**⚠️ 장 중 당일 데이터 불가** — 장 마감(오후 4시 ET) 후에는 당일 날짜도 조회 가능\n\n"
"| 항목 | 내용 |\n" "| 항목 | 내용 |\n"
"|------|------|\n" "|------|------|\n"
"| 피드 | **SIP** (전체 미국 거래소 통합) |\n" "| 피드 | **SIP** (전체 미국 거래소 통합) |\n"
"| 거래량 | **100%** 정확 |\n" "| 거래량 | **100%** 정확 |\n"
"| 조회 범위 | **2016년~어제** |\n" "| 조회 범위 | **2016년~오늘(장 마감 후)** |\n"
"| DB 저장 | 있음 (재요청 시 Alpaca 미사용) |\n\n" "| DB 저장 | 있음 (재요청 시 Alpaca 미사용) |\n\n"
"**권장 용도**: 백테스트, 과거 분봉 분석\n\n" "**권장 용도**: 백테스트, 과거 분봉 분석\n\n"
"- `tickers`: comma-separated, e.g. `AAPL,MSFT,BF-B`\n" "- `tickers`: comma-separated, e.g. `AAPL,MSFT,BF-B`\n"
@ -84,7 +95,7 @@ async def get_alpaca_intraday_multi(
force_refresh: bool = Query(False, description="Re-fetch from Alpaca even if DB has data"), force_refresh: bool = Query(False, description="Re-fetch from Alpaca even if DB has data"),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
): ):
"""Multi-ticker historical intraday bars via Alpaca SIP (up to yesterday).""" """Multi-ticker historical intraday bars via Alpaca SIP (up to today after market close)."""
symbols = [s.strip().upper() for s in tickers.split(",") if s.strip()] symbols = [s.strip().upper() for s in tickers.split(",") if s.strip()]
if not symbols: if not symbols:
raise HTTPException(status_code=400, detail="No tickers provided.") raise HTTPException(status_code=400, detail="No tickers provided.")
@ -95,11 +106,17 @@ async def get_alpaca_intraday_multi(
_start = start_date or yesterday _start = start_date or yesterday
_end = end_date or yesterday _end = end_date or yesterday
if _end >= date.today(): today = date.today()
if _end > today:
raise HTTPException(
status_code=400,
detail="미래 날짜는 조회할 수 없습니다.",
)
if _end == today and not _market_closed_for(today):
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail="이 엔드포인트는 어제(yesterday)까지의 과거 데이터만 조회 가능합니다. " detail="장 중에는 당일 SIP 데이터를 조회할 수 없습니다. "
"당일 실시간 데이터는 GET /api/v1/alpaca/intraday/today 를 사용하세요.", "실시간 데이터는 GET /api/v1/alpaca/intraday/today 를 사용하세요.",
) )
svc = _require_alpaca() svc = _require_alpaca()

Loading…
Cancel
Save