feat: 틱/호가 데이터 출처 표출 UI 추가 및 데이터 수집 개선

- 백테스트 및 실거래 시 틱과 호가의 벤더 출처(ob_source, entry_source) 기록 및 추적 강화 (tail_engine.py)
- 웹 UI '체결디버그'에 [틱:kis / 호가:ls] 형태로 데이터 출처를 직관적으로 표출 (backtest.js, backtest.html)
- LS WebSocket 구독 100건 제한 하드코딩 해제 및 env_config_ext 연동 (ls_ws.py)
- 기타 백테스트 웹 및 DB 관련 최적화 적용
This commit is contained in:
Your Name
2026-09-02 20:53:16 +09:00
parent 2c37771a16
commit 253c95e2c2
11 changed files with 1141 additions and 234 deletions

View File

@@ -1168,15 +1168,41 @@ class TradingOrchestrator:
# 1) LS 전용 테이블 — 호가와 동일: 구독 중이면 적재 (영구만이 아님).
# 봉·VI 는 아래 _ls_should_persist(영구) 유지.
if get_env_bool("LS_WS_TICK_SAVE", False) and self._ls_is_subscribed(code):
tk_str = None
lag_sec = None
ts_dt = payload.get("ts")
che = str(payload.get("chetime") or "").strip()
if isinstance(ts_dt, datetime.datetime) and che and len(che) == 6:
tk_str = ts_dt.strftime("%Y%m%d") + che
try:
che_dt = datetime.datetime.strptime(tk_str, "%Y%m%d%H%M%S")
lag_sec = (ts_dt - che_dt).total_seconds()
except ValueError:
pass
self.db.insert_ls_ws_tick(
code=code,
ts=payload.get("ts"),
ts=ts_dt,
price=float(payload.get("price") or 0),
volume=payload.get("volume"),
tot_volume=payload.get("tot_volume"),
chetime=str(payload.get("chetime") or ""),
chetime=che,
tr_cd=str(payload.get("tr_cd") or ""),
tk=tk_str,
lag_sec=lag_sec,
)
# 가끔 오래된 행 정리 (쓰로틀 — 매 틱마다 DELETE 금지)
try:
keep_lt = int(get_env_int("LS_WS_TICK_KEEP_DAYS", 7) or 7)
if keep_lt > 0 and hasattr(self.db, "cleanup_old_ls_ws_ticks"):
now_m = time.monotonic()
last = float(getattr(self, "_ls_tick_cleanup_mono", 0.0) or 0.0)
if now_m - last >= 3600.0:
self._ls_tick_cleanup_mono = now_m
self.db.cleanup_old_ls_ws_ticks(keep_days=keep_lt)
except Exception:
pass
# 1b) 호가 틱동기 — 체결 1건당 RAM 호가 1장 (스냅 없으면 생략=실매와 동일)
try:
self._maybe_save_ls_orderbook_on_tick(code, payload)
@@ -1206,7 +1232,24 @@ class TradingOrchestrator:
def _on_orderbook(code: str, snap: dict) -> None:
if self._ls_should_persist_orderbook(code):
self.db.insert_ls_ws_orderbook(code=code, snap=snap, market="KR")
tk_str = None
lag_sec = None
snap_time = str(snap.get("snap_time") or "").strip()
if len(snap_time) >= 14:
tk_str = snap_time[:14]
try:
tk_dt = datetime.datetime.strptime(tk_str, "%Y%m%d%H%M%S")
recv_ts_raw = snap.get("recv_ts")
if isinstance(recv_ts_raw, datetime.datetime):
recv_dt = recv_ts_raw
elif isinstance(recv_ts_raw, float):
recv_dt = datetime.datetime.fromtimestamp(recv_ts_raw)
else:
recv_dt = datetime.datetime.now()
lag_sec = (recv_dt - tk_dt).total_seconds()
except Exception:
pass
self.db.insert_ls_ws_orderbook(code=code, snap=snap, market="KR", tk=tk_str, lag_sec=lag_sec)
def _on_vi(code: str, payload: dict) -> None:
if not self._ls_should_persist(code):