research(sweep): 5 thread paralleli — 0 nuovi sleeve, STATARB-RESID LEAD ortogonale+eseguibile
Ricerca onesta su aree inesplorate (harness altlib+xsec_v2_nonmom, tutti i gate incl. study_family_honest anti-selection-on-holdout). Branch main, nessun impatto live, test 143/143. 1 XSEC low-risk cousins (MAX/idio-vol/Amihud) -> 1 LEAD (IVOL), STAT-MODE, DSR 0.37<0.95 2 XSEC momentum-structure vs XS01 -> tutto REDUNDANT (sostituire XS01 distrugge hold) 3 Meta-allocazione dinamica (4 sleeve) -> pesi fissi vincono (gia quasi risk-parity) 4 Segnali ortogonali ETH/BTC (2 gambe) -> STATARB-RESID + DVOLSPREAD LEAD 5 1-gamba a segnale (MACD/RSI/Supertrend/...) -> 0/12 earns_slot (trend=TP01, MR morta, hedge) LEAD principale STATARB-RESID (mean-rev residuo ETH-b*BTC, OLS rolling, 2 gambe): primo stream INSIEME ortogonale (corr->book 0.027, beta-mkt 0.013) ED eseguibile a $600 (haircut ~0, NON STAT-MODE) -> cadono i 2 muri di XS01/opzioni. Resta solo il muro dell'edge (Sharpe 0.84, DSR 0.929 same-sign <0.95). Causalita+fee verificate dal coordinatore. Forward-monitor, non sleeve. Soffitto direzionale ~1.3 riconfermato. Diario 2026-06-29-strategy-search-5threads.md, CLAUDE.md agg. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""Test minimali per scripts/research/meta_allocation.py.
|
||||
|
||||
Verifica le proprieta' STRUTTURALI dell'harness di meta-allocazione (non l'edge — quello e' nel
|
||||
report): (1) i pesi-bersaglio + cash sommano a 1 per riga; (2) gli sleeve inattivi pesano 0;
|
||||
(3) lo schema vol-parity e' CAUSALE (un cambio dei rendimenti in t+k non altera i pesi <= t);
|
||||
(4) il cap del momentum e' rispettato; (5) il motore di simulazione conserva (vol nulla -> equity
|
||||
piatta) e il costo di ribilancio aumenta col turnover.
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from scripts.research import meta_allocation as M
|
||||
|
||||
|
||||
def _toy(n=400, A=4, seed=0):
|
||||
rng = np.random.default_rng(seed)
|
||||
R = rng.normal(0.0005, 0.01, size=(n, A))
|
||||
active = np.ones((n, A), bool)
|
||||
index = pd.date_range("2020-01-01", periods=n, freq="1D", tz="UTC")
|
||||
fixed_w = np.array([0.4125, 0.1875, 0.15, 0.25])
|
||||
return index, R, active, fixed_w
|
||||
|
||||
|
||||
def test_weights_plus_cash_sum_to_one():
|
||||
index, R, active, fixed_w = _toy()
|
||||
for fn in (M.scheme_base, M.scheme_volpar_pure, M.scheme_volpar_tilt,
|
||||
M.scheme_momentum, M.scheme_dd_cash, M.scheme_dd_defensive):
|
||||
W = fn(index, R, active, fixed_w) # ultima colonna = cash
|
||||
s = W.sum(axis=1)
|
||||
assert np.allclose(s, 1.0, atol=1e-9), f"{fn.__name__}: righe non sommano a 1 (max dev {np.abs(s-1).max():.2e})"
|
||||
assert (W >= -1e-12).all(), f"{fn.__name__}: pesi negativi"
|
||||
|
||||
|
||||
def test_inactive_sleeves_get_zero_weight():
|
||||
index, R, active, fixed_w = _toy()
|
||||
active[:, 2] = False # spegni lo sleeve 2 ovunque
|
||||
W = M.scheme_base(index, R, active, fixed_w)
|
||||
assert np.allclose(W[:, 2], 0.0), "uno sleeve inattivo riceve peso non nullo"
|
||||
assert np.allclose(W.sum(axis=1), 1.0)
|
||||
|
||||
|
||||
def test_volparity_is_causal():
|
||||
"""Un cambio dei rendimenti da t0 in poi NON deve alterare i pesi calcolati per t < t0."""
|
||||
index, R, active, fixed_w = _toy(n=400)
|
||||
t0 = 360
|
||||
W1 = M.scheme_volpar_pure(index, R, active, fixed_w)
|
||||
R2 = R.copy(); R2[t0:] *= 50.0 # shock futuro enorme
|
||||
W2 = M.scheme_volpar_pure(index, R2, active, fixed_w)
|
||||
assert np.allclose(W1[:t0], W2[:t0]), "VOL-PARITY non causale: pesi passati dipendono dal futuro"
|
||||
|
||||
|
||||
def test_momentum_respects_cap():
|
||||
index, R, active, fixed_w = _toy()
|
||||
cap = 0.55
|
||||
W = M.scheme_momentum(index, R, active, fixed_w, cap=cap)
|
||||
sleeve_w = W[:, :-1] # escludi cash
|
||||
assert sleeve_w.max() <= cap + 1e-6, f"cap momentum violato: max {sleeve_w.max():.3f} > {cap}"
|
||||
|
||||
|
||||
def test_simulate_flat_when_no_returns():
|
||||
index, R, active, fixed_w = _toy()
|
||||
Rz = np.zeros_like(R)
|
||||
W = M.scheme_base(index, Rz, active, fixed_w)
|
||||
sim = M.simulate(Rz, active, W, cost_rate=0.0)
|
||||
assert np.allclose(sim["daily"].values, 0.0, atol=1e-12), "equity non piatta con rendimenti nulli e costo zero"
|
||||
|
||||
|
||||
def test_rebalance_cost_increases_with_turnover():
|
||||
"""Uno schema ad alto turnover (vol-parity) deve pagare piu' costo del peso-fisso (basso turnover)."""
|
||||
index, R, active, fixed_w = _toy(seed=3)
|
||||
Wb = M.scheme_base(index, R, active, fixed_w)
|
||||
Wv = M.scheme_volpar_pure(index, R, active, fixed_w)
|
||||
tb = M.simulate(R, active, Wb)["turnover_per_year"]
|
||||
tv = M.simulate(R, active, Wv)["turnover_per_year"]
|
||||
assert tv > tb, f"il vol-parity dovrebbe avere turnover > peso-fisso (got {tv:.2f} vs {tb:.2f})"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-q"]))
|
||||
Reference in New Issue
Block a user