"""Loss-guard Hurst: le fade saltano i segnali in regime persistente/trending (rolling-Hurst >= soglia), dove si concentrano stop-loss e perdite. Validato 2026-06-02: filtrare hurst>=0.55 DIMEZZA il DD del PORT06 alzando lo Sharpe. Filtro CAUSALE (close<=i), default off (None).""" import numpy as np import pandas as pd from src.strategies.fade_base import hurst_skip_mask def _df(close): n = len(close) return pd.DataFrame({"timestamp": range(n), "open": close, "high": close, "low": close, "close": close, "volume": [1.0] * n}) def test_mask_off_when_none(): df = _df(np.cumsum(np.random.default_rng(0).normal(size=400)) + 100) m = hurst_skip_mask(df, None) assert m.dtype == bool and not m.any() # None -> nessuno skip def test_mask_flags_persistent_regime(): # serie fortemente TRENDING (persistente, Hurst alto) -> deve essere mascherata (skip) molto trend = np.linspace(100, 300, 600) df = _df(trend) m = hurst_skip_mask(df, hurst_max=0.55, window=100) # dopo il warmup, una rampa pulita e' persistente -> gran parte mascherata assert m[150:].mean() > 0.5 def test_fade_strategy_filters_signals(): """Una fade con hurst_max produce <= segnali del baseline, e tutti i superstiti sono in regime non-persistente (la maschera e' False alla loro barra).""" import importlib rng = np.random.default_rng(1) # serie mean-reverting (anti-persistente) con qualche estensione -> genera fade n = 1200 c = 100 + np.cumsum(rng.normal(scale=0.5, size=n)) c = 100 + (c - c.mean()) * 0.3 # comprimi verso la media (mean-revert) df = _df(c) ts = pd.to_datetime(df["timestamp"], unit="s", utc=True) m = importlib.import_module("scripts.strategies.MR01_bollinger_fade") Strat = next(v for k, v in vars(m).items() if isinstance(v, type) and getattr(v, "__module__", "") == m.__name__ and hasattr(v, "generate_signals")) s = Strat() base = s.generate_signals(df, ts, bb_window=50, k=2.0, sl_atr=2.0) filt = s.generate_signals(df, ts, bb_window=50, k=2.0, sl_atr=2.0, hurst_max=0.55) assert len(filt) <= len(base) # il filtro non aggiunge mai segnali skip = hurst_skip_mask(df, 0.55, 100) assert all(not skip[sig.idx] for sig in filt) # nessun superstite in regime persistente