diff --git a/main.py b/main.py index 51b98f2..aa6b253 100755 --- a/main.py +++ b/main.py @@ -308,7 +308,7 @@ Examples: parser.add_argument("--pair", "-p", default="XAUUSD", help="Pair to analyze: XAUUSD (default: XAUUSD)") parser.add_argument("--timeframe", "-tf", default="H1", - choices=["M5", "M15", "M30", "H1", "H4", "D1"], + choices=list(_TF_BY_NAME), help="Primary analysis timeframe (default: H1)") parser.add_argument("--detail", action="store_true", help="Show full indicator breakdown") @@ -326,6 +326,14 @@ Examples: return parser.parse_args() +# The four validated primary timeframes (the same set the bot and the +# dashboard scan). M30/D1 were never validated as primaries. +_TF_BY_NAME = { + "M5": Timeframe.M5, "M15": Timeframe.M15, + "H1": Timeframe.H1, "H4": Timeframe.H4, +} + + def _resolve_pair(s: str) -> str: aliases = { "XAUUSD": XAUUSD, "XAU/USD": XAUUSD, "GOLD": XAUUSD, @@ -337,35 +345,15 @@ def _resolve_pair(s: str) -> str: def build_config(args) -> PlatformConfig: + """Per-pair tuned config from the pair_signals factory. + + The factory owns the validated confirmation map (M5->[M15,H1], + M15->[M5,H1], H1->[M15,H4], H4->[H1,D1]); passing None here keeps + the CLI on the same map as the bot and the dashboard. A CLI-local + table once paired M5 with the unvalidated [M1, M15]. + """ pair = _resolve_pair(args.pair) - - tf_map = { - "M5": Timeframe.M5, "M15": Timeframe.M15, "M30": Timeframe.M30, - "H1": Timeframe.H1, "H4": Timeframe.H4, "D1": Timeframe.D1, - } - primary_tf = tf_map[args.timeframe] - - tf_confirmations = { - "M5": [Timeframe.M1, Timeframe.M15], - "M15": [Timeframe.M5, Timeframe.H1], - "M30": [Timeframe.M15, Timeframe.H1], - "H1": [Timeframe.M15, Timeframe.H4], - "H4": [Timeframe.H1, Timeframe.D1], - "D1": [Timeframe.H4, Timeframe.W1], - } - confirmation_tfs = tf_confirmations.get( - args.timeframe, [Timeframe.M15, Timeframe.H4] - ) - - # Per-pair tuned configs from the pair_signals factories. - factory = PAIR_PRODUCERS.get(pair) - if factory is not None: - return factory(primary_tf, confirmation_tfs) - - config = PlatformConfig() - config.primary_timeframe = primary_tf - config.confirmation_timeframes = confirmation_tfs - return config + return PAIR_PRODUCERS[pair](_TF_BY_NAME[args.timeframe], None) def run_live(args, config: PlatformConfig): diff --git a/telegram_bot.py b/telegram_bot.py index b1104c6..6d36589 100755 --- a/telegram_bot.py +++ b/telegram_bot.py @@ -497,15 +497,9 @@ def _run_signal_sync( timeframe, [Timeframe.M15, Timeframe.H4] ) # Per-pair tuned configs (regime weights, gates, sessions) come from - # the pair_signals factories; a plain PlatformConfig would silently - # run every pair with XAU defaults. - factory = PAIR_PRODUCERS.get(symbol) - if factory is not None: - config = factory(timeframe, confirmation_tfs) - else: - config = PlatformConfig() - config.primary_timeframe = timeframe - config.confirmation_timeframes = confirmation_tfs + # the pair_signals factories; every scanned symbol is registered there + # (SCAN_PAIRS is validated against PAIR_INFO at import). + config = PAIR_PRODUCERS[symbol](timeframe, confirmation_tfs) aggregator = SignalAggregator(config) aggregator.data_provider = _get_shared_dp() return aggregator.analyze_pair(symbol=symbol, log_source="telegram") diff --git a/test_all.py b/test_all.py index f946668..713c03f 100644 --- a/test_all.py +++ b/test_all.py @@ -1594,14 +1594,19 @@ def _(): assert Timeframe.D1 in config.confirmation_timeframes -@test("build_config all valid timeframes") +@test("build_config: every CLI timeframe uses the pair factory's confirmation map") def _(): - for tf_str in ["M5", "M15", "M30", "H1", "H4", "D1"]: + from pair_signals import xauusd as _xau + for tf_str, tf in (("M5", Timeframe.M5), ("M15", Timeframe.M15), + ("H1", Timeframe.H1), ("H4", Timeframe.H4)): class FakeArgs: pair = "XAUUSD" timeframe = tf_str config = build_config(FakeArgs()) - assert config.primary_timeframe is not None + assert config.primary_timeframe == tf + # The CLI must not carry its own confirmation table: it once gave + # M5 an unvalidated [M1, M15] pairing that no other surface used. + assert config.confirmation_timeframes == _xau._CONFIRMATIONS[tf], tf_str # ══════════════════════════════════════════════════════════════════════