"""STRESS-TEST di TP01 (integrato da strategy-research-2026-06) — robustezza avversariale.

Usa il modulo VERO integrato (src/strategies/trend_portfolio). Oltre a hold-out/cross-asset/multi-TF
(gia' in verify_tp01.py), qui: sweep FEE (fino 0.40% RT), LAG di esecuzione + slippage, PLATEAU dei
parametri (config cherry-picked?), DEFLATED-SHARPE (multiple-testing track A-E).

    uv run python scripts/analysis/stress_tp01.py
"""
from __future__ import annotations
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np
import pandas as pd
from scipy.stats import norm, skew, kurtosis
from src.data.downloader import load_data
from src.strategies.trend_portfolio import TrendPortfolio, resample_4h, simple_returns, CANONICAL

HOLDOUT = pd.Timestamp("2025-01-01", tz="UTC")
DF4H = {a: resample_4h(load_data(a, "1h")) for a in ("BTC", "ETH")}


def combo(cfg, lag_bars=0, fee_side=0.0005):
    """Rendimenti per-barra del portafoglio 50/50 con config cfg, lag extra e fee dati."""
    tp = TrendPortfolio(**{**cfg, "fee_side": fee_side})
    series = {}
    for a in ("BTC", "ETH"):
        df = DF4H[a]
        r = simple_returns(df["close"].values.astype(float))
        tgt = tp.target_series(df)
        held = np.zeros(len(tgt))
        s = 1 + lag_bars
        held[s:] = tgt[:-s]                                  # tenuta = decisa s barre prima (causale + lag)
        net = held * r - fee_side * np.abs(np.diff(held, prepend=0.0))
        net[0] = 0.0
        series[a] = pd.Series(np.clip(net, -0.99, None), index=pd.to_datetime(df["datetime"]))
    J = pd.concat(series, axis=1, join="inner").fillna(0.0)
    return 0.5 * J["BTC"].values + 0.5 * J["ETH"].values, J.index


def met(combo_r, idx):
    rr = combo_r[np.isfinite(combo_r)]
    if len(rr) < 2 or np.std(rr) == 0:
        return dict(sh=0, ret=0, dd=0)
    bpy = 86400 * 365.25 / pd.Series(idx).diff().dt.total_seconds().median()
    eq = np.cumprod(1 + rr); pk = np.maximum.accumulate(eq)
    return dict(sh=float(np.mean(rr) / np.std(rr) * np.sqrt(bpy)),
                ret=float(eq[-1] - 1), dd=float(np.max((pk - eq) / pk)))


def full_ho(cfg, lag_bars=0, fee_side=0.0005):
    cr, idx = combo(cfg, lag_bars, fee_side)
    ho = idx >= HOLDOUT
    return met(cr, idx), met(cr[ho], idx[ho])


def main():
    print("=" * 88)
    print("  STRESS-TEST TP01 (PORT LF4h canonica) — robustezza avversariale")
    print("=" * 88)

    base_f, base_h = full_ho(CANONICAL)
    print(f"\n  BASELINE (4h, fee 0.10% RT): FULL Sh {base_f['sh']:.2f} ret {base_f['ret']*100:+.0f}% DD {base_f['dd']*100:.1f}%"
          f" | HOLD-OUT Sh {base_h['sh']:.2f} ret {base_h['ret']*100:+.1f}% DD {base_h['dd']*100:.1f}%")

    print("\n  (1) SWEEP FEE (RT) — regge fino a 0.40%?")
    print(f"    {'fee RT':<10s}{'FULL Sh':>9s}{'FULL ret':>10s}{'HOLD Sh':>9s}{'HOLD ret':>10s}")
    for frt in (0.0, 0.001, 0.002, 0.004):
        f, h = full_ho(CANONICAL, fee_side=frt / 2)
        print(f"    {frt*100:>5.2f}%   {f['sh']:>8.2f}{f['ret']*100:>+9.0f}%{h['sh']:>9.2f}{h['ret']*100:>+9.1f}%")

    print("\n  (2) LAG di esecuzione + slippage (fee 0.20% per simulare slippage)")
    print(f"    {'scenario':<22s}{'FULL Sh':>9s}{'HOLD Sh':>9s}{'HOLD ret':>10s}")
    for name, lag, frt in [("base", 0, 0.001), ("lag 1 barra (4h)", 1, 0.001),
                           ("lag 2 barre", 2, 0.001), ("lag1 + fee0.20% slip", 1, 0.002)]:
        f, h = full_ho(CANONICAL, lag_bars=lag, fee_side=frt / 2)
        print(f"    {name:<22s}{f['sh']:>8.2f}{h['sh']:>9.2f}{h['ret']*100:>+9.1f}%")

    print("\n  (3) PLATEAU PARAMETRI — la config canonica e' un picco o un altopiano?")
    print(f"    {'variazione':<26s}{'FULL Sh':>9s}{'HOLD Sh':>9s}")
    grid = [
        ("canonica (vt.20 lev2 30/90/180 vw30)", CANONICAL),
        ("target_vol 0.15", {**CANONICAL, "target_vol": 0.15}),
        ("target_vol 0.25", {**CANONICAL, "target_vol": 0.25}),
        ("leverage 1.5", {**CANONICAL, "leverage": 1.5}),
        ("leverage 3.0", {**CANONICAL, "leverage": 3.0}),
        ("horizons 20/60/120", {**CANONICAL, "horizons_days": (20, 60, 120)}),
        ("horizons 60/120/240", {**CANONICAL, "horizons_days": (60, 120, 240)}),
        ("vol_win 20", {**CANONICAL, "vol_win_days": 20}),
        ("vol_win 45", {**CANONICAL, "vol_win_days": 45}),
    ]
    sr_trials = []
    for name, cfg in grid:
        f, h = full_ho(cfg)
        cr, idx = combo(cfg)
        sr_trials.append(cr[np.isfinite(cr)].mean() / cr[np.isfinite(cr)].std())   # Sharpe per-barra
        print(f"    {name:<26s}{f['sh']:>8.2f}{h['sh']:>9.2f}")

    print("\n  (4) DEFLATED SHARPE — corregge il multiple-testing (track A-E + sweep). DSR>0.95 = regge")
    cr, idx = combo(CANONICAL)
    rr = cr[np.isfinite(cr)]
    sr = rr.mean() / rr.std(); T = len(rr)
    g3 = float(skew(rr)); g4 = float(kurtosis(rr, fisher=False))
    var_sr = float(np.var(sr_trials, ddof=1))
    ge = 0.5772156649
    for N in (10, 40, 100):       # N = numero di trial/config provati (conservativo)
        z1 = norm.ppf(1 - 1.0 / N); z2 = norm.ppf(1 - 1.0 / (N * np.e))
        sr0 = np.sqrt(var_sr) * ((1 - ge) * z1 + ge * z2)
        den = np.sqrt(max(1 - g3 * sr + (g4 - 1) / 4.0 * sr ** 2, 1e-9))
        dsr = float(norm.cdf((sr - sr0) * np.sqrt(T - 1) / den))
        bpy = 86400 * 365.25 / pd.Series(idx).diff().dt.total_seconds().median()
        print(f"    N={N:>3d} trial -> soglia-max-attesa Sh {sr0*np.sqrt(bpy):.2f} | DSR {dsr:.3f}  [{'REGGE' if dsr>0.95 else 'NON regge'}]")

    print("\n" + "=" * 88)
    print("  Verdetto: TP01 robusto se regge fee 0.40%+lag (HOLD positivo), plateau (no picco), DSR>0.95.")
    print("=" * 88)


if __name__ == "__main__":
    main()
