"""
ftmo_perspective_annual_v1.py
==============================
Shows year-by-year performance from the FTMO challenge perspective.

Key difference from annual_pnl_analysis_v1.py
-----------------------------------------------
That script showed a COMPOUNDING account where balance grows from
$100k to $1.2M but notional stays fixed. % returns shrink over time
because the denominator grows. That is misleading for FTMO purposes.

This script shows what ACTUALLY matters for prop firm challenges:
  - Fixed $100k account basis every year
  - Fixed $300k notional (0.75% of $100k always)
  - Dollar P&L vs $10k Phase 1 target
  - How many months each year would take to pass Phase 1
  - How many months to pass Phase 2 (5% = $5k)

The point: FTMO challenges always start at $100k with the same rules.
The dollar P&L per year is what matters, not % of a growing balance.

Also shows:
  - Monthly DD chart (not annual) so you can see the FTMO-relevant risk
  - Phase 1 and Phase 2 progress simulation per year

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

Run from project root:
  python src/research/ftmo_perspective_annual_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"

# ── Constants ─────────────────────────────────────────────────────────────────
CHALLENGE_BALANCE = 100_000.0
PHASE1_TARGET     = 10_000.0     # 10%
PHASE2_TARGET     =  5_000.0     # 5%
MAX_OVERALL_DD    = 10_000.0     # 10%
MAX_DAILY_DD      =  5_000.0     # 5%
STOP_PCT          = 0.0025
BASE_RISK_PCT     = 0.0075
BASE_NOTIONAL     = (CHALLENGE_BALANCE * BASE_RISK_PCT) / STOP_PCT   # $300,000

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 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]
            if "zscore_abs" not in df.columns:
                df["zscore_abs"] = 2.75
            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")
            print(f"  Loaded {len(df):,} trades from {fname}")
            return df.sort_values("exit_time").reset_index(drop=True)
    raise FileNotFoundError("No trade log found.")


# ── Simulate challenge progress within a year ─────────────────────────────────
def simulate_challenge_progress(grp: pd.DataFrame, target: float) -> dict:
    """
    Simulates a challenge starting at $0 P&L, running through the year's
    trades in order. Returns when target is hit or year ends.
    """
    grp  = grp.sort_values("exit_time").reset_index(drop=True)
    cum  = 0.0
    peak = 0.0
    max_dd = 0.0
    daily_pnl = {}
    hit_target = False
    breach     = False
    trades_to_target = None

    for i, row in grp.iterrows():
        dk = str(row["exit_time"].date())
        if dk not in daily_pnl:
            daily_pnl[dk] = 0.0

        pnl           = row["dollar_pnl"]
        cum          += pnl
        daily_pnl[dk]+= pnl

        if cum > peak:
            peak = cum
        dd = peak - cum
        if dd > max_dd:
            max_dd = dd

        if daily_pnl[dk] < -MAX_DAILY_DD:
            breach = True
            break
        if cum <= -MAX_OVERALL_DD:
            breach = True
            break
        if cum >= target and not hit_target:
            hit_target       = True
            trades_to_target = i + 1
            break

    return {
        "final_pnl"        : cum,
        "max_dd_dollar"    : max_dd,
        "max_dd_pct"       : max_dd / CHALLENGE_BALANCE * 100,
        "hit_target"       : hit_target,
        "breach"           : breach,
        "trades_to_target" : trades_to_target,
        "total_trades"     : len(grp),
    }


# ── Monthly P&L on fixed account ─────────────────────────────────────────────
def monthly_pnl_fixed(df: pd.DataFrame) -> pd.DataFrame:
    monthly = (df.groupby("month")["dollar_pnl"]
                 .sum()
                 .reset_index())
    monthly["month_dt"] = monthly["month"].dt.to_timestamp()
    monthly["pct"]      = monthly["dollar_pnl"] / CHALLENGE_BALANCE * 100
    return monthly


# ── Print table ───────────────────────────────────────────────────────────────
def print_table(annual_rows: list):
    print(f"\n{'='*110}")
    print("FTMO CHALLENGE PERSPECTIVE  —  Fixed 100k Account  |  300k Notional  |  Real Costs")
    print(f"{'='*110}")
    print(f"\n  {'Year':<6}{'Trades':>7}{'PnL':>10}{'Pct':>7}"
          f"{'MaxDD%':>8}{'P1 Hit':>8}{'P1 Trades':>11}{'P1 Months':>11}"
          f"{'P2 Hit':>8}{'P2 Months':>11}{'Breach':>8}")
    print(f"  {'─'*106}")

    hit_p1 = hit_p2 = breach_yr = 0
    for r in annual_rows:
        p1h  = "YES" if r["p1"]["hit_target"]  else "no"
        p2h  = "YES" if r["p2"]["hit_target"]  else "no"
        br   = "YES" if r["p1"]["breach"] or r["p2"]["breach"] else "-"
        pct  = r["p1"]["final_pnl"] / CHALLENGE_BALANCE * 100

        p1_trades = str(r["p1"]["trades_to_target"]) if r["p1"]["hit_target"] else "-"
        p2_trades = str(r["p2"]["trades_to_target"]) if r["p2"]["hit_target"] else "-"

        tpy = r["total_trades"]
        p1m = (r["p1"]["trades_to_target"] / tpy * 12
               if r["p1"]["hit_target"] and tpy > 0 else float("nan"))
        p2m = (r["p2"]["trades_to_target"] / tpy * 12
               if r["p2"]["hit_target"] and tpy > 0 else float("nan"))

        p1m_str = f"{p1m:.1f}m" if not np.isnan(p1m) else "-"
        p2m_str = f"{p2m:.1f}m" if not np.isnan(p2m) else "-"

        flag = ""
        if r["p1"]["hit_target"]: hit_p1 += 1
        if r["p2"]["hit_target"]: hit_p2 += 1
        if r["p1"]["breach"] or r["p2"]["breach"]: breach_yr += 1

        print(f"  {r['year']:<6}{r['total_trades']:>7}"
              f"  {r['p1']['final_pnl']:>+8,.0f}"
              f"{pct:>7.1f}%"
              f"{r['p1']['max_dd_pct']:>8.2f}%"
              f"{p1h:>8}{p1_trades:>11}{p1m_str:>11}"
              f"{p2h:>8}{p2m_str:>11}"
              f"{br:>8}")

    print(f"\n  {'─'*106}")
    n = len(annual_rows)
    avg_pnl     = np.mean([r["p1"]["final_pnl"] for r in annual_rows])
    avg_p1m     = np.nanmean([
        r["p1"]["trades_to_target"] / r["total_trades"] * 12
        for r in annual_rows if r["p1"]["hit_target"] and r["total_trades"] > 0
    ])
    avg_p2m     = np.nanmean([
        r["p2"]["trades_to_target"] / r["total_trades"] * 12
        for r in annual_rows if r["p2"]["hit_target"] and r["total_trades"] > 0
    ])
    avg_dd      = np.mean([r["p1"]["max_dd_pct"] for r in annual_rows])

    print(f"\n  Phase 1 passed      : {hit_p1}/{n} years ({hit_p1/n*100:.0f}%)")
    print(f"  Phase 2 passed      : {hit_p2}/{n} years ({hit_p2/n*100:.0f}%)")
    print(f"  Breach years        : {breach_yr}/{n}")
    print(f"  Avg annual PnL      : +{avg_pnl:>,.0f}")
    print(f"  Avg Phase 1 time    : {avg_p1m:.1f} months")
    print(f"  Avg Phase 2 time    : {avg_p2m:.1f} months")
    print(f"  Avg combined total  : {avg_p1m + avg_p2m:.1f} months")
    print(f"  Avg year max DD     : {avg_dd:.2f}%  (limit 10%)")


# ── Chart ─────────────────────────────────────────────────────────────────────
def make_chart(df, annual_rows, monthly, save_path):
    BG    = "#0d1117"
    PANEL = "#161b22"
    GRID  = "#21262d"
    TEXT  = "#e6edf3"
    MUTED = "#8b949e"
    CYAN  = "#00d4ff"
    GREEN = "#00ff88"
    RED   = "#ff4444"
    GOLD  = "#ffd700"
    ORNG  = "#ff8c00"

    fig = plt.figure(figsize=(22, 22), facecolor=BG)
    gs  = gridspec.GridSpec(3, 2, figure=fig,
                            hspace=0.50, wspace=0.28,
                            left=0.07, right=0.97,
                            top=0.92, bottom=0.05)

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

    years     = [r["year"] for r in annual_rows]
    pnl_vals  = [r["p1"]["final_pnl"] for r in annual_rows]
    dd_vals   = [r["p1"]["max_dd_pct"] for r in annual_rows]
    tpy_vals  = [r["total_trades"] for r in annual_rows]

    def safe_months(r, phase):
        if r[phase]["hit_target"] and r["total_trades"] > 0:
            return r[phase]["trades_to_target"] / r["total_trades"] * 12
        return np.nan

    p1_months = [safe_months(r, "p1") for r in annual_rows]
    p2_months = [safe_months(r, "p2") for r in annual_rows]
    comb_months = [
        (p1 + p2) if not (np.isnan(p1) or np.isnan(p2)) else np.nan
        for p1, p2 in zip(p1_months, p2_months)
    ]

    # ── Panel 1: Annual PnL (dollar, fixed account) ────────────────────────
    ax1 = fig.add_subplot(gs[0, 0])
    style(ax1, "Annual PnL on Fixed 100k Account (300k notional)")

    cols = [GREEN if v >= 0 else RED for v in pnl_vals]
    bars = ax1.bar(years, [v / 1000 for v in pnl_vals],
                   color=cols, alpha=0.85, width=0.7, zorder=3)
    ax1.axhline(y=PHASE1_TARGET / 1000, color=GOLD, linewidth=1.5,
                linestyle="--", alpha=0.9, label="P1 target (10k)")
    ax1.axhline(y=PHASE2_TARGET / 1000, color=CYAN,  linewidth=1.2,
                linestyle=":",  alpha=0.9, label="P2 target (5k)")
    ax1.axhline(y=0, color=MUTED, linewidth=0.8)

    for bar, val in zip(bars, pnl_vals):
        h = bar.get_height()
        ax1.text(bar.get_x() + bar.get_width() / 2,
                 h + (0.3 if h >= 0 else -1.2),
                 f"{val/1000:.0f}k",
                 ha="center", va="bottom" if h >= 0 else "top",
                 fontsize=7, color=TEXT, fontweight="bold")

    ax1.yaxis.set_major_formatter(
        mticker.FuncFormatter(lambda x, _: f"{x:.0f}k"))
    ax1.set_ylabel("PnL (thousands)", fontsize=9)
    ax1.set_xticks(years)
    ax1.set_xticklabels(years, rotation=45, fontsize=8)
    ax1.legend(facecolor=PANEL, edgecolor=GRID,
               labelcolor=TEXT, fontsize=8)

    # ── Panel 2: Challenge months per year ─────────────────────────────────
    ax2 = fig.add_subplot(gs[0, 1])
    style(ax2, "Months to Pass Phase 1 + Phase 2 per Year")

    x = np.arange(len(years))
    w = 0.35
    valid_p1 = [v if not np.isnan(v) else 0 for v in p1_months]
    valid_p2 = [v if not np.isnan(v) else 0 for v in p2_months]

    b1 = ax2.bar(x - w/2, valid_p1, w, color=CYAN,  alpha=0.8,
                 label="Phase 1 months", zorder=3)
    b2 = ax2.bar(x + w/2, valid_p2, w, color=GREEN, alpha=0.8,
                 label="Phase 2 months", zorder=3)

    # Combined line
    valid_comb = [v if not np.isnan(v) else np.nan for v in comb_months]
    ax2.plot(x, valid_comb, color=GOLD, linewidth=2, marker="D",
             markersize=5, zorder=5, label="Combined total")

    avg_comb = np.nanmean(valid_comb)
    ax2.axhline(y=avg_comb, color=GOLD, linewidth=1, linestyle="--",
                alpha=0.6, label=f"Avg combined: {avg_comb:.1f}m")

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

    for bar, val, yr_idx in zip(b1, valid_p1, range(len(years))):
        if val > 0:
            ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.05,
                     f"{val:.1f}", ha="center", fontsize=6.5, color=CYAN)

    # ── Panel 3: Monthly PnL bars (full series, fixed account) ─────────────
    ax3 = fig.add_subplot(gs[1, :])
    style(ax3, "Monthly PnL — Fixed 100k Account  |  Entire 22-Year History")

    mc = [GREEN if v >= 0 else RED for v in monthly["dollar_pnl"]]
    ax3.bar(range(len(monthly)), monthly["dollar_pnl"] / 1000,
            color=mc, alpha=0.8, width=1.0)
    ax3.axhline(y=0, color=MUTED, linewidth=0.8)
    ax3.axhline(y=PHASE1_TARGET / 1000 / 12, color=GOLD,
                linewidth=1, linestyle="--", alpha=0.6,
                label="Avg monthly needed for P1 (833/mo)")

    # Year separators and labels
    year_starts = []
    prev_yr = None
    for i, m in enumerate(monthly["month_dt"]):
        yr = m.year
        if yr != prev_yr:
            year_starts.append((i, yr))
            if i > 0:
                ax3.axvline(x=i, color=GRID, linewidth=1.2, alpha=0.8)
            prev_yr = yr

    ax3.set_xticks([i for i, _ in year_starts])
    ax3.set_xticklabels([str(yr) for _, yr in year_starts],
                         rotation=45, fontsize=8)
    ax3.yaxis.set_major_formatter(
        mticker.FuncFormatter(lambda x, _: f"{x:.0f}k"))
    ax3.set_ylabel("Monthly PnL (thousands)", fontsize=9)

    pos_months = (monthly["dollar_pnl"] > 0).sum()
    tot_months = len(monthly)
    ax3.text(0.01, 0.95,
             f"Positive months: {pos_months}/{tot_months} "
             f"({pos_months/tot_months*100:.0f}%)",
             transform=ax3.transAxes, ha="left", va="top",
             color=TEXT, fontsize=9, fontweight="bold")
    ax3.legend(facecolor=PANEL, edgecolor=GRID,
               labelcolor=TEXT, fontsize=8)

    # ── Panel 4: Annual max DD ─────────────────────────────────────────────
    ax4 = fig.add_subplot(gs[2, 0])
    style(ax4, "Annual Max Drawdown  (on 100k fixed account)")

    dd_cols = [GREEN if v < 2 else GOLD if v < 5 else RED for v in dd_vals]
    ax4.bar(years, [-v for v in dd_vals], color=dd_cols,
            alpha=0.85, width=0.7, zorder=3)
    ax4.axhline(y=-5,  color=ORNG, linewidth=1.2, linestyle="--",
                alpha=0.8, label="Daily DD limit 5%")
    ax4.axhline(y=-10, color=RED,  linewidth=1.2, linestyle="--",
                alpha=0.8, label="Overall DD limit 10%")
    ax4.yaxis.set_major_formatter(
        mticker.FuncFormatter(lambda x, _: f"{abs(x):.1f}%"))
    ax4.set_ylabel("Max Drawdown %", fontsize=9)
    ax4.set_xticks(years)
    ax4.set_xticklabels(years, rotation=45, fontsize=8)
    ax4.legend(facecolor=PANEL, edgecolor=GRID,
               labelcolor=TEXT, fontsize=8)

    for i, (yr, dd) in enumerate(zip(years, dd_vals)):
        ax4.text(yr, -dd - 0.15, f"{dd:.1f}%",
                 ha="center", fontsize=7, color=TEXT)

    # ── Panel 5: Trade count and win rate ──────────────────────────────────
    ax5 = fig.add_subplot(gs[2, 1])
    style(ax5, "Trades per Year and Win Rate")

    ax5b = ax5.twinx()
    ax5b.set_facecolor(PANEL)

    wr_vals = []
    for r in annual_rows:
        yr_trades = df[df["year"] == r["year"]]
        wr_vals.append(yr_trades["win"].mean() * 100 if len(yr_trades) > 0 else 0)

    ax5.bar(years, tpy_vals, color=CYAN, alpha=0.3, width=0.7, zorder=2)
    ax5b.plot(years, wr_vals, color=GREEN, linewidth=2,
              marker="o", markersize=5, zorder=5)

    avg_wr = np.mean(wr_vals)
    ax5b.axhline(y=avg_wr, color=GREEN, linewidth=0.8, linestyle="--", alpha=0.5)
    ax5b.text(years[0], avg_wr + 1, f"avg {avg_wr:.1f}%",
              color=GREEN, fontsize=8)

    ax5.set_ylabel("Trades", fontsize=9, color=CYAN)
    ax5b.set_ylabel("Win Rate %", fontsize=9, color=GREEN)
    ax5.tick_params(axis="y", colors=CYAN)
    ax5b.tick_params(axis="y", colors=GREEN)
    for sp in ax5b.spines.values():
        sp.set_color(GRID)
    ax5b.set_ylim(0, 105)
    ax5.set_xticks(years)
    ax5.set_xticklabels(years, rotation=45, fontsize=8)

    # ── Suptitle ───────────────────────────────────────────────────────────
    avg_p1 = np.nanmean(p1_months)
    avg_p2 = np.nanmean(p2_months)
    avg_pnl = np.mean(pnl_vals)

    fig.suptitle(
        f"FTMO Challenge Perspective  -  Fixed 100k Account  |  300k Notional\n"
        f"Avg Annual PnL: {avg_pnl/1000:.0f}k  |  "
        f"Avg Phase 1: {avg_p1:.1f} months  |  "
        f"Avg Phase 2: {avg_p2:.1f} months  |  "
        f"Avg Combined: {avg_p1+avg_p2:.1f} months  |  "
        f"Real-World Costs Applied",
        color=TEXT, fontsize=13, fontweight="bold", y=0.965
    )

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


# ── Main ──────────────────────────────────────────────────────────────────────
def main():
    print("=" * 70)
    print("FTMO PERSPECTIVE ANNUAL ANALYSIS")
    print(f"  Challenge account : 100k (fixed, not compounding)")
    print(f"  Base notional     : 300k  (0.75% risk)")
    print(f"  Phase 1 target    : 10k  (+10%)")
    print(f"  Phase 2 target    : 5k   (+5%)")
    print(f"  Daily DD limit    : 5k   (5%)")
    print(f"  Overall DD limit  : 10k  (10%)")
    print("=" * 70)

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

    print("Running annual challenge simulations...")
    annual_rows = []
    for year, grp in df.groupby("year"):
        p1 = simulate_challenge_progress(grp, PHASE1_TARGET)
        p2 = simulate_challenge_progress(grp, PHASE2_TARGET)
        annual_rows.append({
            "year"        : year,
            "total_trades": len(grp),
            "p1"          : p1,
            "p2"          : p2,
        })

    print("Computing monthly P&L...")
    monthly = monthly_pnl_fixed(df)

    print_table(annual_rows)

    print("\nGenerating chart...")
    chart_path = CHARTS_DIR / "ftmo_perspective_chart.png"
    make_chart(df, annual_rows, monthly, chart_path)

    print(f"\n  Chart saved to: {chart_path}")
    print(f"\n  KEY INSIGHT:")
    print(f"  The annual % return shrinks in later years because the")
    print(f"  COMPOUNDING account balance grows ($100k -> $1.2M) but")
    print(f"  the notional stays fixed at 300k.")
    print(f"  For FTMO challenges this does NOT matter - every challenge")
    print(f"  starts fresh at 100k with the same 300k notional.")
    print(f"  The dollar PnL per year is what matters for prop firms.")


if __name__ == "__main__":
    main()
