"""PAPER COMBO — forward-only del combo cross-venue TP01 (Deribit) + GTAA (IB), NUDO vs PROTETTO.

Le due gambe eseguibili a basso capitale (XS01/VRP01 STAT-MODE esclusi), scorrelate (corr ~0.21) ->
blend Sharpe ~1.5, DD dimezzato. Traccia FORWARD-ONLY DUE versioni in parallelo:
  * NUDO     = blend 50/50 TP01+GTAA
  * PROTETTO = stesso blend + GUARDIA-DRAWDOWN -4% (de-risk a 0.4x quando il DD da picco supera -4%,
               ri-rischia a -1.6%). Backtest: MaxDD 8.4%->5.8%, 2022 -4.4%->-1.8%, Sharpe 1.48->1.38
               (diario 2026-06-23-tail-hedge-lab). Le opzioni NON aiutano il grind del 2022 -> escluse.
Crypto compoundato sul grid giorni-di-borsa. NESSUNA esecuzione reale. Mostra posizioni azionabili.

  uv run python scripts/live/paper_combo.py [--status|--reset]
"""
from __future__ import annotations
import sys, json
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np, pandas as pd
from src.portfolio.sleeves import _tp01_returns, _tp01_positions
from src.portfolio.gtaa import gtaa_returns, gtaa_weights

STATE_DIR = PROJECT_ROOT / "data" / "paper_combo"
STATE = STATE_DIR / "state.json"
EQ = STATE_DIR / "equity.csv"
INITIAL = 2000.0
W_CRYPTO = 0.5
DD_TRIGGER = 0.04   # guardia-drawdown della versione PROTETTA


def combo_daily(wc: float = W_CRYPTO) -> pd.Series:
    tp = _tp01_returns()
    if tp.index.tz is None:
        tp.index = tp.index.tz_localize("UTC")
    eq = gtaa_returns().dropna()
    grid = eq.index[eq.index >= tp.index[0]]
    cum = (1.0 + tp).cumprod()
    tpg = (cum.reindex(cum.index.union(grid)).ffill().reindex(grid)).pct_change()
    J = pd.concat({"c": tpg, "e": eq.reindex(grid)}, axis=1).dropna()
    return (wc * J["c"] + (1 - wc) * J["e"]).dropna()


def apply_dd_guard(r: pd.Series, trigger: float = DD_TRIGGER) -> pd.Series:
    """De-risk a 0.4x quando il DD da picco > trigger; ri-rischia a 1.0x quando < 0.4*trigger."""
    rv = r.values; n = len(rv); eq = np.cumprod(1 + rv); pk = np.maximum.accumulate(eq)
    expo = np.ones(n); on = True
    for i in range(1, n):
        ddi = (pk[i - 1] - eq[i - 1]) / pk[i - 1] if pk[i - 1] > 0 else 0.0
        if ddi > trigger: on = False
        if ddi < trigger * 0.4: on = True
        expo[i] = 1.0 if on else 0.4
    return pd.Series(expo * rv, index=r.index)


def both_daily():
    naked = combo_daily()
    return naked, apply_dd_guard(naked)


def load():
    return json.loads(STATE.read_text()) if STATE.exists() else None


def save(st):
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    STATE.write_text(json.dumps(st, indent=2))


def advance():
    naked, guard = both_daily()
    st = load()
    if st is None or "equity_g" not in st:                # init (o migrazione a doppia versione)
        last = str(naked.index[-1])
        st = dict(start=last, last=last, initial=INITIAL, n_days=0, w_crypto=W_CRYPTO, dd_trigger=DD_TRIGGER,
                  equity=INITIAL, peak=INITIAL, max_dd=0.0,
                  equity_g=INITIAL, peak_g=INITIAL, max_dd_g=0.0)
        save(st); EQ.write_text("date,nudo,protetto\n" + f"{last},{INITIAL},{INITIAL}\n")
        return st
    last = pd.Timestamp(st["last"])
    nn = naked[naked.index > last]; gg = guard[guard.index > last]
    if len(nn):
        e = st["equity"]; pk = st["peak"]; dd = st["max_dd"]
        eg = st["equity_g"]; pkg = st["peak_g"]; ddg = st["max_dd_g"]; lines = []
        for d in nn.index:
            e *= (1 + float(nn[d])); pk = max(pk, e); dd = max(dd, (pk - e) / pk if pk > 0 else 0)
            eg *= (1 + float(gg[d])); pkg = max(pkg, eg); ddg = max(ddg, (pkg - eg) / pkg if pkg > 0 else 0)
            lines.append(f"{d},{e:.4f},{eg:.4f}")
        st.update(equity=e, peak=pk, max_dd=dd, equity_g=eg, peak_g=pkg, max_dd_g=ddg,
                  last=str(nn.index[-1]), n_days=st["n_days"] + len(nn))
        save(st)
        with open(EQ, "a") as f:
            f.write("\n".join(lines) + "\n")
    return st


def main():
    a = sys.argv[1:]
    if "--reset" in a:
        for f in (STATE, EQ):
            f.unlink(missing_ok=True)
        print("paper combo azzerato.")
    st = load() if "--status" in a else advance()
    if st is None or "equity_g" not in st:
        st = advance()
    days = (pd.Timestamp(st["last"]) - pd.Timestamp(st["start"])).days
    rn = st["equity"] / st["initial"] - 1; rg = st["equity_g"] / st["initial"] - 1
    gw = gtaa_weights(); asof = gw.pop("_asof", "?"); cash = gw.pop("_cash", None)
    print("PAPER COMBO — TP01 (Deribit) + GTAA (IB), forward-only, blend 50/50")
    print(f"  start {st['start'][:10]} -> last {st['last'][:10]} ({days}g, {st['n_days']} barre)")
    print(f"  NUDO     : eq {st['equity']:.2f}  ret {rn*100:+.2f}%  maxDD {st['max_dd']*100:.1f}%")
    print(f"  PROTETTO : eq {st['equity_g']:.2f}  ret {rg*100:+.2f}%  maxDD {st['max_dd_g']*100:.1f}%  (guardia-DD -{st.get('dd_trigger',DD_TRIGGER)*100:.0f}%)")
    print(f"  --- POSIZIONI AZIONABILI ---")
    print(f"  TP01 (Deribit): {_tp01_positions()}")
    print(f"  GTAA (IB, asof {asof}): " + ", ".join(f"{k} {v:.0%}" for k, v in gw.items() if v) + f"  | cash {cash:.0%}")


if __name__ == "__main__":
    main()
