"""
4H trade-outcome profiler: MFE/MAE analysis to diagnose bad entries vs premature exits.

Classifies IS losing trades into:
  "Never had it"  — MFE < 0.5%  (price never moved in our favor)
  "Gave it back"  — MFE > 2%    but exited at a loss
  "Noisy zone"    — 0.5% <= MFE <= 2%, small loss
"""
import sys
import pandas as pd
import numpy as np
import json
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))
from strategies.strategy_mlp_scores import generate_signals, load_mlp_artifact

DATA_FILE  = "data/mlp/COINBASE_BTCUSD, 240.csv"
PARAMS_FILE = "results/winners/optimization_winner_strategy_mlp_scores_COINBASE_BTCUSD_4H.csv"
IS_START   = "2017-12-01"
IS_END     = "2025-09-30"

def load_params(path):
    df = pd.read_csv(path)
    row = df.iloc[0]
    return row.to_dict()

def main():
    print("Loading data...")
    data = pd.read_csv(DATA_FILE, parse_dates=["time"])
    data = data.sort_values("time").reset_index(drop=True)

    params = load_params(PARAMS_FILE)
    print(f"Params: entry={params['i_long_entry_activation_threshold']}, "
          f"exit={params['i_long_exit_activation_threshold']}, "
          f"conf={params['i_long_exit_activation_confirmation_threshold']}, "
          f"trail={params['i_trailing_stop_threshold']}%")

    print("Generating signals...")
    signals = generate_signals(data, **params)

    # Filter to IS window
    is_mask = (signals["time"] >= IS_START) & (signals["time"] <= IS_END)
    signals_is = signals[is_mask].reset_index(drop=True)
    data_is    = data[(data["time"] >= IS_START) & (data["time"] <= IS_END)].reset_index(drop=True)

    # Build trade list from signals
    trades = []
    in_long = False
    entry_time = entry_price = entry_score = None
    entry_idx = None

    signals_is_list = signals_is.reset_index(drop=True)
    for idx, row in signals_is_list.iterrows():
        if not in_long and row["execute_entry"]:
            in_long = True
            entry_time  = row["time"]
            entry_score = row["activation_score"]
            # Fill price = open of next bar (fill_orders_on_standard_ohlc=true)
            if idx + 1 < len(signals_is_list):
                entry_price = signals_is_list.loc[idx + 1, "open"]
            else:
                entry_price = row["close"]
            entry_idx = idx
        elif in_long and row["execute_exit"]:
            in_long = False
            exit_time  = row["time"]
            # Fill price = open of next bar (unless it's the last bar)
            if idx + 1 < len(signals_is_list):
                exit_price = signals_is_list.loc[idx + 1, "open"]
            else:
                exit_price = row["close"]
            pnl_pct = (exit_price - entry_price) / entry_price * 100.0

            # MFE / MAE from OHLC between entry fill bar and exit bar
            # Use bars from entry+1 (fill bar) through exit bar (inclusive)
            fill_start = entry_idx + 1
            window = signals_is_list.iloc[fill_start: idx + 1]
            if len(window) > 0:
                mfe = (window["high"].max() - entry_price) / entry_price * 100.0
                mae = (window["low"].min()  - entry_price) / entry_price * 100.0
            else:
                mfe = mae = 0.0

            bars_held = len(window)
            trades.append({
                "entry_time":  entry_time,
                "exit_time":   exit_time,
                "entry_price": entry_price,
                "exit_price":  exit_price,
                "entry_score": entry_score,
                "pnl_pct":     pnl_pct,
                "mfe_pct":     mfe,
                "mae_pct":     mae,
                "bars_held":   bars_held,
            })

    df = pd.DataFrame(trades)
    winners = df[df["pnl_pct"] > 0]
    losers  = df[df["pnl_pct"] <= 0]

    print(f"\n{'='*60}")
    print(f"IS Trade Profiling  ({IS_START} → {IS_END})")
    print(f"{'='*60}")
    print(f"Total trades : {len(df)}")
    print(f"Winners      : {len(winners)}  ({100*len(winners)/len(df):.1f}%)")
    print(f"Losers       : {len(losers)}   ({100*len(losers)/len(df):.1f}%)")

    print(f"\n--- Winner stats ---")
    print(f"  Avg PnL   : +{winners['pnl_pct'].mean():.2f}%")
    print(f"  Avg MFE   : +{winners['mfe_pct'].mean():.2f}%")
    print(f"  Avg bars  : {winners['bars_held'].mean():.1f}")
    print(f"  Avg entry score: {winners['entry_score'].mean():.1f}")

    print(f"\n--- Loser stats ---")
    print(f"  Avg PnL   : {losers['pnl_pct'].mean():.2f}%")
    print(f"  Avg MFE   : +{losers['mfe_pct'].mean():.2f}%")
    print(f"  Avg MAE   :  {losers['mae_pct'].mean():.2f}%")
    print(f"  Avg bars  : {losers['bars_held'].mean():.1f}")
    print(f"  Avg entry score: {losers['entry_score'].mean():.1f}")

    # Classify losers
    never_had_it  = losers[losers["mfe_pct"] < 0.5]
    gave_it_back  = losers[losers["mfe_pct"] > 2.0]
    noisy_zone    = losers[(losers["mfe_pct"] >= 0.5) & (losers["mfe_pct"] <= 2.0)]

    total_losers = len(losers)
    print(f"\n--- Loser classification ---")
    print(f"  'Never had it'  (MFE < 0.5%): {len(never_had_it):3d}  ({100*len(never_had_it)/total_losers:.1f}%)")
    print(f"  'Gave it back'  (MFE > 2.0%): {len(gave_it_back):3d}  ({100*len(gave_it_back)/total_losers:.1f}%)")
    print(f"  'Noisy zone' (0.5≤MFE≤2.0%): {len(noisy_zone):3d}  ({100*len(noisy_zone)/total_losers:.1f}%)")

    print(f"\n--- 'Gave it back' detail (top losers by MFE) ---")
    top_gb = gave_it_back.sort_values("mfe_pct", ascending=False).head(10)
    print(f"  {'Entry':19s}  {'Exit':19s}  {'PnL%':>7s}  {'MFE%':>7s}  {'MAE%':>7s}  {'Bars':>5s}")
    for _, r in top_gb.iterrows():
        print(f"  {str(r.entry_time):19s}  {str(r.exit_time):19s}  "
              f"{r.pnl_pct:>7.2f}  {r.mfe_pct:>7.2f}  {r.mae_pct:>7.2f}  {r.bars_held:>5d}")

    print(f"\n--- Entry score distribution (winners vs losers) ---")
    print(f"  Winners entry score: mean={winners['entry_score'].mean():.1f}, "
          f"median={winners['entry_score'].median():.1f}, "
          f"std={winners['entry_score'].std():.1f}")
    print(f"  Losers  entry score: mean={losers['entry_score'].mean():.1f}, "
          f"median={losers['entry_score'].median():.1f}, "
          f"std={losers['entry_score'].std():.1f}")

    # Entry score percentile overlap
    w_scores = winners["entry_score"].values
    l_scores = losers["entry_score"].values
    # What % of losers have entry score BELOW winner 25th percentile
    w_q25 = np.percentile(w_scores, 25)
    below = (l_scores < w_q25).mean() * 100
    print(f"  Winners 25th pct entry score: {w_q25:.1f}")
    print(f"  Losers below that threshold: {below:.1f}%")

    print(f"\n--- Bars-held distribution ---")
    print(f"  Winners avg bars held: {winners['bars_held'].mean():.1f} "
          f"(median {winners['bars_held'].median():.0f})")
    print(f"  Losers  avg bars held: {losers['bars_held'].mean():.1f} "
          f"(median {losers['bars_held'].median():.0f})")

    # How many losers exited in 1-2 bars? (possible premature stop/confirmation exits)
    quick_losses = losers[losers["bars_held"] <= 2]
    print(f"  Losers exiting in ≤2 bars: {len(quick_losses)} ({100*len(quick_losses)/total_losers:.1f}%)")

    # MFE histogram for losers
    print(f"\n--- MFE histogram for losers ---")
    bins = [(-99, 0), (0, 0.5), (0.5, 1.0), (1.0, 2.0), (2.0, 5.0), (5.0, 10.0), (10.0, 99)]
    for lo, hi in bins:
        n = ((losers["mfe_pct"] > lo) & (losers["mfe_pct"] <= hi)).sum()
        bar = "#" * n
        print(f"  ({lo:5.1f}%, {hi:5.1f}%]: {n:3d}  {bar}")

    print(f"\n{'='*60}")
    print("DIAGNOSIS:")
    nhi_pct = 100 * len(never_had_it) / total_losers
    gib_pct = 100 * len(gave_it_back) / total_losers
    if gib_pct > 40:
        print(f"  → PREMATURE EXITS dominate ({gib_pct:.0f}% 'gave it back').")
        print(f"    Approach B (position-aware features) is WARRANTED.")
    elif nhi_pct > 60:
        print(f"  → BAD ENTRIES dominate ({nhi_pct:.0f}% 'never had it').")
        print(f"    Approach B unlikely to help; entry quality is the bottleneck.")
    else:
        print(f"  → Mixed signal: 'gave it back' {gib_pct:.0f}%, 'never had it' {nhi_pct:.0f}%.")
        print(f"    Investigate further before committing to Approach B.")
    print(f"{'='*60}")

if __name__ == "__main__":
    main()
