fix(ws): KIS/키움 WS·조건검색 안정화 및 매도시세 폴백
WS 매니저 spill·조건검색 CNSR 회복·kiwoom_ws_diag 진단 경로를 보강한다. live_sell_price stale RAM/REST 폴백 정합을 유지한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -93,6 +93,8 @@ except ImportError:
|
||||
ProgramCache = None # type: ignore[misc, assignment]
|
||||
ProgramSnapshot = None # type: ignore[misc, assignment]
|
||||
|
||||
from .kiwoom_ws_diag import log_kiwoom_api_msg, log_kiwoom_ws
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
# 메인 클래스
|
||||
@@ -364,11 +366,25 @@ class KiwoomWebSocketPriceCache:
|
||||
def send_json(self, msg: dict) -> bool:
|
||||
"""인증된 WS 에 JSON 전송 (조건검색 CNSRREQ 등)."""
|
||||
if not self.is_authenticated() or not self._ws:
|
||||
log_kiwoom_ws(
|
||||
logger, "send_skip",
|
||||
level="warning",
|
||||
trnm=str(msg.get("trnm") or ""),
|
||||
extra="미인증 또는 ws=None",
|
||||
force=True,
|
||||
)
|
||||
return False
|
||||
try:
|
||||
self._ws.send(json.dumps(msg))
|
||||
return True
|
||||
except Exception as e:
|
||||
log_kiwoom_ws(
|
||||
logger, "send_fail",
|
||||
level="warning",
|
||||
trnm=str(msg.get("trnm") or ""),
|
||||
extra=str(e),
|
||||
force=True,
|
||||
)
|
||||
logger.debug("키움 WS send_json 실패: %s", e)
|
||||
return False
|
||||
|
||||
@@ -667,6 +683,9 @@ class KiwoomWebSocketPriceCache:
|
||||
|
||||
trnm = msg.get("trnm", "")
|
||||
|
||||
if trnm in ("CNSRLST", "CNSRREQ", "CNSRCLR", "REG", "REMOVE", "LOGIN"):
|
||||
log_kiwoom_api_msg(logger, str(trnm or "?"), msg)
|
||||
|
||||
if trnm == "PING":
|
||||
# 키움 PING → 그대로 echo (서버 정책)
|
||||
try:
|
||||
@@ -728,7 +747,10 @@ class KiwoomWebSocketPriceCache:
|
||||
if trnm == "REG":
|
||||
rc = msg.get("return_code")
|
||||
if rc != 0:
|
||||
logger.warning("⚠️ 키움 WS REG 실패: %s", msg.get("return_msg", ""))
|
||||
logger.warning(
|
||||
"⚠️ 키움 WS REG 실패 rc=%s msg=%s",
|
||||
rc, msg.get("return_msg", ""),
|
||||
)
|
||||
return
|
||||
|
||||
if trnm == "REMOVE":
|
||||
@@ -955,11 +977,25 @@ class KiwoomWebSocketPriceCache:
|
||||
def _on_error(self, ws, error) -> None:
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
err_s = str(error or "")
|
||||
log_kiwoom_ws(
|
||||
logger, "on_error",
|
||||
level="warning",
|
||||
extra=err_s,
|
||||
force=True,
|
||||
)
|
||||
logger.warning("⚠️ 키움 WS 오류: %s", error)
|
||||
|
||||
def _on_close(self, ws, close_status_code, close_msg) -> None:
|
||||
self._connected = False
|
||||
self._authenticated = False
|
||||
log_kiwoom_ws(
|
||||
logger, "on_close",
|
||||
level="info",
|
||||
rc=close_status_code,
|
||||
msg=close_msg or "",
|
||||
force=True,
|
||||
)
|
||||
logger.info("🔌 키움 WS 연결 종료 (code=%s msg=%s)",
|
||||
close_status_code, close_msg or "")
|
||||
|
||||
@@ -1079,6 +1115,12 @@ class KiwoomWebSocketPriceCache:
|
||||
self._reconnect_times = [t for t in self._reconnect_times if t > cutoff]
|
||||
if len(self._reconnect_times) >= self.MAX_RECONNECTS_PER_HOUR:
|
||||
wait = 3600 - (now - self._reconnect_times[0])
|
||||
log_kiwoom_ws(
|
||||
logger, "reconnect_hourly_cap",
|
||||
level="warning",
|
||||
extra=f"wait={int(wait)}s count={len(self._reconnect_times)}",
|
||||
force=True,
|
||||
)
|
||||
logger.warning("⚠️ 키움 WS 시간당 재연결 한도 초과 → %ds 대기", int(wait))
|
||||
time.sleep(max(60.0, wait))
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user