Files
kis_bot/scratch/db_stat_compare.py

121 lines
5.1 KiB
Python

"""
ws_price_validation: KIS 틱 vs 키움 틱 빈도(age) 비교
kis_age_ms = 마지막 KIS 시세가 얼마나 오래됐나 (낮을수록 더 자주 옴)
kiwoom_age_ms = 마지막 키움 시세가 얼마나 오래됐나
"""
import sys
sys.path.insert(0, "/home/hoon/kis_bot")
from database import TradeDB
db = TradeDB()
ts_from = "2026-08-07 09:00:00"
try:
print(f"\n{'='*65}")
print(f" KIS 시세 vs 키움 시세 틱 빈도 비교 (ws_price_validation)")
print(f"{'='*65}")
print(f" ※ age_ms = 마지막 시세 수신 후 경과 시간(ms)")
print(f" 낮을수록 더 자주(빠르게) 틱이 오는 것\n")
# 전체 평균 비교
r = db.conn.execute(
"SELECT "
" COUNT(*) as total, "
" SUM(kis_age_ms IS NOT NULL) as kis_cnt, "
" SUM(kiwoom_age_ms IS NOT NULL) as kw_cnt, "
" AVG(kis_age_ms) as avg_kis, "
" AVG(kiwoom_age_ms) as avg_kw, "
" MIN(kis_age_ms) as min_kis, "
" MIN(kiwoom_age_ms) as min_kw, "
" MAX(kis_age_ms) as max_kis, "
" MAX(kiwoom_age_ms) as max_kw "
"FROM ws_price_validation "
"WHERE ts >= %s AND (kis_age_ms IS NOT NULL OR kiwoom_age_ms IS NOT NULL)",
(ts_from,)
).fetchone()
d = dict(r)
total = d["total"] or 0
kis_cnt = int(d["kis_cnt"] or 0)
kw_cnt = int(d["kw_cnt"] or 0)
avg_kis = d["avg_kis"] or 0
avg_kw = d["avg_kw"] or 0
print(f"[ 전체 집계 (9시 이후) ]")
print(f" 총 비교 이벤트 : {total:,}")
print(f" KIS 시세 있는 건 : {kis_cnt:,}건 ({kis_cnt/total*100:.1f}%)")
print(f" 키움 시세 있는 건: {kw_cnt:,}건 ({kw_cnt/total*100:.1f}%)")
print(f"\n[ 시세 나이(age) 비교 — 낮을수록 빠른 틱 ]")
print(f" {'구분':<12} | {'평균 age(ms)':>12} | {'최소(ms)':>9} | {'최대(ms)':>9}")
print(f" {'-'*52}")
print(f" {'KIS':<12} | {avg_kis:>12.0f} | {d['min_kis'] or 0:>9.0f} | {d['max_kis'] or 0:>9.0f}")
print(f" {'키움':<12} | {avg_kw:>12.0f} | {d['min_kw'] or 0:>9.0f} | {d['max_kw'] or 0:>9.0f}")
if avg_kis > 0 and avg_kw > 0:
ratio = avg_kw / avg_kis
faster = "KIS" if avg_kis < avg_kw else "키움"
print(f"\n 비율 (키움age / KIS age): {ratio:.2f}")
print(f"{faster}가 더 자주 틱이 옵니다!")
if ratio >= 4:
print(f" ✅ KIS가 키움보다 약 {ratio:.1f}배 빠름 → KIS로 시세 구독 전환 권장!")
elif ratio >= 2:
print(f" ⚠️ KIS가 약 {ratio:.1f}배 빠름 → 전환 고려할 수준")
elif ratio >= 0.8:
print(f" ➡️ KIS/키움 거의 동등 ({ratio:.1f}배) → 굳이 전환 불필요")
else:
print(f" ❌ 키움이 더 빠름 → KIS 전환 불필요")
# 두 값이 모두 있는 케이스만 정밀 비교
print(f"\n[ 양측 모두 있는 케이스만 비교 ]")
r2 = db.conn.execute(
"SELECT COUNT(*) as cnt, "
"AVG(kis_age_ms) avg_kis, AVG(kiwoom_age_ms) avg_kw, "
"AVG(kiwoom_age_ms - kis_age_ms) as avg_diff "
"FROM ws_price_validation "
"WHERE ts >= %s AND kis_age_ms IS NOT NULL AND kiwoom_age_ms IS NOT NULL",
(ts_from,)
).fetchone()
d2 = dict(r2)
cnt2 = d2.get("cnt") or 0
avg_diff = d2.get("avg_diff") or 0
print(f" 양측 동시 존재 : {cnt2:,}")
print(f" KIS 평균 age : {d2.get('avg_kis') or 0:.0f}ms")
print(f" 키움 평균 age : {d2.get('avg_kw') or 0:.0f}ms")
print(f" 차이 (키움-KIS) : {avg_diff:+.0f}ms ({'KIS가 더 빠름' if avg_diff > 0 else '키움이 더 빠름'})")
# 종목별 KIS vs 키움 상위 5종목
print(f"\n[ 종목별 평균 age 비교 상위 10 ]")
rows = db.conn.execute(
"SELECT code, "
"AVG(kis_age_ms) avg_kis, AVG(kiwoom_age_ms) avg_kw, COUNT(*) cnt "
"FROM ws_price_validation "
"WHERE ts >= %s AND kis_age_ms IS NOT NULL AND kiwoom_age_ms IS NOT NULL "
"GROUP BY code HAVING cnt >= 10 "
"ORDER BY cnt DESC LIMIT 10",
(ts_from,)
).fetchall()
print(f" {'종목':<10} | {'KIS(ms)':>7} | {'키움(ms)':>7} | {'차이(ms)':>8} | {'건수':>5}")
print(f" {'-'*50}")
for row in rows:
d3 = dict(row)
diff = (d3.get("avg_kw") or 0) - (d3.get("avg_kis") or 0)
print(f" {d3['code']:<10} | {d3.get('avg_kis') or 0:>7.0f} | {d3.get('avg_kw') or 0:>7.0f} | {diff:>+8.0f} | {d3['cnt']:>5}")
# 결론
print(f"\n{'='*65}")
print(f" 결론 및 시세 구독 전환 권고")
print(f"{'='*65}")
if avg_kis > 0 and avg_kw > 0:
ratio = avg_kw / avg_kis
if ratio >= 2:
print(f" ✅ KIS 시세가 키움보다 평균 {ratio:.1f}배 빠름")
print(f" → KIS WebSocket으로 시세 구독 전환을 권장합니다!")
print(f" → 특히 스캘핑·돌파 전략의 타점 정밀도가 올라갑니다.")
else:
print(f" ➡️ KIS({avg_kis:.0f}ms) vs 키움({avg_kw:.0f}ms): 차이 {ratio:.1f}배 수준")
print(f" → 굳이 시세 구독을 전환할 만큼 압도적 차이는 아닙니다.")
finally:
db.close()