From 3d0679cf808dd38cd047f1a2eda4910602c1893e Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Wed, 10 Jun 2026 02:40:46 +0800 Subject: [PATCH] Clarify reserved cash currency labels --- tests/strategy_switch_worker_validation.mjs | 3 ++ web/strategy-switch-console/README.md | 3 +- web/strategy-switch-console/README.zh-CN.md | 3 +- .../account-options.example.json | 6 ++++ web/strategy-switch-console/index.html | 32 ++++++++++++++++--- web/strategy-switch-console/page_asset.js | 2 +- web/strategy-switch-console/worker.js | 10 ++++++ 7 files changed, 51 insertions(+), 8 deletions(-) diff --git a/tests/strategy_switch_worker_validation.mjs b/tests/strategy_switch_worker_validation.mjs index 64a5748..fababe2 100644 --- a/tests/strategy_switch_worker_validation.mjs +++ b/tests/strategy_switch_worker_validation.mjs @@ -21,6 +21,7 @@ assert.ok(indexHtml.includes("function hasPrivateConfig()")); assert.ok(indexHtml.includes('el("quick-form").hidden = !showPrivateControls')); assert.ok(indexHtml.includes('id="min-reserved-cash-input"')); assert.ok(indexHtml.includes('id="reserved-cash-ratio-input"')); +assert.ok(indexHtml.includes('function selectedCashCurrency(')); assert.equal(indexHtml.includes("ibkr-primary"), false); assert.equal(indexHtml.includes("longbridge-quant-sg-service"), false); assert.equal(indexHtml.includes('account_selector: "SG"'), false); @@ -135,6 +136,7 @@ const accountOptions = __test.normalizeAccountOptionsPayload( target_name: "hk", account_selector: "HK", default_strategy_profile: "hk_low_vol_dividend_quality_snapshot", + cash_currency: "HKD", }, { key: "sg", @@ -178,6 +180,7 @@ const accountOptions = __test.normalizeAccountOptionsPayload( assert.deepEqual(accountOptions.longbridge[0].supported_domains, ["us_equity", "hk_equity"]); assert.deepEqual(accountOptions.longbridge[1].supported_domains, ["us_equity", "hk_equity"]); assert.deepEqual(accountOptions.ibkr[0].supported_domains, ["us_equity", "hk_equity"]); +assert.equal(accountOptions.longbridge[0].cash_currency, "HKD"); const normalizedReservedCashInputs = __test.normalizeSwitchInputs({ platform: "ibkr", diff --git a/web/strategy-switch-console/README.md b/web/strategy-switch-console/README.md index 56bb2bb..be4778e 100644 --- a/web/strategy-switch-console/README.md +++ b/web/strategy-switch-console/README.md @@ -106,6 +106,7 @@ Each account item supports: "deployment_selector": "demo-ibkr-tqqq", "account_scope": "demo-ibkr-tqqq", "service_name": "interactive-brokers-demo-ibkr-tqqq-service", + "cash_currency": "USD", "default_strategy_profile": "tqqq_growth_income", "supported_domains": ["us_equity", "hk_equity"] } @@ -117,7 +118,7 @@ The Worker validates dispatch inputs against this config, including whether the For signed-in users, `/api/config` also reads the target repositories' current GitHub Variables. It prefers account-specific `CLOUD_RUN_SERVICE_TARGETS_JSON`, then matching `RUNTIME_TARGET_JSON.strategy_profile`, then `STRATEGY_PROFILE`; if none can be read safely, the page falls back to `default_strategy_profile`. -The switch form also accepts optional reserved-cash overrides: minimum reserved cash in USD and reserved-cash ratio. Leave them blank to keep the platform's existing defaults. When set, the Worker passes them to `manual-strategy-switch.yml`, which writes the platform-specific variables such as `IBKR_MIN_RESERVED_CASH_USD` and `IBKR_RESERVED_CASH_RATIO`. +The switch form also accepts optional reserved-cash overrides: minimum reserved cash in the selected account currency and reserved-cash ratio. Set `cash_currency` to `USD` or `HKD` in account config when the account has a fixed cash currency; otherwise the page infers HKD for HK-equity strategy selections and USD for US-equity selections. Leave reserved-cash fields blank to keep the platform's existing defaults. When set, the Worker passes them to `manual-strategy-switch.yml`, which writes the platform-specific variables such as `IBKR_MIN_RESERVED_CASH_USD` and `IBKR_RESERVED_CASH_RATIO`. Successful strategy switches also sync the selected account's `default_strategy_profile` back to the KV `account_options` key. The web endpoint does this immediately after dispatching the workflow, and the manual GitHub workflow calls the Worker's internal sync endpoint after applying platform variables when the `runtime-strategy-switch` environment variable `STRATEGY_SWITCH_CONSOLE_URL` is set. For that workflow callback, set the GitHub environment secret `STRATEGY_SWITCH_SYNC_TOKEN` to the same value as the Worker secret with that name. diff --git a/web/strategy-switch-console/README.zh-CN.md b/web/strategy-switch-console/README.zh-CN.md index 1c3fab3..daf84ff 100644 --- a/web/strategy-switch-console/README.zh-CN.md +++ b/web/strategy-switch-console/README.zh-CN.md @@ -113,6 +113,7 @@ wrangler secret put STRATEGY_SWITCH_ACCOUNT_OPTIONS_JSON < /tmp/strategy-switch- "deployment_selector": "demo-ibkr-tqqq", "account_scope": "demo-ibkr-tqqq", "service_name": "interactive-brokers-demo-ibkr-tqqq-service", + "cash_currency": "USD", "default_strategy_profile": "tqqq_growth_income", "supported_domains": ["us_equity", "hk_equity"] } @@ -124,7 +125,7 @@ Worker 会校验 dispatch 参数必须匹配这里的某个账号项,也会校 登录用户访问 `/api/config` 时,Worker 还会读取目标平台仓库的当前 GitHub Variables。读取优先级是账号匹配的 `CLOUD_RUN_SERVICE_TARGETS_JSON`、匹配的 `RUNTIME_TARGET_JSON.strategy_profile`、`STRATEGY_PROFILE`;都读不到时,页面才回退到 `default_strategy_profile`。 -切换表单也支持可选的预留现金覆盖项:最小预留现金 USD 和预留现金比例。留空表示沿用平台现有默认值。填写后,Worker 会把它们传给 `manual-strategy-switch.yml`,由 workflow 写入平台对应变量,例如 `IBKR_MIN_RESERVED_CASH_USD` 和 `IBKR_RESERVED_CASH_RATIO`。 +切换表单也支持可选的预留现金覆盖项:所选账号币种下的最小预留现金和预留现金比例。如果账号现金币种固定,可以在账号配置里把 `cash_currency` 设为 `USD` 或 `HKD`;否则页面会按所选策略推断,港股策略显示 HKD,美股策略显示 USD。预留现金字段留空表示沿用平台现有默认值。填写后,Worker 会把它们传给 `manual-strategy-switch.yml`,由 workflow 写入平台对应变量,例如 `IBKR_MIN_RESERVED_CASH_USD` 和 `IBKR_RESERVED_CASH_RATIO`。 策略切换成功后也会把当前账号的 `default_strategy_profile` 同步回 KV 的 `account_options` key。网页接口会在触发 workflow 成功后立即同步;如果 `runtime-strategy-switch` 环境变量里配置了 `STRATEGY_SWITCH_CONSOLE_URL`,手动 GitHub workflow 在写入平台变量后也会回调 Worker 内部接口同步。这个 workflow 回调需要 GitHub 环境 secret `STRATEGY_SWITCH_SYNC_TOKEN`,值要和 Worker 里同名 secret 保持一致。 diff --git a/web/strategy-switch-console/account-options.example.json b/web/strategy-switch-console/account-options.example.json index f46f53c..e8629f6 100644 --- a/web/strategy-switch-console/account-options.example.json +++ b/web/strategy-switch-console/account-options.example.json @@ -5,6 +5,7 @@ "label": "hk", "target_name": "hk", "account_selector": "HK", + "cash_currency": "HKD", "default_strategy_profile": "hk_low_vol_dividend_quality_snapshot", "supported_domains": ["us_equity", "hk_equity"] }, @@ -34,6 +35,7 @@ "deployment_selector": "demo-ibkr-tqqq", "account_scope": "demo-ibkr-tqqq", "service_name": "interactive-brokers-demo-ibkr-tqqq-service", + "cash_currency": "USD", "default_strategy_profile": "tqqq_growth_income", "supported_domains": ["us_equity", "hk_equity"] }, @@ -45,6 +47,7 @@ "deployment_selector": "demo-ibkr-soxl", "account_scope": "demo-ibkr-soxl", "service_name": "interactive-brokers-demo-ibkr-soxl-service", + "cash_currency": "USD", "default_strategy_profile": "soxl_soxx_trend_income", "supported_domains": ["us_equity", "hk_equity"] }, @@ -56,6 +59,7 @@ "deployment_selector": "demo-ibkr-dca", "account_scope": "demo-ibkr-dca", "service_name": "interactive-brokers-demo-ibkr-dca-service", + "cash_currency": "USD", "default_strategy_profile": "nasdaq_sp500_smart_dca", "supported_domains": ["us_equity", "hk_equity"] } @@ -65,6 +69,7 @@ "key": "default", "label": "default", "target_name": "default", + "cash_currency": "USD", "default_strategy_profile": "soxl_soxx_trend_income", "supported_domains": ["us_equity"] } @@ -74,6 +79,7 @@ "key": "default", "label": "default", "target_name": "default", + "cash_currency": "USD", "default_strategy_profile": "mega_cap_leader_rotation_top50_balanced", "supported_domains": ["us_equity"] } diff --git a/web/strategy-switch-console/index.html b/web/strategy-switch-console/index.html index b5e73b8..4f6ceff 100644 --- a/web/strategy-switch-console/index.html +++ b/web/strategy-switch-console/index.html @@ -745,7 +745,7 @@

LongBridge

@@ -844,7 +844,7 @@

切换摘要

mode: "模式", live: "实盘", paper: "Dry run", - minReservedCash: "最小预留现金", + minReservedCash: "最小预留现金 ({currency})", reservedCashRatio: "预留现金比例", reservedCashMeta: "留空则沿用当前平台默认值。", reservedCashRatioMeta: "例如 0.03 表示 3%。", @@ -898,7 +898,7 @@

切换摘要

mode: "Mode", live: "Live", paper: "Dry run", - minReservedCash: "Minimum reserved cash", + minReservedCash: "Minimum reserved cash ({currency})", reservedCashRatio: "Reserved cash ratio", reservedCashMeta: "Leave blank to keep the platform default.", reservedCashRatioMeta: "Use 0.03 for 3%.", @@ -994,6 +994,20 @@

切换摘要

return t("usEquity"); } + function strategyDomain(profile) { + return strategyCatalog[profile]?.domain || ""; + } + + function selectedCashCurrency(platform = state.selected, account = selectedAccount(platform)) { + const configured = String(account?.cash_currency || "").trim().toUpperCase(); + if (configured === "USD" || configured === "HKD") return configured; + const domain = strategyDomain(state.forms[platform]?.strategy); + if (domain === "hk_equity") return "HKD"; + const supported = supportedDomainsForAccount(platform, account); + if (supported.length === 1 && supported[0] === "hk_equity") return "HKD"; + return "USD"; + } + function applyStrategyProfiles(rawProfiles) { const profiles = Array.isArray(rawProfiles) && rawProfiles.length ? rawProfiles @@ -1215,9 +1229,10 @@

切换摘要

function reservedCashPolicyText(inputs) { const floor = inputs.min_reserved_cash_usd; const ratio = inputs.reserved_cash_ratio; + const currency = selectedCashCurrency(); if (!floor && !ratio) return t("unchanged"); - if (floor && ratio) return `max($${floor}, ${formatRatioPercent(ratio)})`; - if (floor) return `$${floor}`; + if (floor && ratio) return `max(${floor} ${currency}, ${formatRatioPercent(ratio)})`; + if (floor) return `${floor} ${currency}`; return formatRatioPercent(ratio); } @@ -1340,6 +1355,10 @@

切换摘要

el("strategy-meta").textContent = account ? t("strategyMeta").replace("{domains}", supportedDomainLabel(platform, account)) : ""; + el("min-reserved-cash-label").textContent = t("minReservedCash").replace( + "{currency}", + selectedCashCurrency(platform, account), + ); minReservedCashInput.value = form.minReservedCashUsd; reservedCashRatioInput.value = form.reservedCashRatio; @@ -1507,6 +1526,9 @@

切换摘要

deployment_selector: item.deployment_selector ? String(item.deployment_selector) : "", account_scope: item.account_scope ? String(item.account_scope) : "", service_name: item.service_name ? String(item.service_name) : "", + cash_currency: item.cash_currency || item.market_currency || item.trading_currency + ? String(item.cash_currency || item.market_currency || item.trading_currency).trim().toUpperCase() + : "", supported_domains: normalizeSupportedDomains(platform, item), default_strategy_profile: item.default_strategy_profile || item.strategy_profile ? String(item.default_strategy_profile || item.strategy_profile) diff --git a/web/strategy-switch-console/page_asset.js b/web/strategy-switch-console/page_asset.js index a0123c7..4d0bae4 100644 --- a/web/strategy-switch-console/page_asset.js +++ b/web/strategy-switch-console/page_asset.js @@ -1,2 +1,2 @@ // Generated by scripts/sync_strategy_switch_page_asset.py; do not edit by hand. -export const PAGE_HTML = "\n\n\n \n \n \n QuantRuntimeSettings Strategy Switch\n \n\n\n
\n
\n

策略切换

\n

选平台、目标账号和策略,一次执行完成切换。

\n
\n
\n \n \n \n \n
\n
\n\n
\n
\n 初始化控制台\n

读取策略配置

\n

正在读取登录状态、账号配置和当前状态。

\n
\n
\n
\n\n
\n \n\n
\n
\n
\n 当前平台\n

LongBridge

\n
\n\n \n\n
\n \n\n \n\n
\n 模式\n
\n \n \n
\n
\n\n \n\n \n
\n\n
\n \n

登录后才可执行切换。

\n

\n
\n
\n\n \n
\n
\n\n \n\n\n"; +export const PAGE_HTML = "\n\n\n \n \n \n QuantRuntimeSettings Strategy Switch\n \n\n\n
\n
\n

策略切换

\n

选平台、目标账号和策略,一次执行完成切换。

\n
\n
\n \n \n \n \n
\n
\n\n
\n
\n 初始化控制台\n

读取策略配置

\n

正在读取登录状态、账号配置和当前状态。

\n
\n
\n
\n\n
\n \n\n
\n
\n
\n 当前平台\n

LongBridge

\n
\n\n \n\n
\n \n\n \n\n
\n 模式\n
\n \n \n
\n
\n\n \n\n \n
\n\n
\n \n

登录后才可执行切换。

\n

\n
\n
\n\n \n
\n
\n\n \n\n\n"; diff --git a/web/strategy-switch-console/worker.js b/web/strategy-switch-console/worker.js index 61d6960..e363d4d 100644 --- a/web/strategy-switch-console/worker.js +++ b/web/strategy-switch-console/worker.js @@ -931,6 +931,12 @@ function cleanAccountOption(item, platform, index) { addConfigOptional(option, "deployment_selector", item.deployment_selector, cleanSlug); addConfigOptional(option, "account_scope", item.account_scope, cleanSlug); addConfigOptional(option, "service_name", item.service_name, cleanSlug); + addConfigOptional( + option, + "cash_currency", + item.cash_currency || item.market_currency || item.trading_currency, + cleanCashCurrency, + ); addConfigOptional(option, "default_strategy_profile", item.default_strategy_profile || item.strategy_profile, cleanSlug); addConfigOptional(option, "github_environment", item.github_environment, cleanSlug); addConfigOptional(option, "variable_scope", item.variable_scope, (value, field) => @@ -1018,6 +1024,10 @@ function cleanStrategyDomain(value, fieldName) { return cleanChoice(value, SUPPORTED_STRATEGY_DOMAINS, fieldName); } +function cleanCashCurrency(value, fieldName) { + return cleanChoice(String(value || "").trim().toUpperCase(), ["USD", "HKD"], fieldName); +} + function addConfigOptional(target, key, value, cleaner) { if (value === undefined || value === null || String(value).trim() === "") return; target[key] = cleaner(value, key);