fix(chart): 10분봉 페이지네이션, 장외 캐시, 예측 horizon 캡 일치
reviewer 지적사항 반영: 1. KIS 분봉이 한 번에 30개만 와서 10분봉이 최대 3개만 나오던 문제 → fetch_minute_range() 추가. FID_INPUT_HOUR_1 을 30분씩 후퇴시키며 페이지네이션, 중복 ts 자연 dedupe, max_pages=20 으로 무한루프 방지. _ensure_intraday_fresh 는 last_ts+1m ~ now 빈 구간만 채우므로 평소엔 1~2 페이지로 끝남. 2. 장외/주말에 매번 KIS 를 때리던 문제 → - 주말: 'weekend' 반환, KIS 안 부름 (분봉 endpoint 는 당일만 지원) - 평일 장외 + 오늘 데이터 있음: 'cached_closed' 반환 - 장중 + 10분 이내: 'fresh' 반환 토큰/조회 제한 다시 밟지 않음. 3. 프론트 horizon 입력 60 vs 백엔드 30 불일치 → PredictionPanel 의 cap 을 30 으로 맞춤. 백엔드 predict.py 의 학습/검증 범위와 일치. placeholder 도 '1~30' 으로 명시.
This commit is contained in:
@@ -138,32 +138,60 @@ def _intraday_window_today() -> tuple[datetime, datetime]:
|
||||
|
||||
|
||||
def _ensure_intraday_fresh(conn, code: str) -> str:
|
||||
"""마지막 ohlcv_1m 데이터가 10분 이상 오래됐으면 KIS 에서 보충.
|
||||
"""오늘 윈도우의 ohlcv_1m 을 필요한 만큼만 KIS 에서 보충.
|
||||
|
||||
Returns: 'fresh' | 'refreshed' | 'skipped_missing_key' | 'failed' | 'no_data'
|
||||
분기:
|
||||
- 주말: KIS 분봉 endpoint 는 "당일" 만 지원 → 호출하지 않음. 'weekend'.
|
||||
- 장외 (평일 09:00 이전 또는 15:35 이후) + 이미 오늘 데이터 있음: 'cached_closed'.
|
||||
(마감 후엔 데이터 늘지 않으므로 KIS 호출 의미 없음)
|
||||
- 장중 + last_ts 가 10분 이내: 'fresh' (DB 만 읽음)
|
||||
- 그 외 (장중 stale / 장 막 끝나서 마지막 마감 데이터 1회 필요 / 캐시 비어있음):
|
||||
last_ts+1m ~ now 사이의 빈 구간을 fetch_minute_range 로 페이지네이션 채움.
|
||||
DB 캐시가 그날 데이터를 이미 갖고 있으면 자연히 호출 1~2 페이지로 끝.
|
||||
|
||||
Returns: 'fresh' | 'refreshed' | 'cached_closed' | 'weekend' |
|
||||
'skipped_missing_key' | 'failed' | 'no_data'
|
||||
"""
|
||||
last_ts = conn.execute(
|
||||
text("SELECT MAX(ts) FROM ohlcv_1m WHERE code = :c"),
|
||||
{"c": code},
|
||||
).scalar()
|
||||
now = datetime.now(KST)
|
||||
# 평일 장중 (09:00~15:30) 이 아니면 데이터가 더 들어올 일이 없으니 마지막 캐시 그대로.
|
||||
if now.weekday() >= 5:
|
||||
return "weekend"
|
||||
|
||||
win_start, win_end = _intraday_window_today()
|
||||
last_ts = conn.execute(
|
||||
text(
|
||||
"SELECT MAX(ts) FROM ohlcv_1m WHERE code = :c AND ts >= :s AND ts < :e"
|
||||
),
|
||||
{"c": code, "s": win_start, "e": win_end},
|
||||
).scalar()
|
||||
|
||||
market_open = dtime(9, 0)
|
||||
market_close = dtime(15, 35)
|
||||
in_session = (
|
||||
now.weekday() < 5
|
||||
and market_open <= now.time() <= market_close
|
||||
)
|
||||
if last_ts is not None and (now - last_ts) < timedelta(minutes=10) and in_session:
|
||||
market_close_buffer = dtime(15, 35)
|
||||
in_session = market_open <= now.time() <= market_close_buffer
|
||||
|
||||
# 장외이고 이미 오늘 데이터 있음 → 추가 호출 불필요
|
||||
if not in_session and last_ts is not None:
|
||||
return "cached_closed"
|
||||
|
||||
# 장중 + 10분 이내 갱신 → 추가 호출 불필요
|
||||
if in_session and last_ts is not None and (now - last_ts) < timedelta(minutes=10):
|
||||
return "fresh"
|
||||
|
||||
# fetch 윈도우 = [last_ts+1m or win_start, min(now, win_end)]
|
||||
fetch_to = min(now, win_end)
|
||||
if last_ts is not None and last_ts >= win_start:
|
||||
fetch_from = last_ts + timedelta(minutes=1)
|
||||
else:
|
||||
fetch_from = win_start
|
||||
if fetch_from >= fetch_to:
|
||||
return "fresh"
|
||||
|
||||
try:
|
||||
from app.fetch.kis import SkippedMissingKey, fetch_minute_price
|
||||
from app.fetch.kis import SkippedMissingKey, fetch_minute_range
|
||||
except Exception: # noqa: BLE001
|
||||
return "failed"
|
||||
|
||||
try:
|
||||
rows = fetch_minute_price(code)
|
||||
rows = fetch_minute_range(code, fetch_from, fetch_to)
|
||||
except SkippedMissingKey:
|
||||
return "skipped_missing_key"
|
||||
except Exception: # noqa: BLE001
|
||||
|
||||
Reference in New Issue
Block a user