"""XU02 — Rebalance/Holding Sweep (Low-Vol + Momentum)
MECHANISM: Study how holding period H and portfolio size k interact with signal quality.
Two signals: (1) pure momentum blend (z30+z90 same as XS01), (2) low-vol rank (short volatile, long stable).
Goal: find whether a DIFFERENT H/k pair or signal gives something DISTINCT from XS01.

Hypothesis:
  - XS01 uses H=10, k=5 (momentum). A longer H reduces turnover, captures slower signal decay.
  - Low-vol selection (long stable alts, short volatile ones) is conceptually orthogonal to momentum.
  - Sweep: H in {5,10,20,30}, k in {3,5,8}, signal in {momentum, low_vol}.
  - Keep <=5 backtests: focus on the most contrasting configs.

Grid (5 backtests):
  1. MOM  H=5  k=5  — fast rebalance, same as XS01 direction, more turnover
  2. MOM  H=30 k=5  — slow rebalance, lower turnover, tests signal persistence
  3. LVOL H=10 k=5  — low-vol signal at standard H/k (conceptually distinct from momentum)
  4. LVOL H=20 k=5  — low-vol with slower rebalance
  5. LVOL H=10 k=3  — low-vol tight portfolio, more concentrated
"""
import sys
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/xsec")
import xslib as xs
import numpy as np

print("XU02 — Rebalance/Holding Sweep (Low-Vol + Momentum)")
print("=" * 60)


def mom_blend(P):
    """Z-blend of 30d and 90d momentum (same signal as XS01)."""
    z30 = xs.xs_zscore(xs.past_return(P.close, 30))
    z90 = xs.xs_zscore(xs.past_return(P.close, 90))
    return np.nanmean(np.stack([z30, z90], axis=0), axis=0)


def low_vol(P):
    """Low-vol signal: score = -rolling_std(ret, 30). Higher score = lower vol = long.
    Cross-sectionaly ranks alts: stable (low realized vol) go long, volatile go short."""
    rv = xs.roll_std(P.ret, 30)
    return -rv   # negate: higher = lower vol = prefer long


# 1) Momentum H=5 k=5 — fast rebalance (more turnover, tests short-term signal)
rep1 = xs.study_xs(
    "XU02_MOM_H5k5",
    mom_blend,
    universe="majors", H=5, k=5, long_short=True
)
print(xs.fmt(rep1))
print("JSON:", xs.as_json(rep1))
print()

# 2) Momentum H=30 k=5 — slow rebalance, lower turnover, tests signal persistence
rep2 = xs.study_xs(
    "XU02_MOM_H30k5",
    mom_blend,
    universe="majors", H=30, k=5, long_short=True
)
print(xs.fmt(rep2))
print("JSON:", xs.as_json(rep2))
print()

# 3) Low-vol H=10 k=5 — standard H/k but conceptually distinct signal
rep3 = xs.study_xs(
    "XU02_LVOL_H10k5",
    low_vol,
    universe="majors", H=10, k=5, long_short=True
)
print(xs.fmt(rep3))
print("JSON:", xs.as_json(rep3))
print()

# 4) Low-vol H=20 k=5 — slower rebalance, low-vol is a structural trait (changes slowly)
rep4 = xs.study_xs(
    "XU02_LVOL_H20k5",
    low_vol,
    universe="majors", H=20, k=5, long_short=True
)
print(xs.fmt(rep4))
print("JSON:", xs.as_json(rep4))
print()

# 5) Low-vol H=10 k=3 — tighter portfolio (top/bottom 3 most extreme)
rep5 = xs.study_xs(
    "XU02_LVOL_H10k3",
    low_vol,
    universe="majors", H=10, k=3, long_short=True
)
print(xs.fmt(rep5))
print("JSON:", xs.as_json(rep5))
print()

# Summary
print("=" * 60)
print("SUMMARY: All 5 configs ranked by hold-out Sharpe")
all_reps = [rep1, rep2, rep3, rep4, rep5]
ranked = sorted(all_reps, key=lambda r: r["holdout"].get("sharpe", -999), reverse=True)
for r in ranked:
    h_sh = r["holdout"].get("sharpe", 0)
    f_sh = r["full"]["sharpe"]
    c_xs01 = r["corr_xs01"]
    verdict = r["marginal"].get("verdict", "N/A")
    earns = r["earns_slot"]
    print(f"  {r['name']:25s} FULL={f_sh:+.2f} HOLD={h_sh:+.2f} corr_xs01={c_xs01} "
          f"verdict={verdict} earns_slot={earns}")

# Pick best by marginal robustness -> earns_slot -> hold-out -> distinctness
best = None
for r in ranked:
    if r["earns_slot"]:
        best = r
        break
if best is None:
    # fallback: best hold-out with corr_xs01 < 0.6
    candidates = [r for r in ranked if (r.get("corr_xs01") or 1.0) < 0.6 and r["holdout"].get("sharpe", 0) > 0]
    best = candidates[0] if candidates else ranked[0]

print()
print("BEST CONFIG:")
print(xs.fmt(best))
print("JSON:", xs.as_json(best))
