한투 호가 = 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 키 정리도 같이 들어갔습니다. 파일 단위로 나눌 수 없어서입니다.
138 lines
4.1 KiB
Python
138 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""활발 종목 + 키움만 멈춘 구간 (한투는 계속)."""
|
|
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)
|
|
ELSE NULL
|
|
END,
|
|
'%%Y%%m%%d%%H%%i%%s'
|
|
)
|
|
"""
|
|
|
|
db = TradeDB()
|
|
t0 = "2026-08-19 09:00:00"
|
|
t1 = "2026-08-19 09:35:00"
|
|
|
|
print("== 활발 종목(키움 n>=100) recv 최대공백 키움 vs 한투 ==")
|
|
rows = db.conn.execute(
|
|
f"""
|
|
SELECT k.code, k.n_kw, 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 HAVING n_kw >= 100
|
|
) 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 delta_recv DESC
|
|
""",
|
|
(t0, t1, t0, t1),
|
|
).fetchall()
|
|
print("n_active", len(rows))
|
|
for r in rows[:20]:
|
|
print(dict(r))
|
|
|
|
print("\n== 전종목: 키움 0건인데 한투>0 인 초(연속 침묵) ==")
|
|
rows = db.conn.execute(
|
|
"""
|
|
SELECT recv_ts,
|
|
SUM(source='kis') n_kis,
|
|
SUM(source='kiwoom') n_kw
|
|
FROM ws_ticks
|
|
WHERE recv_ts >= %s AND recv_ts < %s AND source IN ('kis','kiwoom')
|
|
GROUP BY recv_ts
|
|
HAVING SUM(source='kiwoom') = 0 AND SUM(source='kis') > 0
|
|
ORDER BY recv_ts
|
|
""",
|
|
(t0, t1),
|
|
).fetchall()
|
|
print("silent_seconds", len(rows))
|
|
# streak
|
|
streaks = []
|
|
prev = None
|
|
start = None
|
|
n_kis = 0
|
|
sec = 0
|
|
from datetime import datetime as dt
|
|
|
|
def parse(s):
|
|
return dt.strptime(str(s)[:19], "%Y-%m-%d %H:%M:%S")
|
|
|
|
for r in rows:
|
|
ts = parse(r["recv_ts"])
|
|
nk = int(r["n_kis"])
|
|
if prev is None or (ts - prev).total_seconds() != 1:
|
|
if start is not None:
|
|
streaks.append((start, prev, sec, n_kis))
|
|
start = ts
|
|
sec = 1
|
|
n_kis = nk
|
|
else:
|
|
sec += 1
|
|
n_kis += nk
|
|
prev = ts
|
|
if start is not None:
|
|
streaks.append((start, prev, sec, n_kis))
|
|
streaks.sort(key=lambda x: -x[2])
|
|
print("streaks>=5s:")
|
|
for s, e, sec, nk in streaks:
|
|
if sec >= 5:
|
|
print(f" {s} ~ {e} {sec}s kis_ticks={nk}")
|
|
|
|
print("\n== 025980 키움 recv_gap>=10 구간 (그 사이 한투 건수) ==")
|
|
kw = db.conn.execute(
|
|
"""
|
|
SELECT id, tick_time, recv_ts,
|
|
TIMESTAMPDIFF(SECOND, LAG(recv_ts) OVER (ORDER BY id), recv_ts) recv_gap,
|
|
LAG(recv_ts) OVER (ORDER BY id) prev_recv
|
|
FROM ws_ticks
|
|
WHERE source='kiwoom' AND code=%s AND recv_ts >= %s AND recv_ts < %s
|
|
""",
|
|
("025980", t0, t1),
|
|
).fetchall()
|
|
gaps = [dict(r) for r in kw if r["recv_gap"] is not None and int(r["recv_gap"]) >= 10]
|
|
print("n_gaps", len(gaps))
|
|
for g in gaps:
|
|
prev = g["prev_recv"]
|
|
cur = g["recv_ts"]
|
|
kis_n = db.conn.execute(
|
|
"SELECT COUNT(*) n FROM ws_ticks WHERE source=%s AND code=%s "
|
|
"AND recv_ts > %s AND recv_ts < %s",
|
|
("kis", "025980", prev, cur),
|
|
).fetchone()
|
|
print({
|
|
"prev": str(prev),
|
|
"cur": str(cur),
|
|
"kw_gap": g["recv_gap"],
|
|
"kw_tick_time": g["tick_time"],
|
|
"kis_during": kis_n["n"] if kis_n else 0,
|
|
})
|