#!/usr/bin/env python3
"""
꼬리잡기 후보(DB SHORT 유니버스) — 진입 후 고점(MFE) vs 래칫 조기청산 검증.

사용:
  python3 -m kis_trader.backtest.tail_mfe_analysis --start 2026-06-01 --end 2026-06-22
"""
from __future__ import annotations

import argparse
from collections import Counter
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple

from database import TradeDB
from kis_trader.backtest import tail_backtest_common as tbc
from kis_trader.engine import tail_engine as te


def _t2dt(candle_time: str) -> datetime:
    return datetime.strptime(str(candle_time)[:12], "%Y%m%d%H%M")


def _session_peak_after_entry(
    candles: List[Dict],
    entry_time: str,
    entry_price: float,
) -> Tuple[float, float, str]:
    """당일 진입 이후 세션 고점·최대수익%·고점시각."""
    day = str(entry_time)[:8]
    ep = float(entry_price)
    if ep <= 0:
        return ep, 0.0, entry_time
    peak = ep
    peak_t = entry_time
    started = False
    for c in candles:
        ct = str(c.get("candle_time") or "")
        if ct[:8] != day:
            continue
        if not started:
            if ct < str(entry_time)[:12]:
                continue
            started = True
        hi = float(c.get("high") or c.get("close") or 0)
        if hi > peak:
            peak = hi
            peak_t = ct
    mfe_pct = (peak - ep) / ep * 100.0
    return peak, mfe_pct, peak_t


def _avg_profit_rate(trades: List[Dict]) -> Tuple[float, float]:
    wins = [t for t in trades if float(t.get("pnl") or 0) > 0]
    losses = [t for t in trades if float(t.get("pnl") or 0) <= 0]
    aw = (
        sum((float(t["exit"]) - float(t["entry"])) / float(t["entry"]) * 100 for t in wins) / len(wins)
        if wins else 0.0
    )
    al = (
        sum((float(t["exit"]) - float(t["entry"])) / float(t["entry"]) * 100 for t in losses) / len(losses)
        if losses else 0.0
    )
    return aw, al


def _run_scenario(
    label: str,
    candles_by_code: Dict[str, List[Dict]],
    universe_by_slot: Optional[Dict[str, List[str]]],
    base: Dict[str, Any],
    patch: Dict[str, Any],
    slot: float,
    fee_rate: float,
    sell_tax: float,
    mxs: int,
    tb: float,
) -> Dict[str, Any]:
    params = dict(base)
    params.update(patch)
    trades = tbc.run_tail_backtest_web_aligned(
        candles_by_code,
        params,
        universe_by_slot,
        slot_money=slot,
        fee_rate=fee_rate,
        sell_tax=sell_tax,
        max_stocks=mxs,
        total_budget_krw=tb,
    )
    stats = tbc.summarize_tail_trades(trades, total_budget_krw=tb)
    aw, al = _avg_profit_rate(trades)
    reasons = Counter(str(t.get("reason") or t.get("sell_reason") or "?") for t in trades)
    return {
        "label": label,
        "trades": trades,
        "stats": stats,
        "avg_win_pct": aw,
        "avg_loss_pct": al,
        "reasons": dict(reasons),
    }


def analyze_mfe_vs_exit(
    baseline_trades: List[Dict],
    candles_by_code: Dict[str, List[Dict]],
) -> Dict[str, Any]:
    """현재(래칫ON) 체결 건마다 — 실제청산% vs 당일잔여고점(MFE)% 비교."""
    rows: List[Dict[str, Any]] = []
    for t in baseline_trades:
        code = str(t.get("code") or "")
        candles = candles_by_code.get(code) or []
        if not candles:
            continue
        ep = float(t["entry"])
        xp = float(t["exit"])
        et = str(t["entry_time"])
        xt = str(t["exit_time"])
        exit_pct = (xp - ep) / ep * 100.0
        peak, mfe_pct, peak_t = _session_peak_after_entry(candles, et, ep)
        left_pct = mfe_pct - exit_pct
        rows.append({
            "code": code,
            "entry_time": et,
            "exit_time": xt,
            "reason": t.get("reason"),
            "exit_pct": round(exit_pct, 2),
            "mfe_pct": round(mfe_pct, 2),
            "left_on_table_pct": round(left_pct, 2),
            "peak_time": peak_t,
            "reached_5pct": mfe_pct >= 5.0,
            "reached_3pct": mfe_pct >= 3.0,
        })

    n = len(rows)
    if n == 0:
        return {"count": 0}

    avg_exit = sum(r["exit_pct"] for r in rows) / n
    avg_mfe = sum(r["mfe_pct"] for r in rows) / n
    avg_left = sum(r["left_on_table_pct"] for r in rows) / n
    cnt_5 = sum(1 for r in rows if r["reached_5pct"])
    cnt_3 = sum(1 for r in rows if r["reached_3pct"])
    cnt_left_2 = sum(1 for r in rows if r["left_on_table_pct"] >= 2.0)

    top_left = sorted(rows, key=lambda x: x["left_on_table_pct"], reverse=True)[:10]
    return {
        "count": n,
        "avg_exit_pct": round(avg_exit, 2),
        "avg_mfe_pct": round(avg_mfe, 2),
        "avg_left_on_table_pct": round(avg_left, 2),
        "reached_5pct_count": cnt_5,
        "reached_5pct_rate": round(cnt_5 / n * 100, 1),
        "reached_3pct_count": cnt_3,
        "reached_3pct_rate": round(cnt_3 / n * 100, 1),
        "left_ge_2pct_count": cnt_left_2,
        "left_ge_2pct_rate": round(cnt_left_2 / n * 100, 1),
        "top_left_on_table": top_left,
    }


def main() -> None:
    parser = argparse.ArgumentParser(description="꼬리 후보 MFE vs 래칫 검증")
    parser.add_argument("--start", default="2026-06-01")
    parser.add_argument("--end", default="2026-06-30")
    parser.add_argument("--tf", type=int, default=3)
    args = parser.parse_args()

    db = TradeDB()
    start_key, end_key, start_ymd, end_ymd = tbc.date_keys(args.start, args.end)
    candles_by_code, total_candles, _ = tbc.load_tail_candles_by_code(
        db, start_key, end_key, args.tf, rsi_period=14,
    )
    universe_by_slot, src, slot_cnt, _ = tbc.resolve_tail_universe(
        start_ymd, end_ymd, use_saved_history=True, strategy_id="SHORT",
    )
    snap = db.get_merged_env_snapshot()
    fee_rate, sell_tax, slot = tbc.fee_and_slot_from_env_row(snap)
    base = te.get_tail_defaults_from_db(db)
    base["portfolio_mode"] = True
    base["force_eod_exit"] = False
    mxs = int(base.get("max_stocks") or 3)
    tb = float(base.get("total_budget_krw") or 0)
    if tb <= 0:
        tb = float(mxs * slot)
    period_days = max(
        1,
        (datetime.strptime(args.end, "%Y-%m-%d") - datetime.strptime(args.start, "%Y-%m-%d")).days + 1,
    )

    print("=" * 72)
    print(f"꼬리잡기 MFE 검증  {args.start} ~ {args.end}  TF={args.tf}m")
    print(f"유니버스: {src} slots={slot_cnt}  캔들종목={len(candles_by_code)} 봉={total_candles}")
    print(f"래칫(현재DB): {base.get('ratchet_tiers')}")
    print(f"손절ATR mult={base.get('stop_atr_mult')}  익절TP={base.get('take_profit_pct')}")
    print("=" * 72)

    _no_shoulder = {
        "shoulder_min_high": 0.99,
        "shoulder_cut_pct": 0.99,
        "trail_pct": 0.0,
        "trail_arm_pct": 0.0,
    }
    scenarios = [
        ("①현재DB(래칫ON)", {}),
        ("②래칫OFF(ATR익절만)", {**_no_shoulder, "ratchet_tiers": ""}),
        ("③래칫1%이후(1:0.5,3:0.8)", {
            **_no_shoulder, "ratchet_tiers": "1:0.5,3:0.8",
        }),
        ("④래칫2%이후(2:0.5,5:0.8)", {
            **_no_shoulder, "ratchet_tiers": "2:0.5,5:0.8",
        }),
        ("⑤래칫3%이후(3:1.0,5:0.8)", {
            **_no_shoulder, "ratchet_tiers": "3:1.0,5:0.8",
        }),
        ("⑥래칫5%이후(5:1.0,8:0.8)", {
            **_no_shoulder, "ratchet_tiers": "5:1.0,8:0.8",
        }),
        ("⑦어깨만(래칫OFF·0.5%/0.2%)", {
            "ratchet_tiers": "",
            "shoulder_min_high": 0.005,
            "shoulder_cut_pct": 0.002,
            "trail_pct": 0.0,
            "trail_arm_pct": 0.0,
        }),
        ("⑧현재래칫+손절ATR2.0", {"stop_atr_mult": 2.0}),
        ("⑨현재래칫+손절ATR1.0", {"stop_atr_mult": 1.0}),
    ]

    results = []
    for label, patch in scenarios:
        r = _run_scenario(
            label, candles_by_code, universe_by_slot, base, patch,
            slot, fee_rate, sell_tax, mxs, tb,
        )
        results.append(r)
        s = r["stats"]
        rr = abs(r["avg_win_pct"] / r["avg_loss_pct"]) if r["avg_loss_pct"] else 0
        print(
            f"\n{label}\n"
            f"  거래 {s['total_trades']:4} | 승률 {s['win_rate']:5.1f}% | "
            f"PnL {s['total_pnl']:>10,} | PF {s['pf']:.2f} | 보유 {s['avg_hold_min']:.0f}분\n"
            f"  평균익 {r['avg_win_pct']:+.2f}% | 평균손 {r['avg_loss_pct']:+.2f}% | R:R {rr:.2f}\n"
            f"  청산: {r['reasons']}"
        )

    baseline = results[0]["trades"]
    mfe = analyze_mfe_vs_exit(baseline, candles_by_code)
    print("\n" + "=" * 72)
    print("【핵심】①현재(래칫ON) 체결 건 — 실제청산 vs 당일 잔여 고점(MFE)")
    print("=" * 72)
    if mfe.get("count", 0) == 0:
        print("체결 0건 — MFE 분석 불가")
    else:
        print(f"  분석건수: {mfe['count']}")
        print(f"  평균 실제청산: {mfe['avg_exit_pct']:+.2f}%")
        print(f"  평균 당일고점(MFE): {mfe['avg_mfe_pct']:+.2f}%")
        print(f"  평균 놓친 수익: {mfe['avg_left_on_table_pct']:+.2f}%p")
        print(
            f"  +3% 이상 갔던 비율: {mfe['reached_3pct_count']}/{mfe['count']} "
            f"({mfe['reached_3pct_rate']}%)"
        )
        print(
            f"  +5% 이상 갔던 비율: {mfe['reached_5pct_count']}/{mfe['count']} "
            f"({mfe['reached_5pct_rate']}%)"
        )
        print(
            f"  2%p 이상 더 갈 수 있었던 건: {mfe['left_ge_2pct_count']}/{mfe['count']} "
            f"({mfe['left_ge_2pct_rate']}%)"
        )
        print("\n  ▶ 놓친 수익 TOP10 (실제청산 vs 당일고점)")
        for i, row in enumerate(mfe["top_left_on_table"], 1):
            print(
                f"    {i:2}. {row['code']} {row['entry_time']} "
                f"청산{row['exit_pct']:+.1f}%({row['reason']}) "
                f"→ 고점{row['mfe_pct']:+.1f}%(@{row['peak_time']}) "
                f"놓침{row['left_on_table_pct']:+.1f}%p"
            )

    print("\n" + "=" * 72)
    print("시나리오 요약 비교 (PnL 내림차순)")
    print("=" * 72)
    ranked = sorted(results, key=lambda r: r["stats"]["total_pnl"], reverse=True)
    print(f"{'순위':<4} {'시나리오':<28} {'거래':>5} {'승률':>6} {'평균익':>7} {'평균손':>7} {'R:R':>5} {'PF':>5} {'PnL':>12}")
    for i, r in enumerate(ranked, 1):
        s = r["stats"]
        rr = abs(r["avg_win_pct"] / r["avg_loss_pct"]) if r["avg_loss_pct"] else 0
        mark = " ★현재" if r["label"].startswith("①") else ""
        print(
            f"{i:<4} {r['label']:<28} {s['total_trades']:5} {s['win_rate']:5.1f}% "
            f"{r['avg_win_pct']:+6.2f}% {r['avg_loss_pct']:+6.2f}% {rr:5.2f} "
            f"{s['pf']:5.2f} {s['total_pnl']:12,}{mark}"
        )
    best = ranked[0]
    cur = next(r for r in results if r["label"].startswith("①"))
    cur_rank = next(i for i, r in enumerate(ranked, 1) if r["label"].startswith("①"))
    print(
        f"\n▶ 1위: {best['label']} PnL {best['stats']['total_pnl']:,}원 | "
        f"현재DB 순위: {cur_rank}위 PnL {cur['stats']['total_pnl']:,}원"
    )


if __name__ == "__main__":
    main()
