Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions python/scripts/runtime_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions python/tests/test_frozen_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 2 additions & 4 deletions web/strategy-switch-console/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
Loading