#!/usr/bin/env python3 """Optuna JSON(#199·mode) vs 동일 엔진 재평가 — DB 미변경. Usage: python3 -u scripts/momentum_optuna_parity_reeval.py \\ --json kis_trader/backtest/results/optuna_momentum_tpe_20260821_220227.json \\ --out logs/momentum_optuna_parity_OUT.json """ from __future__ import annotations import argparse import json import time import traceback from pathlib import Path def main() -> int: ap = argparse.ArgumentParser() ap.add_argument( "--json", default="kis_trader/backtest/results/optuna_momentum_tpe_20260821_220227.json", ) ap.add_argument("--out", required=True) args = ap.parse_args() t0 = time.time() json_path = Path(args.json) out_path = Path(args.out) d = json.loads(json_path.read_text()) start, end = d["start"], d["end"] slot = float(d["slot_money"]) max_stocks = int(d["max_stocks"]) budget = float(d["total_budget_krw"]) grid_keys = list(d["grid_keys"]) t199 = next(x for x in d["results_all"] if x.get("optuna_trial_number") == 199) mode_params = dict(d["mode_combo"]["params"]) p199 = dict(t199["params"]) from kis_trader.backtest.optuna_momentum import prepare_momentum_search_context from kis_trader.backtest.param_search_momentum import evaluate_momentum_param_combo print( f"prepare {start}~{end} slot={slot} max={max_stocks} budget={budget} OB=off", flush=True, ) ctx = prepare_momentum_search_context( start, end, "tpe", slot_money=slot, max_stocks=max_stocks, total_budget_krw=budget, orderbook_filter="off", market="KR", ) if ctx is None: print("prepare failed", flush=True) return 1 # Optuna 학습과 동일: 호가 OFF. 휩쏘 키는 있으면 OFF (DB ON이어도 비교용 고정) base = dict(ctx.base_fixed) base["_orderbook_filter_enabled"] = False for k in list(base.keys()): if "whipsaw" in k.lower() and isinstance(base[k], (bool, int)): base[k] = False if isinstance(base[k], bool) else 0 print( f"ctx ready candles={len(ctx.codes_candles)} " f"ticks={'yes' if ctx.ticks_by_code else 'no'} " f"ob_snaps={len(ctx.orderbook_by_code)} fee={ctx.fee_rate} tax={ctx.sell_tax}", flush=True, ) print( f"base tick_entry={base.get('backtest_use_tick_entry')} " f"tick_exit={base.get('backtest_use_tick_exit')} " f"ob={base.get('_orderbook_filter_enabled')} " f"whip_keys={[k for k in base if 'whip' in k.lower()]}", flush=True, ) def run_one(label, combo, recorded): print(f"--- eval {label} ---", flush=True) r = evaluate_momentum_param_combo( combo, base_fixed=base, grid_keys=grid_keys, codes_candles=ctx.codes_candles, min_trades=1, min_win_rate=0.0, min_pf=0.0, universe_by_slot=ctx.universe_by_slot, slot_money=ctx.slot_money, max_stocks=ctx.max_stocks, total_budget_krw=ctx.total_budget_krw, fee_rate=ctx.fee_rate, sell_tax=ctx.sell_tax, period_days=ctx.period_days, cache_holder=ctx.cache_holder, ticks_by_code=ctx.ticks_by_code, orderbook_by_code=ctx.orderbook_by_code, program_by_code=ctx.program_by_code, log_verdict_by_code=ctx.log_verdict_by_code, start_key=ctx.start_key, end_key=ctx.end_key, include_trades=False, ) if not r: return {"label": label, "ok": False, "error": "evaluate returned None"} out = { "label": label, "ok": True, "reeval": { "total_pnl": r.get("total_pnl"), "total_trades": r.get("total_trades"), "win_rate": r.get("win_rate"), "pf": r.get("pf"), "mdd": r.get("mdd"), }, "recorded": recorded, "delta_pnl": float(r.get("total_pnl") or 0) - float(recorded.get("total_pnl") or 0), "delta_trades": int(r.get("total_trades") or 0) - int(recorded.get("total_trades") or 0), "match": ( int(r.get("total_pnl") or 0) == int(recorded.get("total_pnl") or 0) and int(r.get("total_trades") or 0) == int(recorded.get("total_trades") or 0) ), } print(json.dumps(out, ensure_ascii=False), flush=True) return out rows = [ run_one( "#199", p199, { "total_pnl": t199["total_pnl"], "total_trades": t199["total_trades"], "win_rate": t199["win_rate"], "pf": t199["pf"], }, ), run_one( "mode_combo", mode_params, { "total_pnl": d["mode_combo"]["backtest"]["total_pnl"], "total_trades": d["mode_combo"]["backtest"]["total_trades"], "win_rate": d["mode_combo"]["backtest"]["win_rate"], "pf": d["mode_combo"]["backtest"]["pf"], }, ), ] report = { "source_json": str(json_path), "period": {"start": start, "end": end}, "portfolio": { "slot_money": slot, "max_stocks": max_stocks, "total_budget_krw": budget, }, "orderbook": "off", "db_touched": False, "elapsed_sec": round(time.time() - t0, 1), "comparisons": rows, "note": ( "Optuna evaluate 경로 재실행" "(웹엔진=run_momentum_backtest_web_aligned). DB 미변경." ), "web_bt_note": ( "지금 웹폼(호가ON+휩쏘ON)과 숫자 비교가 아님. " "정합=JSON기록 vs 동일조건 재시뮬." ), } out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(json.dumps(report, ensure_ascii=False, indent=2)) print("WROTE", out_path, flush=True) print( "FINAL", json.dumps( { r["label"]: { "match": r.get("match"), "delta_pnl": r.get("delta_pnl"), "delta_trades": r.get("delta_trades"), "reeval": r.get("reeval"), } for r in rows }, ensure_ascii=False, ), flush=True, ) return 0 if all(r.get("match") for r in rows if r.get("ok")) else 2 if __name__ == "__main__": try: raise SystemExit(main()) except Exception: traceback.print_exc() raise SystemExit(1)