이번에 들어간 내용
한투 호가 = 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:
@@ -2,9 +2,8 @@
|
||||
"""
|
||||
scripts/apply_optuna_ob_consensus.py
|
||||
====================================
|
||||
1,000회 고속 Optuna 탐색으로 도출된 전략별 호가 수급 합의 파라미터(Consensus)를
|
||||
실제 DB(MariaDB)의 환경변수 스키마에 안전하게 반영하고,
|
||||
'get_env_from_db' 규칙에 따라 100% 재조회 검증을 마치는 통합 반영 스크립트입니다.
|
||||
Optuna 호가 후처리 합의(8방 중 best 또는 구JSON 축분리)를 DB env에 반영.
|
||||
실행 시 recommend_orderbook_parameters()를 **새로 돌림** — JSON postprocess_consensus 와 별개.
|
||||
|
||||
실행 예시:
|
||||
.venv/bin/python3 scripts/apply_optuna_ob_consensus.py --strategy BREAKOUT
|
||||
@@ -30,7 +29,7 @@ from kis_trader.backtest.optuna_orderbook_recommend import (
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="Optuna 호가 합의 파라미터 DB 반영 CLI")
|
||||
ap.add_argument("--strategy", default="MOMENTUM", help="반영 대상 전략명 (MOMENTUM, BREAKOUT, SCALP, TAIL)")
|
||||
ap.add_argument("--n-trials", type=int, default=1000, help="탐색 횟수 (기본 1000회)")
|
||||
ap.add_argument("--n-trials", type=int, default=500, help="탐색 횟수 (기본 500회)")
|
||||
args = ap.parse_args()
|
||||
|
||||
strat = args.strategy.upper()
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
"""
|
||||
scripts/optuna_ob_filter.py
|
||||
===========================
|
||||
실매매 체결 내역과 호가 스냅샷을 연계하여 1,000회 고속 Optuna 탐색을 통해
|
||||
실매매 체결 내역과 호가 스냅샷을 연계하여 축당 500회 고속 Optuna 탐색을 통해
|
||||
진입 호가필터 & 수익구간 호가매도(C가드) 최적 합의 수치를 도출하고 검증하는 CLI 툴입니다.
|
||||
이제 본체 kis_trader.backtest.optuna_orderbook_recommend 모듈과 100% 동일한 로직을 실행합니다.
|
||||
|
||||
실행 예시:
|
||||
.venv/bin/python3 scripts/optuna_ob_filter.py --strategy MOMENTUM --n-trials 1000
|
||||
.venv/bin/python3 scripts/optuna_ob_filter.py --strategy MOMENTUM --n-trials 500
|
||||
.venv/bin/python3 scripts/optuna_ob_filter.py --strategy BREAKOUT --n-trials 1000
|
||||
"""
|
||||
from __future__ import annotations
|
||||
@@ -33,7 +33,7 @@ logger = logging.getLogger("OptunaOBFilterCLI")
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="Optuna 호가 수급 필터 고속 탐색 CLI")
|
||||
ap.add_argument("--strategy", default="MOMENTUM", help="대상 전략명 (예: MOMENTUM, BREAKOUT)")
|
||||
ap.add_argument("--n-trials", type=int, default=1000, help="Optuna 최적화 탐색 횟수 (기본 1000회)")
|
||||
ap.add_argument("--n-trials", type=int, default=500, help="Optuna 최적화 탐색 횟수 (기본 500회)")
|
||||
ap.add_argument("--ob-table", default=None, help="호가 테이블명 (미지정 시 전략명에 맞춰 자동 판별)")
|
||||
args = ap.parse_args()
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ STRATEGIES="${STRATEGIES:-momentum tail breakout scalp}"
|
||||
# 꼬리 TPE 진입모드 — 한 스터디에 섞지 않음. 공백 구분 시 순차 2회.
|
||||
# TAIL_OPTUNA_ENTRY_MODES="align limit_atr"
|
||||
TAIL_OPTUNA_ENTRY_MODES="${TAIL_OPTUNA_ENTRY_MODES:-align}"
|
||||
# 돌파 TPE 손절모드 — 한 스터디에 섞지 않음. 공백 구분 시 순차 2회.
|
||||
# BREAKOUT_OPTUNA_SL_MODES="fixed atr"
|
||||
BREAKOUT_OPTUNA_SL_MODES="${BREAKOUT_OPTUNA_SL_MODES:-fixed}"
|
||||
# kiwoom|ls — 웹 Optuna 이력소스 / CLI UNIVERSE_HISTORY_SOURCE
|
||||
UNIVERSE_HISTORY_SOURCE="${UNIVERSE_HISTORY_SOURCE:-${BACKTEST_UNIVERSE_HISTORY_SOURCE:-kiwoom}}"
|
||||
PY="${PY:-.venv/bin/python}"
|
||||
@@ -40,6 +43,7 @@ MASTER="logs/optuna_4strat_tpe_${START}_${END}_${TS0}_master.log"
|
||||
echo "START=$START END=$END MODE=$MODE TRIALS=$TRIALS"
|
||||
echo "STRATEGIES=$STRATEGIES"
|
||||
echo "TAIL_OPTUNA_ENTRY_MODES=$TAIL_OPTUNA_ENTRY_MODES"
|
||||
echo "BREAKOUT_OPTUNA_SL_MODES=$BREAKOUT_OPTUNA_SL_MODES"
|
||||
echo "UNIVERSE_HISTORY_SOURCE=$UNIVERSE_HISTORY_SOURCE"
|
||||
echo "min_wr=$MIN_WIN_RATE min_pf=$MIN_PF min_trades=$MIN_TRADES"
|
||||
echo "apply-best=OFF orderbook=off n_jobs=1 (사후 results_gated + briefing.md)"
|
||||
@@ -52,6 +56,7 @@ echo "$MASTER" > logs/optuna_4strat_tpe_latest_master.logpath
|
||||
run_one() {
|
||||
local strat="$1"
|
||||
local entry_mode="${2:-}"
|
||||
local sl_mode="${3:-}"
|
||||
local ts study log sort_by
|
||||
ts="$(date +%Y%m%d_%H%M%S)"
|
||||
study="${strat}_tpe_${START//-/}_${END//-/}_${ts}"
|
||||
@@ -60,6 +65,10 @@ run_one() {
|
||||
study="${strat}_${entry_mode}_tpe_${START//-/}_${END//-/}_${ts}"
|
||||
log="logs/optuna_${strat}_${entry_mode}_tpe_${ts}.log"
|
||||
fi
|
||||
if [[ "$strat" == "breakout" && -n "$sl_mode" ]]; then
|
||||
study="${strat}_${sl_mode}_tpe_${START//-/}_${END//-/}_${ts}"
|
||||
log="logs/optuna_${strat}_${sl_mode}_tpe_${ts}.log"
|
||||
fi
|
||||
sort_by="pnl"
|
||||
case "$strat" in
|
||||
momentum|us_momentum|scalp) sort_by="score" ;;
|
||||
@@ -67,7 +76,7 @@ run_one() {
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "-------- [$strat${entry_mode:+/$entry_mode}] START $(date -Is) study=$study univ=$UNIVERSE_HISTORY_SOURCE --------"
|
||||
echo "-------- [$strat${entry_mode:+/$entry_mode}${sl_mode:+/$sl_mode}] START $(date -Is) study=$study univ=$UNIVERSE_HISTORY_SOURCE --------"
|
||||
} | tee -a "$MASTER"
|
||||
echo "$log" > "logs/optuna_${strat}_tpe_latest.logpath"
|
||||
echo "$study" > "logs/optuna_${strat}_tpe_latest.study"
|
||||
@@ -92,6 +101,9 @@ run_one() {
|
||||
if [[ "$strat" == "tail" && -n "$entry_mode" ]]; then
|
||||
cmd_args+=(--entry-mode "$entry_mode")
|
||||
fi
|
||||
if [[ "$strat" == "breakout" && -n "$sl_mode" ]]; then
|
||||
cmd_args+=(--sl-mode "$sl_mode")
|
||||
fi
|
||||
|
||||
if [[ -n "${TICK_SOURCE:-}" ]]; then
|
||||
cmd_args+=("--tick-source" "$TICK_SOURCE")
|
||||
@@ -105,7 +117,7 @@ run_one() {
|
||||
set -e
|
||||
|
||||
{
|
||||
echo "-------- [$strat${entry_mode:+/$entry_mode}] END rc=$rc $(date -Is) --------"
|
||||
echo "-------- [$strat${entry_mode:+/$entry_mode}${sl_mode:+/$sl_mode}] END rc=$rc $(date -Is) --------"
|
||||
echo "LOG=$log"
|
||||
grep -E 'OPTUNA_RESULT_JSON=|OPTUNA_BRIEFING_MD=|Best trial|optuna_best|❌|KeyError|Traceback' "$log" | tail -n 24 || true
|
||||
} | tee -a "$MASTER"
|
||||
@@ -121,6 +133,10 @@ for s in $STRATEGIES; do
|
||||
for em in $TAIL_OPTUNA_ENTRY_MODES; do
|
||||
run_one tail "$em"
|
||||
done
|
||||
elif [[ "$s" == "breakout" ]]; then
|
||||
for sm in $BREAKOUT_OPTUNA_SL_MODES; do
|
||||
run_one breakout "" "$sm"
|
||||
done
|
||||
else
|
||||
run_one "$s"
|
||||
fi
|
||||
|
||||
327
scripts/test_ws_tick_kis_kiwoom_gap.py
Normal file
327
scripts/test_ws_tick_kis_kiwoom_gap.py
Normal file
@@ -0,0 +1,327 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
scripts/test_ws_tick_kis_kiwoom_gap.py
|
||||
=====================================
|
||||
한투(kis) vs 키움(kiwoom) ``ws_ticks`` 밀도·틱타임/수신갭 비교.
|
||||
|
||||
실 WS/주문 호출 없음. DB 조회만.
|
||||
틱매도 비동기 적용 후 ~1시간 뒤, 매도 시각 전후로
|
||||
「키움만 비고 한투는 쌓임」이 줄었는지 볼 때 사용.
|
||||
|
||||
python3 -u scripts/test_ws_tick_kis_kiwoom_gap.py
|
||||
python3 -u scripts/test_ws_tick_kis_kiwoom_gap.py --minutes 60
|
||||
python3 -u scripts/test_ws_tick_kis_kiwoom_gap.py --start '2026-08-19 09:00:00' --end '2026-08-19 10:00:00'
|
||||
python3 -u scripts/test_ws_tick_kis_kiwoom_gap.py --code 025980 --minutes 60
|
||||
|
||||
로그: logs/test_ws_tick_kis_kiwoom_gap_YYYYMMDD_HHMMSS.log
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
|
||||
def _tee_log() -> Path:
|
||||
log_dir = ROOT / "logs"
|
||||
log_dir.mkdir(exist_ok=True)
|
||||
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
path = log_dir / ("test_ws_tick_kis_kiwoom_gap_%s.log" % ts)
|
||||
fp = path.open("w", encoding="utf-8")
|
||||
|
||||
class _Tee:
|
||||
def __init__(self, *files: Any) -> None:
|
||||
self.files = files
|
||||
|
||||
def write(self, s: str) -> None:
|
||||
for f in self.files:
|
||||
f.write(s)
|
||||
f.flush()
|
||||
|
||||
def flush(self) -> None:
|
||||
for f in self.files:
|
||||
f.flush()
|
||||
|
||||
sys.stdout = _Tee(sys.__stdout__, fp) # type: ignore[assignment]
|
||||
sys.stderr = _Tee(sys.__stderr__, fp) # type: ignore[assignment]
|
||||
print("로그 파일:", path)
|
||||
return path
|
||||
|
||||
|
||||
def _cols(db: Any, table: str) -> List[str]:
|
||||
rows = db.conn.execute("SHOW COLUMNS FROM `%s`" % table).fetchall()
|
||||
return [str(r["Field"]) for r in rows]
|
||||
|
||||
|
||||
def _parse_ts(raw: Any) -> Optional[datetime]:
|
||||
s = str(raw or "").strip()
|
||||
if not s:
|
||||
return None
|
||||
for fmt in (
|
||||
"%Y-%m-%d %H:%M:%S.%f",
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
"%Y%m%d%H%M%S",
|
||||
):
|
||||
try:
|
||||
return datetime.strptime(s[:26] if "." in s else s[:19], fmt)
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
return datetime.fromisoformat(s.replace("Z", ""))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _gaps(ts_list: List[datetime]) -> List[float]:
|
||||
if len(ts_list) < 2:
|
||||
return []
|
||||
ordered = sorted(ts_list)
|
||||
out: List[float] = []
|
||||
for i in range(1, len(ordered)):
|
||||
out.append((ordered[i] - ordered[i - 1]).total_seconds())
|
||||
return out
|
||||
|
||||
|
||||
def _gap_stats(gaps: List[float]) -> Dict[str, Any]:
|
||||
if not gaps:
|
||||
return {"n": 0, "max": None, "p50": None, "gt2": 0, "gt5": 0}
|
||||
sg = sorted(gaps)
|
||||
n = len(sg)
|
||||
p50 = sg[n // 2]
|
||||
return {
|
||||
"n": n,
|
||||
"max": round(max(sg), 3),
|
||||
"p50": round(p50, 3),
|
||||
"gt2": sum(1 for x in sg if x >= 2.0),
|
||||
"gt5": sum(1 for x in sg if x >= 5.0),
|
||||
}
|
||||
|
||||
|
||||
def _pick_time_col(cols: List[str]) -> Optional[str]:
|
||||
for c in (
|
||||
"created_at", "updated_at", "order_time", "ord_time",
|
||||
"submitted_at", "ts", "insert_time",
|
||||
):
|
||||
if c in cols:
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(description="kis vs kiwoom ws_ticks 밀도·갭")
|
||||
ap.add_argument("--minutes", type=int, default=60, help="끝시각 기준 최근 N분 (start/end 없을 때)")
|
||||
ap.add_argument("--start", default="", help="YYYY-MM-DD HH:MM:SS")
|
||||
ap.add_argument("--end", default="", help="YYYY-MM-DD HH:MM:SS")
|
||||
ap.add_argument("--code", default="", help="6자리 종목. 비우면 겹치는 종목 상위")
|
||||
ap.add_argument("--limit-codes", type=int, default=15, help="겹치는 종목 리포트 개수")
|
||||
args = ap.parse_args()
|
||||
|
||||
now = datetime.now()
|
||||
if str(args.start or "").strip() and str(args.end or "").strip():
|
||||
start_s = str(args.start).strip()
|
||||
end_s = str(args.end).strip()
|
||||
else:
|
||||
end_dt = now
|
||||
start_dt = end_dt - timedelta(minutes=max(1, int(args.minutes or 60)))
|
||||
start_s = start_dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
end_s = end_dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
from database import TradeDB
|
||||
|
||||
db = TradeDB()
|
||||
print("구간:", start_s, "~", end_s)
|
||||
print("code:", args.code or "(겹치는 종목 자동)")
|
||||
|
||||
tick_cols = _cols(db, "ws_ticks")
|
||||
print("SHOW COLUMNS ws_ticks:", tick_cols)
|
||||
need = {"code", "source", "recv_ts"}
|
||||
missing = sorted(need - set(tick_cols))
|
||||
if missing:
|
||||
print("실패: ws_ticks 컬럼 없음", missing)
|
||||
return 1
|
||||
has_tick_time = "tick_time" in tick_cols
|
||||
|
||||
# 전체 종목수·행수
|
||||
tot = db.conn.execute(
|
||||
"""
|
||||
SELECT
|
||||
COUNT(*) n,
|
||||
SUM(source='kiwoom') n_kw,
|
||||
SUM(source='kis') n_kis,
|
||||
COUNT(DISTINCT CASE WHEN source='kiwoom' THEN code END) codes_kw,
|
||||
COUNT(DISTINCT CASE WHEN source='kis' THEN code END) codes_kis,
|
||||
COUNT(DISTINCT code) codes_either
|
||||
FROM ws_ticks
|
||||
WHERE recv_ts >= %s AND recv_ts < %s AND source IN ('kiwoom','kis')
|
||||
""",
|
||||
(start_s, end_s),
|
||||
).fetchone()
|
||||
print("\n== 구간 합계 ==")
|
||||
print(dict(tot or {}))
|
||||
n_kw = int((tot or {}).get("n_kw") or 0)
|
||||
n_kis = int((tot or {}).get("n_kis") or 0)
|
||||
ratio = round(n_kis / n_kw, 3) if n_kw else None
|
||||
print("kis/kiwoom 행수비:", ratio, "(1에 가까울수록 밀도 비슷. 예전에 ~1.7배면 한투가 더 촘촘)")
|
||||
|
||||
split = 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
|
||||
""",
|
||||
(start_s, end_s),
|
||||
).fetchone()
|
||||
print("종목 커버 키움만/한투만/둘다:", dict(split or {}))
|
||||
|
||||
code_filter = str(args.code or "").strip()
|
||||
if code_filter:
|
||||
codes = [code_filter]
|
||||
else:
|
||||
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') / GREATEST(SUM(source='kiwoom'), 1)) DESC
|
||||
LIMIT %s
|
||||
""",
|
||||
(start_s, end_s, int(args.limit_codes)),
|
||||
).fetchall()
|
||||
codes = [str(r["code"]) for r in rows]
|
||||
print("\n== 겹치는 종목 kis/kw 비 상위 ==")
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
kw = int(d.get("n_kw") or 0)
|
||||
ki = int(d.get("n_kis") or 0)
|
||||
d["kis_per_kw"] = round(ki / kw, 2) if kw else None
|
||||
print(d)
|
||||
|
||||
print("\n== 종목별 recv_ts 갭 (키움 vs 한투) ==")
|
||||
stall_codes = []
|
||||
for code in codes:
|
||||
raw = db.conn.execute(
|
||||
"""
|
||||
SELECT source, recv_ts, tick_time
|
||||
FROM ws_ticks
|
||||
WHERE code=%s AND recv_ts >= %s AND recv_ts < %s
|
||||
AND source IN ('kiwoom','kis')
|
||||
ORDER BY recv_ts
|
||||
""",
|
||||
(code, start_s, end_s),
|
||||
).fetchall() if has_tick_time else db.conn.execute(
|
||||
"""
|
||||
SELECT source, recv_ts
|
||||
FROM ws_ticks
|
||||
WHERE code=%s AND recv_ts >= %s AND recv_ts < %s
|
||||
AND source IN ('kiwoom','kis')
|
||||
ORDER BY recv_ts
|
||||
""",
|
||||
(code, start_s, end_s),
|
||||
).fetchall()
|
||||
by_src: Dict[str, List[datetime]] = {"kiwoom": [], "kis": []}
|
||||
by_fid: Dict[str, List[str]] = {"kiwoom": [], "kis": []}
|
||||
for r in raw:
|
||||
src = str(r.get("source") or "")
|
||||
dtv = _parse_ts(r.get("recv_ts"))
|
||||
if src in by_src and dtv is not None:
|
||||
by_src[src].append(dtv)
|
||||
if has_tick_time and src in by_fid:
|
||||
by_fid[src].append(str(r.get("tick_time") or ""))
|
||||
kw_g = _gap_stats(_gaps(by_src["kiwoom"]))
|
||||
ki_g = _gap_stats(_gaps(by_src["kis"]))
|
||||
rec = {
|
||||
"code": code,
|
||||
"n_kw": len(by_src["kiwoom"]),
|
||||
"n_kis": len(by_src["kis"]),
|
||||
"kis_per_kw": round(len(by_src["kis"]) / len(by_src["kiwoom"]), 2) if by_src["kiwoom"] else None,
|
||||
"kw_recv_gap": kw_g,
|
||||
"kis_recv_gap": ki_g,
|
||||
}
|
||||
# 키움 max gap 이 한투보다 훨씬 크면 수신 스톨 후보
|
||||
kw_max = kw_g.get("max")
|
||||
ki_max = ki_g.get("max")
|
||||
if kw_max is not None and ki_max is not None and kw_max >= 5.0 and kw_max > (ki_max * 2.0 + 1.0):
|
||||
rec["stall_candidate"] = True
|
||||
stall_codes.append(rec)
|
||||
print(rec)
|
||||
|
||||
if has_tick_time and (args.code or rec.get("stall_candidate")):
|
||||
# FID tick_time 이 같은 초로 밀집 vs recv 공백
|
||||
print(" tick_time 샘플 kw", by_fid["kiwoom"][:3], "...", by_fid["kiwoom"][-3:] if by_fid["kiwoom"] else [])
|
||||
print(" tick_time 샘플 kis", by_fid["kis"][:3], "...", by_fid["kis"][-3:] if by_fid["kis"] else [])
|
||||
|
||||
print("\n== 스톨 후보 (키움 recv 갭>=5s 이고 한투 갭의 2배+1 초과) ==")
|
||||
if not stall_codes:
|
||||
print("(없음)")
|
||||
else:
|
||||
for r in stall_codes:
|
||||
print(r)
|
||||
|
||||
# 매도 시각 근처
|
||||
try:
|
||||
ocols = _cols(db, "orders")
|
||||
print("\nSHOW COLUMNS orders:", ocols)
|
||||
tcol = _pick_time_col(ocols)
|
||||
side_col = "side" if "side" in ocols else ("ord_side" if "ord_side" in ocols else None)
|
||||
code_col = "code" if "code" in ocols else None
|
||||
if tcol and side_col and code_col:
|
||||
for c in (tcol, side_col, code_col):
|
||||
if not str(c).replace("_", "").isalnum():
|
||||
raise ValueError("bad col %s" % c)
|
||||
sql = (
|
||||
"SELECT `" + tcol + "` AS ts, `" + code_col + "` AS code, `"
|
||||
+ side_col + "` AS side FROM orders WHERE `" + tcol
|
||||
+ "` >= %s AND `" + tcol + "` < %s"
|
||||
)
|
||||
sells = db.conn.execute(sql, (start_s, end_s)).fetchall()
|
||||
sell_rows = [dict(r) for r in sells if str(r.get("side") or "").upper() in ("SELL", "01", "sell")]
|
||||
if not sell_rows:
|
||||
sell_rows = [dict(r) for r in sells]
|
||||
print("orders 구간 행", len(sells), "매도후보", len(sell_rows), "시각컬럼", tcol)
|
||||
for r in sell_rows[:20]:
|
||||
print(" ", r)
|
||||
else:
|
||||
print("orders 에서 시각/side/code 컬럼을 못 찾음 — 매도 교차는 스킵")
|
||||
except Exception as ex:
|
||||
print("orders 조회 스킵:", ex)
|
||||
|
||||
print("\n판독 힌트:")
|
||||
print("- kis/kw 행수비가 1.5~2+ 이고 키움만 max recv 갭이 크면, 예전처럼 키움 수신이 멈춘 패턴.")
|
||||
print("- 틱매도 비동기 적용 후 매도 시각 전후 키움 갭이 한투와 비슷해지면 1~5 효과.")
|
||||
print("- 한투만 종목이 많으면 구독 유니버스 차이(41 vs 100)이지 스톨이 아님.")
|
||||
print("로그 파일은 이 실행 stdout 과 동일")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_tee_log()
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except SystemExit:
|
||||
raise
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
raise SystemExit(1)
|
||||
Reference in New Issue
Block a user