이번에 들어간 내용

한투 호가 = 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:
Your Name
2026-08-19 22:11:31 +09:00
parent d1dc274f0e
commit 0ecac7cb95
229 changed files with 8487 additions and 1647 deletions

View 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))

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

View 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에 안 들어간 것)")

View 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))

View 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,
})

View 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))

View 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))

View 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]])

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

View 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)}")

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

View 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))

View 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))

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

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

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