Wire config.json overlay into the pair factory (before per-TF deltas)

Implements Task 2: apply_pair_overrides in xauusd.make_config before
the validated per-TF deltas, so e.g. M5 hour-9 appends on top of the
JSON base. Updated Module 9 equivalence test and added three parity pins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPNF37MP9Kuv62B1VBrZ1b
This commit is contained in:
Harwinder Sekhon
2026-07-21 12:59:12 -04:00
parent 7455fcaa6c
commit 3669f5e33e
2 changed files with 52 additions and 0 deletions
+4
View File
@@ -69,6 +69,7 @@ M5-only, same per-timeframe pattern as the M15 overrides.
from typing import Optional, List from typing import Optional, List
from config import PlatformConfig, Timeframe from config import PlatformConfig, Timeframe
from app_config import apply_pair_overrides
_CONFIRMATIONS = { _CONFIRMATIONS = {
Timeframe.M5: [Timeframe.M15, Timeframe.H1], Timeframe.M5: [Timeframe.M15, Timeframe.H1],
@@ -83,6 +84,9 @@ def make_config(
confirmation_tfs: Optional[List[Timeframe]] = None, confirmation_tfs: Optional[List[Timeframe]] = None,
) -> PlatformConfig: ) -> PlatformConfig:
config = 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.primary_timeframe = primary_tf
config.confirmation_timeframes = ( config.confirmation_timeframes = (
confirmation_tfs or _CONFIRMATIONS.get(primary_tf, [Timeframe.M15, Timeframe.H4]) confirmation_tfs or _CONFIRMATIONS.get(primary_tf, [Timeframe.M15, Timeframe.H4])
+48
View File
@@ -1879,6 +1879,7 @@ def _():
primary_timeframe=tf, primary_timeframe=tf,
confirmation_timeframes=ctfs, confirmation_timeframes=ctfs,
) )
app_config.apply_pair_overrides(inline_cfg)
if tf == Timeframe.M15: if tf == Timeframe.M15:
# Documented per-TF overrides (see xauusd.py docstring): # Documented per-TF overrides (see xauusd.py docstring):
# 1R minimum targets + mixed-"trending"-regime block + # 1R minimum targets + mixed-"trending"-regime block +
@@ -2677,6 +2678,53 @@ def _():
pass 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 # SUMMARY
# ══════════════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════════════