From 707bd2a45357ef5443708b9ca6732e86a4b4064d Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Tue, 30 Jun 2026 02:21:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20unified=20config=20=E2=80=94=20platform?= =?UTF-8?q?-config.json=20is=20single=20source=20of=20truth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture change (Phase 1): - build_platform_config.py generates config.js from platform-config.json - worker.js now imports DCA_SUPPORTED_PLATFORMS, PLATFORM_*_VARIABLES from config.js instead of hardcoding - Fix platform-config.json: Schwab dca=true, LongBridge dca=true, add default_strategy_profile for all platforms - Deploy workflow: run build_platform_config.py instead of build_config.py Eliminates duplicate config between worker.js and page_asset.js. Next phase: migrate index.html hardcoded defaults to import config.js. Co-Authored-By: Claude --- .../deploy-strategy-switch-console.yml | 8 +- platform-config.json | 24 +- scripts/build_platform_config.py | 249 ++++++++++ web/strategy-switch-console/config.js | 470 ++++++++++++++++++ .../strategy_profiles_asset.js | 285 ++++++++++- web/strategy-switch-console/worker.js | 29 +- 6 files changed, 1037 insertions(+), 28 deletions(-) create mode 100644 scripts/build_platform_config.py create mode 100644 web/strategy-switch-console/config.js diff --git a/.github/workflows/deploy-strategy-switch-console.yml b/.github/workflows/deploy-strategy-switch-console.yml index 538ed61..47a22f3 100644 --- a/.github/workflows/deploy-strategy-switch-console.yml +++ b/.github/workflows/deploy-strategy-switch-console.yml @@ -51,14 +51,10 @@ jobs: with: node-version: "22" - - name: Build config from platform-config.json - run: | - set -euo pipefail - python3 scripts/build_config.py - - - name: Regenerate bundled assets + - name: Build config & assets from platform-config.json run: | set -euo pipefail + python3 scripts/build_platform_config.py python3 scripts/sync_strategy_switch_page_asset.py - name: Validate Worker assets diff --git a/platform-config.json b/platform-config.json index fe4319f..3c8fc10 100644 --- a/platform-config.json +++ b/platform-config.json @@ -40,7 +40,7 @@ "reserved_cash": true, "income_layer": true, "option_overlay": true, - "dca": false + "dca": true }, "default_account": { "key": "preview", @@ -51,7 +51,10 @@ "hk_equity" ], "cash_currency": "USD", - "default_execution_mode": "live" + "default_execution_mode": "live", + "default_strategy_profile": "tqqq_growth_income", + "reserved_cash_ratio": "0.03", + "min_reserved_cash_usd": "" }, "deployment": { "default_execution_mode": "live", @@ -90,7 +93,10 @@ "hk_equity" ], "cash_currency": "USD", - "default_execution_mode": "live" + "default_execution_mode": "live", + "default_strategy_profile": "soxl_soxx_trend_income", + "reserved_cash_ratio": "0.03", + "min_reserved_cash_usd": "" }, "deployment": { "default_execution_mode": "live", @@ -117,7 +123,7 @@ "reserved_cash": true, "income_layer": true, "option_overlay": true, - "dca": false + "dca": true }, "default_account": { "key": "preview", @@ -127,7 +133,10 @@ "us_equity" ], "cash_currency": "USD", - "default_execution_mode": "live" + "default_execution_mode": "live", + "default_strategy_profile": "soxl_soxx_trend_income", + "reserved_cash_ratio": "0.03", + "min_reserved_cash_usd": "150" }, "deployment": { "default_execution_mode": "live", @@ -164,7 +173,10 @@ "us_equity" ], "cash_currency": "USD", - "default_execution_mode": "live" + "default_execution_mode": "live", + "default_strategy_profile": "ibit_smart_dca", + "reserved_cash_ratio": "0.03", + "min_reserved_cash_usd": "" }, "deployment": { "default_execution_mode": "live", diff --git a/scripts/build_platform_config.py b/scripts/build_platform_config.py new file mode 100644 index 0000000..16e6efa --- /dev/null +++ b/scripts/build_platform_config.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +"""Generate config.js from platform-config.json — single source of truth. + +Reads platform-config.json and produces: + web/strategy-switch-console/config.js + +This file is imported by BOTH index.html (frontend) and worker.js (backend), +replacing the previously hardcoded platformConfig, defaultAccountOptions, +fallbackIncomeLayerDefaults, fallbackOptionOverlayDefaults, and DCA_SUPPORTED_PLATFORMS. +""" + +from __future__ import annotations + +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ROOT / "platform-config.json" +TARGET = ROOT / "web" / "strategy-switch-console" / "config.js" +STRATEGY_TARGET = ROOT / "web" / "strategy-switch-console" / "strategy_profiles_asset.js" +STRATEGY_SOURCE = ROOT / "web" / "strategy-switch-console" / "strategy-profiles.example.json" + + +def build_config_module(config: dict) -> str: + platforms = config["platforms"] + strategies = config["strategies"] + domains = config.get("domains", {}) + + # ── platformConfig (replaces hardcoded in index.html) ── + platform_config = {} + default_accounts = {} + repositories = {} + dcaplat = [] + variable_scopes = {} + + for pid, pdata in platforms.items(): + caps = pdata["capabilities"] + depl = pdata["deployment"] + platform_config[pid] = { + "dry_run_only": depl.get("dry_run_only", False), + "margin_policy": caps.get("margin_policy", False), + "reserved_cash": caps.get("reserved_cash", False), + "income_layer": caps.get("income_layer", False), + "option_overlay": caps.get("option_overlay", False), + "dca": caps.get("dca", False), + "execution_mode": depl.get("default_execution_mode", "live"), + "service_name": depl.get("service_name", ""), + "default_execution_mode": depl.get("default_execution_mode", "live"), + } + if caps.get("dca"): + dcaplat.append(pid) + repositories[pid] = pdata["repository"] + variable_scopes[pid] = pdata.get("variable_scope", "repository") + + # default account options + acct = pdata.get("default_account", {}) + entry = { + "key": acct.get("key", pid), + "label": acct.get("label", pdata.get("label", pid)), + "target_name": acct.get("target_name", acct.get("key", pid)), + "supported_domains": acct.get("supported_domains", pdata.get("supported_domains", [])), + "cash_currency": acct.get("cash_currency", "USD"), + } + for fld in ("default_strategy_profile", "service_name", "account_scope", + "deployment_selector", "account_selector", "default_execution_mode", + "min_reserved_cash_usd", "reserved_cash_ratio", + "cash_only_execution_mode", "dca_mode", "dca_base_investment_usd"): + if fld in acct: + entry[fld] = acct[fld] + if "service_name" not in entry: + entry["service_name"] = depl.get("service_name", "") + if "default_execution_mode" not in entry: + entry["default_execution_mode"] = depl.get("default_execution_mode", "live") + default_accounts[pid] = [entry] + + # ── strategy features ── + income_layer_defaults = {} + option_overlay_defaults = {} + strategy_features = {} + dca_profile_defaults = {} + + for sid, sdata in strategies.items(): + feat = sdata.get("features", {}) + strategy_features[sid] = { + "income_layer": feat.get("income_layer", False), + "option_overlay": feat.get("option_overlay", False), + "dca": feat.get("dca", False), + "combo": feat.get("combo", False), + } + + inc = sdata.get("income_layer_defaults") + if inc: + income_layer_defaults[sid] = { + "startUsd": int(inc.get("start_usd", 0)), + "maxRatio": str(inc.get("max_ratio", "")), + "allocations": inc.get("allocations", {}), + } + + opt = sdata.get("option_overlay_defaults") + if opt: + families = [] + if opt.get("growth_enabled"): + families.append({ + "family": "growth", + "recipe": opt["growth_recipe"], + "startUsd": opt["growth_start_usd"], + "ratio": str(opt.get("nav_budget_ratio", "")), + "ratioKind": "budget", + }) + if opt.get("income_enabled"): + families.append({ + "family": "income", + "recipe": opt["income_recipe"], + "startUsd": opt["income_start_usd"], + "ratio": str(opt.get("nav_risk_ratio", "")), + "ratioKind": "risk", + }) + option_overlay_defaults[sid] = { + "liveGate": opt.get("live_gate", ""), + "liveStatus": opt.get("live_status", ""), + "families": families, + } + + dca = sdata.get("dca_defaults") + if dca: + dca_profile_defaults[sid] = { + "defaultMode": dca.get("default_mode", "fixed"), + "defaultBaseInvestmentUsd": str(dca.get("default_base_investment_usd", "1000")), + } + + # ── domain labels ── + domain_labels = {} + for did, ddata in domains.items(): + domain_labels[did] = { + "zh": ddata.get("label_zh", did), + "en": ddata.get("label_en", did), + } + + # ── reserved cash variable names ── + min_cash_vars = {} + ratio_vars = {} + var_prefixes = { + "longbridge": "LONGBRIDGE", + "ibkr": "IBKR", + "schwab": "SCHWAB", + "firstrade": "FIRSTRADE", + } + for pid, prefix in var_prefixes.items(): + min_cash_vars[pid] = f"{prefix}_MIN_RESERVED_CASH_USD" + ratio_vars[pid] = f"{prefix}_RESERVED_CASH_RATIO" + + # ── Generate JS module ── + lines = [ + "// Generated by scripts/build_platform_config.py; single source of truth.", + f"// Source: platform-config.json", + "", + f"export const PLATFORM_CONFIG = {json.dumps(platform_config, indent=2, ensure_ascii=False)};", + "", + f"export const DEFAULT_ACCOUNT_OPTIONS = {json.dumps(default_accounts, indent=2, ensure_ascii=False)};", + "", + f"export const PLATFORM_REPOSITORIES = {json.dumps(repositories, indent=2, ensure_ascii=False)};", + "", + f"export const DCA_SUPPORTED_PLATFORMS = new Set({json.dumps(dcaplat)});", + "", + f"export const DEFAULT_VARIABLE_SCOPES = {json.dumps(variable_scopes, indent=2, ensure_ascii=False)};", + "", + f"export const DOMAIN_LABELS = {json.dumps(domain_labels, indent=2, ensure_ascii=False)};", + "", + f"export const FALLBACK_INCOME_LAYER_DEFAULTS = {json.dumps(income_layer_defaults, indent=2, ensure_ascii=False)};", + "", + f"export const FALLBACK_OPTION_OVERLAY_DEFAULTS = {json.dumps(option_overlay_defaults, indent=2, ensure_ascii=False)};", + "", + f"export const DCA_PROFILE_DEFAULTS = {json.dumps(dca_profile_defaults, indent=2, ensure_ascii=False)};", + "", + f"export const STRATEGY_FEATURES = {json.dumps(strategy_features, indent=2, ensure_ascii=False)};", + "", + f"export const PLATFORM_MIN_RESERVED_CASH_VARIABLES = {json.dumps(min_cash_vars, indent=2, ensure_ascii=False)};", + "", + f"export const PLATFORM_RESERVED_CASH_RATIO_VARIABLES = {json.dumps(ratio_vars, indent=2, ensure_ascii=False)};", + "", + ] + return "\n".join(lines) + + +def build_strategy_profiles(config: dict) -> str: + """Generate strategy_profiles_asset.js — the strategy catalog.""" + strategies = config["strategies"] + profiles = [] + for sid, sdata in strategies.items(): + feat = sdata.get("features", {}) + entry = { + "profile": sid, + "label": sdata.get("label", sid), + "label_en": sdata.get("label_en", sid), + "label_zh": sdata.get("label", sid), + "domain": sdata.get("domain", ""), + "runtime_enabled": sdata.get("runtime_enabled", False), + "income_layer_enabled": feat.get("income_layer", False), + "option_overlay_enabled": feat.get("option_overlay", False), + "combo_enabled": feat.get("combo", False), + } + if feat.get("combo"): + entry["combo_mode"] = feat.get("combo_mode", "dynamic") + inc = sdata.get("income_layer_defaults") + if inc: + entry["income_layer_start_usd"] = str(inc.get("start_usd", "")) + entry["income_layer_max_ratio"] = str(inc.get("max_ratio", "")) + entry["income_layer_allocations"] = inc.get("allocations", {}) + opt = sdata.get("option_overlay_defaults") + if opt: + entry["option_overlay_live_gate"] = opt.get("live_gate", "") + entry["option_overlay_live_status"] = opt.get("live_status", "") + if opt.get("growth_enabled"): + entry["option_growth_overlay_enabled"] = True + entry["option_growth_overlay_recipe"] = opt["growth_recipe"] + entry["option_growth_overlay_start_usd"] = opt["growth_start_usd"] + entry["option_growth_overlay_nav_budget_ratio"] = str(opt.get("nav_budget_ratio", "")) + dca = sdata.get("dca_defaults") + if dca or feat.get("dca"): + entry["dca_enabled"] = True + entry["dca_default_mode"] = (dca or {}).get("default_mode", "fixed") + entry["dca_default_base_investment_usd"] = str((dca or {}).get("default_base_investment_usd", "1000")) + profiles.append(entry) + + payload = ( + "// Generated by scripts/build_platform_config.py from platform-config.json\n" + f"export const DEFAULT_STRATEGY_PROFILES = {json.dumps(profiles, indent=2, ensure_ascii=False)};\n" + ) + return payload + + +def main() -> int: + config = json.loads(SOURCE.read_text(encoding="utf-8")) + + # Generate config.js + module = build_config_module(config) + TARGET.write_text(module, encoding="utf-8") + + # Generate strategy_profiles_asset.js + profiles = build_strategy_profiles(config) + STRATEGY_TARGET.write_text(profiles, encoding="utf-8") + + print(f"Generated: {TARGET}") + print(f"Generated: {STRATEGY_TARGET}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/web/strategy-switch-console/config.js b/web/strategy-switch-console/config.js new file mode 100644 index 0000000..10fe572 --- /dev/null +++ b/web/strategy-switch-console/config.js @@ -0,0 +1,470 @@ +// Generated by scripts/build_platform_config.py; single source of truth. +// Source: platform-config.json + +export const PLATFORM_CONFIG = { + "longbridge": { + "dry_run_only": false, + "margin_policy": true, + "reserved_cash": true, + "income_layer": true, + "option_overlay": true, + "dca": true, + "execution_mode": "live", + "service_name": "", + "default_execution_mode": "live" + }, + "ibkr": { + "dry_run_only": false, + "margin_policy": true, + "reserved_cash": true, + "income_layer": true, + "option_overlay": true, + "dca": false, + "execution_mode": "live", + "service_name": "", + "default_execution_mode": "live" + }, + "schwab": { + "dry_run_only": false, + "margin_policy": true, + "reserved_cash": true, + "income_layer": true, + "option_overlay": true, + "dca": true, + "execution_mode": "live", + "service_name": "charles-schwab-quant-service", + "default_execution_mode": "live" + }, + "firstrade": { + "dry_run_only": false, + "margin_policy": true, + "reserved_cash": true, + "income_layer": true, + "option_overlay": true, + "dca": true, + "execution_mode": "live", + "service_name": "firstrade-quant-service", + "default_execution_mode": "live" + }, + "qmt": { + "dry_run_only": true, + "margin_policy": false, + "reserved_cash": false, + "income_layer": false, + "option_overlay": false, + "dca": false, + "execution_mode": "paper", + "service_name": "qmt-quant-service", + "default_execution_mode": "paper" + }, + "binance": { + "dry_run_only": false, + "margin_policy": false, + "reserved_cash": false, + "income_layer": false, + "option_overlay": false, + "dca": true, + "execution_mode": "live", + "service_name": "", + "default_execution_mode": "live" + } +}; + +export const DEFAULT_ACCOUNT_OPTIONS = { + "longbridge": [ + { + "key": "preview", + "label": "LongBridge", + "target_name": "preview", + "supported_domains": [ + "us_equity", + "hk_equity" + ], + "cash_currency": "USD", + "default_strategy_profile": "tqqq_growth_income", + "default_execution_mode": "live", + "min_reserved_cash_usd": "", + "reserved_cash_ratio": "0.03", + "service_name": "" + } + ], + "ibkr": [ + { + "key": "preview", + "label": "IBKR", + "target_name": "preview", + "supported_domains": [ + "us_equity", + "hk_equity" + ], + "cash_currency": "USD", + "default_strategy_profile": "soxl_soxx_trend_income", + "default_execution_mode": "live", + "min_reserved_cash_usd": "", + "reserved_cash_ratio": "0.03", + "service_name": "" + } + ], + "schwab": [ + { + "key": "preview", + "label": "Schwab", + "target_name": "preview", + "supported_domains": [ + "us_equity" + ], + "cash_currency": "USD", + "default_strategy_profile": "soxl_soxx_trend_income", + "default_execution_mode": "live", + "min_reserved_cash_usd": "150", + "reserved_cash_ratio": "0.03", + "service_name": "charles-schwab-quant-service" + } + ], + "firstrade": [ + { + "key": "preview", + "label": "Firstrade", + "target_name": "preview", + "supported_domains": [ + "us_equity" + ], + "cash_currency": "USD", + "default_strategy_profile": "ibit_smart_dca", + "default_execution_mode": "live", + "min_reserved_cash_usd": "", + "reserved_cash_ratio": "0.03", + "service_name": "firstrade-quant-service" + } + ], + "qmt": [ + { + "key": "default", + "label": "QMT", + "target_name": "default", + "supported_domains": [ + "cn_equity" + ], + "cash_currency": "CNY", + "default_strategy_profile": "cn_industry_etf_rotation", + "service_name": "qmt-quant-service", + "default_execution_mode": "paper" + } + ], + "binance": [ + { + "key": "default", + "label": "Binance", + "target_name": "default", + "supported_domains": [ + "crypto" + ], + "cash_currency": "USD", + "default_strategy_profile": "crypto_live_pool_rotation", + "service_name": "", + "default_execution_mode": "live" + } + ] +}; + +export const PLATFORM_REPOSITORIES = { + "longbridge": "QuantStrategyLab/LongBridgePlatform", + "ibkr": "QuantStrategyLab/InteractiveBrokersPlatform", + "schwab": "QuantStrategyLab/CharlesSchwabPlatform", + "firstrade": "QuantStrategyLab/FirstradePlatform", + "qmt": "QuantStrategyLab/QmtPlatform", + "binance": "QuantStrategyLab/BinancePlatform" +}; + +export const DCA_SUPPORTED_PLATFORMS = new Set(["longbridge", "schwab", "firstrade", "binance"]); + +export const DEFAULT_VARIABLE_SCOPES = { + "longbridge": "environment", + "ibkr": "repository", + "schwab": "repository", + "firstrade": "repository", + "qmt": "repository", + "binance": "repository" +}; + +export const DOMAIN_LABELS = { + "us_equity": { + "zh": "美股", + "en": "US Equity" + }, + "hk_equity": { + "zh": "港股", + "en": "HK Equity" + }, + "cn_equity": { + "zh": "A股", + "en": "CN A-share" + }, + "crypto": { + "zh": "加密", + "en": "Crypto" + } +}; + +export const FALLBACK_INCOME_LAYER_DEFAULTS = { + "tqqq_growth_income": { + "startUsd": 250000, + "maxRatio": "0.55", + "allocations": { + "SCHD": 0.3, + "DGRO": 0.2, + "SGOV": 0.4, + "SPYI": 0.08, + "QQQI": 0.02 + } + }, + "soxl_soxx_trend_income": { + "startUsd": 150000, + "maxRatio": "0.95", + "allocations": { + "SCHD": 0.15, + "DGRO": 0.1, + "SGOV": 0.7, + "SPYI": 0.04, + "QQQI": 0.01 + } + }, + "global_etf_rotation": { + "startUsd": 500000, + "maxRatio": "0.15", + "allocations": { + "SCHD": 0.4, + "DGRO": 0.25, + "SGOV": 0.3, + "SPYI": 0.05 + } + }, + "russell_top50_leader_rotation": { + "startUsd": 300000, + "maxRatio": "0.25", + "allocations": { + "SCHD": 0.45, + "DGRO": 0.3, + "SGOV": 0.25 + } + }, + "us_equity_combo": { + "startUsd": 300000, + "maxRatio": "0.25", + "allocations": { + "SCHD": 0.25, + "DGRO": 0.25, + "SGOV": 0.2, + "SPYI": 0.15, + "QQQI": 0.15 + } + } +}; + +export const FALLBACK_OPTION_OVERLAY_DEFAULTS = { + "tqqq_growth_income": { + "liveGate": "promotion_required", + "liveStatus": "research_only", + "families": [ + { + "family": "growth", + "recipe": "tqqq_leaps_growth_v1", + "startUsd": "250000", + "ratio": "0.03", + "ratioKind": "budget" + } + ] + }, + "soxl_soxx_trend_income": { + "liveGate": "promotion_required", + "liveStatus": "research_only", + "families": [ + { + "family": "income", + "recipe": "soxx_put_credit_spread_income_v1", + "startUsd": "150000", + "ratio": "0.01", + "ratioKind": "risk" + } + ] + }, + "global_etf_rotation": { + "liveGate": "promotion_required", + "liveStatus": "research_only", + "families": [ + { + "family": "growth", + "recipe": "spy_leaps_growth_v1", + "startUsd": "500000", + "ratio": "0.015", + "ratioKind": "budget" + } + ] + }, + "russell_top50_leader_rotation": { + "liveGate": "promotion_required", + "liveStatus": "research_only", + "families": [ + { + "family": "growth", + "recipe": "spy_leaps_growth_v1", + "startUsd": "300000", + "ratio": "0.015", + "ratioKind": "budget" + } + ] + }, + "us_equity_combo": { + "liveGate": "promotion_required", + "liveStatus": "research_only", + "families": [ + { + "family": "growth", + "recipe": "spy_leaps_growth_v1", + "startUsd": "300000", + "ratio": "0.015", + "ratioKind": "budget" + } + ] + } +}; + +export const DCA_PROFILE_DEFAULTS = { + "nasdaq_sp500_smart_dca": { + "defaultMode": "fixed", + "defaultBaseInvestmentUsd": "1000" + }, + "ibit_smart_dca": { + "defaultMode": "fixed", + "defaultBaseInvestmentUsd": "1000" + }, + "crypto_btc_dca": { + "defaultMode": "fixed", + "defaultBaseInvestmentUsd": "100" + } +}; + +export const STRATEGY_FEATURES = { + "tqqq_growth_income": { + "income_layer": true, + "option_overlay": true, + "dca": false, + "combo": false + }, + "soxl_soxx_trend_income": { + "income_layer": true, + "option_overlay": true, + "dca": false, + "combo": false + }, + "nasdaq_sp500_smart_dca": { + "income_layer": false, + "option_overlay": false, + "dca": true, + "combo": false + }, + "ibit_smart_dca": { + "income_layer": false, + "option_overlay": false, + "dca": true, + "combo": false + }, + "global_etf_rotation": { + "income_layer": true, + "option_overlay": true, + "dca": false, + "combo": false + }, + "russell_top50_leader_rotation": { + "income_layer": true, + "option_overlay": true, + "dca": false, + "combo": false + }, + "us_equity_combo": { + "income_layer": true, + "option_overlay": true, + "dca": false, + "combo": true + }, + "us_equity_combo_leveraged": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": true + }, + "hk_global_etf_tactical_rotation": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": false + }, + "hk_low_vol_dividend_quality_snapshot": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": false + }, + "hk_equity_combo": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": true + }, + "cn_industry_etf_rotation_aggressive": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": false + }, + "cn_stock_momentum_rotation": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": false + }, + "cn_dividend_quality_snapshot": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": false + }, + "cn_equity_combo": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": true + }, + "crypto_btc_dca": { + "income_layer": false, + "option_overlay": false, + "dca": true, + "combo": false + }, + "crypto_trend_rotation": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": false + }, + "crypto_equity_combo": { + "income_layer": false, + "option_overlay": false, + "dca": false, + "combo": true + } +}; + +export const PLATFORM_MIN_RESERVED_CASH_VARIABLES = { + "longbridge": "LONGBRIDGE_MIN_RESERVED_CASH_USD", + "ibkr": "IBKR_MIN_RESERVED_CASH_USD", + "schwab": "SCHWAB_MIN_RESERVED_CASH_USD", + "firstrade": "FIRSTRADE_MIN_RESERVED_CASH_USD" +}; + +export const PLATFORM_RESERVED_CASH_RATIO_VARIABLES = { + "longbridge": "LONGBRIDGE_RESERVED_CASH_RATIO", + "ibkr": "IBKR_RESERVED_CASH_RATIO", + "schwab": "SCHWAB_RESERVED_CASH_RATIO", + "firstrade": "FIRSTRADE_RESERVED_CASH_RATIO" +}; diff --git a/web/strategy-switch-console/strategy_profiles_asset.js b/web/strategy-switch-console/strategy_profiles_asset.js index b5816ea..9196271 100644 --- a/web/strategy-switch-console/strategy_profiles_asset.js +++ b/web/strategy-switch-console/strategy_profiles_asset.js @@ -1,2 +1,283 @@ -// Generated by scripts/sync_strategy_switch_page_asset.py; do not edit by hand. -export const DEFAULT_STRATEGY_PROFILES = [{"profile": "ibit_smart_dca", "label": "IBIT Bitcoin DCA", "label_en": "IBIT Bitcoin DCA", "label_zh": "IBIT比特币定投", "domain": "us_equity", "runtime_enabled": true, "dca_enabled": true, "dca_default_mode": "fixed", "dca_default_base_investment_usd": "1000"}, {"profile": "global_etf_rotation", "label": "Global ETF Rotation", "label_en": "Global ETF Rotation", "label_zh": "全球ETF轮动", "domain": "us_equity", "runtime_enabled": true, "income_layer_enabled": true, "income_layer_start_usd": "500000", "income_layer_max_ratio": "0.15", "income_layer_allocations": {"SCHD": 0.4, "DGRO": 0.25, "SGOV": 0.3, "SPYI": 0.05}, "option_overlay_enabled": true, "option_overlay_live_gate": "promotion_required", "option_overlay_live_status": "research_only", "option_growth_overlay_enabled": true, "option_growth_overlay_recipe": "spy_leaps_growth_v1", "option_growth_overlay_start_usd": "500000", "option_growth_overlay_nav_budget_ratio": 0.015}, {"profile": "soxl_soxx_trend_income", "label": "Semiconductor Trend Income", "label_en": "Semiconductor Trend Income", "label_zh": "半导体趋势收益", "domain": "us_equity", "runtime_enabled": true, "income_layer_enabled": true, "income_layer_start_usd": "150000", "income_layer_max_ratio": "0.95", "income_layer_allocations": {"SCHD": 0.15, "DGRO": 0.1, "SGOV": 0.7, "SPYI": 0.04, "QQQI": 0.01}, "option_overlay_enabled": true, "option_overlay_live_gate": "promotion_required", "option_overlay_live_status": "research_only", "option_income_overlay_enabled": true, "option_income_overlay_recipe": "soxx_put_credit_spread_income_v1", "option_income_overlay_start_usd": "150000", "option_income_overlay_nav_risk_ratio": 0.01}, {"profile": "nasdaq_sp500_smart_dca", "label": "NASDAQ/S&P 500 DCA", "label_en": "NASDAQ/S&P 500 DCA", "label_zh": "纳指标普定投", "domain": "us_equity", "runtime_enabled": true, "dca_enabled": true, "dca_default_mode": "fixed", "dca_default_base_investment_usd": "1000"}, {"profile": "tqqq_growth_income", "label": "NASDAQ Growth Income", "label_en": "NASDAQ Growth Income", "label_zh": "纳斯达克增长收益", "domain": "us_equity", "runtime_enabled": true, "income_layer_enabled": true, "income_layer_start_usd": "250000", "income_layer_max_ratio": "0.55", "income_layer_allocations": {"SCHD": 0.3, "DGRO": 0.2, "SGOV": 0.4, "SPYI": 0.08, "QQQI": 0.02}, "option_overlay_enabled": true, "option_overlay_live_gate": "promotion_required", "option_overlay_live_status": "research_only", "option_growth_overlay_enabled": true, "option_growth_overlay_recipe": "tqqq_leaps_growth_v1", "option_growth_overlay_start_usd": "250000", "option_growth_overlay_nav_budget_ratio": 0.03}, {"profile": "russell_top50_leader_rotation", "label": "Russell Top50 Leaders", "label_en": "Russell Top50 Leaders", "label_zh": "罗素Top50领涨", "domain": "us_equity", "runtime_enabled": true, "income_layer_enabled": true, "income_layer_start_usd": "300000", "income_layer_max_ratio": "0.25", "income_layer_allocations": {"SCHD": 0.45, "DGRO": 0.3, "SGOV": 0.25}, "option_overlay_enabled": true, "option_overlay_live_gate": "promotion_required", "option_overlay_live_status": "research_only", "option_growth_overlay_enabled": true, "option_growth_overlay_recipe": "spy_leaps_growth_v1", "option_growth_overlay_start_usd": "300000", "option_growth_overlay_nav_budget_ratio": 0.015}, {"profile": "us_equity_combo_leveraged", "label": "US Alpha Combo", "label_en": "US Alpha Combo", "label_zh": "美股加速组合", "domain": "us_equity", "runtime_enabled": true, "combo_enabled": true, "combo_mode": "dynamic"}, {"profile": "us_equity_combo", "label": "US Core Combo", "label_en": "US Core Combo", "label_zh": "美股核心组合", "domain": "us_equity", "runtime_enabled": true, "income_layer_enabled": true, "income_layer_start_usd": "300000", "income_layer_max_ratio": "0.25", "income_layer_allocations": {"SCHD": 0.25, "DGRO": 0.25, "SGOV": 0.2, "SPYI": 0.15, "QQQI": 0.15}, "option_overlay_enabled": true, "option_overlay_live_gate": "promotion_required", "option_overlay_live_status": "research_only", "option_growth_overlay_enabled": true, "option_growth_overlay_recipe": "spy_leaps_growth_v1", "option_growth_overlay_start_usd": "300000", "option_growth_overlay_nav_budget_ratio": 0.015, "combo_enabled": true, "combo_mode": "dynamic"}, {"profile": "hk_global_etf_tactical_rotation", "label": "HK ETF Tactical Rotation", "label_en": "HK ETF Tactical Rotation", "label_zh": "港股ETF战术轮动", "domain": "hk_equity", "runtime_enabled": true}, {"profile": "hk_equity_combo", "label": "HK Core Combo", "label_en": "HK Core Combo", "label_zh": "港股恒生组合", "domain": "hk_equity", "runtime_enabled": true, "combo_enabled": true, "combo_mode": "dynamic"}, {"profile": "hk_low_vol_dividend_quality_snapshot", "label": "HK Dividend Quality", "label_en": "HK Dividend Quality", "label_zh": "港股红利质量", "domain": "hk_equity", "runtime_enabled": true}, {"profile": "cn_industry_etf_rotation_aggressive", "label": "CN ETF Rotation", "label_en": "CN ETF Rotation", "label_zh": "A股ETF轮动", "domain": "cn_equity", "runtime_enabled": true}, {"profile": "cn_stock_momentum_rotation", "label": "CN Stock Momentum", "label_en": "CN Stock Momentum", "label_zh": "A股个股动量", "domain": "cn_equity", "runtime_enabled": true}, {"profile": "cn_dividend_quality_snapshot", "label": "CN Dividend Quality", "label_en": "CN Dividend Quality", "label_zh": "A股红利质量", "domain": "cn_equity", "runtime_enabled": true}, {"profile": "cn_equity_combo", "label": "CN Alpha Combo", "label_en": "CN Alpha Combo", "label_zh": "A股进取组合", "domain": "cn_equity", "runtime_enabled": true, "combo_enabled": true, "combo_mode": "dynamic"}, {"profile": "crypto_btc_dca", "label": "BTC DCA", "label_en": "BTC DCA", "label_zh": "BTC定投", "domain": "crypto", "runtime_enabled": true, "dca_enabled": true, "dca_default_mode": "fixed", "dca_default_base_investment_usd": "100"}, {"profile": "crypto_equity_combo", "label": "Crypto Core Combo", "label_en": "Crypto Core Combo", "label_zh": "加密动量组合", "domain": "crypto", "runtime_enabled": true, "combo_enabled": true, "combo_mode": "dynamic"}, {"profile": "crypto_trend_rotation", "label": "Altcoin Trend", "label_en": "Altcoin Trend", "label_zh": "山寨趋势轮动", "domain": "crypto", "runtime_enabled": true}]; +// Generated by scripts/build_platform_config.py from platform-config.json +export const DEFAULT_STRATEGY_PROFILES = [ + { + "profile": "tqqq_growth_income", + "label": "纳斯达克增长收益", + "label_en": "NASDAQ Growth Income", + "label_zh": "纳斯达克增长收益", + "domain": "us_equity", + "runtime_enabled": true, + "income_layer_enabled": true, + "option_overlay_enabled": true, + "combo_enabled": false, + "income_layer_start_usd": "250000", + "income_layer_max_ratio": "0.55", + "income_layer_allocations": { + "SCHD": 0.3, + "DGRO": 0.2, + "SGOV": 0.4, + "SPYI": 0.08, + "QQQI": 0.02 + }, + "option_overlay_live_gate": "promotion_required", + "option_overlay_live_status": "research_only", + "option_growth_overlay_enabled": true, + "option_growth_overlay_recipe": "tqqq_leaps_growth_v1", + "option_growth_overlay_start_usd": "250000", + "option_growth_overlay_nav_budget_ratio": "0.03" + }, + { + "profile": "soxl_soxx_trend_income", + "label": "半导体趋势收益", + "label_en": "Semiconductor Trend Income", + "label_zh": "半导体趋势收益", + "domain": "us_equity", + "runtime_enabled": true, + "income_layer_enabled": true, + "option_overlay_enabled": true, + "combo_enabled": false, + "income_layer_start_usd": "150000", + "income_layer_max_ratio": "0.95", + "income_layer_allocations": { + "SCHD": 0.15, + "DGRO": 0.1, + "SGOV": 0.7, + "SPYI": 0.04, + "QQQI": 0.01 + }, + "option_overlay_live_gate": "promotion_required", + "option_overlay_live_status": "research_only" + }, + { + "profile": "nasdaq_sp500_smart_dca", + "label": "纳指标普定投", + "label_en": "NASDAQ/S&P 500 DCA", + "label_zh": "纳指标普定投", + "domain": "us_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": false, + "dca_enabled": true, + "dca_default_mode": "fixed", + "dca_default_base_investment_usd": "1000" + }, + { + "profile": "ibit_smart_dca", + "label": "IBIT比特币定投", + "label_en": "IBIT Bitcoin DCA", + "label_zh": "IBIT比特币定投", + "domain": "us_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": false, + "dca_enabled": true, + "dca_default_mode": "fixed", + "dca_default_base_investment_usd": "1000" + }, + { + "profile": "global_etf_rotation", + "label": "全球ETF轮动", + "label_en": "Global ETF Rotation", + "label_zh": "全球ETF轮动", + "domain": "us_equity", + "runtime_enabled": true, + "income_layer_enabled": true, + "option_overlay_enabled": true, + "combo_enabled": false, + "income_layer_start_usd": "500000", + "income_layer_max_ratio": "0.15", + "income_layer_allocations": { + "SCHD": 0.4, + "DGRO": 0.25, + "SGOV": 0.3, + "SPYI": 0.05 + }, + "option_overlay_live_gate": "promotion_required", + "option_overlay_live_status": "research_only", + "option_growth_overlay_enabled": true, + "option_growth_overlay_recipe": "spy_leaps_growth_v1", + "option_growth_overlay_start_usd": "500000", + "option_growth_overlay_nav_budget_ratio": "0.015" + }, + { + "profile": "russell_top50_leader_rotation", + "label": "罗素Top50领涨", + "label_en": "Russell Top50 Leaders", + "label_zh": "罗素Top50领涨", + "domain": "us_equity", + "runtime_enabled": true, + "income_layer_enabled": true, + "option_overlay_enabled": true, + "combo_enabled": false, + "income_layer_start_usd": "300000", + "income_layer_max_ratio": "0.25", + "income_layer_allocations": { + "SCHD": 0.45, + "DGRO": 0.3, + "SGOV": 0.25 + }, + "option_overlay_live_gate": "promotion_required", + "option_overlay_live_status": "research_only", + "option_growth_overlay_enabled": true, + "option_growth_overlay_recipe": "spy_leaps_growth_v1", + "option_growth_overlay_start_usd": "300000", + "option_growth_overlay_nav_budget_ratio": "0.015" + }, + { + "profile": "us_equity_combo", + "label": "美股核心组合", + "label_en": "US Core Combo", + "label_zh": "美股核心组合", + "domain": "us_equity", + "runtime_enabled": true, + "income_layer_enabled": true, + "option_overlay_enabled": true, + "combo_enabled": true, + "combo_mode": "dynamic", + "income_layer_start_usd": "300000", + "income_layer_max_ratio": "0.25", + "income_layer_allocations": { + "SCHD": 0.25, + "DGRO": 0.25, + "SGOV": 0.2, + "SPYI": 0.15, + "QQQI": 0.15 + }, + "option_overlay_live_gate": "promotion_required", + "option_overlay_live_status": "research_only", + "option_growth_overlay_enabled": true, + "option_growth_overlay_recipe": "spy_leaps_growth_v1", + "option_growth_overlay_start_usd": "300000", + "option_growth_overlay_nav_budget_ratio": "0.015" + }, + { + "profile": "us_equity_combo_leveraged", + "label": "美股加速组合", + "label_en": "US Alpha Combo", + "label_zh": "美股加速组合", + "domain": "us_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": true, + "combo_mode": "dynamic" + }, + { + "profile": "hk_global_etf_tactical_rotation", + "label": "港股ETF战术轮动", + "label_en": "HK ETF Tactical Rotation", + "label_zh": "港股ETF战术轮动", + "domain": "hk_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": false + }, + { + "profile": "hk_low_vol_dividend_quality_snapshot", + "label": "港股红利质量", + "label_en": "HK Dividend Quality", + "label_zh": "港股红利质量", + "domain": "hk_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": false + }, + { + "profile": "hk_equity_combo", + "label": "港股恒生组合", + "label_en": "HK Core Combo", + "label_zh": "港股恒生组合", + "domain": "hk_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": true, + "combo_mode": "dynamic" + }, + { + "profile": "cn_industry_etf_rotation_aggressive", + "label": "A股ETF轮动", + "label_en": "CN ETF Rotation", + "label_zh": "A股ETF轮动", + "domain": "cn_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": false + }, + { + "profile": "cn_stock_momentum_rotation", + "label": "A股个股动量", + "label_en": "CN Stock Momentum", + "label_zh": "A股个股动量", + "domain": "cn_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": false + }, + { + "profile": "cn_dividend_quality_snapshot", + "label": "A股红利质量", + "label_en": "CN Dividend Quality", + "label_zh": "A股红利质量", + "domain": "cn_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": false + }, + { + "profile": "cn_equity_combo", + "label": "A股进取组合", + "label_en": "CN Alpha Combo", + "label_zh": "A股进取组合", + "domain": "cn_equity", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": true, + "combo_mode": "dynamic" + }, + { + "profile": "crypto_btc_dca", + "label": "BTC定投", + "label_en": "BTC DCA", + "label_zh": "BTC定投", + "domain": "crypto", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": false, + "dca_enabled": true, + "dca_default_mode": "fixed", + "dca_default_base_investment_usd": "100" + }, + { + "profile": "crypto_trend_rotation", + "label": "山寨趋势轮动", + "label_en": "Altcoin Trend", + "label_zh": "山寨趋势轮动", + "domain": "crypto", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": false + }, + { + "profile": "crypto_equity_combo", + "label": "加密动量组合", + "label_en": "Crypto Core Combo", + "label_zh": "加密动量组合", + "domain": "crypto", + "runtime_enabled": true, + "income_layer_enabled": false, + "option_overlay_enabled": false, + "combo_enabled": true, + "combo_mode": "dynamic" + } +]; diff --git a/web/strategy-switch-console/worker.js b/web/strategy-switch-console/worker.js index 942bc43..4ee4a71 100644 --- a/web/strategy-switch-console/worker.js +++ b/web/strategy-switch-console/worker.js @@ -1,6 +1,20 @@ -// deploy: 2026-06-30T01:30Z — force Worker rebuild for page_asset.js sync +// deploy: 2026-06-30 — config driven by platform-config.json import { PAGE_HTML } from "./page_asset.js"; import { DEFAULT_STRATEGY_PROFILES } from "./strategy_profiles_asset.js"; +import { + DCA_SUPPORTED_PLATFORMS, + DEFAULT_VARIABLE_SCOPES, + PLATFORM_REPOSITORIES, + DOMAIN_LABELS, + PLATFORM_MIN_RESERVED_CASH_VARIABLES, + PLATFORM_RESERVED_CASH_RATIO_VARIABLES, + PLATFORM_CONFIG, + DEFAULT_ACCOUNT_OPTIONS, + FALLBACK_INCOME_LAYER_DEFAULTS, + FALLBACK_OPTION_OVERLAY_DEFAULTS, + DCA_PROFILE_DEFAULTS, + STRATEGY_FEATURES, +} from "./config.js"; const DEFAULT_REPOSITORY = "QuantStrategyLab/QuantRuntimeSettings"; const DEFAULT_WORKFLOW = "manual-strategy-switch.yml"; @@ -44,18 +58,6 @@ const DEFAULT_VARIABLE_SCOPE = { qmt: "repository", binance: "repository", }; -const PLATFORM_RESERVED_CASH_RATIO_VARIABLES = { - longbridge: "LONGBRIDGE_RESERVED_CASH_RATIO", - ibkr: "IBKR_RESERVED_CASH_RATIO", - schwab: "SCHWAB_RESERVED_CASH_RATIO", - firstrade: "FIRSTRADE_RESERVED_CASH_RATIO", -}; -const PLATFORM_MIN_RESERVED_CASH_VARIABLES = { - longbridge: "LONGBRIDGE_MIN_RESERVED_CASH_USD", - ibkr: "IBKR_MIN_RESERVED_CASH_USD", - schwab: "SCHWAB_MIN_RESERVED_CASH_USD", - firstrade: "FIRSTRADE_MIN_RESERVED_CASH_USD", -}; const PLATFORM_CASH_ONLY_EXECUTION_VARIABLES = { longbridge: "LONGBRIDGE_CASH_ONLY_EXECUTION", ibkr: "IBKR_CASH_ONLY_EXECUTION", @@ -109,7 +111,6 @@ const DCA_PROFILE_CONFIG = { ibit_smart_dca: { default_mode: "fixed", default_base_investment_usd: "1000" }, crypto_btc_dca: { default_mode: "fixed", default_base_investment_usd: "100" }, }; -const DCA_SUPPORTED_PLATFORMS = new Set(["schwab", "firstrade", "longbridge", "binance"]); const SECURITY_HEADERS = { "Content-Security-Policy": [ "default-src 'self'",