refactor: enhance Optuna backtesting framework, optimize orderbook filtering, and update database management utilities.

This commit is contained in:
Your Name
2026-08-12 10:19:19 +09:00
parent cb7e5037a0
commit c6bd62a25f
218 changed files with 31613 additions and 759 deletions

View File

@@ -294,7 +294,14 @@ class KiwoomWebSocketPriceCache:
return max(0.0, min(1.0, get_env_float("KIWOOM_WS_REG_DEBOUNCE_SEC", 0.12)))
def _orderbook_ws_enabled(self) -> bool:
return get_env_bool("KIWOOM_WS_ORDERBOOK_ENABLED", True)
# 사용자의 혼동 방지를 위해 KIWOOM_WS_ORDERBOOK_ENABLED 대신,
# LIVE_OB_PROVIDER가 kiwoom이거나, 키움 호가 적재(WS_ORDERBOOK_SAVE_KIWOOM)가 켜져있으면 자동 수신.
from ..utils.env import get_env_from_db, get_env_bool
live_ob_provider = (get_env_from_db("LIVE_OB_PROVIDER", "kiwoom") or "kiwoom").strip().lower()
save_kiwoom = get_env_bool("WS_ORDERBOOK_SAVE_KIWOOM", True)
if live_ob_provider == "kiwoom" or save_kiwoom:
return True
return get_env_bool("KIWOOM_WS_ORDERBOOK_ENABLED", False)
def _program_ws_enabled(self) -> bool:
return get_env_bool("KIWOOM_WS_PROGRAM_ENABLED", True)
@@ -594,6 +601,17 @@ class KiwoomWebSocketPriceCache:
token = self._get_kiwoom_token()
if not token:
logger.warning("⚠️ 키움 토큰 발급 실패 → WS 연결 보류 (60s)")
try:
from kis_trader.utils.ops_alert import ops_alert
ops_alert(
"token_kiwoom",
"키움 토큰 발급 실패",
detail="시세 WS·조건검색 불가 → 60s 후 재시도",
level="critical",
session_only=False,
)
except Exception:
pass
time.sleep(60)
return
@@ -666,6 +684,17 @@ class KiwoomWebSocketPriceCache:
self._fire_login_callbacks(ws)
else:
logger.warning("❌ 키움 WS LOGIN 실패 rc=%s msg=%s", rc, rm)
try:
from kis_trader.utils.ops_alert import ops_alert
ops_alert(
"ws_kiwoom_login",
"키움 WS LOGIN 실패",
detail=f"rc={rc} msg={rm}",
level="critical",
session_only=False,
)
except Exception:
pass
# 805004 등 서버 토큰 거부 → 캐시 만료 전이라도 무효화 후 재발급
if self._is_token_rejected_login(rc, rm):
self._invalidate_token_cache(
@@ -775,7 +804,7 @@ class KiwoomWebSocketPriceCache:
filt = self._candle_agg_codes
if filt is None or code in filt:
try:
self._candle_agg.on_tick(code, price, tick_vol, tick_time)
self._candle_agg.on_tick(code, price, tick_vol, tick_time, source="kiwoom")
except Exception as ex:
logger.debug("키움→CandleAggregator on_tick 실패 %s: %s", code, ex)
@@ -787,6 +816,25 @@ class KiwoomWebSocketPriceCache:
)
except Exception as ex:
logger.debug("키움→TickRecorder on_tick 실패 %s: %s", code, ex)
# ── 호가 틱동기: 체결 1건당 RAM 0D 스냅 1장 (LS tick 모드와 동일) ──
if self._trigger_snapshot_recorder is not None:
try:
max_age = float(
getattr(
self._trigger_snapshot_recorder,
"orderbook_tick_max_age_sec",
3.0,
)
or 3.0
)
snap = self.get_orderbook_snapshot(code, max_age_sec=max_age)
if snap is not None:
self._trigger_snapshot_recorder.on_orderbook_tick_sync(
code, snap, snap_time=tick_time,
)
except Exception as ex:
logger.debug("키움→호가틱동기 실패 %s: %s", code, ex)
except (ValueError, TypeError) as e:
logger.debug("키움 0B 파싱 오류 %s: %s", code, e)
@@ -818,7 +866,7 @@ class KiwoomWebSocketPriceCache:
def _send_reg(self, codes: list) -> bool:
"""REG 발송 (그룹 1, 0B+0D 타입). 한 번에 여러 종목 OK."""
if not codes or not self._ws:
if not codes or not self._ws or not self._connected:
return False
try:
self._ws.send(json.dumps({
@@ -839,7 +887,7 @@ class KiwoomWebSocketPriceCache:
def _send_remove(self, codes: list) -> bool:
"""REMOVE 발송."""
if not codes or not self._ws:
if not codes or not self._ws or not self._connected:
return False
try:
self._ws.send(json.dumps({