diff --git a/pair_signals/xauusd.py b/pair_signals/xauusd.py index c911232..942b7e1 100644 --- a/pair_signals/xauusd.py +++ b/pair_signals/xauusd.py @@ -69,6 +69,7 @@ M5-only, same per-timeframe pattern as the M15 overrides. from typing import Optional, List from config import PlatformConfig, Timeframe +from app_config import apply_pair_overrides _CONFIRMATIONS = { Timeframe.M5: [Timeframe.M15, Timeframe.H1], @@ -83,6 +84,9 @@ def make_config( confirmation_tfs: Optional[List[Timeframe]] = None, ) -> PlatformConfig: config = PlatformConfig() + # Deployment overlay (config.json) BEFORE the validated per-TF + # deltas below, so e.g. the M5 hour-9 append lands on the JSON base. + apply_pair_overrides(config) config.primary_timeframe = primary_tf config.confirmation_timeframes = ( confirmation_tfs or _CONFIRMATIONS.get(primary_tf, [Timeframe.M15, Timeframe.H4]) diff --git a/test_all.py b/test_all.py index ed0dfe7..4742056 100644 --- a/test_all.py +++ b/test_all.py @@ -1879,6 +1879,7 @@ def _(): primary_timeframe=tf, confirmation_timeframes=ctfs, ) + app_config.apply_pair_overrides(inline_cfg) if tf == Timeframe.M15: # Documented per-TF overrides (see xauusd.py docstring): # 1R minimum targets + mixed-"trending"-regime block + @@ -2677,6 +2678,53 @@ def _(): pass +@test("app_config: apply_pair_overrides stamps both blocked-hours mirrors") +def _(): + app = AppConfig(pair_overrides={XAUUSD: {"blocked_hours_utc": [3, 4]}}, + scan_pairs=[XAUUSD], + scan_timeframes=[Timeframe.M15]) + cfg = apply_pair_overrides(PlatformConfig(), app) + assert cfg.xau_strategy.blocked_hours_utc == [3, 4] + assert cfg.backtest.blocked_hours_utc == [3, 4] + assert cfg.scan_timeframes == [Timeframe.M15] + # the two mirrors must be independent lists, not aliases + cfg.xau_strategy.blocked_hours_utc.append(5) + assert cfg.backtest.blocked_hours_utc == [3, 4] + + +@test("app_config: M5 factory appends hour 9 on top of a JSON base") +def _(): + from pair_signals import PAIR_PRODUCERS + app = AppConfig(pair_overrides={XAUUSD: {"blocked_hours_utc": [3, 4]}}, + provider_settings={"twelve_data": {"api_key": "test-key"}}) + set_app_config(app) + try: + cfg = PAIR_PRODUCERS[XAUUSD](Timeframe.M5, None) + finally: + set_app_config(_TEST_APP) + assert cfg.xau_strategy.blocked_hours_utc == [3, 4, 9], \ + "validated M5 hour-9 delta must land on top of the JSON base" + assert cfg.backtest.blocked_hours_utc == [3, 4], \ + "mirror carries the base only (per-TF delta is XAU-strategy-side)" + + +@test("app_config: config.json.example is valid and behavior-neutral") +def _(): + import dataclasses + from pair_signals import PAIR_PRODUCERS + example = load_app_config( + os.path.join(os.path.dirname(__file__), "config.json.example")) + for tf in (Timeframe.M5, Timeframe.M15, Timeframe.H1, Timeframe.H4): + baseline = PAIR_PRODUCERS[XAUUSD](tf, None) # under _TEST_APP + set_app_config(example) + try: + with_example = PAIR_PRODUCERS[XAUUSD](tf, None) + finally: + set_app_config(_TEST_APP) + assert dataclasses.asdict(baseline) == dataclasses.asdict(with_example), \ + f"example config.json must be byte-identical to defaults on {tf}" + + # ══════════════════════════════════════════════════════════════════════ # SUMMARY # ══════════════════════════════════════════════════════════════════════