#!/usr/bin/env python3
"""V13 experiment runner: new engine features for exit timing and candidate selection.

Based on V11 champion. Tests:
  A. VWAP trailing exit (exit/floor modes with buffer/activation variants)
  B. Score-based position sizing (top picks get larger positions)
  C. Confirmation bar (filter false breakouts)
  D. Gap fill protection (exit when gap is fully filled)
  E. Max hold time (morning momentum capture, exit before afternoon fade)
  F. Combinations of winners

Usage:
    python -u scripts/v13_experiments.py [--days N]
"""
import subprocess
import sys
import tempfile
import yaml
from pathlib import Path

BASE_CONFIG = "configs/intraday/strategies/orb_gainers_v11.yaml"
DAYS = 200


def load_base():
    with open(BASE_CONFIG) as f:
        return yaml.safe_load(f)


def run_experiment(name: str, overrides: dict, days: int = DAYS) -> dict | None:
    """Run a single backtest with config overrides and return parsed metrics."""
    cfg = load_base()
    for key, val in overrides.items():
        cfg["orb_strategy"][key] = val

    with tempfile.NamedTemporaryFile(
        mode="w", suffix=".yaml", prefix="v13_", delete=False, dir="/tmp"
    ) as f:
        yaml.dump(cfg, f, default_flow_style=False)
        tmp_path = f.name

    cmd = [
        sys.executable, "-u", "-m", "apps.intraday_bt.run",
        "--config", tmp_path,
        "--days", str(days),
    ]

    print(f"\n{'='*70}")
    print(f"  {name}")
    print(f"  Overrides: {overrides or '(V11 baseline)'}")
    print(f"{'='*70}", flush=True)

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=900)
    except subprocess.TimeoutExpired:
        print(f"  TIMEOUT after 900s")
        return None
    finally:
        Path(tmp_path).unlink(missing_ok=True)

    if result.returncode != 0:
        print(f"  FAILED (rc={result.returncode})")
        if result.stderr:
            print(f"  stderr: {result.stderr[-500:]}")
        return None

    output = result.stdout
    metrics = {}
    for line in output.split("\n"):
        line = line.strip()
        if "Total return" in line and "%" in line:
            try:
                val = line.split("│")[-2].strip().replace("%", "").replace("+", "")
                metrics["total_return"] = float(val)
            except (ValueError, IndexError):
                pass
        elif "Max drawdown" in line and "%" in line:
            try:
                val = line.split("│")[-2].strip().replace("%", "").replace("+", "")
                metrics["max_drawdown"] = float(val)
            except (ValueError, IndexError):
                pass
        elif "Sharpe ratio" in line:
            try:
                val = line.split("│")[-2].strip()
                metrics["sharpe"] = float(val)
            except (ValueError, IndexError):
                pass
        elif "Total trades" in line:
            try:
                val = line.split("│")[-2].strip()
                metrics["trades"] = int(val)
            except (ValueError, IndexError):
                pass
        elif "Win rate" in line and "%" in line:
            try:
                val = line.split("│")[-2].strip().replace("%", "").replace("+", "")
                metrics["win_rate"] = float(val)
            except (ValueError, IndexError):
                pass
        elif "Profit factor" in line:
            try:
                val = line.split("│")[-2].strip()
                metrics["profit_factor"] = float(val)
            except (ValueError, IndexError):
                pass

    if not metrics:
        print("  WARNING: Could not parse metrics from output")
        for line in output.split("\n")[-30:]:
            print(f"    {line}")
        return None

    print(f"  => return={metrics.get('total_return', '?'):+.2f}%, "
          f"DD={metrics.get('max_drawdown', '?'):.2f}%, "
          f"Sharpe={metrics.get('sharpe', '?'):.2f}, "
          f"trades={metrics.get('trades', '?')}, "
          f"WR={metrics.get('win_rate', '?'):.1f}%, "
          f"PF={metrics.get('profit_factor', '?'):.3f}", flush=True)
    return metrics


def main():
    days = DAYS
    if "--days" in sys.argv:
        idx = sys.argv.index("--days")
        days = int(sys.argv[idx + 1])

    results = {}

    # V11 baseline (no overrides)
    results["V11_baseline"] = run_experiment("V11 Baseline (control)", {}, days)

    # ═══════════════════════════════════════════════════════════════════════
    # A. VWAP Exit — running VWAP as dynamic support/resistance
    # ════════════════════════════════��══════════════════════════════════════

    # A1: Simple VWAP cross exit (exit when close < VWAP)
    results["V13a1_vwap_exit"] = run_experiment(
        "V13a1: VWAP exit (close < VWAP → exit)",
        {"vwap_exit_mode": "exit"}, days,
    )

    # A2: VWAP exit with buffer (close < VWAP - 0.3ATR → exit)
    results["V13a2_vwap_buf03"] = run_experiment(
        "V13a2: VWAP exit with 0.3ATR buffer",
        {"vwap_exit_mode": "exit", "vwap_exit_buffer_atr": 0.3}, days,
    )

    # A3: VWAP exit only after 1R reached (avoid premature exit)
    results["V13a3_vwap_after1r"] = run_experiment(
        "V13a3: VWAP exit after reaching 1R",
        {"vwap_exit_mode": "exit", "vwap_exit_after_r": 1.0}, days,
    )

    # A4: VWAP exit after 1R with buffer
    results["V13a4_vwap_1r_buf"] = run_experiment(
        "V13a4: VWAP exit after 1R + 0.3ATR buffer",
        {"vwap_exit_mode": "exit", "vwap_exit_after_r": 1.0, "vwap_exit_buffer_atr": 0.3},
        days,
    )

    # A5: VWAP as trailing stop floor (VWAP raises the trailing stop minimum)
    results["V13a5_vwap_floor"] = run_experiment(
        "V13a5: VWAP floor (trailing stop can't go below VWAP)",
        {"vwap_exit_mode": "floor"}, days,
    )

    # A6: VWAP floor with buffer
    results["V13a6_vwap_floor_buf"] = run_experiment(
        "V13a6: VWAP floor with 0.3ATR buffer",
        {"vwap_exit_mode": "floor", "vwap_exit_buffer_atr": 0.3}, days,
    )

    # ════════���═══════════════════════════════════════════════════��══════════
    # B. Score-Based Position Sizing — top picks get larger positions
    # ═════════════��═══════════════════════════════════��═════════════════════

    results["V13b1_score_1.5x"] = run_experiment(
        "V13b1: Score sizing 1.5x (top pick = 1.5× base risk)",
        {"score_sizing_multiplier": 1.5}, days,
    )
    results["V13b2_score_2.0x"] = run_experiment(
        "V13b2: Score sizing 2.0x (top pick = 2× base risk)",
        {"score_sizing_multiplier": 2.0}, days,
    )
    results["V13b3_score_3.0x"] = run_experiment(
        "V13b3: Score sizing 3.0x (top pick = 3× base risk)",
        {"score_sizing_multiplier": 3.0}, days,
    )

    # ════���════════════��══════════════════════════════��══════════════════════
    # C. Confirmation Bar — require follow-through after breakout
    # ═════════════════���═════════════════════════════════════════════════════

    results["V13c_confirm"] = run_experiment(
        "V13c: Require confirmation bar (next bar must close above entry)",
        {"require_confirmation_bar": True}, days,
    )

    # ═���════════════��════════════════════════════════════════════════════════
    # D. Gap Fill Protection — exit when catalyst is rejected
    # ════��════════════════���═════════════════════════════════════════════════

    results["V13d_gap_fill"] = run_experiment(
        "V13d: Gap fill exit (close < prev_close → exit immediately)",
        {"exit_on_gap_fill": True}, days,
    )

    # ══════════════════════════��════════════════════════════════════════════
    # E. Max Hold Time — capture morning momentum, avoid afternoon fade
    # ══════════════���════════════════════════════════��═══════════════════════

    results["V13e1_hold_60"] = run_experiment(
        "V13e1: Max hold 60min (1 hour)",
        {"max_hold_minutes": 60}, days,
    )
    results["V13e2_hold_90"] = run_experiment(
        "V13e2: Max hold 90min (1.5 hours)",
        {"max_hold_minutes": 90}, days,
    )
    results["V13e3_hold_120"] = run_experiment(
        "V13e3: Max hold 120min (2 hours)",
        {"max_hold_minutes": 120}, days,
    )
    results["V13e4_hold_180"] = run_experiment(
        "V13e4: Max hold 180min (3 hours)",
        {"max_hold_minutes": 180}, days,
    )

    # ═══════════════════════════════════════════════════════════════���═══════
    # F. Combinations — combine promising features
    # ══════════════════���═══════════════════════════���════════════════════════

    # F1: VWAP exit + score sizing
    results["V13f1_vwap_score"] = run_experiment(
        "V13f1: VWAP exit after 1R + score sizing 2x",
        {"vwap_exit_mode": "exit", "vwap_exit_after_r": 1.0, "score_sizing_multiplier": 2.0},
        days,
    )

    # F2: VWAP exit + confirmation bar
    results["V13f2_vwap_confirm"] = run_experiment(
        "V13f2: VWAP exit after 1R + confirmation bar",
        {"vwap_exit_mode": "exit", "vwap_exit_after_r": 1.0, "require_confirmation_bar": True},
        days,
    )

    # F3: VWAP exit + gap fill + max hold 120
    results["V13f3_vwap_gap_hold"] = run_experiment(
        "V13f3: VWAP exit after 1R + gap fill + max hold 120",
        {
            "vwap_exit_mode": "exit", "vwap_exit_after_r": 1.0,
            "exit_on_gap_fill": True, "max_hold_minutes": 120,
        },
        days,
    )

    # F4: Score sizing + confirmation + gap fill
    results["V13f4_score_confirm_gap"] = run_experiment(
        "V13f4: Score 2x + confirmation + gap fill",
        {"score_sizing_multiplier": 2.0, "require_confirmation_bar": True, "exit_on_gap_fill": True},
        days,
    )

    # F5: Kitchen sink — all features
    results["V13f5_kitchen_sink"] = run_experiment(
        "V13f5: All features combined",
        {
            "vwap_exit_mode": "exit", "vwap_exit_after_r": 1.0, "vwap_exit_buffer_atr": 0.3,
            "score_sizing_multiplier": 2.0,
            "require_confirmation_bar": True,
            "exit_on_gap_fill": True,
            "max_hold_minutes": 120,
        },
        days,
    )

    # ��══════════════════════════════════════════════════════════════════════
    # Summary table
    # ════════════���═══════════════════���══════════════════════════════════════

    print(f"\n\n{'='*95}")
    print("  V13 EXPERIMENT RESULTS SUMMARY (base: V11 champion)")
    print(f"{'='*95}")
    print(f"{'Experiment':<30} {'Return':>9} {'DD':>9} {'Sharpe':>7} {'Trades':>7} {'WR':>7} {'PF':>7}")
    print(f"{'-'*30} {'-'*9} {'-'*9} {'-'*7} {'-'*7} {'-'*7} {'-'*7}")

    baseline_ret = (results.get("V11_baseline") or {}).get("total_return", 0)
    for name, m in results.items():
        if m is None:
            print(f"{name:<30} {'FAILED':>9}")
            continue
        ret = m.get("total_return", 0)
        delta = ret - baseline_ret
        ret_str = f"{ret:+.2f}%"
        dd = f"{m.get('max_drawdown', 0):.2f}%"
        sh = f"{m.get('sharpe', 0):.2f}"
        tr = f"{m.get('trades', 0)}"
        wr = f"{m.get('win_rate', 0):.1f}%"
        pf = f"{m.get('profit_factor', 0):.3f}"
        marker = " <== BASE" if name == "V11_baseline" else (f" ({delta:+.2f}pp)" if delta != 0 else "")
        print(f"{name:<30} {ret_str:>9} {dd:>9} {sh:>7} {tr:>7} {wr:>7} {pf:>7}{marker}")


if __name__ == "__main__":
    main()
