#!/usr/bin/env python3
"""V14 experiment runner: bar close confirmation entry + V12 validation.

Based on V11 champion. Tests:
  G. Bar close confirmation entry (entry_on_bar_close) — require breakout bar's
     CLOSE above ORB level, not just HIGH touching it. Filters wick-only false breakouts.
  H. Combinations with score_sizing_multiplier (the only confirmed V13 improvement)

Key insight from V13: ~7 false breakout trades (wick-only ORB high touches) cause ~$4,900
in losses. The biased confirmation bar experiment showed +77% by retroactively canceling these.
entry_on_bar_close achieves similar filtering without lookahead — trader waits for the 5-min
bar to close, enters at the close price.

Usage:
    python -u scripts/v14_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="v14_", 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)

    # ═══════════════════════════════════════════════════════════════════════
    # G. Bar close confirmation entry — wick filter
    # ═══════════════════════════════════════════════════════════════════════

    results["V14g1_bar_close"] = run_experiment(
        "V14g1: Bar close entry (require close above breakout level)",
        {"entry_on_bar_close": True}, days,
    )

    # ═══════════════════════════════════════════════════════════════════════
    # H. Combinations with confirmed improvements
    # ═══════════════════════════════════════════════════════════════════════

    # H1: Bar close + score sizing 1.5x (V12 candidate)
    results["V14h1_close_score1.5"] = run_experiment(
        "V14h1: Bar close + score sizing 1.5x (V12 candidate)",
        {"entry_on_bar_close": True, "score_sizing_multiplier": 1.5}, days,
    )

    # H2: Bar close + score sizing 2.0x
    results["V14h2_close_score2.0"] = run_experiment(
        "V14h2: Bar close + score sizing 2.0x",
        {"entry_on_bar_close": True, "score_sizing_multiplier": 2.0}, days,
    )

    # H3: Score sizing 1.5x alone (V13 confirmed, re-validate)
    results["V14h3_score1.5_only"] = run_experiment(
        "V14h3: Score sizing 1.5x only (V13 confirmed, re-validate)",
        {"score_sizing_multiplier": 1.5}, days,
    )

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

    print(f"\n\n{'='*95}")
    print("  V14 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()
