From 0847e5149da03a48e513394d5eed9fec413ad0c6 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:41:57 +0800 Subject: [PATCH] fix: resolve strategy aliases from the generated display source runtime_settings and the console Worker each kept a two-entry alias literal while the display config defines three, so bitcoin_etf_smart_dca resolved to itself and skipped domain, allowed-mode and frozen checks. Read the generated alias map in both places and cover the bypass with a regression test. --- python/scripts/runtime_settings.py | 18 ++++++++++++++---- python/tests/test_frozen_selection.py | 11 +++++++++++ web/strategy-switch-console/worker.js | 6 ++---- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/python/scripts/runtime_settings.py b/python/scripts/runtime_settings.py index db1dcdb5..7ea33785 100644 --- a/python/scripts/runtime_settings.py +++ b/python/scripts/runtime_settings.py @@ -26,6 +26,7 @@ EXAMPLE_TARGETS_DIR = ROOT / "examples" / "targets" LOCAL_POLICY_PATH = ROOT / "local" / "policy.json" PLATFORM_CONFIG_PATH = ROOT / "platform-config.json" +DISPLAY_NAMES_PATH = ROOT / "python" / "scripts" / "display_names.json" SUPPORTED_PLATFORMS = { "schwab": {"plugin_mounts_prefix": "SCHWAB_", "repository": "QuantStrategyLab/CharlesSchwabPlatform"}, @@ -413,6 +414,18 @@ def load_platform_config() -> dict[str, Any]: return config if isinstance(config, dict) else {} +def strategy_aliases() -> dict[str, str]: + """Load the generated display aliases so alias spellings cannot skip policy checks.""" + if not DISPLAY_NAMES_PATH.exists(): + return {} + with DISPLAY_NAMES_PATH.open("r", encoding="utf-8") as handle: + payload = json.load(handle) + aliases = payload.get("strategy_aliases") if isinstance(payload, dict) else None + if not isinstance(aliases, dict): + return {} + return {str(key): str(value) for key, value in aliases.items() if isinstance(value, str)} + + def target_path_id(path: Path) -> str | None: relative = None for base in (LOCAL_TARGETS_DIR, EXAMPLE_TARGETS_DIR): @@ -855,10 +868,7 @@ def validate_runtime_target_strategy_policy(runtime_target: dict[str, Any], erro return profile = str(runtime_target.get("strategy_profile") or "").strip() normalized_profile = profile.lower().replace("-", "_") - canonical_profile = { - "global_macro_etf_rotation": "global_etf_rotation", - "global_etf_confidence_vol_gate": "global_etf_rotation", - }.get(normalized_profile, normalized_profile) + canonical_profile = strategy_aliases().get(normalized_profile, normalized_profile) strategy = strategies.get(canonical_profile) if not isinstance(strategy, dict) and canonical_profile != profile: strategy = strategies.get(profile) diff --git a/python/tests/test_frozen_selection.py b/python/tests/test_frozen_selection.py index 667fb747..0f05a73a 100644 --- a/python/tests/test_frozen_selection.py +++ b/python/tests/test_frozen_selection.py @@ -50,6 +50,17 @@ def test_core_profile_spellings_keep_existing_dry_run_policy(self): {'platform_id': 'ibkr', 'strategy_profile': profile, 'execution_mode': 'dry_run'}, errors) self.assertEqual(errors, []) + def test_display_alias_cannot_skip_strategy_policy(self): + errors = [] + runtime.validate_runtime_target_strategy_policy( + {'platform_id': 'binance', 'strategy_profile': 'bitcoin_etf_smart_dca', 'execution_mode': 'dry_run'}, + errors, + ) + self.assertTrue( + any('domain us_equity is not supported by binance' in error for error in errors), + errors, + ) + def test_policy_lookup_preserves_original_legacy_target_and_fingerprint(self): target = {'platform_id': 'ibkr', 'strategy_profile': 'GLOBAL-ETF-ROTATION', 'execution_mode': 'live'} digest = runtime.runtime_target_fingerprint(target) diff --git a/web/strategy-switch-console/worker.js b/web/strategy-switch-console/worker.js index fb3eda42..7c75e04e 100644 --- a/web/strategy-switch-console/worker.js +++ b/web/strategy-switch-console/worker.js @@ -23,6 +23,7 @@ import { assertBinanceFactsHistoryBindings, BINANCE_WALLET_HISTORY_MAX_DAYS, } from "./binance_account_facts.js"; import { DEFAULT_STRATEGY_PROFILES } from "./strategy_profiles_asset.js"; +import { DISPLAY_NAMES } from "./display_names_data.js"; import { DCA_SUPPORTED_PLATFORMS, DEFAULT_VARIABLE_SCOPES as DEFAULT_VARIABLE_SCOPE, @@ -15623,10 +15624,7 @@ function parseStrategyProfiles(raw, fieldName = "strategy profiles") { } function isFrozenStrategyProfile(strategy) { - const aliases = { - global_macro_etf_rotation: "global_etf_rotation", - global_etf_confidence_vol_gate: "global_etf_rotation", - }; + const aliases = DISPLAY_NAMES.strategy_aliases || {}; // Normalize only the policy lookup; retain the original profile in payloads. const lookup = String(strategy?.profile || "").trim().toLowerCase().replaceAll("-", "_"); const profile = aliases[lookup] || lookup;