#!/usr/bin/env python3
"""Inspect and render the rejected D48 Hold1 training comparison."""

from __future__ import annotations

import json
import zipfile
from pathlib import Path

import matplotlib.pyplot as plt
import pandas as pd


HERE = Path(__file__).resolve().parent
ZIP = HERE / "backtest-result-2026-07-21_06-39-39.zip"
BASE = "VolatilityBreakoutHold1Baseline"
CAND = "VolatilityBreakoutHold1"


def load() -> dict:
    with zipfile.ZipFile(ZIP) as archive:
        name = next(n for n in archive.namelist() if n.endswith(".json") and "config" not in n)
        return json.loads(archive.read(name))


def pair_frame(result: dict, strategy: str) -> pd.DataFrame:
    rows = [r for r in result["strategy"][strategy]["results_per_pair"] if r["key"] != "TOTAL"]
    return pd.DataFrame(rows).set_index("key")


def main() -> int:
    result = load(); base = result["strategy"][BASE]; cand = result["strategy"][CAND]
    base_trades = pd.DataFrame(base["trades"]); cand_trades = pd.DataFrame(cand["trades"])
    base_index = {(r.pair, int(r.open_timestamp)): r for r in base_trades.itertuples()}
    matched = []
    for row in cand_trades.itertuples():
        prior = base_index.get((row.pair, int(row.open_timestamp) - 300_000))
        if prior is not None:
            matched.append({"pair": row.pair, "base_profit": prior.profit_ratio, "hold1_profit": row.profit_ratio, "base_open": prior.open_rate, "hold1_open": row.open_rate, "same_exit": prior.close_timestamp == row.close_timestamp})
    matched_df = pd.DataFrame(matched)

    base_daily = pd.DataFrame(base["daily_profit"], columns=["date", "profit"]); base_daily.date = pd.to_datetime(base_daily.date); base_daily["cum"] = base_daily.profit.cumsum()
    cand_daily = pd.DataFrame(cand["daily_profit"], columns=["date", "profit"]); cand_daily.date = pd.to_datetime(cand_daily.date); cand_daily["cum"] = cand_daily.profit.cumsum()
    bp = pair_frame(result, BASE); cp = pair_frame(result, CAND); pairs = bp.index.union(cp.index)
    pair_delta = cp.reindex(pairs).profit_total_abs.fillna(0) - bp.reindex(pairs).profit_total_abs.fillna(0)

    summary = {
        "decision": "reject_training",
        "protocol": {"timerange": base["timerange"], "timeframe": base["timeframe"], "fee": 0.00035, "protections": base["enable_protections"], "starting_balance": base["starting_balance"]},
        "baseline": {"trades": base["total_trades"], "profit_pct": base["profit_total"] * 100, "profit_factor": base["profit_factor"], "winrate": base["winrate"], "max_drawdown_pct": base["max_drawdown_account"] * 100},
        "hold1": {"trades": cand["total_trades"], "profit_pct": cand["profit_total"] * 100, "profit_factor": cand["profit_factor"], "winrate": cand["winrate"], "max_drawdown_pct": cand["max_drawdown_account"] * 100},
        "delta": {"trades": cand["total_trades"] - base["total_trades"], "profit_percentage_points": (cand["profit_total"] - base["profit_total"]) * 100, "profit_factor": cand["profit_factor"] - base["profit_factor"], "max_drawdown_percentage_points": (cand["max_drawdown_account"] - base["max_drawdown_account"]) * 100},
        "exact_five_minute_pairs": {"count": len(matched_df), "same_exit_count": int(matched_df.same_exit.sum()) if not matched_df.empty else 0, "mean_profit_ratio_delta_pct": float((matched_df.hold1_profit - matched_df.base_profit).mean() * 100) if not matched_df.empty else None, "hold1_better_count": int((matched_df.hold1_profit > matched_df.base_profit).sum()) if not matched_df.empty else 0},
        "pair_profit_delta_usdt": {p: float(v) for p, v in pair_delta.sort_values().items()},
    }
    (HERE / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")

    fig, axes = plt.subplots(2, 2, figsize=(18, 11), dpi=160, facecolor="white")
    ax = axes[0, 0]; ax.plot(base_daily.date, base_daily.cum, label="D48 baseline", color="#111827", linewidth=1.8); ax.plot(cand_daily.date, cand_daily.cum, label="Hold1", color="#2563EB", linewidth=1.8); ax.axhline(0, color="#94A3B8", linewidth=.8); ax.set_title("Cumulative closed-trade profit (USDT)", loc="left", fontweight="bold"); ax.legend(frameon=False)
    ax = axes[0, 1]; colors=["#B91C1C" if v < 0 else "#15803D" for v in pair_delta.sort_values()]; ax.barh([p.split('/')[0] for p in pair_delta.sort_values().index], pair_delta.sort_values().values, color=colors); ax.axvline(0,color="#64748B",linewidth=.8); ax.set_title("Hold1 − baseline profit by pair (USDT)",loc="left",fontweight="bold")
    ax = axes[1, 0]
    if not matched_df.empty:
        ax.scatter(matched_df.base_profit*100, matched_df.hold1_profit*100, s=20, alpha=.55, color="#2563EB"); lo=min(ax.get_xlim()[0],ax.get_ylim()[0]); hi=max(ax.get_xlim()[1],ax.get_ylim()[1]); ax.plot([lo,hi],[lo,hi],linestyle="--",color="#94A3B8",linewidth=1); ax.set_xlim(lo,hi); ax.set_ylim(lo,hi)
    ax.set_xlabel("Baseline trade profit %"); ax.set_ylabel("Hold1 trade profit %"); ax.set_title(f"Exact pair +5m matches (n={len(matched_df)})",loc="left",fontweight="bold")
    ax = axes[1, 1]; labels=["Profit %","Profit factor","Win rate %","Max DD %","Trades / 10"]; b=[base["profit_total"]*100,base["profit_factor"],base["winrate"]*100,base["max_drawdown_account"]*100,base["total_trades"]/10]; c=[cand["profit_total"]*100,cand["profit_factor"],cand["winrate"]*100,cand["max_drawdown_account"]*100,cand["total_trades"]/10]; x=range(len(labels)); ax.bar([i-.18 for i in x],b,width=.36,label="Baseline",color="#111827"); ax.bar([i+.18 for i in x],c,width=.36,label="Hold1",color="#2563EB"); ax.set_xticks(list(x),labels); ax.set_title("Training gate metrics",loc="left",fontweight="bold"); ax.legend(frameon=False)
    for ax in axes.flat:
        ax.grid(True,alpha=.12); ax.set_facecolor("white");
        for spine in ax.spines.values(): spine.set_color("#CBD5E1")
    fig.suptitle("D48 Hold1 · training comparison · 2026-01-01 to 2026-05-01",fontsize=16,fontweight="bold"); fig.text(.5,.02,"12 pairs · 5m · 2x isolated · fee 0.035%/side · protections enabled · cache none",ha="center",color="#64748B"); fig.tight_layout(rect=[0,.05,1,.94]); fig.savefig(HERE/"hold1_training_comparison.png",bbox_inches="tight",facecolor="white"); plt.close(fig)
    print(json.dumps(summary,ensure_ascii=False,indent=2)); return 0


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