"""
export_trade_logs_v1.py
=======================
Exports full trade-level logs for the two finalist candidates:
  - growth_52h  : threshold 2.75 / fib 0.786 / hold 52h / stop 0.25%
  - smoother_25h: threshold 2.75 / fib 0.786 / hold 25h / stop 0.25%

Each trade captures:
  entry_time, exit_time, signal, entry_price, exit_price,
  stop_hit, mae, mfe, return, equity, peak, drawdown, win, trade_num, candidate

MAE (Maximum Adverse Excursion) — worst move against the position during hold.
MFE (Maximum Favourable Excursion) — best move in position direction during hold.
Both expressed as decimals relative to entry price. MAE is always negative.

Output:
  data/processed/trades/trades_growth_52h.csv
  data/processed/trades/trades_smoother_25h.csv

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

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

import pandas as pd
from pathlib import Path
import sys

# ── Path setup ────────────────────────────────────────────────────────────────
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))

from research.combined_candidate_matrix_v1 import (
    build_frozen_signals,
    get_first_m15_idx_at_or_after,
    find_entry_pullback,
)
from ingestion.price_loader_15m import load_eurusd_15m

# ── Constants ─────────────────────────────────────────────────────────────────
STOP         = 0.0025   # 0.25% hard stop
SPREAD_COST  = 0.0001   # 1 pip spread cost
ALLOWED_HOURS = set(range(7, 17))  # London + NY session

OUTPUT_DIR = BASE_PATH / "data" / "processed" / "trades"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

# ── Candidates ────────────────────────────────────────────────────────────────
CANDIDATES = [
    {
        "name"      : "growth_52h",
        "threshold" : 2.75,
        "fib"       : 0.786,
        "hold_hours": 52,
    },
    {
        "name"      : "smoother_25h",
        "threshold" : 2.75,
        "fib"       : 0.786,
        "hold_hours": 25,
    },
]


# ── Core simulation (extended to capture MAE / MFE) ──────────────────────────
def simulate_trade_full(
    m15         : pd.DataFrame,
    entry_time  : pd.Timestamp,
    entry_price : float,
    signal      : int,
    hold_hours  : int,
    stop        : float = STOP,
    spread_cost : float = SPREAD_COST,
) -> dict | None:
    """
    Simulates a single trade and returns full diagnostic data.

    Parameters
    ----------
    m15         : 15-minute OHLCV DataFrame (sorted, reset index)
    entry_time  : Timestamp of trade entry
    entry_price : Price at entry (Fibonacci pullback level)
    signal      : 1 = long, -1 = short
    hold_hours  : Maximum hold duration in hours
    stop        : Hard stop as decimal (0.0025 = 0.25%)
    spread_cost : One-way spread cost as decimal

    Returns
    -------
    dict with trade metrics or None if data unavailable
    """
    entry_idx = get_first_m15_idx_at_or_after(m15, entry_time)
    if entry_idx is None:
        return None

    hold_bars = hold_hours * 4   # 15-min bars per hour = 4
    exit_idx  = min(entry_idx + hold_bars, len(m15) - 1)
    path      = m15.iloc[entry_idx : exit_idx + 1].copy()
    if path.empty:
        return None

    exit_price = float(path.iloc[-1]["close"])

    if signal == 1:  # ── Long ──────────────────────────────────────────────
        worst_low  = path["low"].min()
        best_high  = path["high"].max()
        # MAE: how far did price move against us (negative)
        mae        = (worst_low  - entry_price) / entry_price
        # MFE: how far did price move in our favour (positive)
        mfe        = (best_high  - entry_price) / entry_price
        stop_hit   = mae < -stop
        raw_ret    = -stop if stop_hit else (exit_price / entry_price - 1)

    else:            # ── Short ─────────────────────────────────────────────
        worst_high = path["high"].max()
        best_low   = path["low"].min()
        # MAE: adverse move for a short (price rose) — stored as negative
        mae        = -((worst_high - entry_price) / entry_price)
        # MFE: favourable move for a short (price fell) — stored as positive
        mfe        = (entry_price - best_low) / entry_price
        stop_hit   = (worst_high - entry_price) / entry_price > stop
        raw_ret    = -stop if stop_hit else -(exit_price / entry_price - 1)

    final_return = raw_ret - spread_cost

    return {
        "entry_time" : entry_time,
        "exit_time"  : path.iloc[-1]["datetime"],
        "signal"     : signal,
        "entry_price": entry_price,
        "exit_price" : exit_price,
        "stop_hit"   : stop_hit,
        "mae"        : mae,           # always <= 0 (adverse)
        "mfe"        : mfe,           # always >= 0 (favourable)
        "return"     : final_return,
    }


# ── Runner ────────────────────────────────────────────────────────────────────
def run_and_export(
    threshold   : float,
    fib         : float,
    hold_hours  : int,
    name        : str,
) -> pd.DataFrame | None:
    """
    Runs the full trade simulation for one candidate and saves the log to CSV.
    Returns the trade DataFrame.
    """
    print(f"\n{'─'*60}")
    print(f"Running candidate: {name}")
    print(f"  threshold={threshold} | fib={fib} | hold={hold_hours}h | stop={STOP:.2%}")

    # Build signals (same frozen logic as combined_candidate_matrix_v1)
    signals = build_frozen_signals(
        threshold=threshold,
        allowed_hours=ALLOWED_HOURS,
    )
    print(f"  Signal rows available: {len(signals)}")

    # Load 15M price data
    m15 = load_eurusd_15m().sort_values("datetime").reset_index(drop=True)

    # ── Trade loop (one trade at a time) ──────────────────────────────────────
    trades         = []
    last_exit_time = None

    for _, sig in signals.iterrows():
        signal_time = sig["datetime"]

        # Skip if still inside previous trade's hold window
        if last_exit_time is not None and signal_time < last_exit_time:
            continue

        # Find Fibonacci pullback entry on 15M
        entry = find_entry_pullback(
            m15=m15,
            signal_row=sig,
            fib=fib,
            wait_hours=6,
        )
        if entry is None:
            continue

        # Simulate trade with full diagnostics
        trade = simulate_trade_full(
            m15        =m15,
            entry_time =entry["entry_time"],
            entry_price=entry["entry_price"],
            signal     =int(sig["signal"]),
            hold_hours =hold_hours,
        )
        if trade is None:
            continue

        trades.append(trade)
        last_exit_time = trade["exit_time"]

    # ── Build DataFrame ───────────────────────────────────────────────────────
    if not trades:
        print(f"  [WARNING] No trades generated for {name}")
        return None

    df = pd.DataFrame(trades)

    # Equity curve and drawdown
    df["equity"]    = (1 + df["return"]).cumprod()
    df["peak"]      = df["equity"].cummax()
    df["drawdown"]  = df["equity"] / df["peak"] - 1
    df["win"]       = (df["return"] > 0).astype(int)
    df["trade_num"] = range(1, len(df) + 1)
    df["candidate"] = name
    df["hold_hours"]= hold_hours

    # Losing streak
    max_streak = 0
    streak     = 0
    for r in df["return"]:
        if r <= 0:
            streak    += 1
            max_streak = max(max_streak, streak)
        else:
            streak = 0

    # ── Print summary ─────────────────────────────────────────────────────────
    print(f"\n  Results for {name}:")
    print(f"    Trades        : {len(df)}")
    print(f"    Win rate      : {df['win'].mean():.4%}")
    print(f"    Avg return    : {df['return'].mean():.6f}")
    print(f"    Final equity  : {df['equity'].iloc[-1]:.6f}")
    print(f"    Max drawdown  : {df['drawdown'].min():.4%}")
    print(f"    Max lose streak: {max_streak}")
    print(f"    Stop hit rate : {df['stop_hit'].mean():.4%}")
    print(f"    Avg MAE       : {df['mae'].mean():.6f}  ({df['mae'].mean()*100:.3f}%)")
    print(f"    Avg MFE       : {df['mfe'].mean():.6f}  ({df['mfe'].mean()*100:.3f}%)")

    # ── Save to CSV ───────────────────────────────────────────────────────────
    out_path = OUTPUT_DIR / f"trades_{name}.csv"
    df.to_csv(out_path, index=False)
    print(f"\n  Saved: {out_path}")

    return df


# ── Entry point ───────────────────────────────────────────────────────────────
def main():
    print("=" * 60)
    print("EXPORT TRADE LOGS V1")
    print(f"Output directory: {OUTPUT_DIR}")
    print("=" * 60)

    all_results = {}
    for c in CANDIDATES:
        df = run_and_export(
            threshold =c["threshold"],
            fib       =c["fib"],
            hold_hours=c["hold_hours"],
            name      =c["name"],
        )
        if df is not None:
            all_results[c["name"]] = df

    # ── Side-by-side summary ──────────────────────────────────────────────────
    print(f"\n{'='*60}")
    print("SIDE-BY-SIDE SUMMARY")
    print(f"{'='*60}")

    metrics = [
        ("Trades"          , lambda d: len(d)),
        ("Win rate"        , lambda d: f"{d['win'].mean():.4%}"),
        ("Avg return"      , lambda d: f"{d['return'].mean():.6f}"),
        ("Final equity"    , lambda d: f"{d['equity'].iloc[-1]:.6f}"),
        ("Max DD"          , lambda d: f"{d['drawdown'].min():.4%}"),
        ("Avg MAE"         , lambda d: f"{d['mae'].mean()*100:.4f}%"),
        ("Avg MFE"         , lambda d: f"{d['mfe'].mean()*100:.4f}%"),
        ("Stop hit rate"   , lambda d: f"{d['stop_hit'].mean():.4%}"),
    ]

    header = f"{'Metric':<20}" + "".join(f"{k:<20}" for k in all_results)
    print(header)
    print("-" * len(header))
    for label, fn in metrics:
        row = f"{label:<20}"
        for df in all_results.values():
            row += f"{fn(df):<20}"
        print(row)

    print("\nAll trade logs exported successfully.")
    print("Next step: run ftmo_sequence_simulator_v1.py")


if __name__ == "__main__":
    main()
