"""
track_record_v1.py
===================
Professional track record report with full performance metrics.

Metrics calculated:
  - Total Return, CAGR
  - Sharpe Ratio (annualised, risk-free rate 4%)
  - Sortino Ratio (annualised, downside deviation only)
  - Calmar Ratio (CAGR / Max DD)
  - Max Drawdown, Avg Drawdown
  - Win Rate, Profit Factor
  - Avg Win, Avg Loss, Payoff Ratio
  - Max Consecutive Wins / Losses
  - Recovery Factor
  - Monthly stats: best/worst month, % positive months
  All broken down year by year AND as full-period totals.

Place this file in:
  C:\\Users\\paul_\\OneDrive\\fx_macro_intraday\\src\\research\\track_record_v1.py

Run from project root:
  python src/research/track_record_v1.py
"""

import pandas as pd
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib as mpl
mpl.rcParams["text.usetex"] = False
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.ticker as mticker
from pathlib import Path
import sys

BASE_PATH  = Path(__file__).resolve().parents[2]
SRC_PATH   = BASE_PATH / "src"
if str(SRC_PATH) not in sys.path:
    sys.path.append(str(SRC_PATH))

TRADES_DIR = BASE_PATH / "data" / "processed" / "trades"
CHARTS_DIR = BASE_PATH / "data" / "processed"

ACCOUNT_START = 100_000.0
STOP_PCT      = 0.0025
BASE_RISK_PCT = 0.0075
BASE_NOTIONAL = (ACCOUNT_START * BASE_RISK_PCT) / STOP_PCT  # $300,000
RISK_FREE_RATE= 0.04   # 4% annualised risk-free rate
YEARS         = 22.0
TRADING_DAYS  = 252

ZSCORE_BANDS = [
    (2.75, 3.50, 1.0),
    (3.50, 4.50, 1.5),
    (4.50, 99.0, 2.0),
]

def get_multiplier(z):
    for lo, hi, mult in ZSCORE_BANDS:
        if lo <= z < hi:
            return mult
    return ZSCORE_BANDS[-1][2]


# ── Load and enrich trades ────────────────────────────────────────────────────
def load_trades():
    for fname in ["trades_real_costs.csv", "trades_eurusd_final.csv"]:
        path = TRADES_DIR / fname
        if path.exists():
            df = pd.read_csv(path, parse_dates=["entry_time", "exit_time"])
            ret_col = "return_real" if "return_real" in df.columns else "return"
            df["ret"]        = df[ret_col]
            df["zscore_abs"] = df.get("zscore_abs", pd.Series([2.75]*len(df)))
            df["multiplier"] = df["zscore_abs"].apply(get_multiplier)
            df["notional"]   = (BASE_NOTIONAL * df["multiplier"]).clip(upper=BASE_NOTIONAL*3)
            df["dollar_pnl"] = df["ret"] * df["notional"]
            df["win"]        = (df["ret"] > 0).astype(int)
            df["year"]       = df["exit_time"].dt.year
            df["month"]      = df["exit_time"].dt.to_period("M")
            df = df.sort_values("exit_time").reset_index(drop=True)
            df["cum_pnl"]    = df["dollar_pnl"].cumsum()
            df["balance"]    = ACCOUNT_START + df["cum_pnl"]
            df["peak"]       = df["balance"].cummax()
            df["drawdown"]   = (df["balance"] - df["peak"]) / df["peak"] * 100
            print(f"  Loaded {len(df):,} trades from {fname}")
            return df
    raise FileNotFoundError("Run real_world_costs_v1.py first.")


# ── Core metrics calculator ───────────────────────────────────────────────────
def calc_metrics(df: pd.DataFrame, period_years: float,
                 start_balance: float) -> dict:
    if df.empty or len(df) < 2:
        return {}

    returns    = df["ret"].values
    dollar_pnl = df["dollar_pnl"].values
    wins       = df["win"].values

    n          = len(df)
    n_wins     = wins.sum()
    n_losses   = n - n_wins

    # Equity curve from period start
    equity = start_balance + dollar_pnl.cumsum()
    peak   = np.maximum.accumulate(equity)
    dd_pct = (equity - peak) / peak * 100

    final_bal  = equity[-1]
    total_ret  = (final_bal / start_balance - 1) * 100
    cagr       = ((final_bal / start_balance) ** (1 / period_years) - 1) * 100 \
                 if period_years > 0 else 0

    max_dd     = dd_pct.min()
    avg_dd     = dd_pct[dd_pct < 0].mean() if (dd_pct < 0).any() else 0

    # P&L stats
    win_returns  = returns[wins == 1]
    loss_returns = returns[wins == 0]
    avg_win      = win_returns.mean()  * 100 if len(win_returns)  > 0 else 0
    avg_loss     = loss_returns.mean() * 100 if len(loss_returns) > 0 else 0
    payoff       = abs(avg_win / avg_loss) if avg_loss != 0 else 0

    gross_profit = dollar_pnl[dollar_pnl > 0].sum()
    gross_loss   = abs(dollar_pnl[dollar_pnl < 0].sum())
    profit_factor= gross_profit / gross_loss if gross_loss > 0 else float("inf")

    # Consecutive streaks
    max_consec_wins = max_consec_losses = cur_w = cur_l = 0
    for r in returns:
        if r > 0:
            cur_w += 1; cur_l = 0
        else:
            cur_l += 1; cur_w = 0
        max_consec_wins   = max(max_consec_wins,   cur_w)
        max_consec_losses = max(max_consec_losses, cur_l)

    # Sharpe ratio — annualised
    # Use dollar returns scaled to % of start balance for consistency
    pct_returns  = dollar_pnl / start_balance
    trades_per_yr= n / period_years
    rf_per_trade = (1 + RISK_FREE_RATE) ** (1 / trades_per_yr) - 1
    excess       = pct_returns - rf_per_trade
    sharpe       = (excess.mean() / excess.std() * np.sqrt(trades_per_yr)) \
                   if excess.std() > 0 else 0

    # Sortino ratio — uses downside deviation only
    downside     = excess[excess < 0]
    down_dev     = np.sqrt((downside**2).mean()) * np.sqrt(trades_per_yr) \
                   if len(downside) > 0 else 0.0001
    sortino      = (excess.mean() * trades_per_yr) / down_dev if down_dev > 0 else 0

    # Calmar ratio
    calmar       = (cagr / abs(max_dd)) if max_dd < 0 else 0

    # Recovery factor
    net_profit   = final_bal - start_balance
    recovery     = (net_profit / abs(net_profit / (cagr/100))) \
                   if max_dd < 0 else 0
    recovery     = abs(net_profit / (start_balance * abs(max_dd) / 100)) \
                   if max_dd < 0 else 0

    # Monthly stats
    df2 = df.copy()
    df2["month"] = df2["exit_time"].dt.to_period("M")
    monthly_pnl  = df2.groupby("month")["dollar_pnl"].sum()
    pos_months   = (monthly_pnl > 0).sum()
    tot_months   = len(monthly_pnl)

    return {
        "trades"          : n,
        "trades_per_yr"   : n / period_years,
        "win_rate"        : n_wins / n * 100,
        "profit_factor"   : profit_factor,
        "avg_win_pct"     : avg_win,
        "avg_loss_pct"    : avg_loss,
        "payoff_ratio"    : payoff,
        "gross_profit"    : gross_profit,
        "gross_loss"      : gross_loss,
        "net_pnl"         : final_bal - start_balance,
        "total_return"    : total_ret,
        "cagr"            : cagr,
        "start_balance"   : start_balance,
        "end_balance"     : final_bal,
        "max_dd"          : max_dd,
        "avg_dd"          : avg_dd,
        "sharpe"          : sharpe,
        "sortino"         : sortino,
        "calmar"          : calmar,
        "recovery_factor" : recovery,
        "max_consec_wins" : max_consec_wins,
        "max_consec_losses": max_consec_losses,
        "pos_months"      : pos_months,
        "tot_months"      : tot_months,
        "best_month"      : monthly_pnl.max() if len(monthly_pnl) > 0 else 0,
        "worst_month"     : monthly_pnl.min() if len(monthly_pnl) > 0 else 0,
    }


# ── Print full track record ───────────────────────────────────────────────────
def print_track_record(df: pd.DataFrame, annual_metrics: list,
                       full_metrics: dict):

    print(f"\n{'='*100}")
    print("EURUSD MACRO LEAD-LAG MODEL  —  PROFESSIONAL TRACK RECORD")
    print(f"  Real-world execution costs applied  |  Fixed 300k notional  |  0.75% risk per trade")
    print(f"{'='*100}")

    # Full period summary
    fm = full_metrics
    print(f"""
  FULL PERIOD SUMMARY  (2003 - 2026  |  22 years)
  {'─'*70}
  Starting capital     : $100,000
  Final balance        : ${fm['end_balance']:>12,.0f}
  Net profit           : ${fm['net_pnl']:>+12,.0f}
  Total return         : {fm['total_return']:>+8.1f}%
  CAGR                 : {fm['cagr']:>+8.2f}% per year

  Sharpe ratio         : {fm['sharpe']:>8.3f}  (annualised, RF 4%)
  Sortino ratio        : {fm['sortino']:>8.3f}  (annualised, downside only)
  Calmar ratio         : {fm['calmar']:>8.3f}  (CAGR / Max DD)
  Recovery factor      : {fm['recovery_factor']:>8.3f}

  Max drawdown         : {fm['max_dd']:>+8.2f}%
  Avg drawdown         : {fm['avg_dd']:>+8.2f}%

  Total trades         : {fm['trades']:>8,}   ({fm['trades_per_yr']:.1f}/year)
  Win rate             : {fm['win_rate']:>8.2f}%
  Profit factor        : {fm['profit_factor']:>8.3f}
  Avg winning trade    : {fm['avg_win_pct']:>+8.3f}%
  Avg losing trade     : {fm['avg_loss_pct']:>+8.3f}%
  Payoff ratio         : {fm['payoff_ratio']:>8.3f}  (avg win / avg loss)
  Max consec. wins     : {fm['max_consec_wins']:>8}
  Max consec. losses   : {fm['max_consec_losses']:>8}

  Positive months      : {fm['pos_months']}/{fm['tot_months']} ({fm['pos_months']/fm['tot_months']*100:.0f}%)
  Best month           : ${fm['best_month']:>+10,.0f}
  Worst month          : ${fm['worst_month']:>+10,.0f}
""")

    # Year-by-year table
    print(f"  YEAR-BY-YEAR BREAKDOWN")
    print(f"  {'─'*98}")
    print(f"  {'Year':<6}{'Trades':>7}{'WR%':>7}{'Net PnL':>10}{'Ret%':>7}"
          f"{'MaxDD%':>8}{'Sharpe':>8}{'Sortino':>9}{'Calmar':>8}"
          f"{'PF':>7}{'Payoff':>8}{'Best Mo':>10}{'Worst Mo':>10}")
    print(f"  {'─'*98}")

    for m in annual_metrics:
        if not m:
            continue
        yr   = m.get("year", "")
        flag = " *" if m.get("max_dd", 0) < -5 else ""
        print(f"  {yr:<6}{m['trades']:>7}{m['win_rate']:>7.1f}"
              f"  {m['net_pnl']:>+8,.0f}{m['total_return']:>7.1f}%"
              f"{m['max_dd']:>8.2f}%"
              f"{m['sharpe']:>8.2f}{m['sortino']:>9.2f}"
              f"{m['calmar']:>8.2f}"
              f"{m['profit_factor']:>7.2f}{m['payoff_ratio']:>8.2f}"
              f"  {m['best_month']:>+8,.0f}  {m['worst_month']:>+8,.0f}{flag}")

    print(f"  {'─'*98}")

    # Averages row
    valid = [m for m in annual_metrics if m and m.get("trades",0) > 10]
    if valid:
        print(f"  {'AVG':<6}"
              f"{np.mean([m['trades'] for m in valid]):>7.0f}"
              f"{np.mean([m['win_rate'] for m in valid]):>7.1f}"
              f"  {np.mean([m['net_pnl'] for m in valid]):>+8,.0f}"
              f"{np.mean([m['total_return'] for m in valid]):>7.1f}%"
              f"{np.mean([m['max_dd'] for m in valid]):>8.2f}%"
              f"{np.mean([m['sharpe'] for m in valid]):>8.2f}"
              f"{np.mean([m['sortino'] for m in valid]):>9.2f}"
              f"{np.mean([m['calmar'] for m in valid]):>8.2f}"
              f"{np.mean([m['profit_factor'] for m in valid]):>7.2f}"
              f"{np.mean([m['payoff_ratio'] for m in valid]):>8.2f}")

    print(f"\n  * years where intra-year DD exceeded 5%")

    # Regime analysis
    print(f"\n  REGIME ANALYSIS")
    print(f"  {'─'*60}")
    regimes = [
        ("Pre-crisis low vol",  2003, 2007),
        ("Financial crisis",    2008, 2009),
        ("QE era",              2010, 2014),
        ("Brexit / divergence", 2015, 2018),
        ("COVID + low rates",   2019, 2021),
        ("Hiking cycle",        2022, 2026),
    ]
    for label, y1, y2 in regimes:
        sub = df[(df["year"] >= y1) & (df["year"] <= y2)]
        if sub.empty:
            continue
        yrs = y2 - y1 + 1
        pnl = sub["dollar_pnl"].sum()
        wr  = sub["win"].mean() * 100
        n   = len(sub)
        ret = pnl / ACCOUNT_START * 100
        print(f"  {label:<28} {y1}-{y2}  "
              f"n={n:>4}  WR={wr:.1f}%  "
              f"PnL={pnl:>+9,.0f}  Ret={ret:>+6.1f}%  "
              f"{'POSITIVE' if pnl > 0 else 'NEGATIVE'}")


# ── Chart ─────────────────────────────────────────────────────────────────────
def make_chart(df: pd.DataFrame, annual_metrics: list, save_path: Path):
    BG    = "#0d1117"
    PANEL = "#161b22"
    GRID  = "#21262d"
    TEXT  = "#e6edf3"
    MUTED = "#8b949e"
    GREEN = "#00ff88"
    RED   = "#ff4444"
    CYAN  = "#00d4ff"
    GOLD  = "#ffd700"
    ORNG  = "#ff8c00"
    PURP  = "#cc88ff"

    fig = plt.figure(figsize=(22, 26), facecolor=BG)
    gs  = gridspec.GridSpec(4, 2, figure=fig,
                            hspace=0.50, wspace=0.30,
                            left=0.07, right=0.97,
                            top=0.93, bottom=0.04)

    def style(ax, title):
        ax.set_facecolor(PANEL)
        ax.set_title(title, color=TEXT, fontsize=10,
                     fontweight="bold", pad=10)
        ax.tick_params(colors=TEXT, labelsize=8)
        for sp in ax.spines.values():
            sp.set_color(GRID)
        ax.grid(True, color=GRID, linewidth=0.5, alpha=0.6)
        ax.yaxis.label.set_color(TEXT)
        ax.xaxis.label.set_color(TEXT)

    valid = [m for m in annual_metrics if m and m.get("trades",0) > 10]
    years = [m["year"] for m in valid]

    # ── Panel 1: Equity curve + drawdown (full width) ─────────────────────
    ax1 = fig.add_subplot(gs[0, :])
    style(ax1, "Equity Curve & Drawdown  |  100k Start  |  Fixed 300k Notional  |  Real Costs")

    ax1b = ax1.twinx()
    ax1b.set_facecolor(PANEL)

    ax1.fill_between(df["exit_time"], df["balance"]/1e3,
                     ACCOUNT_START/1e3, alpha=0.15, color=GREEN)
    ax1.plot(df["exit_time"], df["balance"]/1e3,
             color=GREEN, linewidth=1.5, label="Balance", zorder=5)

    ax1b.fill_between(df["exit_time"], df["drawdown"], 0,
                      alpha=0.3, color=RED)
    ax1b.plot(df["exit_time"], df["drawdown"],
              color=RED, linewidth=0.7, alpha=0.8, label="Drawdown %")

    ax1b.axhline(y=-10, color=RED, linewidth=1, linestyle="--",
                 alpha=0.6, label="DD limit 10%")

    ax1.yaxis.set_major_formatter(
        mticker.FuncFormatter(lambda x,_: f"{x:.0f}k"))
    ax1b.yaxis.set_major_formatter(
        mticker.FuncFormatter(lambda x,_: f"{abs(x):.1f}%"))

    ax1.set_ylabel("Balance", fontsize=9)
    ax1b.set_ylabel("Drawdown %", fontsize=9, color=RED)
    ax1b.tick_params(colors=RED)
    for sp in ax1b.spines.values():
        sp.set_color(GRID)

    lines1, labels1 = ax1.get_legend_handles_labels()
    lines2, labels2 = ax1b.get_legend_handles_labels()
    ax1.legend(lines1+lines2, labels1+labels2,
               facecolor=PANEL, edgecolor=GRID, labelcolor=TEXT,
               fontsize=8, loc="upper left")

    # ── Panel 2: Sharpe and Sortino by year ───────────────────────────────
    ax2 = fig.add_subplot(gs[1, 0])
    style(ax2, "Sharpe & Sortino Ratio  (annualised, RF 4%)")

    x  = np.arange(len(years))
    w  = 0.35
    sh = [m["sharpe"]  for m in valid]
    so = [m["sortino"] for m in valid]

    ax2.bar(x-w/2, sh, w, color=CYAN,  alpha=0.8, label="Sharpe",  zorder=3)
    ax2.bar(x+w/2, so, w, color=PURP,  alpha=0.8, label="Sortino", zorder=3)
    ax2.axhline(y=1.0, color=GOLD, linewidth=1.2, linestyle="--",
                alpha=0.8, label="Good (1.0)")
    ax2.axhline(y=2.0, color=GREEN, linewidth=1.2, linestyle="--",
                alpha=0.8, label="Excellent (2.0)")
    ax2.axhline(y=0,   color=MUTED, linewidth=0.8)

    ax2.set_xticks(x)
    ax2.set_xticklabels(years, rotation=45, fontsize=7)
    ax2.legend(facecolor=PANEL, edgecolor=GRID,
               labelcolor=TEXT, fontsize=7, loc="upper right")

    # Overall Sharpe annotation
    fm_sharpe  = calc_metrics(df, YEARS, ACCOUNT_START)["sharpe"]
    fm_sortino = calc_metrics(df, YEARS, ACCOUNT_START)["sortino"]
    ax2.text(0.02, 0.97,
             f"Full period Sharpe:  {fm_sharpe:.2f}\n"
             f"Full period Sortino: {fm_sortino:.2f}",
             transform=ax2.transAxes, ha="left", va="top",
             color=TEXT, fontsize=8, fontfamily="monospace",
             bbox=dict(boxstyle="round,pad=0.3",
                       facecolor="#1c2128", edgecolor=GRID, alpha=0.9))

    # ── Panel 3: Profit factor and payoff ratio ───────────────────────────
    ax3 = fig.add_subplot(gs[1, 1])
    style(ax3, "Profit Factor & Payoff Ratio by Year")

    pf = [m["profit_factor"] for m in valid]
    po = [m["payoff_ratio"]  for m in valid]

    ax3b = ax3.twinx()
    ax3b.set_facecolor(PANEL)
    for sp in ax3b.spines.values():
        sp.set_color(GRID)

    ax3.bar(x, pf, 0.6, color=GREEN, alpha=0.6, label="Profit Factor", zorder=3)
    ax3b.plot(x, po, color=GOLD, linewidth=2, marker="o",
              markersize=4, label="Payoff Ratio", zorder=5)
    ax3.axhline(y=1.0, color=RED,  linewidth=1, linestyle="--", alpha=0.6)
    ax3.axhline(y=1.5, color=GOLD, linewidth=1, linestyle="--", alpha=0.6,
                label="PF 1.5")

    ax3.set_xticks(x)
    ax3.set_xticklabels(years, rotation=45, fontsize=7)
    ax3.set_ylabel("Profit Factor", fontsize=9, color=GREEN)
    ax3b.set_ylabel("Payoff Ratio", fontsize=9, color=GOLD)
    ax3b.tick_params(colors=GOLD)
    ax3.tick_params(axis="y", colors=GREEN)

    lines1, l1 = ax3.get_legend_handles_labels()
    lines2, l2 = ax3b.get_legend_handles_labels()
    ax3.legend(lines1+lines2, l1+l2,
               facecolor=PANEL, edgecolor=GRID,
               labelcolor=TEXT, fontsize=7)

    # ── Panel 4: Annual returns bar ───────────────────────────────────────
    ax4 = fig.add_subplot(gs[2, 0])
    style(ax4, "Annual Return % on Fixed 100k Account")

    ret_vals = [m["total_return"] for m in valid]
    cols4    = [GREEN if v >= 0 else RED for v in ret_vals]
    ax4.bar(x, ret_vals, 0.7, color=cols4, alpha=0.85, zorder=3)
    ax4.axhline(y=0,  color=MUTED, linewidth=0.8)
    ax4.axhline(y=10, color=GOLD,  linewidth=1, linestyle="--",
                alpha=0.7, label="P1 target (10%)")
    ax4.axhline(y=np.mean(ret_vals), color=CYAN, linewidth=1,
                linestyle="--", alpha=0.7,
                label=f"Avg: {np.mean(ret_vals):.1f}%")

    for i, v in enumerate(ret_vals):
        ax4.text(i, v + (0.3 if v>=0 else -1.2),
                 f"{v:.0f}%", ha="center", fontsize=6.5, color=TEXT)

    ax4.set_xticks(x)
    ax4.set_xticklabels(years, rotation=45, fontsize=7)
    ax4.yaxis.set_major_formatter(
        mticker.FuncFormatter(lambda v,_: f"{v:.0f}%"))
    ax4.legend(facecolor=PANEL, edgecolor=GRID,
               labelcolor=TEXT, fontsize=7)

    # ── Panel 5: Max drawdown by year ─────────────────────────────────────
    ax5 = fig.add_subplot(gs[2, 1])
    style(ax5, "Annual Max Drawdown %")

    dd_vals = [m["max_dd"] for m in valid]
    cols5   = [GREEN if v > -2 else GOLD if v > -5 else RED for v in dd_vals]
    ax5.bar(x, [-v for v in dd_vals], 0.7, color=cols5, alpha=0.85, zorder=3)
    ax5.axhline(y=5,  color=ORNG, linewidth=1.2, linestyle="--",
                alpha=0.8, label="Daily limit 5%")
    ax5.axhline(y=10, color=RED,  linewidth=1.2, linestyle="--",
                alpha=0.8, label="Overall limit 10%")

    for i, v in enumerate(dd_vals):
        ax5.text(i, -v + 0.1, f"{abs(v):.1f}%",
                 ha="center", fontsize=6.5, color=TEXT)

    ax5.set_xticks(x)
    ax5.set_xticklabels(years, rotation=45, fontsize=7)
    ax5.yaxis.set_major_formatter(
        mticker.FuncFormatter(lambda v,_: f"{abs(v):.1f}%"))
    ax5.legend(facecolor=PANEL, edgecolor=GRID,
               labelcolor=TEXT, fontsize=7)

    # ── Panel 6: Win rate and profit factor scatter ───────────────────────
    ax6 = fig.add_subplot(gs[3, 0])
    style(ax6, "Win Rate vs Profit Factor  (each dot = 1 year)")

    wr_vals = [m["win_rate"]      for m in valid]
    pf_vals = [m["profit_factor"] for m in valid]
    yr_vals = [m["year"]          for m in valid]

    sc = ax6.scatter(wr_vals, pf_vals, c=range(len(valid)),
                     cmap="cool", s=80, zorder=5, alpha=0.9)
    ax6.axhline(y=1.0, color=MUTED, linewidth=0.8, linestyle="--")
    ax6.axvline(x=50,  color=MUTED, linewidth=0.8, linestyle="--")

    for wr, pf, yr in zip(wr_vals, pf_vals, yr_vals):
        ax6.annotate(str(yr), (wr, pf),
                     xytext=(4, 4), textcoords="offset points",
                     fontsize=6.5, color=TEXT)

    ax6.set_xlabel("Win Rate %", fontsize=9)
    ax6.set_ylabel("Profit Factor", fontsize=9)
    ax6.text(0.02, 0.97, "Top-right = best years",
             transform=ax6.transAxes, color=MUTED,
             fontsize=7, va="top")

    # ── Panel 7: Rolling 12-month Sharpe ─────────────────────────────────
    ax7 = fig.add_subplot(gs[3, 1])
    style(ax7, "Rolling 12-Month Sharpe Ratio")

    # Build monthly returns
    df2 = df.copy()
    df2["month_dt"] = df2["exit_time"].dt.to_period("M").dt.to_timestamp()
    monthly = df2.groupby("month_dt").agg(
        pnl=("dollar_pnl","sum"),
        n  =("dollar_pnl","count"),
    ).reset_index()
    monthly["ret_pct"]  = monthly["pnl"] / ACCOUNT_START

    # 12-month rolling Sharpe
    rf_monthly = (1 + RISK_FREE_RATE) ** (1/12) - 1
    excess_m   = monthly["ret_pct"] - rf_monthly
    roll_mean  = excess_m.rolling(12).mean()
    roll_std   = excess_m.rolling(12).std()
    roll_sharpe= (roll_mean / roll_std * np.sqrt(12)).fillna(0)

    ax7.fill_between(monthly["month_dt"], roll_sharpe, 0,
                     where=roll_sharpe >= 0, alpha=0.3, color=GREEN)
    ax7.fill_between(monthly["month_dt"], roll_sharpe, 0,
                     where=roll_sharpe < 0, alpha=0.3, color=RED)
    ax7.plot(monthly["month_dt"], roll_sharpe,
             color=CYAN, linewidth=1.2)
    ax7.axhline(y=0,   color=MUTED, linewidth=0.8)
    ax7.axhline(y=1.0, color=GOLD, linewidth=1, linestyle="--",
                alpha=0.7, label="Sharpe 1.0")
    ax7.axhline(y=2.0, color=GREEN, linewidth=1, linestyle="--",
                alpha=0.7, label="Sharpe 2.0")
    ax7.set_ylabel("Rolling Sharpe", fontsize=9)
    ax7.legend(facecolor=PANEL, edgecolor=GRID,
               labelcolor=TEXT, fontsize=7)

    # ── Suptitle ──────────────────────────────────────────────────────────
    fm = calc_metrics(df, YEARS, ACCOUNT_START)
    fig.suptitle(
        f"EURUSD Macro Lead-Lag Model  -  Professional Track Record\n"
        f"2003-2026  |  4,019 Trades  |  "
        f"CAGR {fm['cagr']:.1f}%  |  "
        f"Sharpe {fm['sharpe']:.2f}  |  "
        f"Sortino {fm['sortino']:.2f}  |  "
        f"Max DD {fm['max_dd']:.2f}%  |  "
        f"Win Rate {fm['win_rate']:.1f}%  |  "
        f"Real Costs Applied",
        color=TEXT, fontsize=12, fontweight="bold", y=0.965
    )

    plt.savefig(save_path, dpi=150, bbox_inches="tight",
                facecolor=fig.get_facecolor())
    plt.close()
    print(f"\n  Chart saved: {save_path}")


# ── Main ──────────────────────────────────────────────────────────────────────
def main():
    print("=" * 70)
    print("TRACK RECORD V1  —  Professional Performance Metrics")
    print("=" * 70)

    print("\nLoading trades...")
    df = load_trades()

    # Build full-period metrics
    print("Calculating full-period metrics...")
    full_metrics = calc_metrics(df, YEARS, ACCOUNT_START)

    # Build year-by-year metrics
    print("Calculating year-by-year metrics...")
    annual_metrics = []
    balance = ACCOUNT_START

    for year, grp in df.groupby("year"):
        grp = grp.sort_values("exit_time").reset_index(drop=True)
        m   = calc_metrics(grp, 1.0, balance)
        if m:
            m["year"]    = year
            m["year_ret"]= m["total_return"]
        annual_metrics.append(m)
        balance = m.get("end_balance", balance) if m else balance

    # Print track record
    print_track_record(df, annual_metrics, full_metrics)

    # Save CSV
    rows = []
    for m in annual_metrics:
        if m and m.get("trades", 0) > 0:
            rows.append({
                "year"              : m.get("year"),
                "trades"            : m.get("trades"),
                "win_rate_pct"      : round(m.get("win_rate",0), 2),
                "net_pnl"           : round(m.get("net_pnl",0), 0),
                "total_return_pct"  : round(m.get("total_return",0), 2),
                "max_dd_pct"        : round(m.get("max_dd",0), 2),
                "sharpe"            : round(m.get("sharpe",0), 3),
                "sortino"           : round(m.get("sortino",0), 3),
                "calmar"            : round(m.get("calmar",0), 3),
                "profit_factor"     : round(m.get("profit_factor",0), 3),
                "payoff_ratio"      : round(m.get("payoff_ratio",0), 3),
                "avg_win_pct"       : round(m.get("avg_win_pct",0), 4),
                "avg_loss_pct"      : round(m.get("avg_loss_pct",0), 4),
                "max_consec_wins"   : m.get("max_consec_wins"),
                "max_consec_losses" : m.get("max_consec_losses"),
                "best_month"        : round(m.get("best_month",0), 0),
                "worst_month"       : round(m.get("worst_month",0), 0),
                "pos_months_pct"    : round(m.get("pos_months",0) /
                                            max(m.get("tot_months",1),1)*100, 1),
            })

    csv_path = TRADES_DIR / "track_record_annual.csv"
    pd.DataFrame(rows).to_csv(csv_path, index=False)
    print(f"\n  CSV saved: {csv_path}")

    # Generate chart
    print("\nGenerating chart...")
    chart_path = CHARTS_DIR / "track_record_chart.png"
    make_chart(df, annual_metrics, chart_path)

    print(f"\n{'='*70}")
    print("TRACK RECORD SUMMARY")
    print(f"{'='*70}")
    fm = full_metrics
    print(f"  CAGR             : {fm['cagr']:.2f}%")
    print(f"  Total return     : {fm['total_return']:.0f}%")
    print(f"  Sharpe ratio     : {fm['sharpe']:.3f}")
    print(f"  Sortino ratio    : {fm['sortino']:.3f}")
    print(f"  Calmar ratio     : {fm['calmar']:.3f}")
    print(f"  Max drawdown     : {fm['max_dd']:.2f}%")
    print(f"  Profit factor    : {fm['profit_factor']:.3f}")
    print(f"  Win rate         : {fm['win_rate']:.2f}%")
    print(f"  Payoff ratio     : {fm['payoff_ratio']:.3f}")
    print(f"  Recovery factor  : {fm['recovery_factor']:.3f}")
    print(f"  Positive months  : {fm['pos_months']}/{fm['tot_months']} ({fm['pos_months']/fm['tot_months']*100:.0f}%)")


if __name__ == "__main__":
    main()
