#!/usr/bin/env python3
"""
이전 모멘텀 fine #1 파라미터 고정 × 래칫만 A/B (apply 없음).

기준 JSON: optuna_momentum_fine_20260716_014654.json (best ~+83k, 래칫 축 없음)
기간: 2026-07-15 / 포트 120만 / orderbook off
"""
from __future__ import annotations

import json
import os
import sys
import time
from datetime import datetime

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if ROOT not in sys.path:
    sys.path.insert(0, ROOT)

os.environ.setdefault("MOMENTUM_BACKTEST_REST_WARMUP", "1")
os.environ.setdefault("MOMENTUM_BACKTEST_REST_WARMUP_BARS", "700")
os.environ.setdefault("MOMENTUM_BACKTEST_REST_SLEEP_SEC", "0.25")

from kis_trader.backtest.optuna_common import announce_optuna_json_path
from kis_trader.backtest.optuna_momentum import prepare_momentum_search_context
from kis_trader.backtest.param_search_momentum import evaluate_momentum_param_combo
from kis_trader.utils.env import get_env_from_db

# 래칫 후보: OFF + 실매 + fine 격자 대표 + 최근 Optuna 선호
RATCHET_CASES = [
    ("OFF", ""),
    ("LIVE", "5:2,10:1.5"),
    ("OPTUNA_TOP", "2:1.5,5:1"),
    ("MID", "2:1,5:0.8,8:0.6"),
]

BASE_JSON = os.path.join(
    ROOT,
    "kis_trader/backtest/results/optuna_momentum_fine_20260716_014654.json",
)


def main() -> int:
    with open(BASE_JSON, encoding="utf-8") as f:
        src = json.load(f)
    base_combo = dict(src["results"][0]["params"])
    start = str(src.get("start") or "2026-07-15")
    end = str(src.get("end") or start)

    print("=" * 72, flush=True)
    print("모멘텀 래칫 A/B | 이전 fine#1 고정 | apply 없음", flush=True)
    print(f"기준 JSON: {BASE_JSON}", flush=True)
    print(f"기간: {start} ~ {end}", flush=True)
    print(f"고정 params: {json.dumps(base_combo, ensure_ascii=False)}", flush=True)
    print(
        f"DB MOMENTUM_RATCHET_TIERS(참고): {get_env_from_db('MOMENTUM_RATCHET_TIERS', '')!r}",
        flush=True,
    )
    print("=" * 72, flush=True)

    t0 = time.time()
    ctx = prepare_momentum_search_context(
        start,
        end,
        "fine",
        orderbook_filter="off",
    )
    if ctx is None:
        print("❌ context 준비 실패", flush=True)
        return 1
    print(
        f"✅ context OK | {time.time() - t0:.1f}s | "
        f"slot={ctx.slot_money:,.0f} max={ctx.max_stocks} budget={ctx.total_budget_krw:,.0f}",
        flush=True,
    )

    rows = []
    for label, ratchet in RATCHET_CASES:
        combo = dict(base_combo)
        combo["ratchet_tiers"] = ratchet
        print("-" * 72, flush=True)
        print(f"▶ 케이스 {label} | ratchet_tiers={ratchet!r}", flush=True)
        t1 = time.time()
        result = evaluate_momentum_param_combo(
            combo,
            base_fixed=ctx.base_fixed,
            grid_keys=list(ctx.grid_keys) + ["ratchet_tiers"],
            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,
        )
        elapsed = time.time() - t1
        if result is None:
            row = {
                "label": label,
                "ratchet_tiers": ratchet,
                "ok": False,
                "elapsed_sec": round(elapsed, 2),
                "note": "evaluate None",
            }
            print(f"  ❌ None ({elapsed:.1f}s)", flush=True)
        else:
            row = {
                "label": label,
                "ratchet_tiers": ratchet,
                "ok": True,
                "elapsed_sec": round(elapsed, 2),
                "total_pnl": float(result.get("total_pnl") or 0),
                "total_trades": int(result.get("total_trades") or 0),
                "win_rate": float(result.get("win_rate") or 0),
                "pf": float(result.get("pf") or 0) if result.get("pf") is not None else None,
            }
            print(
                f"  ✅ pnl={row['total_pnl']:,.0f} | trades={row['total_trades']} | "
                f"wr={row['win_rate']:.1f}% | pf={row['pf']} | {elapsed:.1f}s",
                flush=True,
            )
        rows.append(row)

    ok_rows = [r for r in rows if r.get("ok")]
    ok_rows.sort(key=lambda r: (-float(r["total_pnl"]), -int(r["total_trades"])))

    print("=" * 72, flush=True)
    print("📊 A/B 결과 (PnL 내림차순)", flush=True)
    for i, r in enumerate(ok_rows, 1):
        print(
            f"  {i}. [{r['label']}] ratchet={r['ratchet_tiers']!r} | "
            f"pnl={r['total_pnl']:,.0f} | trades={r['total_trades']} | wr={r['win_rate']:.1f}%",
            flush=True,
        )
    if len(ok_rows) >= 2:
        best, worst = ok_rows[0], ok_rows[-1]
        print(
            f"Δ best−worst = {best['total_pnl'] - worst['total_pnl']:+,.0f}원 "
            f"({best['label']} vs {worst['label']})",
            flush=True,
        )
        off = next((r for r in ok_rows if r["label"] == "OFF"), None)
        live = next((r for r in ok_rows if r["label"] == "LIVE"), None)
        if off and live:
            print(
                f"Δ OFF−LIVE = {off['total_pnl'] - live['total_pnl']:+,.0f}원 "
                f"(OFF {off['total_pnl']:,.0f} / LIVE {live['total_pnl']:,.0f})",
                flush=True,
            )
    print("=" * 72, flush=True)

    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    out_dir = os.path.join(ROOT, "kis_trader/backtest/results")
    os.makedirs(out_dir, exist_ok=True)
    out_path = os.path.join(out_dir, f"momentum_ratchet_ab_{ts}.json")
    out = {
        "kind": "momentum_ratchet_ab",
        "apply": False,
        "base_json": BASE_JSON,
        "base_pnl_reported": src["results"][0].get("total_pnl"),
        "base_params": base_combo,
        "start": start,
        "end": end,
        "slot_money": int(ctx.slot_money),
        "max_stocks": int(ctx.max_stocks),
        "total_budget_krw": int(ctx.total_budget_krw),
        "cases": rows,
        "ranked": ok_rows,
        "elapsed_sec": round(time.time() - t0, 1),
    }
    with open(out_path, "w", encoding="utf-8") as f:
        json.dump(out, f, indent=2, ensure_ascii=False)
    announce_optuna_json_path(
        out_path, strategy="momentum", mode="ratchet_ab", note="래칫 A/B 최종 JSON",
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
