Files
PythagorasGoal/scripts/research/xsec/verify_survivors.py
Adriano Dal Pastro 9612560479 research(xsec): sweep cross-sectional su Hyperliquid (43 script/257 config) + verifica avversariale
Nuova harness condivisa xslib.py (panel HL certificato, score per-asset causale, book
long-k/short-k vol-targeted leak-free) + 43 script in runs/ su 11 famiglie (MOM/REV/VOL/
DIST/LIQ/VAL/STRUCT/UNIV). Scoring = earns_slot (full>0 AND hold-out>0 AND marginal ADDS
al portafoglio live AND corr XS01<0.6, con jackknife drop-one-month).

Find: 42/257 config earns_slot=True, ma TUTTE con corr TP01 -0.2..-0.4 e PnL ~solo 2025.
Verify (verify_survivors.py, 3 scettici deterministici):
 - S1 redundancy: cluster low-vol = UNA scommessa (XV01=XU02=1.00, XV02/XV03 r 0.44-0.67);
   XM09/XL02/XS06b/XR02 distinti (corr media off-diag +0.20).
 - S2 short-beta: cluster low-vol carica 0.44-0.70 su short-market -> NON market-neutral,
   e' un tilt short-alt-beta di regime. XM09(0.08)/XR02(-0.21) NON short-beta.
 - S3 per-anno: cluster low-vol decade (XV01/XU02 2026 -0.09); XL02 morto (2025 -0.14,
   2026 -0.43); XM09 (0.82/0.50/0.74) e XR02 (0.84/0.40/2.68) positivi in tutti e 3 gli anni.

Esito: nessuna sleeve nuova. Cluster low-vol RIGETTATO (regime-bet), XL02 RIGETTATO (overfit).
2 LEAD genuini (XM09 trend-gated x-sec momentum, XR02 reversal vol-gated) -> forward-monitor,
non deployabili (panel 2.5y regime unico + STAT-MODE esecuzione). Portafoglio live invariato.

Incluso anche options_vrp_managed.py (A/B VRP01 hold-to-expiry vs gestione attiva del doc
credit-spread): la gestione attiva DISTRUGGE l'edge (combo FULL managed Sh -1.29 vs HtE +0.96,
il delta-exit taglia i vincenti) -> scartata, VRP01 resta hold-to-expiry.

Diari: 2026-06-20-xsec-strategies-sweep.md, 2026-06-20-vrp-active-management.md.
gitignore: data/paper_portfolio/ (stato runtime paper) + scripts/research/xsec/runs/out/ (output rigenerabile).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 21:36:57 +00:00

146 lines
6.7 KiB
Python

"""verify_survivors — adversarial 'Verify' phase for the xsec sweep (2026-06-20).
The Find phase flagged 42/257 cross-sectional configs as earns_slot=True on the certified
Hyperliquid panel. ALL the slot-earners share two tells: (a) strongly NEGATIVE corr to TP01
(-0.2..-0.4), (b) PnL concentrated in 2025. Hypothesis under test (the only thing that matters
before promoting any of them to a sleeve):
"These are not N independent edges. They are ONE regime bet — short the high-beta alt junk
during the 2024-26 alt-bear — wearing many masks (low-vol, low-beta, low-corr, reversal,
trend-gated-mom). The drop-one-month jackknife is robust only WITHIN that single regime."
Three skeptics, deterministic (no agents):
S1 (distinctness/redundancy): mutual correlation matrix of the strongest survivor per family.
If they're all mutually >0.6 correlated -> one bet, not many.
S2 (short-beta tell): correlation of each survivor to two reference factors built on the SAME
panel: SHORTBETA = book ranking by -roll_beta; SHORTMKT = -equal-weight alt-market return.
A genuinely market-neutral factor should NOT load heavily on "short the market".
S3 (single-regime): per-calendar-year Sharpe. If the edge is ~entirely 2025, the 2.5y panel
has ONE up-for-the-factor regime and the hold-out (2025-26) cannot prove robustness.
Run: uv run python scripts/research/xsec/verify_survivors.py
"""
import sys
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parent))
import xslib as xs # noqa: E402
import altlib as al # noqa: E402 (via xslib sys.path)
def roll_corr_to_market(ret, win):
"""Rolling corr of each asset's return to the equal-weight market (causal)."""
mkt = pd.Series(xs.market_ret(ret))
out = np.full_like(ret, np.nan)
for a in range(ret.shape[1]):
out[:, a] = pd.Series(ret[:, a]).rolling(win, min_periods=max(5, win // 2)).corr(mkt).values
return out
def book(universe, score_fn, H=10, k=5, long_short=True):
p = xs.load_panel(universe)
return xs.xs_backtest(p, score_fn(p), H=H, k=k, long_short=long_short)
# ── strongest representative survivor per family (from the Find-phase output) ──
SURV = {
"XV02_lowidiovol": lambda: book("majors", lambda P: -xs.roll_std(xs.residual_return(P.ret, 60), 30)),
"XV01_lowvol": lambda: book("majors", lambda P: -xs.roll_std(P.ret, 30)),
"XV03_lowbeta": lambda: book("all", lambda P: -xs.roll_beta(P.ret, 60)),
"XS06b_lowcorr": lambda: book("all", lambda P: -roll_corr_to_market(P.ret, 60)),
"XU02_lowvol_maj": lambda: book("majors", lambda P: -xs.roll_std(P.ret, 30), k=5),
"XM09_trendgmom": lambda: book("all", lambda P: _trend_gated_mom(P, 60)),
"XL02_voltrendmom": lambda: book("majors", lambda P: xs.xs_zscore(xs.past_return(P.close, 60)) + xs.xs_zscore(xs.volume_z(P.vol, 30))),
"XR02_revgated": lambda: book("majors", lambda P: _vol_gated_rev(P, 3), H=3),
}
def _trend_gated_mom(P, L):
"""XS momentum, but zeroed on days the equal-weight market trailing-sum is non-positive."""
s = xs.past_return(P.close, L)
mkt = xs.market_ret(P.ret)
up = pd.Series(mkt).rolling(L, min_periods=L // 2).sum().values > 0
out = s.copy()
out[~up, :] = np.nan # flat (no ranking) when market not trending up
return out
def _vol_gated_rev(P, L):
"""Short-term reversal, active only when market realized vol is in its high regime."""
rev = -xs.past_return(P.close, L)
mvol = pd.Series(xs.market_ret(P.ret)).rolling(20, min_periods=10).std()
thr = mvol.expanding(min_periods=60).quantile(0.70).values
hi = (mvol.values > thr)
out = rev.copy()
out[~hi, :] = np.nan
return out
# reference factors (the suspected single underlying bet)
REF = {
"SHORTBETA": lambda: book("all", lambda P: -xs.roll_beta(P.ret, 60)), # explicit short-high-beta
"SHORTMKT": None, # -equal-weight alt market
}
def main():
print("=" * 96)
print(" ADVERSARIAL VERIFY — are the xsec survivors one regime bet (short alt-beta) or N edges?")
print("=" * 96)
series = {n: al._to_daily(f()) for n, f in SURV.items()}
# SHORTMKT reference = negative equal-weight alt-market daily return (vol-targeted like a book)
p_all = xs.load_panel("all")
mkt = pd.Series(-xs.market_ret(p_all.ret), index=p_all.index)
series["SHORTBETA"] = al._to_daily(book("all", lambda P: -xs.roll_beta(P.ret, 60)))
series["SHORTMKT"] = al._to_daily(mkt)
df = pd.DataFrame(series).dropna(how="all")
names = list(SURV.keys())
# ── S1: mutual correlation matrix ─────────────────────────────────────────
print("\n[S1] Mutual correlation matrix of survivors (>0.6 = same bet):")
C = df[names].corr()
hdr = " " + " ".join(f"{n[:8]:>8s}" for n in names)
print(hdr)
for n in names:
row = " ".join(f"{C.loc[n, m]:>8.2f}" for m in names)
print(f" {n:<16s} {row}")
iu = [(a, b) for i, a in enumerate(names) for b in names[i + 1:]]
pair_corrs = [C.loc[a, b] for a, b in iu]
print(f" --> mean off-diagonal corr = {np.mean(pair_corrs):+.2f} "
f"(share |r|>0.6: {np.mean([abs(x) > 0.6 for x in pair_corrs]) * 100:.0f}%)")
# ── S2: load on short-beta / short-market ─────────────────────────────────
print("\n[S2] Correlation of each survivor to the suspected single bet:")
print(f" {'survivor':<18s} {'corr_SHORTBETA':>15s} {'corr_SHORTMKT':>15s}")
for n in names:
cb = df[n].corr(df["SHORTBETA"])
cm = df[n].corr(df["SHORTMKT"])
print(f" {n:<18s} {cb:>15.2f} {cm:>15.2f}")
# ── S3: per-year Sharpe (single-regime test) ──────────────────────────────
print("\n[S3] Per-calendar-year Sharpe (is the edge ~entirely 2025?):")
print(f" {'survivor':<18s} {'2024':>8s} {'2025':>8s} {'2026':>8s}")
for n in names:
s = df[n].dropna()
cells = []
for y in (2024, 2025, 2026):
sy = s[s.index.year == y]
cells.append(f"{al._sh(sy):>8.2f}" if len(sy) > 20 else f"{'--':>8s}")
print(f" {n:<18s} " + " ".join(cells))
print("\n" + "=" * 96)
print(" VERDICT logic: high mutual corr + high SHORTBETA/SHORTMKT load + 2025-only Sharpe")
print(" => one short-alt-beta regime bet on a single-regime 2.5y panel. LEAD/forward-monitor,")
print(" NOT a sleeve (cannot prove it survives an alt-bull regime flip).")
print("=" * 96)
if __name__ == "__main__":
main()