feat(portfolio): XS01 cross-sectional (Hyperliquid) BATTE il portafoglio -> TP01 70% + XS01 30%

Espansione universo (su input utente "storico da cerbero"): il Cerbero MCP col token MAINNET serve
Hyperliquid (230 perp REALI, storia nativa dal 2024). fetch_hyperliquid.py certifica 19 alt liquidi
a 1d (flat 0%, cross-venue 4-9 bps vs Binance) -> data/raw/hl_*_1d.parquet. Abilita le strategie
CROSS-SECTIONAL (impossibili a 2 asset).

XS01 = cross-sectional momentum market-neutral (long 5 forti / short 5 deboli su ret 30g, ogni 10g,
vol-target 20%). Validato onesto: plateau (config/k/subset), fee-robusto (0.3% RT), scorrelato a TP01
(-0.06), positivo OGNI anno 2024-26, meccanismo complementare (lavora nella dispersione quando TP01
e' in cash). Diverso dal regime-luck RV bocciato (19 asset, plateau, ogni anno+).

Contributo al portafoglio (outer-join + pesi rinormalizzati per sleeve a date diverse):
  TP01-solo FULL 1.30 / HOLD 0.31  ->  TP01 70% + XS01 30%: FULL 1.41 / HOLD 1.15, DD giu', ~ogni anno+.
-> XS01 BATTE il portafoglio esistente: inserito in active_sleeves.

Caveat (documentati): storia XS ~2.5 anni; STAT-MODE (book 19 gambe non eseguibile a 2k -> ~20k),
sleeve diagnostico/forward-monitor. portfolio.combine ora outer-join+renorm. 12 test passano.
Diario 2026-06-19-hyperliquid-xsec.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-19 20:05:45 +00:00
parent 18f22160b2
commit a5a61ac7e3
10 changed files with 512 additions and 15 deletions
+16 -3
View File
@@ -17,8 +17,8 @@ from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np
from src.data.downloader import load_data
from scripts.analysis.research_lab import backtest, buy_hold, mc_pvalue, VAL_START, HOLDOUT_START
import pandas as pd
from scripts.analysis.research_lab import backtest, buy_hold, mc_pvalue, load_tf, ts, _net_series, VAL_START, HOLDOUT_START
def load_signal(path):
@@ -58,7 +58,7 @@ def main():
res = {"asset": asset, "tf": tf, "sigfile": sigfile}
try:
signal = load_signal(sigfile)
df = load_data(asset, tf)
df = load_tf(asset, tf)
pos = np.asarray(signal(df, asset, tf), float)
res["n"] = int(len(df))
res["len_ok"] = bool(len(pos) == len(df))
@@ -81,6 +81,19 @@ def main():
null_p=round(p, 4),
beats_bh=bool(full.sharpe > bh.sharpe and oos.sharpe > 0),
)
# breadth per-anno (pre-hold-out): % anni positivi, anni rossi consecutivi
net, _, _, _ = _net_series(df, pos)
s = pd.Series(net, index=ts(df))
s = s[s.index < pd.Timestamp(HOLDOUT_START, tz="UTC")]
yr = {int(y): float((1 + g).prod() - 1) for y, g in s.groupby(s.index.year)}
vals = list(yr.values())
max_consec_red = 0; cur = 0
for v in vals:
cur = cur + 1 if v < 0 else 0
max_consec_red = max(max_consec_red, cur)
res["per_year_preho"] = {y: round(v, 3) for y, v in yr.items()}
res["pct_years_pos"] = round(sum(v > 0 for v in vals) / len(vals), 2) if vals else 0.0
res["max_consec_red_years"] = int(max_consec_red)
if holdout:
ho = backtest(df, pos, tf, lo=HOLDOUT_START)
res["holdout_sharpe"] = round(ho.sharpe, 3)