이번에 들어간 내용
한투 호가 = 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 키 정리도 같이 들어갔습니다. 파일 단위로 나눌 수 없어서입니다.
This commit is contained in:
43
scratch/check_kiwoom_lag5.py
Normal file
43
scratch/check_kiwoom_lag5.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/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
|
||||
|
||||
LAG = """
|
||||
TIMESTAMPDIFF(
|
||||
SECOND,
|
||||
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'
|
||||
),
|
||||
recv_ts
|
||||
)
|
||||
"""
|
||||
|
||||
db = TradeDB()
|
||||
rows = db.conn.execute(
|
||||
"SELECT code, tick_time, tick_time_raw, recv_ts, price, volume, "
|
||||
+ LAG
|
||||
+ " AS lag_sec FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND "
|
||||
+ LAG
|
||||
+ " > 5 ORDER BY lag_sec DESC LIMIT 10",
|
||||
("kiwoom", "2026-08-19 09:00:00"),
|
||||
).fetchall()
|
||||
print("today kiwoom lag>5")
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
# 어제 10:40-11:51 키움 행 (삭제 후 잔존)
|
||||
n = db.conn.execute(
|
||||
"SELECT COUNT(*) AS n FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND recv_ts < %s",
|
||||
("kiwoom", "2026-08-18 10:40:00", "2026-08-18 11:51:00"),
|
||||
).fetchone()
|
||||
print("yesterday 10:40-11:51 kiwoom n", dict(n))
|
||||
131
scratch/check_kiwoom_tick_lag.py
Normal file
131
scratch/check_kiwoom_tick_lag.py
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""어제(8/18) 키움 FID20 동결·지연 틱 버그가 오늘(8/19)에도 있는지 DB 검증."""
|
||||
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
|
||||
|
||||
|
||||
def main() -> None:
|
||||
db = TradeDB()
|
||||
cols = [r["Field"] for r in db.conn.execute("SHOW COLUMNS FROM ws_ticks").fetchall()]
|
||||
print("ws_ticks cols:", cols)
|
||||
need = {"code", "tick_time", "recv_ts", "source", "volume", "price"}
|
||||
missing = need - set(cols)
|
||||
if missing:
|
||||
print("MISSING", missing)
|
||||
return
|
||||
has_raw = "tick_time_raw" in cols
|
||||
raw_sel = "tick_time_raw" if has_raw else "NULL AS tick_time_raw"
|
||||
print("tick_time_raw:", has_raw)
|
||||
|
||||
# recv_ts 초 단위, tick_time 은 HHMMSS 또는 YYYYMMDDHHMMSS
|
||||
lag_expr = """
|
||||
TIMESTAMPDIFF(
|
||||
SECOND,
|
||||
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'
|
||||
),
|
||||
recv_ts
|
||||
)
|
||||
"""
|
||||
|
||||
windows = [
|
||||
("어제동결창 10:40-12:00", "2026-08-18 10:40:00", "2026-08-18 12:00:00"),
|
||||
("어제아침 09:00-09:15", "2026-08-18 09:00:00", "2026-08-18 09:15:00"),
|
||||
("오늘아침 09:00-now", "2026-08-19 09:00:00", "2026-08-19 23:59:59"),
|
||||
("오늘전체 recv", "2026-08-19 00:00:00", "2026-08-19 23:59:59"),
|
||||
]
|
||||
|
||||
for title, a, b in windows:
|
||||
sql = (
|
||||
"SELECT COUNT(*) AS n, "
|
||||
"MIN(recv_ts) AS min_recv, MAX(recv_ts) AS max_recv, "
|
||||
f"MIN({lag_expr}) AS min_lag, "
|
||||
f"MAX({lag_expr}) AS max_lag, "
|
||||
f"AVG({lag_expr}) AS avg_lag, "
|
||||
f"SUM(CASE WHEN {lag_expr} > 5 THEN 1 ELSE 0 END) AS n_lag5, "
|
||||
f"SUM(CASE WHEN {lag_expr} > 120 THEN 1 ELSE 0 END) AS n_lag120, "
|
||||
f"SUM(CASE WHEN {lag_expr} > 600 THEN 1 ELSE 0 END) AS n_lag600 "
|
||||
"FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND recv_ts < %s"
|
||||
)
|
||||
row = db.conn.execute(sql, ("kiwoom", a, b)).fetchone()
|
||||
print(f"\n== {title} kiwoom ==")
|
||||
print(dict(row) if row else None)
|
||||
|
||||
sql2 = (
|
||||
"SELECT COUNT(*) AS n, "
|
||||
f"MAX({lag_expr}) AS max_lag, "
|
||||
f"AVG({lag_expr}) AS avg_lag, "
|
||||
f"SUM(CASE WHEN {lag_expr} > 5 THEN 1 ELSE 0 END) AS n_lag5 "
|
||||
"FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND recv_ts < %s"
|
||||
)
|
||||
rowk = db.conn.execute(sql2, ("kis", a, b)).fetchone()
|
||||
print(" kis:", dict(rowk) if rowk else None)
|
||||
|
||||
# 오늘 키움: tick_time 최빈(동결 시 한 시각에 몰림)
|
||||
print("\n== 오늘 키움 tick_time 상위 8 ==")
|
||||
top = db.conn.execute(
|
||||
"SELECT tick_time, COUNT(*) AS n, MIN(recv_ts) AS min_recv, MAX(recv_ts) AS max_recv "
|
||||
"FROM ws_ticks WHERE source=%s AND recv_ts >= %s "
|
||||
"GROUP BY tick_time ORDER BY n DESC LIMIT 8",
|
||||
("kiwoom", "2026-08-19 09:00:00"),
|
||||
).fetchall()
|
||||
for r in top or []:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 어제동결창 키움 tick_time 상위 5 ==")
|
||||
top_y = db.conn.execute(
|
||||
"SELECT tick_time, COUNT(*) AS n, MIN(recv_ts) AS min_recv, MAX(recv_ts) AS max_recv "
|
||||
"FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND recv_ts < %s "
|
||||
"GROUP BY tick_time ORDER BY n DESC LIMIT 5",
|
||||
("kiwoom", "2026-08-18 10:40:00", "2026-08-18 12:00:00"),
|
||||
).fetchall()
|
||||
for r in top_y or []:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 오늘 키움 lag>120 샘플 8 ==")
|
||||
samples = db.conn.execute(
|
||||
"SELECT code, tick_time, "
|
||||
+ raw_sel
|
||||
+ ", recv_ts, price, volume, "
|
||||
+ lag_expr
|
||||
+ " AS lag_sec "
|
||||
"FROM ws_ticks WHERE source=%s AND recv_ts >= %s "
|
||||
f"AND {lag_expr} > 120 "
|
||||
"ORDER BY recv_ts DESC LIMIT 8",
|
||||
("kiwoom", "2026-08-19 09:00:00"),
|
||||
).fetchall()
|
||||
if not samples:
|
||||
print("(없음)")
|
||||
for r in samples or []:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 오늘 키움 최근 5건 ==")
|
||||
recent = db.conn.execute(
|
||||
"SELECT code, tick_time, "
|
||||
+ raw_sel
|
||||
+ ", recv_ts, price, "
|
||||
+ lag_expr
|
||||
+ " AS lag_sec "
|
||||
"FROM ws_ticks WHERE source=%s AND recv_ts >= %s "
|
||||
"ORDER BY recv_ts DESC LIMIT 5",
|
||||
("kiwoom", "2026-08-19 09:00:00"),
|
||||
).fetchall()
|
||||
for r in recent or []:
|
||||
print(dict(r))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
32
scratch/check_skip_not_in_db.py
Normal file
32
scratch/check_skip_not_in_db.py
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/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
|
||||
|
||||
db = TradeDB()
|
||||
# skip 로그: 09:08:49 fid20=090844 025980 — DB에 09:08:49 recv + tick 090844 있는지
|
||||
rows = db.conn.execute(
|
||||
"SELECT code, tick_time, tick_time_raw, recv_ts, price FROM ws_ticks "
|
||||
"WHERE source=%s AND code=%s AND recv_ts >= %s AND recv_ts <= %s "
|
||||
"ORDER BY recv_ts LIMIT 20",
|
||||
("kiwoom", "025980", "2026-08-19 09:08:40", "2026-08-19 09:09:00"),
|
||||
).fetchall()
|
||||
print("025980 around skip 09:08:49")
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
rows2 = db.conn.execute(
|
||||
"SELECT code, tick_time, tick_time_raw, recv_ts, price FROM ws_ticks "
|
||||
"WHERE source=%s AND tick_time_raw=%s AND recv_ts >= %s AND recv_ts <= %s "
|
||||
"LIMIT 10",
|
||||
("kiwoom", "090844", "2026-08-19 09:08:40", "2026-08-19 09:09:10"),
|
||||
).fetchall()
|
||||
print("tick_time_raw=090844 near 09:08:49")
|
||||
for r in rows2:
|
||||
print(dict(r))
|
||||
if not rows2:
|
||||
print("(없음 — skip된 catch-up이 DB에 안 들어간 것)")
|
||||
142
scratch/compare_kiwoom_kis_tick_gaps.py
Normal file
142
scratch/compare_kiwoom_kis_tick_gaps.py
Normal file
@@ -0,0 +1,142 @@
|
||||
#!/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))
|
||||
137
scratch/compare_kiwoom_only_stalls.py
Normal file
137
scratch/compare_kiwoom_only_stalls.py
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/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,
|
||||
})
|
||||
52
scratch/explain_025980_gap.py
Normal file
52
scratch/explain_025980_gap.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""025980 id 전후 + kiwoom 공백 구간만 (짧게)."""
|
||||
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
|
||||
|
||||
db = TradeDB()
|
||||
print("== id 63275500~63275520 ==")
|
||||
rows = db.conn.execute(
|
||||
"SELECT id, source, code, tick_time, tick_time_raw, recv_ts, price, volume "
|
||||
"FROM ws_ticks WHERE id BETWEEN %s AND %s ORDER BY id",
|
||||
(63275500, 63275520),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== kiwoom 09:05:06~09:05:40 recv 초별 ==")
|
||||
rows = db.conn.execute(
|
||||
"SELECT recv_ts, COUNT(*) n, COUNT(DISTINCT code) codes, "
|
||||
"MIN(tick_time) min_tt, MAX(tick_time) max_tt "
|
||||
"FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND recv_ts <= %s "
|
||||
"GROUP BY recv_ts ORDER BY recv_ts",
|
||||
("kiwoom", "2026-08-19 09:05:06", "2026-08-19 09:05:40"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 025980 kiwoom tick_time distinct 09:05:06~09:05:34 ==")
|
||||
rows = db.conn.execute(
|
||||
"SELECT tick_time, tick_time_raw, COUNT(*) n, MIN(recv_ts) min_r, MAX(recv_ts) max_r "
|
||||
"FROM ws_ticks WHERE source=%s AND code=%s AND recv_ts >= %s AND recv_ts <= %s "
|
||||
"GROUP BY tick_time, tick_time_raw ORDER BY min_r",
|
||||
("kiwoom", "025980", "2026-08-19 09:05:06", "2026-08-19 09:05:34"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== kis 같은 초 025980 있는지 ==")
|
||||
rows = db.conn.execute(
|
||||
"SELECT COUNT(*) n, MIN(tick_time) min_tt, MAX(tick_time) max_tt, "
|
||||
"MIN(recv_ts) min_r, MAX(recv_ts) max_r FROM ws_ticks "
|
||||
"WHERE source=%s AND code=%s AND recv_ts >= %s AND recv_ts <= %s",
|
||||
("kis", "025980", "2026-08-19 09:05:06", "2026-08-19 09:05:34"),
|
||||
).fetchone()
|
||||
print(dict(rows))
|
||||
115
scratch/explain_025980_lag28.py
Normal file
115
scratch/explain_025980_lag28.py
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
"""025980 lag~28s vs 어제 아침 max~5s — 스키마 확인 후 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
|
||||
from kis_trader.utils.env import get_env_from_db
|
||||
|
||||
LAG = """
|
||||
TIMESTAMPDIFF(
|
||||
SECOND,
|
||||
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'
|
||||
),
|
||||
recv_ts
|
||||
)
|
||||
"""
|
||||
|
||||
db = TradeDB()
|
||||
cols = [r["Field"] for r in db.conn.execute("SHOW COLUMNS FROM ws_ticks").fetchall()]
|
||||
print("ws_ticks cols:", cols)
|
||||
|
||||
print("LIVE_MAX", get_env_from_db("KIWOOM_TICK_LIVE_MAX_LAG_SEC"))
|
||||
print("TIME_MAX", get_env_from_db("KIWOOM_TICK_TIME_MAX_LAG_SEC"))
|
||||
print("FLUSH", get_env_from_db("WS_TICK_DB_FLUSH_SEC"))
|
||||
print("BATCH", get_env_from_db("WS_TICK_DB_BATCH_SIZE"))
|
||||
|
||||
sel = [
|
||||
c
|
||||
for c in (
|
||||
"id",
|
||||
"code",
|
||||
"tick_time",
|
||||
"tick_time_raw",
|
||||
"recv_ts",
|
||||
"price",
|
||||
"volume",
|
||||
"source",
|
||||
)
|
||||
if c in cols
|
||||
]
|
||||
sel_sql = ", ".join(sel)
|
||||
|
||||
print("\n== 오늘 kiwoom lag>5 ==")
|
||||
rows = db.conn.execute(
|
||||
f"SELECT {sel_sql}, {LAG} AS lag_sec FROM ws_ticks "
|
||||
"WHERE source=%s AND recv_ts >= %s AND recv_ts < %s AND "
|
||||
f"{LAG} > 5 ORDER BY lag_sec DESC LIMIT 20",
|
||||
("kiwoom", "2026-08-19 09:00:00", "2026-08-19 09:15:00"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 025980 09:04:50~09:06:00 (id순) ==")
|
||||
rows = db.conn.execute(
|
||||
f"SELECT {sel_sql}, {LAG} AS lag_sec FROM ws_ticks "
|
||||
"WHERE source=%s AND code=%s AND recv_ts >= %s AND recv_ts <= %s "
|
||||
"ORDER BY id",
|
||||
("kiwoom", "025980", "2026-08-19 09:04:50", "2026-08-19 09:06:00"),
|
||||
).fetchall()
|
||||
print("n", len(rows))
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
# 같은 recv 초에 몰린 종목 수 (WS 스레드 스톨 흔적)
|
||||
print("\n== 오늘 09:05:30~09:05:40 recv 초별 건수/종목 ==")
|
||||
rows = db.conn.execute(
|
||||
"SELECT recv_ts, COUNT(*) n, COUNT(DISTINCT code) codes "
|
||||
"FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND recv_ts <= %s "
|
||||
"GROUP BY recv_ts ORDER BY recv_ts",
|
||||
("kiwoom", "2026-08-19 09:05:30", "2026-08-19 09:05:40"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 오늘 09:00~09:10 lag 분포 ==")
|
||||
rows = db.conn.execute(
|
||||
f"SELECT {LAG} AS lag_sec, COUNT(*) n FROM ws_ticks "
|
||||
"WHERE source=%s AND recv_ts >= %s AND recv_ts < %s "
|
||||
f"GROUP BY {LAG} ORDER BY lag_sec DESC LIMIT 15",
|
||||
("kiwoom", "2026-08-19 09:00:00", "2026-08-19 09:10:00"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 어제 09:00~09:15 lag 분포 top ==")
|
||||
rows = db.conn.execute(
|
||||
f"SELECT {LAG} AS lag_sec, COUNT(*) n FROM ws_ticks "
|
||||
"WHERE source=%s AND recv_ts >= %s AND recv_ts < %s "
|
||||
f"GROUP BY {LAG} ORDER BY lag_sec DESC LIMIT 15",
|
||||
("kiwoom", "2026-08-18 09:00:00", "2026-08-18 09:15:00"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 어제 10:51~12:00 (잔존) lag 분포 top ==")
|
||||
rows = db.conn.execute(
|
||||
f"SELECT {LAG} AS lag_sec, COUNT(*) n FROM ws_ticks "
|
||||
"WHERE source=%s AND recv_ts >= %s AND recv_ts < %s "
|
||||
f"GROUP BY {LAG} ORDER BY lag_sec DESC LIMIT 10",
|
||||
("kiwoom", "2026-08-18 10:51:00", "2026-08-18 12:00:00"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
52
scratch/kiwoom_silence_overlap.py
Normal file
52
scratch/kiwoom_silence_overlap.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/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
|
||||
|
||||
db = TradeDB()
|
||||
windows = [
|
||||
("2026-08-19 09:05:07", "2026-08-19 09:05:33"),
|
||||
("2026-08-19 09:08:50", "2026-08-19 09:09:01"),
|
||||
("2026-08-19 09:11:09", "2026-08-19 09:14:55"),
|
||||
("2026-08-19 09:14:57", "2026-08-19 09:19:11"),
|
||||
("2026-08-19 09:27:24", "2026-08-19 09:31:32"),
|
||||
]
|
||||
kw_codes = {
|
||||
r["code"]
|
||||
for r in db.conn.execute(
|
||||
"SELECT DISTINCT code FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND recv_ts < %s",
|
||||
("kiwoom", "2026-08-19 09:00:00", "2026-08-19 09:35:00"),
|
||||
).fetchall()
|
||||
}
|
||||
print("kiwoom codes today morning", len(kw_codes))
|
||||
for a, b in windows:
|
||||
rows = db.conn.execute(
|
||||
"SELECT code, COUNT(*) n FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND recv_ts <= %s "
|
||||
"GROUP BY code ORDER BY n DESC LIMIT 8",
|
||||
("kis", a, b),
|
||||
).fetchall()
|
||||
tot = db.conn.execute(
|
||||
"SELECT COUNT(*) n, COUNT(DISTINCT code) codes FROM ws_ticks "
|
||||
"WHERE source=%s AND recv_ts >= %s AND recv_ts <= %s",
|
||||
("kis", a, b),
|
||||
).fetchone()
|
||||
overlap_n = 0
|
||||
overlap_codes = 0
|
||||
for r in db.conn.execute(
|
||||
"SELECT code, COUNT(*) n FROM ws_ticks WHERE source=%s AND recv_ts >= %s AND recv_ts <= %s GROUP BY code",
|
||||
("kis", a, b),
|
||||
).fetchall():
|
||||
if r["code"] in kw_codes:
|
||||
overlap_n += int(r["n"])
|
||||
overlap_codes += 1
|
||||
print(f"\n{a} ~ {b}")
|
||||
print(" kis", dict(tot), "overlap_kw_univ ticks", overlap_n, "codes", overlap_codes)
|
||||
print(" top", [dict(x) for x in rows[:5]])
|
||||
64
scratch/kiwoom_tick_lag_evidence.py
Normal file
64
scratch/kiwoom_tick_lag_evidence.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""키움 tick_time vs recv_ts 증거 (DB, 즉시 실행)."""
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
sys.path.insert(0, "/home/hoon/kis_bot")
|
||||
from database import TradeDB
|
||||
|
||||
WATCH = ("089970", "005930", "028670", "178320")
|
||||
|
||||
db = TradeDB()
|
||||
try:
|
||||
print("=== 키움: recv 최근 5분 vs tick_time lag ===")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT code, tick_time, recv_ts, price, volume,
|
||||
TIMESTAMPDIFF(SECOND,
|
||||
STR_TO_DATE(tick_time, '%%Y%%m%%d%%H%%i%%s'),
|
||||
recv_ts) AS lag_sec
|
||||
FROM ws_ticks
|
||||
WHERE source='kiwoom'
|
||||
AND recv_ts >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
|
||||
ORDER BY recv_ts DESC
|
||||
LIMIT 30
|
||||
"""
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
print(
|
||||
f" {d['code']} tick={d['tick_time']} recv={d['recv_ts']} "
|
||||
f"lag={d['lag_sec']}s vol={d['volume']} px={d['price']}"
|
||||
)
|
||||
|
||||
print("\n=== vol=0 vs vol>0 (오늘 kiwoom, recv>=10:43) ===")
|
||||
r = db.conn.execute(
|
||||
"""
|
||||
SELECT
|
||||
SUM(CASE WHEN COALESCE(volume,0)=0 THEN 1 ELSE 0 END) vol0,
|
||||
SUM(CASE WHEN COALESCE(volume,0)>0 THEN 1 ELSE 0 END) vol_pos,
|
||||
COUNT(*) total
|
||||
FROM ws_ticks
|
||||
WHERE source='kiwoom' AND recv_ts >= '2026-08-18 10:43:00'
|
||||
"""
|
||||
).fetchone()
|
||||
print(dict(r))
|
||||
|
||||
print("\n=== 관심종목 오늘 kiwoom tick_time max vs recv max ===")
|
||||
for code in WATCH:
|
||||
row = db.conn.execute(
|
||||
"""
|
||||
SELECT COUNT(*) n,
|
||||
MAX(tick_time) tmax, MAX(recv_ts) rmax,
|
||||
MAX(TIMESTAMPDIFF(SECOND,
|
||||
STR_TO_DATE(tick_time, '%%Y%%m%%d%%H%%i%%s'),
|
||||
recv_ts)) max_lag
|
||||
FROM ws_ticks
|
||||
WHERE source='kiwoom' AND code=%s
|
||||
AND recv_ts >= '2026-08-18 09:00:00'
|
||||
""",
|
||||
(code,),
|
||||
).fetchone()
|
||||
print(f" {code}: {dict(row)}")
|
||||
finally:
|
||||
db.close()
|
||||
24
scratch/print_ws_feed_env.py
Normal file
24
scratch/print_ws_feed_env.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/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 kis_trader.utils.env import get_env_from_db, get_env_bool
|
||||
|
||||
keys = (
|
||||
"LIVE_TICK_PROVIDER",
|
||||
"LIVE_OB_PROVIDER",
|
||||
"WS_SUBSCRIBE_KIS_MINIMAL",
|
||||
"WS_SUBSCRIBE_SPILL",
|
||||
"WS_TICK_SAVE_KIS",
|
||||
"WS_TICK_SAVE_KIWOOM",
|
||||
"WS_PROVIDER",
|
||||
"KIS_MOCK",
|
||||
"BREAKOUT_TICK_SELL_ENABLED",
|
||||
"TICK_SELL_ENABLED",
|
||||
"KIWOOM_WS_MAX_SUBSCRIPTIONS",
|
||||
)
|
||||
for k in keys:
|
||||
print(f"{k}={get_env_from_db(k, '')!r} bool={get_env_bool(k, False)}")
|
||||
52
scratch/set_ws_price_max_age_zero.py
Normal file
52
scratch/set_ws_price_max_age_zero.py
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
"""WS_PRICE_MAX_AGE_SEC 를 0(마지막 RAM)으로 맞춤. 스키마 확인 후 1회만."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
from database import TradeDB
|
||||
from kis_trader.utils.env import get_env_from_db, invalidate_merged_env_cache
|
||||
|
||||
|
||||
def main() -> None:
|
||||
db = TradeDB()
|
||||
try:
|
||||
cols = [
|
||||
dict(r)["Field"]
|
||||
for r in db.conn.execute("SHOW COLUMNS FROM env_config").fetchall()
|
||||
]
|
||||
print("env_config has WS_PRICE_MAX_AGE_SEC:", "WS_PRICE_MAX_AGE_SEC" in cols)
|
||||
ext_cols = [
|
||||
dict(r)["Field"]
|
||||
for r in db.conn.execute("SHOW COLUMNS FROM env_config_ext").fetchall()
|
||||
]
|
||||
print("env_config_ext cols:", ext_cols)
|
||||
before = get_env_from_db("WS_PRICE_MAX_AGE_SEC", "")
|
||||
print("before get_env_from_db:", repr(before))
|
||||
|
||||
if "WS_PRICE_MAX_AGE_SEC" in cols:
|
||||
db.conn.execute(
|
||||
"UPDATE env_config SET WS_PRICE_MAX_AGE_SEC=%s",
|
||||
("0",),
|
||||
)
|
||||
print("updated env_config.WS_PRICE_MAX_AGE_SEC=0")
|
||||
db.conn.execute(
|
||||
"INSERT INTO env_config_ext (env_key, env_value, updated_at) "
|
||||
"VALUES (%s, %s, NOW()) "
|
||||
"ON DUPLICATE KEY UPDATE env_value=VALUES(env_value), "
|
||||
"updated_at=VALUES(updated_at)",
|
||||
("WS_PRICE_MAX_AGE_SEC", "0"),
|
||||
)
|
||||
print("upsert env_config_ext WS_PRICE_MAX_AGE_SEC=0")
|
||||
invalidate_merged_env_cache()
|
||||
after = get_env_from_db("WS_PRICE_MAX_AGE_SEC", "")
|
||||
print("after get_env_from_db:", repr(after))
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
41
scratch/tail_live_params.py
Normal file
41
scratch/tail_live_params.py
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
"""실매 꼬리 TRIGGER 핵심값 + 오늘 탈락 사유는 저널에서 집계."""
|
||||
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
|
||||
from kis_trader.utils.env import get_env_bool, get_env_from_db, get_env_float, get_env_int, invalidate_merged_env_cache
|
||||
|
||||
invalidate_merged_env_cache()
|
||||
db = TradeDB() # noqa: F841 — 캐시 워밍
|
||||
keys = [
|
||||
"TAIL_SKIP_HTS_SCAN_DUPES",
|
||||
"TAIL_USE_INTRADAY_DROP",
|
||||
"TAIL_BAR_CHG_MIN_PCT",
|
||||
"TAIL_BAR_CHG_MAX_PCT",
|
||||
"TAIL_TIME_START",
|
||||
"TAIL_TIME_END",
|
||||
"TAIL_MIN_DROP_RATE",
|
||||
"TAIL_MIN_RECOVERY_RATIO",
|
||||
"TAIL_MAX_RECOVERY_3M",
|
||||
"TAIL_RATIO_MIN",
|
||||
"TAIL_PCT_MIN",
|
||||
"TAIL_HIGH_CHASE_THR",
|
||||
"TAIL_USE_HIGH_CHASE_FILTER",
|
||||
"TAIL_USE_RSI_FILTER",
|
||||
"TAIL_RSI_THRESHOLD",
|
||||
"TAIL_ENTRY_MODE",
|
||||
"SHORT_UNIVERSE_SOURCE",
|
||||
"CONDITION_SHORT_NAME",
|
||||
]
|
||||
print("=== live DB/env ===")
|
||||
for k in keys:
|
||||
raw = get_env_from_db(k, "")
|
||||
print(f"{k}: raw={raw!r}")
|
||||
print("skip_hts bool", get_env_bool("TAIL_SKIP_HTS_SCAN_DUPES", False))
|
||||
print("bar_min", get_env_float("TAIL_BAR_CHG_MIN_PCT", -10.0))
|
||||
print("bar_max", get_env_float("TAIL_BAR_CHG_MAX_PCT", -0.5))
|
||||
print("time_start", get_env_int("TAIL_TIME_START", 930))
|
||||
109
scratch/tick_density_same_stock.py
Normal file
109
scratch/tick_density_same_stock.py
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
"""같은 종목·같은 분에 키움 vs 한투 틱 밀도 (스톨 전)."""
|
||||
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
|
||||
from kis_trader.utils.env import get_env_from_db, get_env_int
|
||||
|
||||
db = TradeDB()
|
||||
print("LIVE_TICK", get_env_from_db("LIVE_TICK_PROVIDER", ""))
|
||||
print("LIVE_OB", get_env_from_db("LIVE_OB_PROVIDER", ""))
|
||||
print("KW_MAX", get_env_from_db("KIWOOM_WS_MAX_SUBSCRIPTIONS", ""), "def", get_env_int("KIWOOM_WS_MAX_SUBSCRIPTIONS", 100))
|
||||
print("SCOPE", get_env_from_db("WS_TICK_RECORD_SCOPE", ""))
|
||||
print("TICK_SELL", get_env_from_db("TICK_SELL_ENABLED", ""), "BO", get_env_from_db("BREAKOUT_TICK_SELL_ENABLED", ""))
|
||||
|
||||
print("\n== 09:00~09:05 (매도 전) 겹치는 종목 틱수 ==")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT code,
|
||||
SUM(source='kiwoom') n_kw,
|
||||
SUM(source='kis') n_kis
|
||||
FROM ws_ticks
|
||||
WHERE recv_ts >= %s AND recv_ts < %s AND source IN ('kiwoom','kis')
|
||||
GROUP BY code
|
||||
HAVING SUM(source='kiwoom') > 0 AND SUM(source='kis') > 0
|
||||
ORDER BY SUM(source='kis') DESC
|
||||
LIMIT 15
|
||||
""",
|
||||
("2026-08-19 09:00:00", "2026-08-19 09:05:00"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
kw = int(d["n_kw"] or 0)
|
||||
ki = int(d["n_kis"] or 0)
|
||||
d["kis_per_kw"] = round(ki / kw, 2) if kw else None
|
||||
print(d)
|
||||
|
||||
print("\n== 025980 분별 틱수 09:00~09:20 ==")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT DATE_FORMAT(recv_ts, '%%H:%%i') mm,
|
||||
SUM(source='kiwoom') n_kw,
|
||||
SUM(source='kis') n_kis
|
||||
FROM ws_ticks
|
||||
WHERE code=%s AND recv_ts >= %s AND recv_ts < %s AND source IN ('kiwoom','kis')
|
||||
GROUP BY DATE_FORMAT(recv_ts, '%%H:%%i')
|
||||
ORDER BY mm
|
||||
""",
|
||||
("025980", "2026-08-19 09:00:00", "2026-08-19 09:20:00"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 같은 초 025980 09:04:50~09:05:06 (건강 구간) ==")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT DATE_FORMAT(recv_ts, '%%H:%%i:%%s') ss,
|
||||
SUM(source='kiwoom') n_kw,
|
||||
SUM(source='kis') n_kis
|
||||
FROM ws_ticks
|
||||
WHERE code=%s AND recv_ts >= %s AND recv_ts <= %s AND source IN ('kiwoom','kis')
|
||||
GROUP BY DATE_FORMAT(recv_ts, '%%H:%%i:%%s')
|
||||
ORDER BY ss
|
||||
""",
|
||||
("025980", "2026-08-19 09:04:50", "2026-08-19 09:05:06"),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n== 09:00~09:35 distinct 종목: 키움만 / 한투만 / 둘다 ==")
|
||||
r = db.conn.execute(
|
||||
"""
|
||||
SELECT
|
||||
COUNT(DISTINCT CASE WHEN source='kiwoom' THEN code END) kw,
|
||||
COUNT(DISTINCT CASE WHEN source='kis' THEN code END) kis,
|
||||
COUNT(DISTINCT code) either
|
||||
FROM ws_ticks
|
||||
WHERE recv_ts >= %s AND recv_ts < %s AND source IN ('kiwoom','kis')
|
||||
""",
|
||||
("2026-08-19 09:00:00", "2026-08-19 09:35:00"),
|
||||
).fetchone()
|
||||
print(dict(r))
|
||||
only = db.conn.execute(
|
||||
"""
|
||||
SELECT
|
||||
SUM(srcs='kis') only_kis,
|
||||
SUM(srcs='kiwoom') only_kw,
|
||||
SUM(srcs='both') both_n
|
||||
FROM (
|
||||
SELECT code,
|
||||
CASE
|
||||
WHEN SUM(source='kis')>0 AND SUM(source='kiwoom')>0 THEN 'both'
|
||||
WHEN SUM(source='kis')>0 THEN 'kis'
|
||||
ELSE 'kiwoom'
|
||||
END srcs
|
||||
FROM ws_ticks
|
||||
WHERE recv_ts >= %s AND recv_ts < %s AND source IN ('kiwoom','kis')
|
||||
GROUP BY code
|
||||
) z
|
||||
""",
|
||||
("2026-08-19 09:00:00", "2026-08-19 09:35:00"),
|
||||
).fetchone()
|
||||
print(dict(only))
|
||||
78
scratch/tick_today_verify.py
Normal file
78
scratch/tick_today_verify.py
Normal file
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env python3
|
||||
"""오늘 ws_ticks KIS vs kiwoom 검증."""
|
||||
import sys
|
||||
sys.path.insert(0, "/home/hoon/kis_bot")
|
||||
from database import TradeDB
|
||||
|
||||
TODAY = "20260818"
|
||||
T0 = f"{TODAY}090000"
|
||||
|
||||
db = TradeDB()
|
||||
try:
|
||||
print("=== 오늘 09:00~ (tick_time 기준) ===")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT source, COUNT(*) n, COUNT(DISTINCT code) codes,
|
||||
MIN(tick_time) tmin, MAX(tick_time) tmax
|
||||
FROM ws_ticks
|
||||
WHERE tick_time >= %s
|
||||
GROUP BY source ORDER BY n DESC
|
||||
""",
|
||||
(T0,),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n=== recv_ts 오늘 (서버 수신 시각) ===")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT source, COUNT(*) n,
|
||||
MIN(recv_ts) rmin, MAX(recv_ts) rmax
|
||||
FROM ws_ticks
|
||||
WHERE recv_ts >= '2026-08-18 09:00:00'
|
||||
GROUP BY source ORDER BY n DESC
|
||||
""",
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n=== 11:20~11:24 구간 (tick_time) ===")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT source, COUNT(*) n, MAX(tick_time) tmax, MAX(recv_ts) rmax
|
||||
FROM ws_ticks
|
||||
WHERE tick_time >= '20260818112000'
|
||||
GROUP BY source
|
||||
""",
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n=== kiwoom: tick_time vs recv_ts 불일치 (오늘) ===")
|
||||
r = db.conn.execute(
|
||||
"""
|
||||
SELECT COUNT(*) n
|
||||
FROM ws_ticks
|
||||
WHERE source='kiwoom' AND recv_ts >= '2026-08-18 10:43:00'
|
||||
AND tick_time < '20260818104300'
|
||||
""",
|
||||
).fetchone()
|
||||
print(f" recv>=10:43 인데 tick_time<10:43: {dict(r)['n']:,}")
|
||||
|
||||
print("\n=== 089970 오늘 source별 ===")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT source, COUNT(*) n,
|
||||
MIN(tick_time) tmin, MAX(tick_time) tmax,
|
||||
MAX(recv_ts) rmax
|
||||
FROM ws_ticks
|
||||
WHERE code='089970' AND tick_time >= %s
|
||||
GROUP BY source
|
||||
""",
|
||||
(T0,),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
213
scratch/tuesday_status_check.py
Normal file
213
scratch/tuesday_status_check.py
Normal file
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
"""화요일 장중 상태: DB 수집 + 전략별 매매 스냅샷."""
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
sys.path.insert(0, "/home/hoon/kis_bot")
|
||||
|
||||
from database import TradeDB
|
||||
|
||||
TODAY = "2026-08-18"
|
||||
SINCE = f"{TODAY} 09:00:00"
|
||||
|
||||
|
||||
def cols(db, table):
|
||||
return [dict(r)["Field"] for r in db.conn.execute(f"SHOW COLUMNS FROM {table}").fetchall()]
|
||||
|
||||
|
||||
def main():
|
||||
db = TradeDB()
|
||||
try:
|
||||
print(f"=== 화요일 상태 스냅샷 ({datetime.now():%Y-%m-%d %H:%M:%S}) ===\n")
|
||||
|
||||
# --- ws_ticks ---
|
||||
wt_cols = cols(db, "ws_ticks")
|
||||
print(f"ws_ticks cols: {wt_cols[:8]}...")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT source, COUNT(*) n,
|
||||
MIN(tick_time) tmin, MAX(tick_time) tmax,
|
||||
COUNT(DISTINCT code) codes
|
||||
FROM ws_ticks
|
||||
WHERE tick_time >= %s
|
||||
GROUP BY source
|
||||
ORDER BY n DESC
|
||||
""",
|
||||
(SINCE,),
|
||||
).fetchall()
|
||||
print("\n--- ws_ticks 오늘 09:00~ (source별) ---")
|
||||
if not rows:
|
||||
print("(없음)")
|
||||
else:
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
print(
|
||||
f" {d['source']:12s} rows={d['n']:>8,} codes={d['codes']:>4} "
|
||||
f"{d['tmin']} ~ {d['tmax']}"
|
||||
)
|
||||
|
||||
rows5 = db.conn.execute(
|
||||
"""
|
||||
SELECT source, code, COUNT(*) n
|
||||
FROM ws_ticks
|
||||
WHERE tick_time >= %s
|
||||
GROUP BY source, code
|
||||
ORDER BY n DESC
|
||||
LIMIT 10
|
||||
""",
|
||||
(SINCE,),
|
||||
).fetchall()
|
||||
print("\n--- ws_ticks 종목 TOP10 ---")
|
||||
for r in rows5:
|
||||
d = dict(r)
|
||||
print(f" {d['source']:12s} {d['code']} {d['n']:,}")
|
||||
|
||||
# --- ws_orderbook ---
|
||||
if "ws_orderbook" in [t for t in ["ws_orderbook"]]:
|
||||
ob_cols = cols(db, "ws_orderbook")
|
||||
print(f"\nws_orderbook cols sample: {ob_cols[:10]}")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT source, COUNT(*) n,
|
||||
MIN(snap_time) tmin, MAX(snap_time) tmax,
|
||||
COUNT(DISTINCT code) codes
|
||||
FROM ws_orderbook
|
||||
WHERE snap_time >= %s
|
||||
GROUP BY source
|
||||
ORDER BY n DESC
|
||||
""",
|
||||
(SINCE,),
|
||||
).fetchall()
|
||||
print("\n--- ws_orderbook 오늘 09:00~ ---")
|
||||
if not rows:
|
||||
print("(없음)")
|
||||
else:
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
print(
|
||||
f" {d['source']:16s} rows={d['n']:>7,} codes={d['codes']:>4} "
|
||||
f"{d['tmin']} ~ {d['tmax']}"
|
||||
)
|
||||
|
||||
# --- history ---
|
||||
if "target_candidates_history" in ["target_candidates_history"]:
|
||||
hcols = cols(db, "target_candidates_history")
|
||||
print(f"\ntarget_candidates_history event_time type: ", end="")
|
||||
for c in hcols:
|
||||
if c == "event_time":
|
||||
print("found")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT strategy_id, COUNT(*) n,
|
||||
MIN(event_time) tmin, MAX(event_time) tmax
|
||||
FROM target_candidates_history
|
||||
WHERE event_time >= %s
|
||||
GROUP BY strategy_id
|
||||
ORDER BY n DESC
|
||||
""",
|
||||
(SINCE,),
|
||||
).fetchall()
|
||||
print("\n--- target_candidates_history 오늘 ---")
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
print(
|
||||
f" {d['strategy_id']:12s} rows={d['n']:>5} "
|
||||
f"{d['tmin']} ~ {d['tmax']}"
|
||||
)
|
||||
|
||||
# --- ls_ws_candles if exists ---
|
||||
try:
|
||||
ccols = cols(db, "ls_ws_candles")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT COUNT(*) n, COUNT(DISTINCT code) codes,
|
||||
MIN(candle_time) tmin, MAX(candle_time) tmax
|
||||
FROM ls_ws_candles
|
||||
WHERE candle_time >= %s
|
||||
""",
|
||||
(SINCE,),
|
||||
).fetchone()
|
||||
d = dict(rows)
|
||||
print(f"\n--- ls_ws_candles 오늘: rows={d['n']:,} codes={d['codes']} {d['tmin']} ~ {d['tmax']} ---")
|
||||
except Exception as e:
|
||||
print(f"\n--- ls_ws_candles: skip ({e}) ---")
|
||||
|
||||
# --- active_trades / trades today ---
|
||||
for tbl in ("active_trades", "trade_history"):
|
||||
try:
|
||||
tcols = cols(db, tbl)
|
||||
sid_col = "strategy_id" if "strategy_id" in tcols else None
|
||||
if not sid_col:
|
||||
continue
|
||||
time_col = None
|
||||
for cand in ("buy_time", "created_at", "updated_at", "entry_time"):
|
||||
if cand in tcols:
|
||||
time_col = cand
|
||||
break
|
||||
if tbl == "active_trades":
|
||||
rows = db.conn.execute(
|
||||
f"SELECT strategy_id, code, buy_price, buy_time, qty FROM {tbl} ORDER BY buy_time DESC"
|
||||
).fetchall()
|
||||
print(f"\n--- {tbl} (현재 보유) ---")
|
||||
if not rows:
|
||||
print(" (없음)")
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
print(
|
||||
f" {d.get('strategy_id','?'):12s} {d.get('code','?')} "
|
||||
f"qty={d.get('qty')} buy={d.get('buy_price')} @ {d.get('buy_time')}"
|
||||
)
|
||||
elif time_col:
|
||||
rows = db.conn.execute(
|
||||
f"""
|
||||
SELECT strategy_id, COUNT(*) n
|
||||
FROM {tbl}
|
||||
WHERE {time_col} >= %s
|
||||
GROUP BY strategy_id
|
||||
ORDER BY n DESC
|
||||
""",
|
||||
(SINCE,),
|
||||
).fetchall()
|
||||
print(f"\n--- {tbl} 오늘 매매건 ({time_col}) ---")
|
||||
if not rows:
|
||||
print(" (없음)")
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
print(f" {d['strategy_id']:12s} n={d['n']}")
|
||||
except Exception as e:
|
||||
print(f"\n--- {tbl}: skip ({e}) ---")
|
||||
|
||||
# recent orders if table exists
|
||||
for tbl in ("orders", "order_log"):
|
||||
try:
|
||||
ocols = cols(db, tbl)
|
||||
if "strategy_id" not in ocols:
|
||||
continue
|
||||
tcol = "order_time" if "order_time" in ocols else "created_at"
|
||||
rows = db.conn.execute(
|
||||
f"""
|
||||
SELECT strategy_id, side, code, status, {tcol}
|
||||
FROM {tbl}
|
||||
WHERE {tcol} >= %s
|
||||
ORDER BY {tcol} DESC
|
||||
LIMIT 30
|
||||
""",
|
||||
(SINCE,),
|
||||
).fetchall()
|
||||
print(f"\n--- {tbl} 최근 30건 ---")
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
print(
|
||||
f" {d.get('strategy_id','?'):10s} {d.get('side','?'):4s} "
|
||||
f"{d.get('code','?')} {d.get('status','?')} @ {d.get(tcol)}"
|
||||
)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
100
scratch/tuesday_trades_env.py
Normal file
100
scratch/tuesday_trades_env.py
Normal file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
sys.path.insert(0, "/home/hoon/kis_bot")
|
||||
from database import TradeDB
|
||||
|
||||
SINCE = "2026-08-18 09:00:00"
|
||||
TICK_SINCE = "20260818090000"
|
||||
|
||||
db = TradeDB()
|
||||
try:
|
||||
print("=== active_trades ===")
|
||||
rows = db.conn.execute(
|
||||
"""SELECT strategy, code, name, status, current_qty, target_qty,
|
||||
avg_buy_price, buy_date, updated_at
|
||||
FROM active_trades ORDER BY updated_at DESC"""
|
||||
).fetchall()
|
||||
if not rows:
|
||||
print(" (없음)")
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
print(
|
||||
f" [{d['strategy']}] {d['code']} {d.get('name','')} "
|
||||
f"status={d['status']} qty={d['current_qty']}/{d['target_qty']} "
|
||||
f"avg={d['avg_buy_price']} buy={d['buy_date']} upd={d['updated_at']}"
|
||||
)
|
||||
|
||||
print("\n=== trade_history 오늘 (sell_date) ===")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT sell_date, strategy, code, name, qty, buy_price, sell_price, sell_reason
|
||||
FROM trade_history
|
||||
WHERE sell_date >= %s
|
||||
ORDER BY sell_date DESC
|
||||
LIMIT 30
|
||||
""",
|
||||
(SINCE,),
|
||||
).fetchall()
|
||||
if not rows:
|
||||
print(" (없음)")
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
print(
|
||||
f" {d['sell_date']} [{d['strategy']}] {d['code']} {d['name']} "
|
||||
f"qty={d['qty']} {d['buy_price']}→{d['sell_price']} {d['sell_reason']}"
|
||||
)
|
||||
|
||||
print("\n=== ws_ticks 오늘만 (09:00~) source별 ===")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT source, COUNT(*) n, COUNT(DISTINCT code) codes,
|
||||
MIN(tick_time) tmin, MAX(tick_time) tmax,
|
||||
MAX(recv_ts) max_recv
|
||||
FROM ws_ticks
|
||||
WHERE tick_time >= %s
|
||||
GROUP BY source
|
||||
""",
|
||||
(TICK_SINCE,),
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n=== ws_ticks 10:00 이후 source별 ===")
|
||||
rows = db.conn.execute(
|
||||
"""
|
||||
SELECT source, COUNT(*) n, MAX(tick_time) tmax, MAX(recv_ts) max_recv
|
||||
FROM ws_ticks
|
||||
WHERE tick_time >= '20260818100000'
|
||||
GROUP BY source
|
||||
""",
|
||||
).fetchall()
|
||||
for r in rows:
|
||||
print(dict(r))
|
||||
|
||||
print("\n=== SHORT history 오늘 ===")
|
||||
r = db.conn.execute(
|
||||
"""
|
||||
SELECT COUNT(*) n, MIN(event_time) tmin, MAX(event_time) tmax
|
||||
FROM target_candidates_history
|
||||
WHERE strategy_id='SHORT' AND event_time >= %s
|
||||
""",
|
||||
(SINCE,),
|
||||
).fetchone()
|
||||
print(dict(r))
|
||||
|
||||
print("\n=== env (수집) ===")
|
||||
for k in (
|
||||
"WS_TICK_SAVE_ENABLED",
|
||||
"WS_ORDERBOOK_SAVE_ENABLED",
|
||||
"WS_ORDERBOOK_COLLECT_ENABLED",
|
||||
"STRATEGY_SHORT_ENABLED",
|
||||
"STRATEGY_BREAKOUT_ENABLED",
|
||||
):
|
||||
row = db.conn.execute(
|
||||
"SELECT config_value FROM env_config_ext WHERE config_key=%s LIMIT 1", (k,)
|
||||
).fetchone()
|
||||
v = dict(row)["config_value"] if row else "(no row)"
|
||||
print(f" {k} = {v}")
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
Reference in New Issue
Block a user