#!/usr/bin/env python3 """Optuna 결과 JSON vs 현재 DB — 백테 비교표를 로그 파일 끝에 append (DB 미저장).""" from __future__ import annotations import argparse import json 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.backtest import tail_backtest_common as tbc from kis_trader.engine import tail_engine as te COMPARE_KEYS = [ "entry_mode", "min_drop_rate", "min_recovery_ratio", "tail_ratio_min", "tail_pct_min", "max_rec_3m", "shoulder_min_high", "shoulder_cut_pct", "stop_atr_mult", "target_atr_mult", "atr_sl_min_pct", "atr_sl_max_pct", "atr_tp_min_pct", "atr_tp_max_pct", "max_daily_change", "max_loss_krw", "limit_atr_mult", "tail_vol_mult", "tail_vol_win", "ratchet_tiers", "rsi_threshold", "cooldown_min", "bar_chg_min_pct", "bar_chg_max_pct", "symbol_daily_loss_limit_krw", "symbol_daily_loss_limit_pct", "reentry_min_edge_krw", "reentry_require_nonneg", "pattern_pin", "pattern_engulfing", "pattern_piercing", "max_daily", "max_spread_pct", "min_bid_ask_ratio", ] def _same(a, b) -> bool: if a == b: return True try: return abs(float(a) - float(b)) < 1e-9 except Exception: return str(a) == str(b) def _run(candles_by_code, universe, base, port, fee, tax, slot, budget, meta, overrides): params = dict(base) tbc.merge_tail_portfolio_into_params(params, port) params.update(overrides or {}) trades = tbc.run_tail_backtest_web_aligned( candles_by_code, params, universe, slot_money=slot, fee_rate=fee, sell_tax=tax, total_budget_krw=budget, meta_out=dict(meta), ) wins = [t for t in trades if float(t.get("pnl") or 0) > 0] pnl = sum(float(t.get("pnl") or 0) for t in trades) wr = (len(wins) / len(trades) * 100.0) if trades else 0.0 return { "trades": len(trades), "wins": len(wins), "wr": wr, "pnl": pnl, "params": {k: params.get(k) for k in COMPARE_KEYS}, } def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--json", required=True, help="optuna_tail_*.json 경로") ap.add_argument("--log", required=True, help="append 대상 로그 경로") ap.add_argument("--date", default="2026-07-10", help="백테 일자 YYYY-MM-DD") args = ap.parse_args() json_path = Path(args.json) log_path = Path(args.log) data = json.loads(json_path.read_text(encoding="utf-8")) best = (data.get("results") or [None])[0] if not best: msg = "❌ Optuna results 비어 있음 — 비교 스킵\n" with log_path.open("a", encoding="utf-8") as f: f.write(msg) print(msg, end="") return 1 best_params = best.get("params") or {} day = args.date start_key = day.replace("-", "") + "0000" end_key = day.replace("-", "") + "2359" start_ymd, end_ymd = start_key[:8], end_key[:8] db = TradeDB() base = te.get_tail_defaults_from_db(db) universe, src, n_slots, _ = tbc.resolve_tail_universe( start_ymd, end_ymd, use_saved_history=True, strategy_id="SHORT", ) tf = int(base.get("timeframe") or 3) rsi = int(base.get("rsi_period") or 14) candles_by_code, _, _ = tbc.load_tail_candles_by_code( db, start_key, end_key, tf, rsi_period=rsi, ) row = db.conn.execute("SELECT * FROM env_config ORDER BY id DESC LIMIT 1").fetchone() r = dict(row) if row else {} fee, tax, _ = tbc.fee_and_slot_from_env_row(r) port = tbc.resolve_tail_portfolio_params(r, base) slot = float(port["slot_money"]) budget = float(port["total_budget_krw"]) meta = {"db": db, "start_key": start_key, "end_key": end_key} cur = _run(candles_by_code, universe, base, port, fee, tax, slot, budget, meta, {}) bst = _run( candles_by_code, universe, base, port, fee, tax, slot, budget, meta, best_params, ) delta = bst["pnl"] - cur["pnl"] trial_no = best.get("optuna_trial_number") or data.get("optuna_best_trial_number") mode = data.get("mode") or "?" elapsed = data.get("elapsed_sec") lines = [] lines.append("") lines.append("=" * 72) lines.append(f"[COMPARE] CURRENT_DB vs OPTUNA_{mode.upper()}_BEST | {day} | DB미저장") lines.append("=" * 72) lines.append(f"json={json_path}") lines.append(f"universe={src} slots={n_slots} | trial=#{trial_no} elapsed={elapsed}s") lines.append("") lines.append("| 구분 | 거래 | 승 | 승률 | 손익 |") lines.append("|------|------|----|------|------|") lines.append( f"| CURRENT_DB | {cur['trades']} | {cur['wins']} | {cur['wr']:.1f}% | {cur['pnl']:+,.0f} |" ) lines.append( f"| OPTUNA_BEST | {bst['trades']} | {bst['wins']} | {bst['wr']:.1f}% | {bst['pnl']:+,.0f} |" ) lines.append(f"| Δ(best-current) | | | | {delta:+,.0f} |") lines.append("") lines.append("| 파라미터 | CURRENT_DB | OPTUNA_BEST | diff |") lines.append("|----------|------------|--------------|------|") for k in COMPARE_KEYS: a = cur["params"].get(k) b = bst["params"].get(k) mark = "" if _same(a, b) else "<<" lines.append(f"| {k} | {a} | {b} | {mark} |") lines.append("") lines.append("DB 저장 없음 (--apply-best 미사용)") lines.append("=" * 72) lines.append("") text = "\n".join(lines) with log_path.open("a", encoding="utf-8") as f: f.write(text) print(text, end="") return 0 if __name__ == "__main__": raise SystemExit(main())