8b086f4f49
12mo grid campaign (2025-07-08 → 2026-07-08, 1-pip round-trip cost, train/val split at 2026-01-08). Donor config scored -6.7R/34 trades. Findings: transitioning regime -27R/88 wide-open; A-grade entries lose (-26R) while B-grade wins; Fridays -17R; hours 8/12/13/18/19 UTC bleed; score 30-35 band holds the train-half losses; trailing (0.45R arm/0.5R dist) beats static TP1 and gold's 0.6/0.75. Best-found config: -1.06R / 53 trades / PF 0.93 / 62.3% WR — breakeven, FAILS acceptance (positive both halves, PF>=1.3). Per plan, EUR ships functional but stays out of default scan_pairs. Grid results and methodology documented in the module docstring; full numbers in backtest/cache/eurv2_*.csv.
122 lines
4.6 KiB
Python
122 lines
4.6 KiB
Python
"""EUR/USD signal producer config.
|
|
|
|
Re-tuned 2026-07-10 against the realistic backtester (1-pip round-trip
|
|
cost, next-open fills, raw-path resolution) on 2025-07-08 → 2026-07-08
|
|
M5 with M15/H1 confirmations. RESULT: no configuration passed the
|
|
acceptance rule (positive in BOTH train and val halves, PF >= 1.3).
|
|
|
|
The M5 defaults below are the best found — roughly breakeven
|
|
(-1.1R / 53 trades / PF 0.93 / 62% WR on 12mo) versus the donor
|
|
branch's -6.7R. Findings that shaped them:
|
|
- transitioning regime bled -27R/88 trades wide-open → blocked
|
|
- A-grade entries LOSE (-26R) while B-grade wins — max conviction
|
|
fires at move extremes (same pathology as gold M5)
|
|
- Fridays (-17R) and hours 8, 12, 13, 18, 19 UTC negative
|
|
- score 30-35 band is where the train-half losses live → floor 35
|
|
- trailing exits beat static TP1 (arm 0.45R / dist 0.5R best)
|
|
|
|
EUR/USD therefore ships FUNCTIONAL BUT OFF by default: it is not in
|
|
PlatformConfig.scan_pairs. Scan it on demand (main.py --pair EURUSD,
|
|
or add to scan_pairs) but do not treat M5 signals as having a
|
|
validated edge. M15 tuning is a future session.
|
|
"""
|
|
|
|
from typing import Optional, List
|
|
from config import PlatformConfig, Timeframe, EURUSD
|
|
|
|
_CONFIRMATIONS = {
|
|
Timeframe.M5: [Timeframe.M15, Timeframe.H1],
|
|
Timeframe.M15: [Timeframe.M5, Timeframe.H1],
|
|
Timeframe.H1: [Timeframe.M15, Timeframe.H4],
|
|
Timeframe.H4: [Timeframe.H1, Timeframe.D1],
|
|
}
|
|
|
|
_TRENDING = {
|
|
"ema_trend": 2.0, "rsi": 1.0, "macd": 2.0,
|
|
"bollinger": 0.5, "keltner": 1.0, "roc": 1.5,
|
|
"candlestick": 0.5, "divergence": 2.0, "adx": 1.0,
|
|
}
|
|
|
|
EUR_REGIME_WEIGHTS = {
|
|
"trending_up": _TRENDING,
|
|
"trending_down": _TRENDING,
|
|
"trending": _TRENDING,
|
|
"ranging": {
|
|
"ema_trend": 0.3, "rsi": 2.0, "macd": 1.0,
|
|
"bollinger": 2.0, "keltner": 1.5, "roc": 0.5,
|
|
"candlestick": 0.8, "divergence": 2.5, "adx": 0.5,
|
|
},
|
|
"transitioning": {
|
|
"ema_trend": 1.5, "rsi": 1.5, "macd": 2.0,
|
|
"bollinger": 1.0, "keltner": 1.5, "roc": 2.0,
|
|
"candlestick": 0.8, "divergence": 2.0, "adx": 1.0,
|
|
},
|
|
}
|
|
|
|
_M5_TRENDING = {
|
|
"ema_trend": 1.5, "rsi": 1.0, "macd": 2.5,
|
|
"bollinger": 0.5, "keltner": 2.0, "roc": 2.0,
|
|
"candlestick": 0.3, "divergence": 1.0, "adx": 1.0,
|
|
}
|
|
|
|
M5_REGIME_WEIGHTS = {
|
|
"trending_up": _M5_TRENDING,
|
|
"trending_down": _M5_TRENDING,
|
|
"trending": _M5_TRENDING,
|
|
"ranging": {
|
|
"ema_trend": 0.3, "rsi": 1.5, "macd": 1.5,
|
|
"bollinger": 1.5, "keltner": 2.0, "roc": 1.0,
|
|
"candlestick": 0.3, "divergence": 1.5, "adx": 0.5,
|
|
},
|
|
"transitioning": {
|
|
"ema_trend": 1.0, "rsi": 1.0, "macd": 2.5,
|
|
"bollinger": 0.5, "keltner": 2.5, "roc": 2.0,
|
|
"candlestick": 0.3, "divergence": 1.0, "adx": 1.0,
|
|
},
|
|
}
|
|
|
|
|
|
def make_config(
|
|
primary_tf: Timeframe,
|
|
confirmation_tfs: Optional[List[Timeframe]] = None,
|
|
) -> PlatformConfig:
|
|
config = PlatformConfig()
|
|
config.symbol = EURUSD
|
|
config.display_name = "EUR/USD"
|
|
config.pip_value = 0.0001
|
|
config.primary_timeframe = primary_tf
|
|
config.confirmation_timeframes = (
|
|
confirmation_tfs or _CONFIRMATIONS.get(primary_tf, [Timeframe.M15, Timeframe.H4])
|
|
)
|
|
|
|
# XAU gates are display-gated in the aggregator; disabling here is
|
|
# belt-and-braces so no gold logic can ever fire on an EUR config.
|
|
config.xau_strategy.enabled = False
|
|
|
|
config.backtest.session_start_hour = 7
|
|
config.backtest.session_end_hour = 20
|
|
config.backtest.blocked_hours_utc = [22, 23, 0, 1, 2, 3]
|
|
config.backtest.allowed_weekdays = [0, 1, 2, 3, 4]
|
|
|
|
if primary_tf == Timeframe.M5:
|
|
config.technical.regime_weights_override = M5_REGIME_WEIGHTS
|
|
config.technical.swing_sr_lookback = 20
|
|
config.eur_strategy.sl_atr_floor = 0.7
|
|
config.eur_strategy.sl_atr_multiplier = 0.3
|
|
config.eur_strategy.min_quality_grades = ["A+", "A", "B"]
|
|
# Best-found 2026-07-10 (breakeven, see module docstring):
|
|
config.eur_strategy.min_entry_score = 35.0
|
|
config.eur_strategy.min_entry_confidence = 45.0
|
|
config.eur_strategy.blocked_vol_regimes = []
|
|
config.eur_strategy.min_indicator_agreement = 0
|
|
config.eur_strategy.allowed_weekdays = [0, 1, 2, 3] # Fridays bled -17R
|
|
config.backtest.allowed_weekdays = [0, 1, 2, 3]
|
|
config.backtest.blocked_hours_utc = [22, 23, 0, 1, 2, 3, 8, 12, 13, 18, 19]
|
|
# Trailing exit beat static TP1; earlier/tighter than gold's 0.6/0.75.
|
|
config.signals.trail_arm_r = 0.45
|
|
config.signals.trail_dist_r = 0.5
|
|
else:
|
|
config.technical.regime_weights_override = EUR_REGIME_WEIGHTS
|
|
|
|
return config
|