Files
PythagorasGoal/tests/test_meta_allocation.py
Adriano Dal Pastro cff5fa2bf5 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>
2026-06-29 20:50:33 +00:00

84 lines
3.5 KiB
Python

"""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"]))