"""CMB04 — Momentum + Low-Vol Filter HYPOTHESIS: TSMOM long-flat taken only when realized vol is below its rolling median (avoid high-vol whipsaw). Vol-target the rest. Grid: 2 vol-filter windows (30d vs 60d rolling median lookback) x 2 TFs (1d, 12h) = 4 cells total. Best config chosen by min(BTC,ETH) holdout Sharpe. """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt") import altlib as al import numpy as np def cmb04_target(df, vol_filter_days: int = 30): """ TSMOM multi-horizon (1m/3m/6m) long-flat, gated by a low-vol filter: - Compute realized vol (30d) at each bar. - Compute rolling median of that vol over vol_filter_days. - Only take the TSMOM signal when realized_vol < rolling_median (low-vol regime). - In high-vol regime: go flat (0). - Vol-target the resulting direction. """ c = df["close"].values.astype(float) bpd = al.bars_per_day(df) bpy = bpd * 365.25 # --- TSMOM multi-horizon direction (1m, 3m, 6m) --- horizons = (30 * bpd, 90 * bpd, 180 * bpd) direction = np.zeros(len(c)) for h in horizons: h = int(h) sig = np.full(len(c), np.nan) if h < len(c): sig[h:] = np.sign(c[h:] / c[:-h] - 1.0) direction += np.nan_to_num(sig, nan=0.0) # Majority vote -> long or flat direction = np.clip(np.sign(direction), 0.0, 1.0) # long-flat only # --- Realized vol (30d causal) --- rv_win = max(2, 30 * bpd) r = al.simple_returns(c) rv = al.realized_vol(r, rv_win, bpy) # --- Rolling median of realized vol over vol_filter_days --- med_win = max(2, vol_filter_days * bpd) rv_median = ( al._series_if_array(rv).rolling(med_win, min_periods=max(2, med_win // 2)).median().values if hasattr(al, "_series_if_array") else __import__("pandas").Series(rv).rolling(med_win, min_periods=max(2, med_win // 2)).median().values ) # --- Gate: only enter when rv < median (low-vol regime) --- low_vol_gate = np.where( np.isfinite(rv) & np.isfinite(rv_median) & (rv < rv_median), 1.0, 0.0 ) gated_direction = direction * low_vol_gate # --- Vol-target the gated direction --- pos = al.vol_target(gated_direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0) return pos def make_target_fn(vol_filter_days: int): def fn(df): return cmb04_target(df, vol_filter_days=vol_filter_days) return fn if __name__ == "__main__": import pandas as pd best_rep = None best_hold = -9.0 best_label = "" configs = [ ("CMB04-vf30", 30), ("CMB04-vf60", 60), ] for label, vfd in configs: fn = make_target_fn(vfd) rep = al.study_weights(label, fn, tfs=("1d", "12h")) v = rep["verdict"] h = v.get("best_holdout_sharpe", -9) print(al.fmt(rep)) print(f" [grid] {label}: holdout={h:.3f}") if h > best_hold: best_hold = h best_rep = rep best_label = label print("\n=== BEST CONFIG ===", best_label) print(al.fmt(best_rep)) print("JSON:", al.as_json(best_rep))