""" 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()