Skip to content

Commit 39a13af

Browse files
committed
Clarify pending reserved cash summary
1 parent b71060b commit 39a13af

4 files changed

Lines changed: 111 additions & 22 deletions

File tree

tests/strategy_switch_worker_validation.mjs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ assert.ok(indexHtml.includes('function selectedCashCurrency('));
2525
assert.ok(indexHtml.includes('function currentReservedCashPolicyText('));
2626
assert.ok(indexHtml.includes('reservedCashTouched: false'));
2727
assert.ok(indexHtml.includes('.reserve-ratio-block'));
28+
assert.ok(indexHtml.includes('.summary-row.pending'));
29+
assert.ok(indexHtml.includes('function currentEntryHasState('));
30+
assert.equal(indexHtml.includes('placeholder="150"'), false);
31+
assert.equal(indexHtml.includes('placeholder="0.03"'), false);
2832
assert.equal(indexHtml.includes("ibkr-primary"), false);
2933
assert.equal(indexHtml.includes("longbridge-quant-sg-service"), false);
3034
assert.equal(indexHtml.includes('account_selector: "SG"'), false);
@@ -339,6 +343,38 @@ try {
339343
globalThis.fetch = originalFetch;
340344
}
341345

346+
globalThis.fetch = async (url) => {
347+
const requestUrl = String(url);
348+
if (requestUrl.endsWith("/CLOUD_RUN_SERVICE_TARGETS_JSON")) {
349+
return new Response("", { status: 404 });
350+
}
351+
if (requestUrl.endsWith("/LONGBRIDGE_MIN_RESERVED_CASH_USD")) {
352+
return new Response(JSON.stringify({ value: "150" }), {
353+
status: 200,
354+
headers: { "Content-Type": "application/json" },
355+
});
356+
}
357+
if (requestUrl.endsWith("/LONGBRIDGE_RESERVED_CASH_RATIO")) {
358+
return new Response(JSON.stringify({ value: "0.03" }), {
359+
status: 200,
360+
headers: { "Content-Type": "application/json" },
361+
});
362+
}
363+
return new Response("", { status: 404 });
364+
};
365+
try {
366+
const currentStrategies = await __test.loadCurrentStrategies(
367+
{ longbridge: [accountOptions.longbridge[0]] },
368+
{ RUNTIME_SETTINGS_DISPATCH_TOKEN: "test-token" },
369+
);
370+
assert.equal(currentStrategies.longbridge.hk.strategy_profile, undefined);
371+
assert.equal(currentStrategies.longbridge.hk.min_reserved_cash_usd, "150");
372+
assert.equal(currentStrategies.longbridge.hk.reserved_cash_ratio, "0.03");
373+
assert.equal(currentStrategies.longbridge.hk.source, "RESERVED_CASH_VARIABLES");
374+
} finally {
375+
globalThis.fetch = originalFetch;
376+
}
377+
342378
const longbridgeHk = __test.assertConfiguredAccount(
343379
{
344380
platform: "longbridge",

web/strategy-switch-console/index.html

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,13 @@
571571
border-bottom: 0;
572572
}
573573

574+
.summary-row.pending {
575+
margin: 0 -16px;
576+
padding-inline: 13px 16px;
577+
border-left: 3px solid var(--platform-color, var(--accent));
578+
background: color-mix(in srgb, var(--platform-color, var(--accent)) 6%, #ffffff);
579+
}
580+
574581
.summary-row dt {
575582
color: var(--muted);
576583
font-size: 12px;
@@ -588,6 +595,11 @@
588595
overflow-wrap: anywhere;
589596
}
590597

598+
.summary-row.pending dd {
599+
color: var(--platform-color, var(--accent));
600+
font-weight: 800;
601+
}
602+
591603
.summary-actions {
592604
display: flex;
593605
gap: 10px;
@@ -764,13 +776,13 @@ <h2 id="platform-title">LongBridge</h2>
764776

765777
<label class="control-block min-reserve-block">
766778
<span id="min-reserved-cash-label">最小预留现金</span>
767-
<input id="min-reserved-cash-input" type="number" inputmode="decimal" min="0" step="1" placeholder="150">
779+
<input id="min-reserved-cash-input" type="number" inputmode="decimal" min="0" step="1">
768780
<span class="selection-meta" data-i18n="reservedCashMeta">留空则沿用当前平台默认值。</span>
769781
</label>
770782

771783
<label class="control-block reserve-ratio-block">
772784
<span data-i18n="reservedCashRatio">预留现金比例</span>
773-
<input id="reserved-cash-ratio-input" type="number" inputmode="decimal" min="0" max="1" step="0.01" placeholder="0.03">
785+
<input id="reserved-cash-ratio-input" type="number" inputmode="decimal" min="0" max="1" step="0.01">
774786
<span class="selection-meta" data-i18n="reservedCashRatioMeta">例如 0.03 表示 3%。</span>
775787
</label>
776788
</div>
@@ -864,9 +876,9 @@ <h2 data-i18n="summary">切换摘要</h2>
864876
paper: "Dry run",
865877
minReservedCash: "最小预留现金 ({currency})",
866878
reservedCashRatio: "预留现金比例",
867-
reservedCashMeta: "留空则沿用当前平台默认值。",
879+
reservedCashMeta: "例如 150;留空则不覆盖当前值。",
868880
reservedCashRatioMeta: "例如 0.03 表示 3%。",
869-
summary: "当前配置状态",
881+
summary: "当前 / 待提交",
870882
copySummary: "复制状态",
871883
loginToRun: "登录后切换",
872884
loadingConfig: "读取配置中",
@@ -883,7 +895,8 @@ <h2 data-i18n="summary">切换摘要</h2>
883895
repository: "平台仓库",
884896
selectedAccount: "账号",
885897
selectedMarket: "市场",
886-
reservedCashPolicy: "预留现金",
898+
reservedCashPolicy: "当前预留现金",
899+
pendingReservedCashPolicy: "待提交预留现金",
887900
unchanged: "不变",
888901
copied: "已复制状态",
889902
dispatching: "正在触发 workflow...",
@@ -894,7 +907,7 @@ <h2 data-i18n="summary">切换摘要</h2>
894907
usEquity: "美股",
895908
hkEquity: "港股",
896909
currentStrategy: "当前策略",
897-
nextStrategy: "选择策略",
910+
nextStrategy: "切换策略",
898911
notRead: "未读取",
899912
},
900913
en: {
@@ -918,9 +931,9 @@ <h2 data-i18n="summary">切换摘要</h2>
918931
paper: "Dry run",
919932
minReservedCash: "Minimum reserved cash ({currency})",
920933
reservedCashRatio: "Reserved cash ratio",
921-
reservedCashMeta: "Leave blank to keep the platform default.",
934+
reservedCashMeta: "Example: 150. Leave blank to avoid overriding the current value.",
922935
reservedCashRatioMeta: "Use 0.03 for 3%.",
923-
summary: "Current Config",
936+
summary: "Current / Pending",
924937
copySummary: "Copy state",
925938
loginToRun: "Sign in to switch",
926939
loadingConfig: "Loading config",
@@ -937,7 +950,8 @@ <h2 data-i18n="summary">切换摘要</h2>
937950
repository: "Repository",
938951
selectedAccount: "Account",
939952
selectedMarket: "Market",
940-
reservedCashPolicy: "Reserved cash",
953+
reservedCashPolicy: "Current reserved cash",
954+
pendingReservedCashPolicy: "Pending reserved cash",
941955
unchanged: "Unchanged",
942956
copied: "State copied",
943957
dispatching: "Dispatching workflow...",
@@ -948,7 +962,7 @@ <h2 data-i18n="summary">切换摘要</h2>
948962
usEquity: "US equity",
949963
hkEquity: "HK equity",
950964
currentStrategy: "Current strategy",
951-
nextStrategy: "Selected strategy",
965+
nextStrategy: "Switch strategy",
952966
notRead: "Not read",
953967
},
954968
};
@@ -1113,12 +1127,21 @@ <h2 data-i18n="summary">切换摘要</h2>
11131127
.map((value) => String(value));
11141128
for (const key of keys) {
11151129
const entry = byPlatform[key];
1116-
const profile = cleanStrategyProfile(entry?.strategy_profile);
1117-
if (profile) return entry;
1130+
if (currentEntryHasState(entry)) return entry;
11181131
}
11191132
return null;
11201133
}
11211134

1135+
function currentEntryHasState(entry) {
1136+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false;
1137+
return Boolean(
1138+
cleanStrategyProfile(entry.strategy_profile) ||
1139+
cleanDisplayNumber(entry.min_reserved_cash_usd ?? entry.reserved_cash_floor_usd) ||
1140+
cleanDisplayRatio(entry.reserved_cash_ratio) ||
1141+
normalizeExecutionMode(entry.execution_mode, entry.dry_run_only),
1142+
);
1143+
}
1144+
11221145
function currentStrategyForAccount(platform, account) {
11231146
const entry = currentEntryForAccount(platform, account);
11241147
const profile = cleanStrategyProfile(entry?.strategy_profile);
@@ -1309,6 +1332,10 @@ <h2 data-i18n="summary">切换摘要</h2>
13091332
);
13101333
}
13111334

1335+
function pendingReservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform)) {
1336+
return reservedCashPolicyText(inputs, platform, account, t("unchanged"));
1337+
}
1338+
13121339
function formatRatioPercent(value) {
13131340
const numeric = Number(value);
13141341
if (!Number.isFinite(numeric)) return String(value);
@@ -1318,15 +1345,19 @@ <h2 data-i18n="summary">切换摘要</h2>
13181345
function summaryRows(inputs) {
13191346
const account = selectedAccount();
13201347
const currentProfile = currentStrategyForAccount(state.selected, account);
1348+
const nextProfile = cleanStrategyProfile(inputs.strategy_profile);
1349+
const currentStrategyText = currentProfile ? strategyLabel(currentProfile) : t("notRead");
1350+
const nextStrategyChanged = !currentProfile || currentProfile !== nextProfile;
13211351
const rows = [
13221352
[t("repository"), state.repositories[state.selected] || defaultRepositories[state.selected]],
13231353
[t("selectedAccount"), account.label],
1354+
[t("currentStrategy"), currentStrategyText],
13241355
[t("selectedMarket"), supportedDomainLabel(state.selected, account)],
13251356
[t("reservedCashPolicy"), currentReservedCashPolicyText(state.selected, account)],
1326-
[t("nextStrategy"), strategyLabel(inputs.strategy_profile)],
1357+
[t("pendingReservedCashPolicy"), pendingReservedCashPolicyText(inputs, state.selected, account), "pending"],
13271358
];
1328-
if (state.auth.allowed) {
1329-
rows.splice(2, 0, [t("currentStrategy"), currentProfile ? strategyLabel(currentProfile) : t("notRead")]);
1359+
if (nextStrategyChanged && nextProfile) {
1360+
rows.push([t("nextStrategy"), strategyLabel(nextProfile), "pending"]);
13301361
}
13311362
return rows;
13321363
}
@@ -1452,9 +1483,10 @@ <h2 data-i18n="summary">切换摘要</h2>
14521483
const list = el("summary-list");
14531484
list.replaceChildren();
14541485
document.querySelector(".summary-head h2").textContent = t("summary");
1455-
for (const [label, value] of summaryRows(inputs)) {
1486+
for (const [label, value, rowClass] of summaryRows(inputs)) {
14561487
const row = document.createElement("div");
14571488
row.className = "summary-row";
1489+
if (rowClass) row.classList.add(rowClass);
14581490
const labelNode = document.createElement("dt");
14591491
labelNode.textContent = label;
14601492
const valueNode = document.createElement("dd");
@@ -1630,13 +1662,16 @@ <h2 data-i18n="summary">切换摘要</h2>
16301662
normalized[platform] = {};
16311663
for (const [key, entry] of Object.entries(raw[platform])) {
16321664
const profile = cleanStrategyProfile(entry?.strategy_profile);
1633-
if (!profile) continue;
1665+
const minReservedCashUsd = cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd);
1666+
const reservedCashRatio = cleanDisplayRatio(entry?.reserved_cash_ratio);
1667+
const executionMode = normalizeExecutionMode(entry?.execution_mode, entry?.dry_run_only);
1668+
if (!profile && !minReservedCashUsd && !reservedCashRatio && !executionMode) continue;
16341669
normalized[platform][String(key)] = {
16351670
strategy_profile: profile,
1636-
execution_mode: normalizeExecutionMode(entry?.execution_mode, entry?.dry_run_only),
1671+
execution_mode: executionMode,
16371672
dry_run_only: entry?.dry_run_only === true || entry?.dry_run_only === "true" || entry?.dry_run_only === "1",
1638-
min_reserved_cash_usd: cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd),
1639-
reserved_cash_ratio: cleanDisplayRatio(entry?.reserved_cash_ratio),
1673+
min_reserved_cash_usd: minReservedCashUsd,
1674+
reserved_cash_ratio: reservedCashRatio,
16401675
source: entry?.source ? String(entry.source) : "",
16411676
};
16421677
}

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: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,11 +559,20 @@ async function resolveCurrentStrategyForAccount({ platform, option, optionsCount
559559
const serviceTargetsValue = await readVariable(repository, "repository", "", "CLOUD_RUN_SERVICE_TARGETS_JSON");
560560
const serviceTarget = runtimeTargetFromServiceTargets(serviceTargetsValue, platform, option);
561561
const serviceTargetProfile = cleanCurrentStrategy(serviceTarget?.strategy_profile);
562+
const serviceTargetReservedCashPayload = reservedCashPayloadFromObject(platform, serviceTarget);
562563
if (serviceTargetProfile) {
563564
return {
564565
strategy_profile: serviceTargetProfile,
565566
...runtimeModePayload(serviceTarget),
566-
...reservedCashPayloadFromObject(platform, serviceTarget),
567+
...serviceTargetReservedCashPayload,
568+
source: "CLOUD_RUN_SERVICE_TARGETS_JSON",
569+
variable_scope: "repository",
570+
};
571+
}
572+
if (Object.keys(serviceTargetReservedCashPayload).length) {
573+
return {
574+
...runtimeModePayload(serviceTarget),
575+
...serviceTargetReservedCashPayload,
567576
source: "CLOUD_RUN_SERVICE_TARGETS_JSON",
568577
variable_scope: "repository",
569578
};
@@ -611,6 +620,15 @@ async function resolveCurrentStrategyForAccount({ platform, option, optionsCount
611620
}
612621
}
613622

623+
if (Object.keys(reservedCashPayload).length) {
624+
return {
625+
...reservedCashPayload,
626+
source: "RESERVED_CASH_VARIABLES",
627+
variable_scope: variableScope,
628+
github_environment: githubEnvironment || "",
629+
};
630+
}
631+
614632
return null;
615633
}
616634

0 commit comments

Comments
 (0)