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>
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user