ls증권 히스토리 구독 넣음
This commit is contained in:
214
scripts/post_optuna_mid_enroll_verify.py
Executable file
214
scripts/post_optuna_mid_enroll_verify.py
Executable file
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Optuna apply 이후 — 현재 DB로 웹정렬 백테 1회씩 + 요약 리포트.
|
||||
|
||||
.venv/bin/python -u scripts/post_optuna_mid_enroll_verify.py \\
|
||||
--start 2026-07-21 --end 2026-07-23 --report logs/report.txt
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
|
||||
def _run_kr_cli(strat: str, start: str, end: str) -> Dict[str, Any]:
|
||||
py = ROOT / ".venv" / "bin" / "python"
|
||||
if not py.exists():
|
||||
py = Path(sys.executable)
|
||||
out_json = ROOT / "logs" / f"verify_bt_{strat}_{int(time.time())}.json"
|
||||
cmd = [
|
||||
str(py), "-u", str(ROOT / "scripts" / "run_strategy_backtest_cli.py"),
|
||||
"--strategy", strat,
|
||||
"--start", start,
|
||||
"--end", end,
|
||||
"--universe", "history",
|
||||
]
|
||||
# CLI may print summary; also try to capture via env if supported
|
||||
p = subprocess.run(cmd, cwd=str(ROOT), capture_output=True, text=True, timeout=3600)
|
||||
text = (p.stdout or "") + "\n" + (p.stderr or "")
|
||||
summary: Dict[str, Any] = {
|
||||
"strategy": strat,
|
||||
"rc": p.returncode,
|
||||
"tail": "\n".join(text.strip().splitlines()[-40:]),
|
||||
}
|
||||
# parse common lines
|
||||
for line in text.splitlines():
|
||||
low = line.lower()
|
||||
if "total_pnl" in low or "총손익" in line or "trades=" in low or "거래" in line:
|
||||
summary.setdefault("hits", []).append(line.strip())
|
||||
out_json.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
summary["log_json"] = str(out_json)
|
||||
return summary
|
||||
|
||||
|
||||
def _run_us_web(start: str, end: str, symbol: str = "") -> Dict[str, Any]:
|
||||
"""US 웹백테 — Flask 핸들러와 동일 경로가 아니면 엔진 params+포트폴리오 요약만."""
|
||||
from database import TradeDB
|
||||
from kis_trader.utils.env import invalidate_merged_env_cache
|
||||
from kis_trader.backtest import scalping_backtest_common as sbc
|
||||
from kis_trader.backtest.us_momentum_web_backtest import (
|
||||
build_us_momentum_engine_params,
|
||||
load_us_candles,
|
||||
)
|
||||
from kis_trader.engine.momentum_engine import get_momentum_defaults_from_db
|
||||
|
||||
invalidate_merged_env_cache()
|
||||
db = TradeDB()
|
||||
ui_def = get_momentum_defaults_from_db()
|
||||
args: Dict[str, Any] = {"start": start, "end": end, "use_saved_history": True}
|
||||
if symbol:
|
||||
args["symbol"] = symbol
|
||||
args["use_stock_cfg"] = True
|
||||
try:
|
||||
from kis_trader.backtest.us_momentum_web_backtest import run_us_momentum_web_backtest
|
||||
out = run_us_momentum_web_backtest(args, db=db, ui_def=ui_def)
|
||||
trades = out.get("trades") or out.get("trade_list") or []
|
||||
if not isinstance(trades, list):
|
||||
trades = []
|
||||
pnl = sum(float(t.get("pnl") or 0) for t in trades)
|
||||
wins = sum(1 for t in trades if float(t.get("pnl") or 0) > 0)
|
||||
n = len(trades)
|
||||
return {
|
||||
"strategy": f"us_momentum:{symbol or 'GLOBAL'}",
|
||||
"rc": 0,
|
||||
"trades": n,
|
||||
"wins": wins,
|
||||
"pnl": pnl,
|
||||
"wr": (wins / n * 100.0) if n else 0.0,
|
||||
}
|
||||
except Exception as e:
|
||||
# 최소 로드 스모크
|
||||
filt = [symbol] if symbol else None
|
||||
codes_candles, _, _, codes = load_us_candles(db, start, end, codes_filter=filt)
|
||||
return {
|
||||
"strategy": f"us_momentum:{symbol or 'GLOBAL'}",
|
||||
"rc": 1,
|
||||
"error": str(e),
|
||||
"codes": len(codes or []),
|
||||
"candle_codes": len(codes_candles or {}),
|
||||
"trades": 0,
|
||||
"wins": 0,
|
||||
"pnl": 0.0,
|
||||
"wr": 0.0,
|
||||
}
|
||||
|
||||
|
||||
def _latest_optuna_json(prefix: str) -> str:
|
||||
d = ROOT / "kis_trader" / "backtest" / "results"
|
||||
files = sorted(d.glob(f"optuna_{prefix}*.json"), key=lambda p: p.stat().st_mtime)
|
||||
return str(files[-1]) if files else ""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--start", required=True)
|
||||
ap.add_argument("--end", required=True)
|
||||
ap.add_argument("--report", required=True)
|
||||
ap.add_argument(
|
||||
"--us-only",
|
||||
action="store_true",
|
||||
help="해외 모멘텀 스모크만 (국장 CLI 스킵)",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
lines: List[str] = []
|
||||
lines.append(f"# mid-enroll verify {args.start}..{args.end}")
|
||||
lines.append(f"ts={time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
if args.us_only:
|
||||
lines.append("scope=us_only")
|
||||
lines.append("")
|
||||
|
||||
# 최근 Optuna JSON 경로
|
||||
prefs = [
|
||||
"us_momentum_tpe_",
|
||||
"us_momentum_tpe_fee_",
|
||||
"us_momentum_QQQM_",
|
||||
"us_momentum_SPCX_",
|
||||
"us_momentum_SPYM_",
|
||||
"us_momentum_TSLA_",
|
||||
]
|
||||
if not args.us_only:
|
||||
prefs.extend(
|
||||
(
|
||||
"momentum_tpe_",
|
||||
"tail_tpe_",
|
||||
"breakout_tpe_",
|
||||
"scalp_tpe_",
|
||||
)
|
||||
)
|
||||
for pref in prefs:
|
||||
p = _latest_optuna_json(pref)
|
||||
if p:
|
||||
lines.append(f"json:{pref} -> {p}")
|
||||
|
||||
if not args.us_only:
|
||||
lines.append("")
|
||||
lines.append("## KR web-aligned BT (current DB after apply)")
|
||||
for strat in ("momentum", "tail", "breakout", "scalp"):
|
||||
print(f"[verify] KR {strat} ...", flush=True)
|
||||
try:
|
||||
s = _run_kr_cli(strat, args.start, args.end)
|
||||
lines.append(f"### {strat} rc={s.get('rc')}")
|
||||
for h in s.get("hits") or []:
|
||||
lines.append(f" {h}")
|
||||
lines.append(f" log={s.get('log_json')}")
|
||||
except Exception as e:
|
||||
lines.append(f"### {strat} ERR {e}")
|
||||
|
||||
lines.append("")
|
||||
lines.append("## US web BT (global + cfg symbols)")
|
||||
for sym in ("", "QQQM", "SPCX", "SPYM", "TSLA"):
|
||||
tag = sym or "GLOBAL"
|
||||
print(f"[verify] US {tag} ...", flush=True)
|
||||
try:
|
||||
s = _run_us_web(args.start, args.end, sym)
|
||||
lines.append(
|
||||
f"### us:{tag} trades={s.get('trades')} wr={s.get('wr'):.1f}% "
|
||||
f"pnl={s.get('pnl'):.4f}"
|
||||
)
|
||||
if s.get("error"):
|
||||
lines.append(f" error={s.get('error')}")
|
||||
except Exception as e:
|
||||
lines.append(f"### us:{tag} ERR {e}")
|
||||
|
||||
# 실매 봇 재시작 (apply 반영)
|
||||
lines.append("")
|
||||
lines.append("## restart kis_trader_main")
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["sudo", "systemctl", "restart", "kis_trader_main.service"],
|
||||
capture_output=True, text=True, timeout=120,
|
||||
)
|
||||
time.sleep(2)
|
||||
a = subprocess.run(
|
||||
["systemctl", "is-active", "kis_trader_main.service"],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
)
|
||||
lines.append(f"restart_rc={r.returncode} active={a.stdout.strip()}")
|
||||
except Exception as e:
|
||||
lines.append(f"restart ERR {e}")
|
||||
|
||||
# 실매 정합 메모
|
||||
lines.append("")
|
||||
lines.append("## live match notes")
|
||||
lines.append("- ALIGN_DEFER_MID_BAR_ENROLL + LIVE_ALIGN_ENTRY_FROM_TICK_BUFFER 가 실매·백테 공통")
|
||||
lines.append("- 장중 실매 체결 로그의 entry_src=ws_ticks|ohlc_open / 탈락-중편입시가애매 로 확인")
|
||||
lines.append("- 미장 10시(22:00 KST) 전 us apply 완료 여부: master 로그 US 구간 END 시각 확인")
|
||||
|
||||
report = Path(args.report)
|
||||
report.parent.mkdir(parents=True, exist_ok=True)
|
||||
report.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
print("\n".join(lines), flush=True)
|
||||
print(f"REPORT={report}", flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user