mirror of
https://github.com/PyP-Quant/quant-trading-strategy-templates.git
synced 2026-07-27 18:47:47 +00:00
tune xauusd template for precision breakout
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"}}
|
||||
|
||||
Reference in New Issue
Block a user