"""MIC08 — OBV Trend Hypothesis: On-balance-volume trend: long when OBV above its EMA (volume confirms price). Long-flat. Continuous weights via al.study_weights. Grid: obv_ema_period in (20, 50) x tfs (1d, 12h) = 4 total backtests. """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt") import altlib as al import numpy as np def compute_obv(df) -> np.ndarray: """Compute On-Balance-Volume causally.""" close = df["close"].values volume = df["volume"].values n = len(close) obv = np.zeros(n) for i in range(1, n): if close[i] > close[i - 1]: obv[i] = obv[i - 1] + volume[i] elif close[i] < close[i - 1]: obv[i] = obv[i - 1] - volume[i] else: obv[i] = obv[i - 1] return obv def make_target(ema_period: int): def target(df) -> np.ndarray: obv = compute_obv(df) obv_ema = al.ema(obv, ema_period) # Long when OBV > its EMA, flat otherwise signal = np.where(obv > obv_ema, 1.0, 0.0) # Use vol-targeting to size the position sized = al.vol_target(signal, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0) return sized return target # Grid search: 2 EMA periods x 2 timeframes = 4 total backtests results = [] for ema_p in (20, 50): rep = al.study_weights( f"MIC08-OBV-EMA{ema_p}", make_target(ema_p), tfs=("1d", "12h"), ) results.append((rep["verdict"].get("best_holdout_sharpe", -9), ema_p, rep)) # Pick best by hold-out Sharpe results.sort(key=lambda x: x[0], reverse=True) best_holdout, best_ema, best_rep = results[0] print(f"\n=== Best config: EMA period={best_ema} ===") print(al.fmt(best_rep)) print("JSON:", al.as_json(best_rep))