"""Strategia #3 candidata: ROTAZIONE cross-sectional momentum (multi-crypto).
Una sola strategia che usa l'INTERO paniere: ad ogni ribilanciamento alloca il
capitale agli asset con momentum migliore (long-only). Cattura la dispersione tra
crypto (gli alt forti corrono molto piu' di BTC nei bull) senza shortare nulla.

Onesto: i pesi a close[i] usano solo rendimenti passati; il rendimento del bar
i->i+1 e' realizzato con quei pesi. Fee sul turnover. Allineamento per timestamp.
"""
from __future__ import annotations

import sys
from pathlib import Path

import numpy as np
import pandas as pd

PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))

from scripts.analysis.honest_lab import get_df, available_assets, FEE_RT  # noqa: E402

LEV = 3.0
GROSS = 0.45        # esposizione lorda = LEV*POS del singolo (0.15*3) per confronto equo


def build_panel(assets: list[str], tf: str) -> pd.DataFrame:
    """Matrice close allineata per timestamp (inner join)."""
    closes = {}
    for a in assets:
        df = get_df(a, tf)
        s = pd.Series(df["close"].values,
                      index=pd.to_datetime(df["timestamp"], unit="ms", utc=True))
        closes[a] = s[~s.index.duplicated()]
    panel = pd.DataFrame(closes).dropna()
    return panel


def simulate_rotation(panel: pd.DataFrame, lookback=30, top_k=2,
                      fee_rt=FEE_RT, gross=GROSS, abs_filter=True,
                      oos_frac=0.0) -> dict:
    """Ad ogni barra: ranking per rendimento passato `lookback`; pesi uguali sui
    top_k con momentum>0 (se abs_filter); altrimenti cash. gross = esposizione tot.
    oos_frac>0: parte a investire solo dall'ultimo frac del campione."""
    P = panel.values
    T, N = P.shape
    rets = np.zeros_like(P)
    rets[1:] = P[1:] / P[:-1] - 1
    years = panel.index.year.values
    start = max(lookback + 1, int(T * (1 - oos_frac)) if oos_frac else lookback + 1)
    cap = peak = 1000.0
    max_dd = 0.0
    w = np.zeros(N)
    yearly: dict[int, float] = {}
    turn_total = 0.0
    for i in range(start, T - 1):
        mom = P[i] / P[i - lookback] - 1
        order = np.argsort(mom)[::-1]
        new_w = np.zeros(N)
        chosen = [j for j in order if (mom[j] > 0 or not abs_filter)][:top_k]
        if chosen:
            for j in chosen:
                new_w[j] = gross / len(chosen)
        # fee sul turnover (one-way = fee_rt/2 su ogni variazione di peso)
        turnover = np.abs(new_w - w).sum()
        cap -= cap * turnover * (fee_rt / 2)
        turn_total += turnover
        w = new_w
        port_ret = float(np.dot(w, rets[i + 1]))      # rendimento bar i->i+1
        cap = max(cap * (1 + port_ret), 10.0)
        peak = max(peak, cap); max_dd = max(max_dd, (peak - cap) / peak)
        yearly[years[i]] = yearly.get(years[i], 0.0) + port_ret * 100
    return {
        "ret": (cap / 1000 - 1) * 100,
        "dd": max_dd * 100,
        "turnover": turn_total,
        "yearly": yearly,
        "pos_years": sum(1 for v in yearly.values() if v > 0),
        "n_years": len(yearly),
    }


if __name__ == "__main__":
    assets = available_assets()
    print(f"ROTATION cross-sectional momentum — fee {FEE_RT*100:.2f}% RT, gross {GROSS} | OOS 30%")
    print(f"  Paniere: {assets}")
    for tf in ["1d", "4h"]:
        panel = build_panel(assets, tf)
        print(f"\n  === {tf} === panel {panel.shape[0]} barre, {panel.index[0].date()} -> {panel.index[-1].date()}")
        print(f"  {'config':<22s}{'FULL%':>9s}{'OOS%':>9s}{'DD%':>6s}{'Turn':>7s}{'AnniP':>8s}")
        for lb in [20, 30, 60, 90]:
            for k in [1, 2, 3]:
                full = simulate_rotation(panel, lookback=lb, top_k=k)
                oos = simulate_rotation(panel, lookback=lb, top_k=k, oos_frac=0.30)
                anni = f"{full['pos_years']}/{full['n_years']}"
                print(f"  lb{lb:<3d} top{k:<14d}{full['ret']:>+9.0f}{oos['ret']:>+9.0f}"
                      f"{full['dd']:>6.0f}{full['turnover']:>7.0f}{anni:>8s}")
