"""EQ-TREND01 — Trend DIFENSIVO time-series su SPY (analogo equity di TP01).

Il momentum cross-sectional settoriale e' morto (EQ-MOM01). Ma nel crypto l'unica cosa che ha retto
NON era un alpha relative-value: era TP01, un trend DIFENSIVO che taglia il drawdown restando vicino
al ritorno. L'equity ha lo stesso buco: SPY buy&hold fa Sharpe ~0.51 ma con maxDD 55% (due bear -50%:
2000-02 e 2008-09). Domanda: un trend long-flat su SPY ALZA il Sharpe e DIMEZZA il DD restando
investito nei tori? (NON cerchiamo di battere il CAGR — cerchiamo il taglio del rischio, come TP01.)

DATI: cache su disco eq_spy/eq_tlt (ADJUSTED), via eqlib (nessun IB).
COSTRUZIONE (causale, stile TP01): TSMOM multi-orizzonte [21,63,126,252]g (1/3/6/12 mesi); target =
frazione di orizzonti in trend-up (0..1, allocazione graduale). Vol-target opz. Posizione decisa a
<=i-1, tenuta da i. Netto fee sul turnover. Varianti: long-flat (cash in risk-off), long-bonds
(TLT in risk-off, solo dal 2016), e SMA-200 binario (Faber) come riferimento classico.

GIUDIZIO: vs SPY buy&hold (CAGR/Sharpe full-pre15-OOS15+/maxDD/time-in-market), marginale vs SPY,
DD nei due bear storici, plateau (orizzonti/vol-target/cap leva), sweep fee.
"""
import sys
from pathlib import Path
import numpy as np, pandas as pd

ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "scripts" / "research"))
import eqlib
from eqlib import load_eq
from eq_sector_momentum import _sh, _cagr, _dd, EQ_HOLDOUT, spy_bh

ANN = np.sqrt(252.0)


def _series(sym):
    d = load_eq(sym)["close"].astype(float)
    return pd.Series(d.values, index=d.index)


def tsmom_exposure(close: pd.Series, horizons=(21, 63, 126, 252), target_vol=None,
                   lev_cap=1.0) -> pd.Series:
    """Esposizione SPY in [0, lev_cap]: frazione di orizzonti in trend-up, opz. vol-targeted (causale)."""
    px = close.values; n = len(px); tgt = np.zeros(n)
    mh = max(horizons)
    for i in range(mh, n):
        tgt[i] = np.mean([1.0 if px[i] > px[i - H] else 0.0 for H in horizons])
    s = pd.Series(tgt, index=close.index)
    if target_vol:
        ret = close.pct_change()
        rv = ret.rolling(63, min_periods=20).std().shift(1) * ANN
        scale = np.clip(np.nan_to_num(target_vol / rv.replace(0, np.nan).values, nan=0.0), 0, lev_cap / 0.0 if False else 10.0)
        s = (s * scale).clip(0, lev_cap)
    else:
        s = s.clip(0, lev_cap)
    return s


def sma_timing(close: pd.Series, win=200) -> pd.Series:
    """Faber: long se close > SMA(win), altrimenti flat. Binario {0,1}."""
    sma = close.rolling(win, min_periods=win // 2).mean()
    return (close > sma).astype(float)


def backtest(close: pd.Series, exposure: pd.Series, risk_off: pd.Series | None = None,
             fee_side=0.0002) -> pd.Series:
    """Rendimenti netti: held = exposure ritardata di 1 (causale). La quota non in SPY (1-held, se
    exposure<=1) va in risk_off (es. TLT) o cash (0). Fee sul turnover di SPY."""
    ret = close.pct_change().fillna(0.0).values
    exp = exposure.reindex(close.index).fillna(0.0).values
    held = np.zeros(len(exp)); held[1:] = exp[:-1]
    net = held * ret
    if risk_off is not None:
        ro = risk_off.reindex(close.index).pct_change().fillna(0.0).values
        cash_w = np.clip(1.0 - held, 0.0, 1.0)        # quota fuori da SPY -> bonds
        net = net + cash_w * ro
    net = net - fee_side * np.abs(np.diff(held, prepend=0.0))
    return pd.Series(net, index=close.index)


def _row(name, r, common, bench=None):
    r = r.reindex(common).fillna(0.0)
    h = r[r.index >= EQ_HOLDOUT]; ii = r[r.index < EQ_HOLDOUT]
    tim = float((r != 0).mean()) * 100
    extra = ""
    if bench is not None:
        J = pd.concat({"r": r, "b": bench.reindex(common).fillna(0.0)}, axis=1).dropna()
        extra = f"  corr {J['r'].corr(J['b']):+.2f}"
    print(f"  {name:24} CAGR {_cagr(r.values, r.index)*100:>5.1f}%  Sh {_sh(r):>5.2f} "
          f"(pre15 {_sh(ii):>5.2f}|OOS {_sh(h):>5.2f})  maxDD {_dd(r.values)*100:>4.0f}%  inMkt {tim:>3.0f}%{extra}")


def _bear_dd(r, common, lo, hi, label):
    seg = r.reindex(common).fillna(0.0)
    seg = seg[(seg.index >= pd.Timestamp(lo, tz="UTC")) & (seg.index <= pd.Timestamp(hi, tz="UTC"))]
    return f"{label}: {_dd(seg.values)*100:.0f}%"


def main():
    print("=" * 100)
    print("  EQ-TREND01 — Trend DIFENSIVO time-series su SPY (analogo di TP01)")
    print("=" * 100)
    spy_px = _series("SPY"); spy = spy_bh()
    common = spy_px.index[spy_px.index >= spy_px.index[252]]   # warmup 1y
    print(f"  periodo {common[0].date()}..{common[-1].date()}  ({len(common)}g)  OOS = {EQ_HOLDOUT.date()}+\n")

    print("  --- BASELINE ---")
    _row("SPY buy&hold", spy, common)
    print("\n  --- TREND long-flat (cash in risk-off) ---")
    _row("SMA-200 (Faber)", backtest(spy_px, sma_timing(spy_px)), common, bench=spy)
    _row("TSMOM lf cap1.0", backtest(spy_px, tsmom_exposure(spy_px)), common, bench=spy)
    _row("TSMOM lf vt15 cap1.0", backtest(spy_px, tsmom_exposure(spy_px, target_vol=0.15, lev_cap=1.0)), common, bench=spy)
    _row("TSMOM lf vt15 cap1.5", backtest(spy_px, tsmom_exposure(spy_px, target_vol=0.15, lev_cap=1.5)), common, bench=spy)

    print("\n  --- TREND long-BONDS (TLT in risk-off, solo dove TLT esiste: 2016+) ---")
    tlt = _series("TLT")
    cb = spy_px.index[(spy_px.index >= tlt.index[0])]
    _row("SPY b&h (2016+)", spy.reindex(cb), cb)
    _row("TSMOM lf+TLT (2016+)", backtest(spy_px, tsmom_exposure(spy_px), risk_off=tlt), cb, bench=spy)

    # MARGINALE vs SPY + DD nei bear
    base = backtest(spy_px, tsmom_exposure(spy_px))
    print("\n  --- MARGINALE vs SPY (TSMOM lf cap1.0) ---")
    J = pd.concat({"spy": spy, "c": base}, axis=1, join="inner").dropna(); JH = J[J.index >= EQ_HOLDOUT]
    print(f"    corr full {J['spy'].corr(J['c']):+.3f} | OOS {JH['spy'].corr(JH['c']):+.3f}")
    for wt in (0.5, 1.0):
        bf = _sh((1-wt)*J['spy']+wt*J['c']) - _sh(J['spy']); bh = _sh((1-wt)*JH['spy']+wt*JH['c']) - _sh(JH['spy'])
        lbl = "100% TREND" if wt == 1.0 else f"{int((1-wt)*100)}/{int(wt*100)} SPY/TREND"
        print(f"    {lbl:16}: uplift Sharpe FULL {bf:+.3f}  OOS {bh:+.3f}")
    print("    DD nei bear storici (TSMOM vs SPY):")
    for lo, hi, lbl in [("2000-03-01","2002-12-31","dot-com"), ("2007-10-01","2009-06-30","GFC"),
                        ("2020-02-01","2020-04-30","COVID"), ("2022-01-01","2022-12-31","2022")]:
        print(f"      {lbl:8}  TSMOM {_bear_dd(base,common,lo,hi,'')}  | SPY {_bear_dd(spy,common,lo,hi,'')}")

    # PLATEAU
    print("\n  --- PLATEAU (Sharpe FULL/pre15/OOS, maxDD, CAGR) long-flat cap1.0 ---")
    print(f"    {'horizons':22} {'FULL':>6} {'pre15':>6} {'OOS':>6} {'DD%':>5} {'CAGR%':>6}")
    for hz in [(63,126,252),(21,63,126,252),(126,252),(50,200),(200,)]:
        ex = sma_timing(spy_px, 200) if hz == (200,) else tsmom_exposure(spy_px, horizons=hz)
        r = backtest(spy_px, ex); h=r[r.index>=EQ_HOLDOUT]; ii=r[r.index<EQ_HOLDOUT]
        tag = "SMA-200" if hz==(200,) else "x".join(map(str,hz))
        print(f"    {tag:22} {_sh(r):>6.2f} {_sh(ii):>6.2f} {_sh(h):>6.2f} {_dd(r.values)*100:>5.0f} {_cagr(r.values,r.index)*100:>6.1f}")

    print("\n  --- SWEEP FEE (TSMOM lf cap1.0) ---")
    for fee in (0.0, 0.0002, 0.0005, 0.001):
        r = backtest(spy_px, tsmom_exposure(spy_px), fee_side=fee)
        print(f"    fee {fee*100:.2f}%/lato: Sh {_sh(r):.2f}  maxDD {_dd(r.values)*100:.0f}%")


if __name__ == "__main__":
    main()
