215 lines
7.6 KiB
Python
Executable File
215 lines
7.6 KiB
Python
Executable File
#!/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())
|