"""Adversarial dissection of the SELECTION-FREE relative-value basket vs TP01."""
from __future__ import annotations
import importlib.util, sys
from pathlib import Path
import numpy as np
import pandas as pd

HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
import ortholib as ol
import altlib as al

AGENTS = HERE / "agents"


def _book(path: Path):
    spec = importlib.util.spec_from_file_location(path.stem, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod.book


def _sh(s) -> float:
    r = np.asarray(pd.Series(s).dropna().values, float)
    return float(np.mean(r) / np.std(r) * np.sqrt(365.25)) if len(r) > 2 and np.std(r) > 0 else 0.0


def _dd(s) -> float:
    eq = (1.0 + pd.Series(s).dropna()).cumprod()
    return float((eq / eq.cummax() - 1.0).min())


def uplift_at(cand: pd.Series, B: pd.Series, lo=None, hi=None, w=0.25) -> float:
    J = pd.concat({"B": B, "C": cand}, axis=1, join="inner").dropna()
    if lo is not None:
        J = J[J.index >= pd.Timestamp(lo, tz="UTC")]
    if hi is not None:
        J = J[J.index < pd.Timestamp(hi, tz="UTC")]
    if len(J) < 30:
        return float("nan")
    return _sh((1 - w) * J["B"] + w * J["C"]) - _sh(J["B"])


def main():
    B = al.tp01_baseline_daily()

    # ---- collect ALL books, classify ----
    rows = {}
    meta = {}
    for p in sorted(AGENTS.glob("agent_*.py")):
        try:
            ev = ol.eval_book(_book(p))
        except Exception as e:
            print(f"skip {p.stem}: {e}"); continue
        d = ev["daily"]
        if d.std() == 0:
            print(f"skip degenerate {p.stem}"); continue
        rows[p.stem.replace("agent_", "")] = d
        meta[p.stem.replace("agent_", "")] = ev

    names = list(rows)
    M = pd.concat(rows, axis=1, join="inner").dropna()
    print(f"\nBooks loaded: {len(names)}  common-date matrix: {M.shape[0]} days "
          f"[{M.index.min().date()} .. {M.index.max().date()}]")

    # selection-free: ALL non-degenerate, market-neutral. exclude only the OBVIOUS
    # degenerate DILUTE (rebalance_harvest, sharpe<0 standalone & negative every metric).
    excl = ["12_rebalance_harvest"]
    keep = [n for n in names if n not in excl]
    print(f"Basket = equal-weight of {len(keep)} books (excluded only {excl})")

    basket = M[keep].mean(axis=1)
    basket_all = M.mean(axis=1)  # truly ALL incl rebalance_harvest

    for label, bk in [("BASKET(excl harvest)", basket), ("BASKET(all 18)", basket_all)]:
        print(f"\n===== {label} =====")
        Bal = pd.concat({"B": B, "C": bk}, axis=1, join="inner").dropna()
        corr = Bal["B"].corr(Bal["C"])
        print(f"  standalone Sharpe {_sh(bk):.3f}  maxDD {_dd(bk):.3f}  corr->TP01 {corr:.3f}  "
              f"n={len(bk)}")
        # full + per-cut uplift (forward windows from cut)
        print(f"  uplift (w=0.25, window = [cut, end)):")
        for cut in ["2018-01-01", "2021-01-01", "2022-01-01", "2023-01-01", "2024-01-01", "2025-01-01"]:
            u = uplift_at(bk, B, lo=cut)
            print(f"    from {cut[:4]}+ : {u:+.3f}")
        # PRE-2025 ONLY (exclude suspect window)
        u_pre = uplift_at(bk, B, hi="2025-01-01")
        u_2025 = uplift_at(bk, B, lo="2025-01-01")
        print(f"  uplift 2019-2024 ONLY (exclude suspect window): {u_pre:+.3f}")
        print(f"  uplift 2025+ ONLY                              : {u_2025:+.3f}")
        # per-year standalone returns & per-year uplift
        print(f"  per-year: standalone ret | blend-uplift(w0.25, that year only)")
        for y in sorted(set(bk.index.year)):
            sub = bk[bk.index.year == y]
            ret = float((1 + sub).prod() - 1)
            uy = uplift_at(bk, B, lo=f"{y}-01-01", hi=f"{y+1}-01-01")
            print(f"    {y}: ret {ret:+7.2%}   uplift {uy:+.3f}")

    # ---- decompose full-sample uplift: pre vs post 2025 ----
    print("\n===== UPLIFT DECOMPOSITION (basket excl harvest, w=0.25) =====")
    full = uplift_at(basket, B, lo="2018-01-01")
    print(f"  full-sample uplift              {full:+.3f}")
    print(f"  pre-2025 (2019-2024)            {uplift_at(basket,B,hi='2025-01-01'):+.3f}")
    print(f"  2025+ only                      {uplift_at(basket,B,lo='2025-01-01'):+.3f}")

    # ---- fee sensitivity: rebuild basket at higher fees ----
    print("\n===== FEE SENSITIVITY (rebuild every book's daily net at higher RT fee) =====")
    for fee_side in [0.0005, 0.0010, 0.0015]:
        dd = {}
        for p in sorted(AGENTS.glob("agent_*.py")):
            nm = p.stem.replace("agent_", "")
            if nm in excl: continue
            try:
                ev = ol.eval_book(_book(p), fee_side=fee_side)
                if ev["daily"].std() > 0:
                    dd[nm] = ev["daily"]
            except Exception:
                pass
        Mf = pd.concat(dd, axis=1, join="inner").dropna()
        bk = Mf.mean(axis=1)
        u_full = uplift_at(bk, B, lo="2018-01-01")
        u_pre = uplift_at(bk, B, hi="2025-01-01")
        u_25 = uplift_at(bk, B, lo="2025-01-01")
        print(f"  RT={2*fee_side*100:.2f}%  standalone Sh {_sh(bk):+.3f}  "
              f"uplift full {u_full:+.3f} | pre25 {u_pre:+.3f} | 25+ {u_25:+.3f}")

    # ---- turnover / executability of the basket ----
    print("\n===== EXECUTION REALISM (per-book at $600, basket-averaged legs) =====")
    tns = [meta[n]["turnover_per_year"] for n in keep]
    print(f"  per-book turnover/yr: min {min(tns):.1f} med {np.median(tns):.1f} max {max(tns):.1f}")
    print(f"  Basket = mean of {len(keep)} books; each leg capped 0.5. Averaged turnover lower.")
    # build averaged book legs to measure REAL basket turnover & notional
    btc, eth = ol.aligned()
    wbs, wes = [], []
    for n in keep:
        wb, we = _book(AGENTS / f"agent_{n}.py")(btc, eth)
        wbs.append(np.nan_to_num(np.asarray(wb, float)))
        wes.append(np.nan_to_num(np.asarray(we, float)))
    wb = np.clip(np.mean(wbs, axis=0), -0.5, 0.5)
    we = np.clip(np.mean(wes, axis=0), -0.5, 0.5)
    pb = np.zeros(len(wb)); pb[1:] = wb[:-1]
    pe = np.zeros(len(we)); pe[1:] = we[:-1]
    turn = np.abs(np.diff(pb, prepend=0.0)) + np.abs(np.diff(pe, prepend=0.0))
    turn_yr = turn.sum() / (len(turn) / 365.25)
    max_leg = float(np.max(np.maximum(np.abs(pb), np.abs(pe))))
    print(f"  AVERAGED-BASKET book: turnover/yr {turn_yr:.1f}  max-leg-frac {max_leg:.3f}  "
          f"max per-leg notional @$600 = ${max_leg*600:.0f}")
    print(f"  typical daily notional traded @$600 satellite (say 20% slot=$120): "
          f"${turn_yr/365.25*120:.2f}/day both legs")


if __name__ == "__main__":
    main()
