fix(시세): LS spill-only·토큰 파일캐시·호가 snap_time 3초컷

- _feed_fallback 미러 OFF, LS cap/grace/hold RAM을 KIS·키움 spill과 정합
- LS 접근토큰 .ls_token_cache_*.json (재시작 재사용, revoke 루프 없음)
- 호가 RAM을 틱과 동일 LIVE_FEED_FALLBACK(snap_time)로 컷, 필터 max_age=0은 유지
- 익절 지정가 로그에 실제 호가 벤더(kis/kiwoom/ls 1·2·3차) 표기

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Your Name
2026-08-27 15:21:21 +09:00
parent ba2f9f0953
commit 5e44b86f8b
24 changed files with 1923 additions and 203 deletions

View File

@@ -58,6 +58,8 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Set
logger = logging.getLogger("KiwoomWebSocket")
from .ws_reconnect_backoff import ws_reconnect_delay_for_attempt
# ── 환경변수 헬퍼 (KIS WS 와 동일 패턴, fallback 포함) ────────────────────
try:
@@ -130,9 +132,7 @@ class KiwoomWebSocketPriceCache:
SUB_TYPE_ORDERBOOK = "0D" # 주식호가잔량
SUB_TYPE_PROGRAM = "0w" # 종목프로그램매매
# 재연결 정책
RECONNECT_BASE_DELAY_SEC = 5.0
RECONNECT_MAX_DELAY_SEC = 300.0
# 재연결 sleep — ws_reconnect_backoff (기본 1,3,5,7,10초)
MAX_RECONNECTS_PER_HOUR = 6
MAX_RECONNECT_ATTEMPTS = 10
STABLE_CONN_RESET_SEC = 300.0 # 5분 안정 연결 후 끊기면 카운터 초기화
@@ -194,7 +194,6 @@ class KiwoomWebSocketPriceCache:
# 재연결 추적
self._reconnect_count = 0
self._reconnect_times: list = []
self._reconnect_delay = self.RECONNECT_BASE_DELAY_SEC
self._last_connect_time: float = 0.0
# REG 레이트리밋 회피: 단건 subscribe 는 디바운스 후 묶어 전송
@@ -683,7 +682,6 @@ class KiwoomWebSocketPriceCache:
self._authenticated = True
# 안정 LOGIN 성공 → 재연결 카운터 리셋 (주말 Bye 후 소모분 복구)
self._reconnect_count = 0
self._reconnect_delay = self.RECONNECT_BASE_DELAY_SEC
logger.info("✅ 키움 WS LOGIN OK")
# 디바운스 타이머 취소 — LOGIN 직후 일괄 REG 와 이중 전송 방지
with self._reg_timer_lock:
@@ -756,7 +754,7 @@ class KiwoomWebSocketPriceCache:
elif sub_type == self.SUB_TYPE_ORDERBOOK and self._orderbook_cache:
try:
snap = self._orderbook_cache.update_from_kiwoom_0d(code, values)
if self._trigger_snapshot_recorder is not None:
if snap is not None and self._trigger_snapshot_recorder is not None:
tt = str(values.get("20") or values.get(self.FID_TICK_TIME) or "").strip()
self._trigger_snapshot_recorder.on_orderbook(snap, snap_time=tt or None)
except Exception as ex:
@@ -1075,7 +1073,6 @@ class KiwoomWebSocketPriceCache:
if (self._last_connect_time > 0
and now - self._last_connect_time >= self.STABLE_CONN_RESET_SEC):
self._reconnect_count = 0
self._reconnect_delay = self.RECONNECT_BASE_DELAY_SEC
# 시간당 한도 초과 → 1시간 대기
cutoff = now - 3600
@@ -1105,14 +1102,13 @@ class KiwoomWebSocketPriceCache:
self._invalidate_token_cache("max reconnect cooldown")
time.sleep(cool)
self._reconnect_count = 0
self._reconnect_delay = self.RECONNECT_BASE_DELAY_SEC
self._reconnect_times.clear()
return
delay = min(self._reconnect_delay, self.RECONNECT_MAX_DELAY_SEC)
attempt = self._reconnect_count + 1
delay = ws_reconnect_delay_for_attempt(attempt)
logger.info("⏳ 키움 WS %ds 후 재연결 시도 (#%d)",
int(delay), self._reconnect_count + 1)
int(delay), attempt)
time.sleep(delay)
self._reconnect_times.append(time.time())
self._reconnect_count += 1
self._reconnect_delay = min(self._reconnect_delay * 2, self.RECONNECT_MAX_DELAY_SEC)