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:
@@ -82,10 +82,19 @@ class StrategyPortfolio:
|
||||
return {s.name: s.weight / tot for s in self.sleeves}
|
||||
|
||||
def combined_daily(self, lo=None, hi=None) -> pd.Series:
|
||||
"""Combina gli sleeve per peso. OUTER-join: sleeve con date d'inizio diverse
|
||||
(es. TP01 dal 2019, uno nuovo dal 2024) -> ogni giorno i pesi sono RINORMALIZZATI
|
||||
fra i soli sleeve con dato disponibile (uno sleeve "si attiva" quando parte la sua
|
||||
storia). Cosi' non si tronca il portafoglio alla finestra comune."""
|
||||
w = self.weights()
|
||||
cols = {s.name: s.daily() for s in self.sleeves}
|
||||
J = pd.concat(cols, axis=1, join="inner").dropna()
|
||||
combo = sum(w[c] * J[c] for c in J.columns)
|
||||
J = pd.concat(cols, axis=1, join="outer").sort_index()
|
||||
wv = np.array([w[c] for c in J.columns], float)
|
||||
active = J.notna().values * wv # peso solo dove c'e' dato
|
||||
rowsum = active.sum(axis=1, keepdims=True)
|
||||
wnorm = np.divide(active, rowsum, out=np.zeros_like(active), where=rowsum > 0)
|
||||
combo = pd.Series(np.nansum(np.nan_to_num(J.values) * wnorm, axis=1), index=J.index)
|
||||
combo = combo[J.notna().any(axis=1).values] # togli i giorni senza alcun dato
|
||||
if lo is not None:
|
||||
combo = combo[combo.index >= lo]
|
||||
if hi is not None:
|
||||
|
||||
Reference in New Issue
Block a user