diff --git a/templates/xauusd-atr-breakout/README.md b/templates/xauusd-atr-breakout/README.md index 5654d55..e82dfce 100644 --- a/templates/xauusd-atr-breakout/README.md +++ b/templates/xauusd-atr-breakout/README.md @@ -1,27 +1,31 @@ -# XAUUSD ATR Breakout Continuation +# XAUUSD ATR Precision Breakout -Custom Python breakout + continuation baseline for XAUUSD. +Custom Python precision-breakout baseline for XAUUSD. This project avoids ML on purpose. It is useful as a transparent benchmark to compare against heavier gold models like XGBoost or LightGBM. -The default gate is intentionally responsive so PPE produces more events than a strict long-range breakout filter. It combines: +The default gate is intentionally selective for small accounts. It prioritizes avoiding churn over forcing trades. The continuation paths are available as parameters, but they are disabled by default because one-day lab tests showed the stricter breakout profile had cleaner drawdown. + +It includes: - hard ATR breakout entries -- near-breakout pressure entries -- trend-continuation entries after range pressure +- optional near-breakout pressure entries +- optional trend-continuation entries after range pressure - EMA trend alignment - short momentum measured in ATR units Current defaults: - `lookback`: `96` -- `breakout_window`: `12` -- `atr_mult`: `0.05` +- `breakout_window`: `24` +- `atr_mult`: `0.03` - `near_breakout_atr`: `0.18` - `pullback_atr`: `0.35` -- `min_momentum_atr`: `0.08` +- `min_momentum_atr`: `0` - `fast_ema`: `8` - `slow_ema`: `34` +- `enable_near_breakout`: `false` +- `enable_continuation`: `false` - assigned runtime: Modal, because this starter uses `pandas` -If it overtrades, raise `near_breakout_atr` more carefully than `atr_mult`: `atr_mult` controls hard breakout distance, while `near_breakout_atr` and `pullback_atr` control the extra continuation paths. +For more activity, enable `enable_near_breakout` first. Enable `enable_continuation` only after PPE confirms the drawdown remains acceptable. diff --git a/templates/xauusd-atr-breakout/quant.config.json b/templates/xauusd-atr-breakout/quant.config.json index 3e3f0a9..708d4bf 100644 --- a/templates/xauusd-atr-breakout/quant.config.json +++ b/templates/xauusd-atr-breakout/quant.config.json @@ -7,13 +7,15 @@ "parameters": { "lookback": 96, "atr_window": 14, - "breakout_window": 12, - "atr_mult": 0.05, + "breakout_window": 24, + "atr_mult": 0.03, "near_breakout_atr": 0.18, "pullback_atr": 0.35, - "min_momentum_atr": 0.08, + "min_momentum_atr": 0, "fast_ema": 8, - "slow_ema": 34 + "slow_ema": 34, + "enable_near_breakout": false, + "enable_continuation": false }, "training_requirements": [ "numpy", diff --git a/templates/xauusd-atr-breakout/strategy.py b/templates/xauusd-atr-breakout/strategy.py index bd18066..f7afd1c 100644 --- a/templates/xauusd-atr-breakout/strategy.py +++ b/templates/xauusd-atr-breakout/strategy.py @@ -29,29 +29,33 @@ def train(data, config): "params": { "lookback": int(params.get("lookback", 96)), "atr_window": int(params.get("atr_window", 14)), - "breakout_window": int(params.get("breakout_window", 12)), - "atr_mult": float(params.get("atr_mult", 0.05)), + "breakout_window": int(params.get("breakout_window", 24)), + "atr_mult": float(params.get("atr_mult", 0.03)), "near_breakout_atr": float(params.get("near_breakout_atr", 0.18)), "pullback_atr": float(params.get("pullback_atr", 0.35)), - "min_momentum_atr": float(params.get("min_momentum_atr", 0.08)), + "min_momentum_atr": float(params.get("min_momentum_atr", 0.0)), "fast_ema": int(params.get("fast_ema", 8)), "slow_ema": int(params.get("slow_ema", 34)), + "enable_near_breakout": bool(params.get("enable_near_breakout", False)), + "enable_continuation": bool(params.get("enable_continuation", False)), }, - "name": "xauusd_atr_breakout_v2", - }, {"training_bars": int(len(data)), "model": "modal_python_rule_breakout_continuation"} + "name": "xauusd_atr_breakout_precision", + }, {"training_bars": int(len(data)), "model": "modal_python_rule_precision_breakout"} def predict(model, market_data, config): params = {**model.get("params", {}), **config.get("parameters", {})} lookback = int(params.get("lookback", 96)) atr_window = int(params.get("atr_window", 14)) - breakout_window = int(params.get("breakout_window", 12)) - atr_mult = float(params.get("atr_mult", 0.05)) + breakout_window = int(params.get("breakout_window", 24)) + atr_mult = float(params.get("atr_mult", 0.03)) near_breakout_atr = float(params.get("near_breakout_atr", 0.18)) pullback_atr = float(params.get("pullback_atr", 0.35)) - min_momentum_atr = float(params.get("min_momentum_atr", 0.08)) + min_momentum_atr = float(params.get("min_momentum_atr", 0.0)) fast_ema = int(params.get("fast_ema", 8)) slow_ema = int(params.get("slow_ema", 34)) + enable_near_breakout = bool(params.get("enable_near_breakout", False)) + enable_continuation = bool(params.get("enable_continuation", False)) candles = market_data.get("candles", []) if len(candles) < lookback: return {"signal": "HOLD", "confidence": 0.0, "metadata": {"reason": "not_enough_candles"}} @@ -108,11 +112,11 @@ def predict(model, market_data, config): # enter before the hard breakout instead of waiting for an extreme close. upper_pressure = (high - close) / atr lower_pressure = (close - low) / atr - if trend_up and upper_pressure <= near_breakout_atr and momentum_3 >= min_momentum_atr: + if enable_near_breakout and trend_up and upper_pressure <= near_breakout_atr and momentum_3 >= min_momentum_atr: edge = max(near_breakout_atr - upper_pressure, 0.0) quality = _clip01(max(ema_gap_atr, 0.0) * 0.35 + max(momentum_3, 0.0) * 0.35 + close_position * 0.20) return {"signal": "UP", "confidence": confidence(edge, quality, floor=0.57), "metadata": {**meta, "setup": "near_breakout_high"}} - if trend_down and lower_pressure <= near_breakout_atr and momentum_3 <= -min_momentum_atr: + if enable_near_breakout and trend_down and lower_pressure <= near_breakout_atr and momentum_3 <= -min_momentum_atr: edge = max(near_breakout_atr - lower_pressure, 0.0) quality = _clip01(max(-ema_gap_atr, 0.0) * 0.35 + max(-momentum_3, 0.0) * 0.35 + (1.0 - close_position) * 0.20) return {"signal": "DOWN", "confidence": confidence(edge, quality, floor=0.57), "metadata": {**meta, "setup": "near_breakout_low"}} @@ -120,11 +124,11 @@ def predict(model, market_data, config): # Continuation path: after a breakout, gold often retests the fast EMA # without closing outside the range again. This keeps the system alive # while still requiring trend and momentum context. - if trend_up and close > high - atr * pullback_atr and prev_close <= close and momentum_6 >= 0: + if enable_continuation and trend_up and close > high - atr * pullback_atr and prev_close <= close and momentum_6 >= 0: edge = max((close - (high - atr * pullback_atr)) / atr, 0.0) quality = _clip01(max(ema_gap_atr, 0.0) * 0.35 + max(momentum_6, 0.0) * 0.25 + close_position * 0.25) return {"signal": "UP", "confidence": confidence(edge, quality, floor=0.55), "metadata": {**meta, "setup": "trend_continuation_high"}} - if trend_down and close < low + atr * pullback_atr and prev_close >= close and momentum_6 <= 0: + if enable_continuation and trend_down and close < low + atr * pullback_atr and prev_close >= close and momentum_6 <= 0: edge = max(((low + atr * pullback_atr) - close) / atr, 0.0) quality = _clip01(max(-ema_gap_atr, 0.0) * 0.35 + max(-momentum_6, 0.0) * 0.25 + (1.0 - close_position) * 0.25) return {"signal": "DOWN", "confidence": confidence(edge, quality, floor=0.55), "metadata": {**meta, "setup": "trend_continuation_low"}}