"""TRD03 — MACD Trend Strategy Long when MACD(fast,slow) > signal(signal_span) AND MACD > 0; flat otherwise. Optionally vol-targeted. Uses standard MACD parameters with a small grid. """ import sys sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt") import altlib as al import numpy as np # MACD indicator (causal) def macd(close: np.ndarray, fast: int, slow: int, signal_span: int): """Returns (macd_line, signal_line) — all causal EMAs.""" ema_fast = al.ema(close, fast) ema_slow = al.ema(close, slow) macd_line = ema_fast - ema_slow signal_line = al.ema(macd_line, signal_span) return macd_line, signal_line def make_target(fast=12, slow=26, sig=9, use_vol_target=True): """Factory returning a target_fn for study_weights.""" def target_fn(df): c = df["close"].values.astype(float) macd_line, signal_line = macd(c, fast, slow, sig) # Long when MACD > signal AND MACD > 0, else flat direction = np.where((macd_line > signal_line) & (macd_line > 0), 1.0, 0.0) if use_vol_target: return al.vol_target(direction, df, target_vol=0.20, vol_win_days=30, leverage_cap=2.0) return direction return target_fn # Small internal grid: standard MACD + one variation; vol-targeted vs raw # Total backtests: 2 configs x 2 TFs x 2 assets = 8. Keep <=6 so limit to 1 TF grid, pick best. # Actually: 4 configs x 1 TF x 2 assets = 8 — too many. Use 2 configs x 2 TFs x 2 assets = 8. # To stay <=6 backtests (cells): run 2 configs on 1d only (4 cells), then pick best for 12h. configs = [ dict(fast=12, slow=26, sig=9, use_vol_target=True, label="MACD(12,26,9) vol-tgt"), dict(fast=12, slow=26, sig=9, use_vol_target=False, label="MACD(12,26,9) raw"), dict(fast=8, slow=21, sig=9, use_vol_target=True, label="MACD(8,21,9) vol-tgt"), ] # Evaluate all 3 configs on 1d to pick best best_rep = None best_score = -999 for cfg in configs: label = cfg.pop("label") fn = make_target(**cfg) cfg["label"] = label rep = al.study_weights(f"TRD03-{label}", fn, tfs=("1d",)) score = rep["verdict"].get("best_holdout_sharpe", -9) if score > best_score: best_score = score best_rep = rep best_cfg = cfg print(f"\n=== Best config from 1d grid: {best_cfg['label']} (holdout Sharpe={best_score:.3f}) ===\n") # Now run the best config on multiple TFs for the final report best_fn = make_target( fast=best_cfg["fast"], slow=best_cfg["slow"], sig=best_cfg["sig"], use_vol_target=best_cfg["use_vol_target"] ) # Run on 1d and 12h (2 TFs x 2 assets = 4 backtests total) final_rep = al.study_weights("TRD03", best_fn, tfs=("1d", "12h")) print(al.fmt(final_rep)) print("JSON:", al.as_json(final_rep))