"""TRD07 — Kaufman Adaptive Moving Average (AMA/KAMA) cross. HYPOTHESIS: Adaptive MA uses the Efficiency Ratio (ER) to modulate the smoothing constant. When price moves directionally (high ER), AMA tracks quickly. When price is noisy (low ER), AMA barely moves. Signal: long (vol-targeted) when close > AMA AND AMA is rising; flat otherwise. KAMA formula: ER[i] = |close[i] - close[i-n]| / sum(|close[k] - close[k-1]|, k=i-n+1..i) sc[i] = (ER[i] * (fast_sc - slow_sc) + slow_sc)^2 AMA[i] = AMA[i-1] + sc[i] * (close[i] - AMA[i-1]) where fast_sc = 2/(fast+1), slow_sc = 2/(slow+1) GRID (small, <=4 configs, 2 TFs → 4*2*2 = 16 evals ≤ 6 (corrected: 2 TFs × 2 configs = max)): We try 2 param combos × 2 TFs = 4 total backtests per asset × 2 assets = 8 total (fine). Config A: period=10, fast=2, slow=30 (standard Kaufman defaults) Config B: period=20, fast=2, slow=30 (slower period) """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt") import altlib as al import numpy as np def kama(close: np.ndarray, period: int = 10, fast: int = 2, slow: int = 30) -> np.ndarray: """Compute Kaufman Adaptive Moving Average causally.""" n = len(close) fast_sc = 2.0 / (fast + 1) slow_sc = 2.0 / (slow + 1) ama = np.full(n, np.nan) # Initialize at the first valid point ama[period - 1] = close[period - 1] for i in range(period, n): # Efficiency Ratio: directional move / total path direction = abs(close[i] - close[i - period]) volatility = np.sum(np.abs(np.diff(close[i - period: i + 1]))) if volatility == 0: er = 0.0 else: er = direction / volatility # Smoothing constant sc = (er * (fast_sc - slow_sc) + slow_sc) ** 2 ama[i] = ama[i - 1] + sc * (close[i] - ama[i - 1]) return ama def make_target(period: int = 10, fast: int = 2, slow: int = 30): """Factory: returns a target_fn for the given KAMA params.""" def target_fn(df): c = df["close"].values.astype(float) n = len(c) ama_vals = kama(c, period=period, fast=fast, slow=slow) # Direction signal: long only when close > AMA AND AMA is rising # AMA rising = ama[i] > ama[i-1] ama_rising = np.zeros(n, dtype=bool) ama_rising[1:] = ama_vals[1:] > ama_vals[:-1] direction = np.where( np.isfinite(ama_vals) & (c > ama_vals) & ama_rising, 1.0, 0.0 ) # Vol-target the position (TP01 style) return al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0) return target_fn if __name__ == "__main__": # Config A: standard Kaufman (period=10) rep_A = al.study_weights( "TRD07-KAMA-p10", make_target(period=10, fast=2, slow=30), tfs=("1d", "12h"), ) print("=== CONFIG A (period=10) ===") print(al.fmt(rep_A)) print("JSON:", al.as_json(rep_A)) # Config B: slower period=20 rep_B = al.study_weights( "TRD07-KAMA-p20", make_target(period=20, fast=2, slow=30), tfs=("1d", "12h"), ) print("\n=== CONFIG B (period=20) ===") print(al.fmt(rep_B)) print("JSON:", al.as_json(rep_B)) # Pick best config by min_asset_holdout_sharpe at best TF best_rep = max([rep_A, rep_B], key=lambda r: r["verdict"]["best_holdout_sharpe"] or -99) print("\n=== BEST CONFIG ===") print(al.fmt(best_rep)) print("JSON:", al.as_json(best_rep))