Skip to content

Commit a91298c

Browse files
authored
Clarify reserved cash currency labels (#41)
1 parent f32f2ab commit a91298c

7 files changed

Lines changed: 51 additions & 8 deletions

File tree

tests/strategy_switch_worker_validation.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ assert.ok(indexHtml.includes("function hasPrivateConfig()"));
2121
assert.ok(indexHtml.includes('el("quick-form").hidden = !showPrivateControls'));
2222
assert.ok(indexHtml.includes('id="min-reserved-cash-input"'));
2323
assert.ok(indexHtml.includes('id="reserved-cash-ratio-input"'));
24+
assert.ok(indexHtml.includes('function selectedCashCurrency('));
2425
assert.equal(indexHtml.includes("ibkr-primary"), false);
2526
assert.equal(indexHtml.includes("longbridge-quant-sg-service"), false);
2627
assert.equal(indexHtml.includes('account_selector: "SG"'), false);
@@ -135,6 +136,7 @@ const accountOptions = __test.normalizeAccountOptionsPayload(
135136
target_name: "hk",
136137
account_selector: "HK",
137138
default_strategy_profile: "hk_low_vol_dividend_quality_snapshot",
139+
cash_currency: "HKD",
138140
},
139141
{
140142
key: "sg",
@@ -178,6 +180,7 @@ const accountOptions = __test.normalizeAccountOptionsPayload(
178180
assert.deepEqual(accountOptions.longbridge[0].supported_domains, ["us_equity", "hk_equity"]);
179181
assert.deepEqual(accountOptions.longbridge[1].supported_domains, ["us_equity", "hk_equity"]);
180182
assert.deepEqual(accountOptions.ibkr[0].supported_domains, ["us_equity", "hk_equity"]);
183+
assert.equal(accountOptions.longbridge[0].cash_currency, "HKD");
181184

182185
const normalizedReservedCashInputs = __test.normalizeSwitchInputs({
183186
platform: "ibkr",

web/strategy-switch-console/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Each account item supports:
106106
"deployment_selector": "demo-ibkr-tqqq",
107107
"account_scope": "demo-ibkr-tqqq",
108108
"service_name": "interactive-brokers-demo-ibkr-tqqq-service",
109+
"cash_currency": "USD",
109110
"default_strategy_profile": "tqqq_growth_income",
110111
"supported_domains": ["us_equity", "hk_equity"]
111112
}
@@ -117,7 +118,7 @@ The Worker validates dispatch inputs against this config, including whether the
117118

118119
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`.
119120

120-
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`.
121+
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`.
121122

122123
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.
123124

web/strategy-switch-console/README.zh-CN.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ wrangler secret put STRATEGY_SWITCH_ACCOUNT_OPTIONS_JSON < /tmp/strategy-switch-
113113
"deployment_selector": "demo-ibkr-tqqq",
114114
"account_scope": "demo-ibkr-tqqq",
115115
"service_name": "interactive-brokers-demo-ibkr-tqqq-service",
116+
"cash_currency": "USD",
116117
"default_strategy_profile": "tqqq_growth_income",
117118
"supported_domains": ["us_equity", "hk_equity"]
118119
}
@@ -124,7 +125,7 @@ Worker 会校验 dispatch 参数必须匹配这里的某个账号项,也会校
124125

125126
登录用户访问 `/api/config` 时,Worker 还会读取目标平台仓库的当前 GitHub Variables。读取优先级是账号匹配的 `CLOUD_RUN_SERVICE_TARGETS_JSON`、匹配的 `RUNTIME_TARGET_JSON.strategy_profile``STRATEGY_PROFILE`;都读不到时,页面才回退到 `default_strategy_profile`
126127

127-
切换表单也支持可选的预留现金覆盖项:最小预留现金 USD 和预留现金比例。留空表示沿用平台现有默认值。填写后,Worker 会把它们传给 `manual-strategy-switch.yml`,由 workflow 写入平台对应变量,例如 `IBKR_MIN_RESERVED_CASH_USD``IBKR_RESERVED_CASH_RATIO`
128+
切换表单也支持可选的预留现金覆盖项:所选账号币种下的最小预留现金和预留现金比例。如果账号现金币种固定,可以在账号配置里把 `cash_currency` 设为 `USD``HKD`;否则页面会按所选策略推断,港股策略显示 HKD,美股策略显示 USD。预留现金字段留空表示沿用平台现有默认值。填写后,Worker 会把它们传给 `manual-strategy-switch.yml`,由 workflow 写入平台对应变量,例如 `IBKR_MIN_RESERVED_CASH_USD``IBKR_RESERVED_CASH_RATIO`
128129

129130
策略切换成功后也会把当前账号的 `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 保持一致。
130131

web/strategy-switch-console/account-options.example.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"label": "hk",
66
"target_name": "hk",
77
"account_selector": "HK",
8+
"cash_currency": "HKD",
89
"default_strategy_profile": "hk_low_vol_dividend_quality_snapshot",
910
"supported_domains": ["us_equity", "hk_equity"]
1011
},
@@ -34,6 +35,7 @@
3435
"deployment_selector": "demo-ibkr-tqqq",
3536
"account_scope": "demo-ibkr-tqqq",
3637
"service_name": "interactive-brokers-demo-ibkr-tqqq-service",
38+
"cash_currency": "USD",
3739
"default_strategy_profile": "tqqq_growth_income",
3840
"supported_domains": ["us_equity", "hk_equity"]
3941
},
@@ -45,6 +47,7 @@
4547
"deployment_selector": "demo-ibkr-soxl",
4648
"account_scope": "demo-ibkr-soxl",
4749
"service_name": "interactive-brokers-demo-ibkr-soxl-service",
50+
"cash_currency": "USD",
4851
"default_strategy_profile": "soxl_soxx_trend_income",
4952
"supported_domains": ["us_equity", "hk_equity"]
5053
},
@@ -56,6 +59,7 @@
5659
"deployment_selector": "demo-ibkr-dca",
5760
"account_scope": "demo-ibkr-dca",
5861
"service_name": "interactive-brokers-demo-ibkr-dca-service",
62+
"cash_currency": "USD",
5963
"default_strategy_profile": "nasdaq_sp500_smart_dca",
6064
"supported_domains": ["us_equity", "hk_equity"]
6165
}
@@ -65,6 +69,7 @@
6569
"key": "default",
6670
"label": "default",
6771
"target_name": "default",
72+
"cash_currency": "USD",
6873
"default_strategy_profile": "soxl_soxx_trend_income",
6974
"supported_domains": ["us_equity"]
7075
}
@@ -74,6 +79,7 @@
7479
"key": "default",
7580
"label": "default",
7681
"target_name": "default",
82+
"cash_currency": "USD",
7783
"default_strategy_profile": "mega_cap_leader_rotation_top50_balanced",
7884
"supported_domains": ["us_equity"]
7985
}

web/strategy-switch-console/index.html

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ <h2 id="platform-title">LongBridge</h2>
745745
</div>
746746

747747
<label class="control-block">
748-
<span data-i18n="minReservedCash">最小预留现金</span>
748+
<span id="min-reserved-cash-label">最小预留现金</span>
749749
<input id="min-reserved-cash-input" type="number" inputmode="decimal" min="0" step="1" placeholder="150">
750750
<span class="selection-meta" data-i18n="reservedCashMeta">留空则沿用当前平台默认值。</span>
751751
</label>
@@ -844,7 +844,7 @@ <h2 data-i18n="summary">切换摘要</h2>
844844
mode: "模式",
845845
live: "实盘",
846846
paper: "Dry run",
847-
minReservedCash: "最小预留现金",
847+
minReservedCash: "最小预留现金 ({currency})",
848848
reservedCashRatio: "预留现金比例",
849849
reservedCashMeta: "留空则沿用当前平台默认值。",
850850
reservedCashRatioMeta: "例如 0.03 表示 3%。",
@@ -898,7 +898,7 @@ <h2 data-i18n="summary">切换摘要</h2>
898898
mode: "Mode",
899899
live: "Live",
900900
paper: "Dry run",
901-
minReservedCash: "Minimum reserved cash",
901+
minReservedCash: "Minimum reserved cash ({currency})",
902902
reservedCashRatio: "Reserved cash ratio",
903903
reservedCashMeta: "Leave blank to keep the platform default.",
904904
reservedCashRatioMeta: "Use 0.03 for 3%.",
@@ -994,6 +994,20 @@ <h2 data-i18n="summary">切换摘要</h2>
994994
return t("usEquity");
995995
}
996996

997+
function strategyDomain(profile) {
998+
return strategyCatalog[profile]?.domain || "";
999+
}
1000+
1001+
function selectedCashCurrency(platform = state.selected, account = selectedAccount(platform)) {
1002+
const configured = String(account?.cash_currency || "").trim().toUpperCase();
1003+
if (configured === "USD" || configured === "HKD") return configured;
1004+
const domain = strategyDomain(state.forms[platform]?.strategy);
1005+
if (domain === "hk_equity") return "HKD";
1006+
const supported = supportedDomainsForAccount(platform, account);
1007+
if (supported.length === 1 && supported[0] === "hk_equity") return "HKD";
1008+
return "USD";
1009+
}
1010+
9971011
function applyStrategyProfiles(rawProfiles) {
9981012
const profiles = Array.isArray(rawProfiles) && rawProfiles.length
9991013
? rawProfiles
@@ -1215,9 +1229,10 @@ <h2 data-i18n="summary">切换摘要</h2>
12151229
function reservedCashPolicyText(inputs) {
12161230
const floor = inputs.min_reserved_cash_usd;
12171231
const ratio = inputs.reserved_cash_ratio;
1232+
const currency = selectedCashCurrency();
12181233
if (!floor && !ratio) return t("unchanged");
1219-
if (floor && ratio) return `max($${floor}, ${formatRatioPercent(ratio)})`;
1220-
if (floor) return `$${floor}`;
1234+
if (floor && ratio) return `max(${floor} ${currency}, ${formatRatioPercent(ratio)})`;
1235+
if (floor) return `${floor} ${currency}`;
12211236
return formatRatioPercent(ratio);
12221237
}
12231238

@@ -1340,6 +1355,10 @@ <h2 data-i18n="summary">切换摘要</h2>
13401355
el("strategy-meta").textContent = account
13411356
? t("strategyMeta").replace("{domains}", supportedDomainLabel(platform, account))
13421357
: "";
1358+
el("min-reserved-cash-label").textContent = t("minReservedCash").replace(
1359+
"{currency}",
1360+
selectedCashCurrency(platform, account),
1361+
);
13431362
minReservedCashInput.value = form.minReservedCashUsd;
13441363
reservedCashRatioInput.value = form.reservedCashRatio;
13451364

@@ -1507,6 +1526,9 @@ <h2 data-i18n="summary">切换摘要</h2>
15071526
deployment_selector: item.deployment_selector ? String(item.deployment_selector) : "",
15081527
account_scope: item.account_scope ? String(item.account_scope) : "",
15091528
service_name: item.service_name ? String(item.service_name) : "",
1529+
cash_currency: item.cash_currency || item.market_currency || item.trading_currency
1530+
? String(item.cash_currency || item.market_currency || item.trading_currency).trim().toUpperCase()
1531+
: "",
15101532
supported_domains: normalizeSupportedDomains(platform, item),
15111533
default_strategy_profile: item.default_strategy_profile || item.strategy_profile
15121534
? String(item.default_strategy_profile || item.strategy_profile)

web/strategy-switch-console/page_asset.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/strategy-switch-console/worker.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,12 @@ function cleanAccountOption(item, platform, index) {
931931
addConfigOptional(option, "deployment_selector", item.deployment_selector, cleanSlug);
932932
addConfigOptional(option, "account_scope", item.account_scope, cleanSlug);
933933
addConfigOptional(option, "service_name", item.service_name, cleanSlug);
934+
addConfigOptional(
935+
option,
936+
"cash_currency",
937+
item.cash_currency || item.market_currency || item.trading_currency,
938+
cleanCashCurrency,
939+
);
934940
addConfigOptional(option, "default_strategy_profile", item.default_strategy_profile || item.strategy_profile, cleanSlug);
935941
addConfigOptional(option, "github_environment", item.github_environment, cleanSlug);
936942
addConfigOptional(option, "variable_scope", item.variable_scope, (value, field) =>
@@ -1018,6 +1024,10 @@ function cleanStrategyDomain(value, fieldName) {
10181024
return cleanChoice(value, SUPPORTED_STRATEGY_DOMAINS, fieldName);
10191025
}
10201026

1027+
function cleanCashCurrency(value, fieldName) {
1028+
return cleanChoice(String(value || "").trim().toUpperCase(), ["USD", "HKD"], fieldName);
1029+
}
1030+
10211031
function addConfigOptional(target, key, value, cleaner) {
10221032
if (value === undefined || value === null || String(value).trim() === "") return;
10231033
target[key] = cleaner(value, key);

0 commit comments

Comments
 (0)