feat(XS01): affina con blend di lookback [30,90] — FULL 0.80->1.10, portafoglio 1.41->1.48
Come TP01 fonde gli orizzonti, XS01 ora fonde 30g+90g del momentum cross-sectional (z-score per lookback, mediato). Sweep: [30,90] e' il sweet spot (fonde i due singoli robusti, anti-overfit): XS01 standalone FULL 0.80->1.10, DD 21%->14%, corr a TP01 -0.06->-0.12, 100% anni+. Portafoglio TP01 70 + XS01 30: FULL Sh 1.41->1.48, DD 5.2%->4.6%, ~€/g 1.65->1.78; hold-out 1.15->1.06 (calo marginale dentro il rumore). Piu' robusto (due orizzonti) + diversifica meglio -> promosso. sleeves.XS_CFG lookbacks=(30,90), engine _xsec_returns usa lo score blended. 12 test ok. Diario 2026-06-19-xsec-blend.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,10 @@ def tp01_sleeve(weight: float = 1.0) -> Sleeve:
|
||||
# trend di TP01: lavora quando TP01 e' in cash). Validato: scripts/portfolio/xsec_research.py.
|
||||
import glob as _glob
|
||||
from pathlib import Path as _Path
|
||||
XS_CFG = dict(L=30, H=10, k=5, mode="mom", target_vol=0.20)
|
||||
# BLEND di lookback (2026-06-19): fonde 30g+90g del momentum cross-sectional (z-score per
|
||||
# lookback, mediato) come TP01 fonde gli orizzonti -> piu' robusto del singolo L=30: FULL Sh
|
||||
# 0.80->1.10, DD 21%->14%, corr a TP01 -0.06->-0.12, 100% anni+. Diario 2026-06-19-xsec-blend.md.
|
||||
XS_CFG = dict(lookbacks=(30, 90), H=10, k=5, mode="mom", target_vol=0.20)
|
||||
_HL_DIR = _Path(__file__).resolve().parents[2] / "data" / "raw"
|
||||
# UNIVERSO ESPLICITO = 19 ALT LIQUIDI MAJOR. NB (2026-06-19): allargare a 52 asset (incluso
|
||||
# small-cap WIF/JUP/ORDI/PYTH/TAO...) DILUISCE l'edge -> momentum cross-section NEGATIVO sui 52.
|
||||
@@ -75,15 +78,23 @@ def _xsec_returns() -> pd.Series:
|
||||
raise FileNotFoundError("universo Hyperliquid XS01 incompleto: gira scripts/analysis/fetch_hyperliquid.py")
|
||||
C = pd.concat(cols, axis=1, join="inner").sort_index().dropna()
|
||||
px = C.values; n, A = px.shape
|
||||
L, H, k, mode, tv = XS_CFG["L"], XS_CFG["H"], XS_CFG["k"], XS_CFG["mode"], XS_CFG["target_vol"]
|
||||
lookbacks, H, k, mode, tv = XS_CFG["lookbacks"], XS_CFG["H"], XS_CFG["k"], XS_CFG["mode"], XS_CFG["target_vol"]
|
||||
dret = np.vstack([np.zeros(A), px[1:] / px[:-1] - 1.0])
|
||||
W = np.zeros((n, A)); w = np.zeros(A)
|
||||
for i in range(n):
|
||||
if i >= L and i % H == 0:
|
||||
order = np.argsort(px[i] / px[i - L] - 1.0)
|
||||
w = np.zeros(A); lo, hi = order[:k], order[-k:]
|
||||
if mode == "mom": w[hi] = 0.5 / k; w[lo] = -0.5 / k
|
||||
else: w[lo] = 0.5 / k; w[hi] = -0.5 / k
|
||||
if i >= max(lookbacks) and i % H == 0:
|
||||
score = np.zeros(A); cnt = 0 # blend: media z-score cross-sectional per lookback
|
||||
for L in lookbacks:
|
||||
rL = px[i] / px[i - L] - 1.0
|
||||
sd = rL.std()
|
||||
if sd > 0:
|
||||
score += (rL - rL.mean()) / sd; cnt += 1
|
||||
if cnt:
|
||||
score /= cnt
|
||||
order = np.argsort(score)
|
||||
w = np.zeros(A); lo, hi = order[:k], order[-k:]
|
||||
if mode == "mom": w[hi] = 0.5 / k; w[lo] = -0.5 / k
|
||||
else: w[lo] = 0.5 / k; w[hi] = -0.5 / k
|
||||
W[i] = w
|
||||
gross = np.zeros(n); gross[1:] = np.sum(W[:-1] * dret[1:], axis=1)
|
||||
turn = np.zeros(n); turn[0] = np.abs(W[0]).sum(); turn[1:] = np.abs(np.diff(W, axis=0)).sum(axis=1)
|
||||
|
||||
Reference in New Issue
Block a user