한투 호가 = 2번째 앱키 전용 키 없거나 start 실패 시 메인에 H0STASP0 안 붙임. 운영설정 WS_ORDERBOOK_SAVE_KIS 빨간 danger. LS RAM 합집합 후보∪보유∪영구∪grace. sync_targets와 split reconcile 둘 다. 틱 DB 영구 게이트는 그대로. 분봉 쓰레기 → 다음 소스 봉 통째 그 분 틱 0건이거나 전부 봉끝 대비 LIVE_FEED_FALLBACK_MAX_AGE_SEC 초과면 구멍. 메인 WS → 2차 → LS → REST → rollup. CANDLE_GARBAGE_FALLBACK 기본 true. 파일: feed_fallback.py(신규), ws_manager.py, kis_ws.py, candle_series.py, bt_candle_source.py, live_config_schema.py, database.py, 스모크, MD 2개. 같은 ws_manager/database/kis_ws/live_config에는 직전 커밋 이후 쌓여 있던 시세 폴백·ENV 키 정리도 같이 들어갔습니다. 파일 단위로 나눌 수 없어서입니다.
143 lines
4.6 KiB
Python
143 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""오늘 키움 vs 한투 종목별 틱 공백 비교 (1회)."""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from database import TradeDB
|
|
|
|
TT = """
|
|
STR_TO_DATE(
|
|
CASE
|
|
WHEN CHAR_LENGTH(tick_time) >= 14 THEN LEFT(tick_time, 14)
|
|
WHEN CHAR_LENGTH(tick_time) = 6 THEN CONCAT(DATE_FORMAT(recv_ts, '%%Y%%m%%d'), tick_time)
|
|
ELSE NULL
|
|
END,
|
|
'%%Y%%m%%d%%H%%i%%s'
|
|
)
|
|
"""
|
|
|
|
db = TradeDB()
|
|
cols = [r["Field"] for r in db.conn.execute("SHOW COLUMNS FROM ws_ticks").fetchall()]
|
|
need = {"code", "tick_time", "recv_ts", "source", "id"}
|
|
missing = need - set(cols)
|
|
if missing:
|
|
raise SystemExit(f"ws_ticks missing {missing} have={cols}")
|
|
|
|
t0 = "2026-08-19 09:00:00"
|
|
t1 = "2026-08-19 09:35:00"
|
|
print("window", t0, "~", t1)
|
|
|
|
print("\n== source 전체 (recv 연속 공백 / tick_time 연속 공백) ==")
|
|
rows = db.conn.execute(
|
|
f"""
|
|
SELECT source,
|
|
COUNT(*) n,
|
|
COUNT(DISTINCT code) codes,
|
|
MAX(recv_gap) max_recv_gap,
|
|
AVG(recv_gap) avg_recv_gap,
|
|
SUM(recv_gap >= 10) n_recv_ge10,
|
|
SUM(recv_gap >= 20) n_recv_ge20,
|
|
MAX(tt_gap) max_tt_gap,
|
|
AVG(tt_gap) avg_tt_gap,
|
|
SUM(tt_gap >= 10) n_tt_ge10,
|
|
SUM(tt_gap >= 20) n_tt_ge20
|
|
FROM (
|
|
SELECT source, code,
|
|
TIMESTAMPDIFF(SECOND, LAG(recv_ts) OVER (PARTITION BY source, code ORDER BY id), recv_ts) recv_gap,
|
|
TIMESTAMPDIFF(SECOND, LAG({TT}) OVER (PARTITION BY source, code ORDER BY {TT}, id), {TT}) tt_gap
|
|
FROM ws_ticks
|
|
WHERE recv_ts >= %s AND recv_ts < %s AND source IN ('kiwoom','kis')
|
|
) x
|
|
GROUP BY source
|
|
""",
|
|
(t0, t1),
|
|
).fetchall()
|
|
for r in rows:
|
|
print(dict(r))
|
|
|
|
print("\n== 키움 recv 공백 TOP15 (같은 종목 한투 대비) ==")
|
|
rows = db.conn.execute(
|
|
f"""
|
|
SELECT k.code,
|
|
k.n_kw n_kiwoom, s.n_kis,
|
|
k.max_recv max_recv_kw, s.max_recv max_recv_kis,
|
|
(k.max_recv - IFNULL(s.max_recv, 0)) delta_recv,
|
|
k.max_tt max_tt_kw, s.max_tt max_tt_kis
|
|
FROM (
|
|
SELECT code, COUNT(*) n_kw,
|
|
MAX(recv_gap) max_recv, MAX(tt_gap) max_tt
|
|
FROM (
|
|
SELECT code,
|
|
TIMESTAMPDIFF(SECOND, LAG(recv_ts) OVER (PARTITION BY code ORDER BY id), recv_ts) recv_gap,
|
|
TIMESTAMPDIFF(SECOND, LAG({TT}) OVER (PARTITION BY code ORDER BY {TT}, id), {TT}) tt_gap
|
|
FROM ws_ticks
|
|
WHERE source='kiwoom' AND recv_ts >= %s AND recv_ts < %s
|
|
) a
|
|
GROUP BY code
|
|
) k
|
|
LEFT JOIN (
|
|
SELECT code, COUNT(*) n_kis,
|
|
MAX(recv_gap) max_recv, MAX(tt_gap) max_tt
|
|
FROM (
|
|
SELECT code,
|
|
TIMESTAMPDIFF(SECOND, LAG(recv_ts) OVER (PARTITION BY code ORDER BY id), recv_ts) recv_gap,
|
|
TIMESTAMPDIFF(SECOND, LAG({TT}) OVER (PARTITION BY code ORDER BY {TT}, id), {TT}) tt_gap
|
|
FROM ws_ticks
|
|
WHERE source='kis' AND recv_ts >= %s AND recv_ts < %s
|
|
) b
|
|
GROUP BY code
|
|
) s ON s.code = k.code
|
|
ORDER BY k.max_recv DESC
|
|
LIMIT 15
|
|
""",
|
|
(t0, t1, t0, t1),
|
|
).fetchall()
|
|
for r in rows:
|
|
print(dict(r))
|
|
|
|
print("\n== 025980 단독 ==")
|
|
rows = db.conn.execute(
|
|
f"""
|
|
SELECT source, COUNT(*) n,
|
|
MAX(recv_gap) max_recv, AVG(recv_gap) avg_recv,
|
|
MAX(tt_gap) max_tt, AVG(tt_gap) avg_tt,
|
|
SUM(recv_gap >= 10) n_recv_ge10
|
|
FROM (
|
|
SELECT source,
|
|
TIMESTAMPDIFF(SECOND, LAG(recv_ts) OVER (PARTITION BY source ORDER BY id), recv_ts) recv_gap,
|
|
TIMESTAMPDIFF(SECOND, LAG({TT}) OVER (PARTITION BY source ORDER BY {TT}, id), {TT}) tt_gap
|
|
FROM ws_ticks
|
|
WHERE code=%s AND recv_ts >= %s AND recv_ts < %s AND source IN ('kiwoom','kis')
|
|
) x
|
|
GROUP BY source
|
|
""",
|
|
("025980", t0, t1),
|
|
).fetchall()
|
|
for r in rows:
|
|
print(dict(r))
|
|
|
|
print("\n== 키움 recv>=10초 공백이 있는 종목 수 vs 한투 ==")
|
|
for src in ("kiwoom", "kis"):
|
|
r = db.conn.execute(
|
|
f"""
|
|
SELECT COUNT(*) codes_with_gap10 FROM (
|
|
SELECT code, MAX(recv_gap) mx FROM (
|
|
SELECT code,
|
|
TIMESTAMPDIFF(SECOND, LAG(recv_ts) OVER (PARTITION BY code ORDER BY id), recv_ts) recv_gap
|
|
FROM ws_ticks
|
|
WHERE source=%s AND recv_ts >= %s AND recv_ts < %s
|
|
) a
|
|
GROUP BY code
|
|
HAVING mx >= 10
|
|
) z
|
|
""",
|
|
(src, t0, t1),
|
|
).fetchone()
|
|
print(src, dict(r))
|