Skip to content

Commit 5b5d5e1

Browse files
committed
Disable no-op strategy switch actions
1 parent b44f302 commit 5b5d5e1

3 files changed

Lines changed: 78 additions & 15 deletions

File tree

tests/strategy_switch_worker_validation.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@ assert.ok(indexHtml.includes('id="min-reserved-cash-input"'));
2323
assert.ok(indexHtml.includes('id="reserved-cash-ratio-input"'));
2424
assert.ok(indexHtml.includes('function selectedCashCurrency('));
2525
assert.ok(indexHtml.includes('function currentReservedCashPolicyText('));
26+
assert.ok(indexHtml.includes('function hasPendingChanges('));
27+
assert.ok(indexHtml.includes('function pendingChangeState('));
2628
assert.ok(indexHtml.includes('reservedCashTouched: false'));
2729
assert.ok(indexHtml.includes('.reserve-ratio-block'));
2830
assert.ok(indexHtml.includes('.summary-row.pending'));
2931
assert.ok(indexHtml.includes('function currentEntryHasState('));
32+
assert.ok(indexHtml.includes('changes.reserveCashChanged'));
33+
assert.ok(indexHtml.includes('!hasPendingChange'));
34+
assert.ok(indexHtml.includes('noChangesNote'));
3035
assert.equal(indexHtml.includes('placeholder="150"'), false);
3136
assert.equal(indexHtml.includes('placeholder="0.03"'), false);
3237
assert.equal(indexHtml.includes("ibkr-primary"), false);

web/strategy-switch-console/index.html

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -884,11 +884,13 @@ <h2 data-i18n="summary">切换摘要</h2>
884884
loadingConfig: "读取配置中",
885885
configureAccounts: "配置账号后切换",
886886
runSwitch: "一键切换",
887+
noChanges: "无变更",
887888
readonlyNote: "登录后才可执行切换。",
888889
publicReadonly: "登录后查看账号配置。",
889890
loadingConfigNote: "正在读取账号配置和当前状态。",
890891
missingConfigNote: "账号配置未加载,暂时不能执行。",
891892
readyNote: "点击后会触发 workflow,并同步目标平台服务。",
893+
noChangesNote: "当前选择与已读取配置一致。",
892894
invalidStrategyNote: "当前账号没有可执行策略,暂时不能切换。",
893895
noAccount: "没有账号选项",
894896
noStrategy: "没有支持的策略",
@@ -897,6 +899,7 @@ <h2 data-i18n="summary">切换摘要</h2>
897899
selectedMarket: "市场",
898900
reservedCashPolicy: "当前预留现金",
899901
pendingReservedCashPolicy: "待提交预留现金",
902+
pendingMode: "待提交模式",
900903
unchanged: "不变",
901904
copied: "已复制状态",
902905
dispatching: "正在触发 workflow...",
@@ -939,11 +942,13 @@ <h2 data-i18n="summary">切换摘要</h2>
939942
loadingConfig: "Loading config",
940943
configureAccounts: "Configure accounts",
941944
runSwitch: "Switch now",
945+
noChanges: "No changes",
942946
readonlyNote: "Sign in to switch.",
943947
publicReadonly: "Sign in to view account config.",
944948
loadingConfigNote: "Reading account config and current state.",
945949
missingConfigNote: "Account config is not loaded, so switching is disabled.",
946950
readyNote: "This dispatches the workflow and syncs the target platform service.",
951+
noChangesNote: "The current selection matches the readable config.",
947952
invalidStrategyNote: "This account has no runnable strategy, so switching is disabled.",
948953
noAccount: "No accounts",
949954
noStrategy: "No supported strategies",
@@ -952,6 +957,7 @@ <h2 data-i18n="summary">切换摘要</h2>
952957
selectedMarket: "Market",
953958
reservedCashPolicy: "Current reserved cash",
954959
pendingReservedCashPolicy: "Pending reserved cash",
960+
pendingMode: "Pending mode",
955961
unchanged: "Unchanged",
956962
copied: "State copied",
957963
dispatching: "Dispatching workflow...",
@@ -1304,8 +1310,10 @@ <h2 data-i18n="summary">切换摘要</h2>
13041310
]) {
13051311
if (account[field]) inputs[field] = account[field];
13061312
}
1307-
if (form.reservedCashRatio) inputs.reserved_cash_ratio = form.reservedCashRatio;
1308-
if (form.minReservedCashUsd) inputs.min_reserved_cash_usd = form.minReservedCashUsd;
1313+
if (form.reservedCashTouched) {
1314+
if (form.reservedCashRatio) inputs.reserved_cash_ratio = form.reservedCashRatio;
1315+
if (form.minReservedCashUsd) inputs.min_reserved_cash_usd = form.minReservedCashUsd;
1316+
}
13091317
return inputs;
13101318
}
13111319

@@ -1333,7 +1341,46 @@ <h2 data-i18n="summary">切换摘要</h2>
13331341
}
13341342

13351343
function pendingReservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform)) {
1336-
return reservedCashPolicyText(inputs, platform, account, t("unchanged"));
1344+
return reservedCashPolicyText(pendingReservePolicy(inputs, platform, account).inputs, platform, account, t("unchanged"));
1345+
}
1346+
1347+
function pendingReservePolicy(inputs, platform = state.selected, account = selectedAccount(platform)) {
1348+
const current = currentReservePolicyForAccount(platform, account);
1349+
const floorOverride = cleanDisplayNumber(inputs.min_reserved_cash_usd);
1350+
const ratioOverride = cleanDisplayRatio(inputs.reserved_cash_ratio);
1351+
const changed = Boolean(
1352+
(floorOverride && floorOverride !== current.minReservedCashUsd) ||
1353+
(ratioOverride && ratioOverride !== current.reservedCashRatio),
1354+
);
1355+
return {
1356+
changed,
1357+
inputs: {
1358+
min_reserved_cash_usd: floorOverride || current.minReservedCashUsd,
1359+
reserved_cash_ratio: ratioOverride || current.reservedCashRatio,
1360+
},
1361+
};
1362+
}
1363+
1364+
function pendingChangeState(inputs, platform = state.selected, account = selectedAccount(platform)) {
1365+
const currentProfile = currentStrategyForAccount(platform, account);
1366+
const nextProfile = cleanStrategyProfile(inputs.strategy_profile);
1367+
const currentEntry = currentEntryForAccount(platform, account);
1368+
const currentMode = normalizeExecutionMode(currentEntry?.execution_mode, currentEntry?.dry_run_only);
1369+
const reserve = pendingReservePolicy(inputs, platform, account);
1370+
return {
1371+
currentProfile,
1372+
nextProfile,
1373+
currentMode,
1374+
strategyChanged: Boolean(nextProfile && (!currentProfile || currentProfile !== nextProfile)),
1375+
modeChanged: Boolean(currentMode && inputs.execution_mode && currentMode !== inputs.execution_mode),
1376+
reserveCashChanged: reserve.changed,
1377+
reserve,
1378+
};
1379+
}
1380+
1381+
function hasPendingChanges(inputs, platform = state.selected, account = selectedAccount(platform)) {
1382+
const changes = pendingChangeState(inputs, platform, account);
1383+
return Boolean(changes.strategyChanged || changes.modeChanged || changes.reserveCashChanged);
13371384
}
13381385

13391386
function formatRatioPercent(value) {
@@ -1344,20 +1391,23 @@ <h2 data-i18n="summary">切换摘要</h2>
13441391

13451392
function summaryRows(inputs) {
13461393
const account = selectedAccount();
1347-
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;
1394+
const changes = pendingChangeState(inputs, state.selected, account);
1395+
const currentStrategyText = changes.currentProfile ? strategyLabel(changes.currentProfile) : t("notRead");
13511396
const rows = [
13521397
[t("repository"), state.repositories[state.selected] || defaultRepositories[state.selected]],
13531398
[t("selectedAccount"), account.label],
13541399
[t("currentStrategy"), currentStrategyText],
13551400
[t("selectedMarket"), supportedDomainLabel(state.selected, account)],
13561401
[t("reservedCashPolicy"), currentReservedCashPolicyText(state.selected, account)],
1357-
[t("pendingReservedCashPolicy"), pendingReservedCashPolicyText(inputs, state.selected, account), "pending"],
13581402
];
1359-
if (nextStrategyChanged && nextProfile) {
1360-
rows.push([t("nextStrategy"), strategyLabel(nextProfile), "pending"]);
1403+
if (changes.reserveCashChanged) {
1404+
rows.push([t("pendingReservedCashPolicy"), pendingReservedCashPolicyText(inputs, state.selected, account), "pending"]);
1405+
}
1406+
if (changes.modeChanged) {
1407+
rows.push([t("pendingMode"), modeLabel(inputs.execution_mode), "pending"]);
1408+
}
1409+
if (changes.strategyChanged && changes.nextProfile) {
1410+
rows.push([t("nextStrategy"), strategyLabel(changes.nextProfile), "pending"]);
13611411
}
13621412
return rows;
13631413
}
@@ -1519,17 +1569,25 @@ <h2 data-i18n="summary">切换摘要</h2>
15191569
const hasPrivateAccounts = state.configSource === "private";
15201570
const loadingConfig = state.configSource === "loading";
15211571
const hasValidStrategy = hasValidStrategySelection();
1522-
dispatch.disabled = !state.auth.allowed || loadingConfig || !hasPrivateAccounts || !hasValidStrategy;
1572+
const hasPendingChange = hasPrivateAccounts && hasValidStrategy && hasPendingChanges(buildInputs());
1573+
dispatch.disabled = !state.auth.allowed || loadingConfig || !hasPrivateAccounts || !hasValidStrategy || !hasPendingChange;
15231574
dispatch.textContent = state.auth.allowed
1524-
? (loadingConfig ? t("loadingConfig") : (hasPrivateAccounts ? t("runSwitch") : t("configureAccounts")))
1575+
? (loadingConfig
1576+
? t("loadingConfig")
1577+
: (hasPrivateAccounts ? (hasValidStrategy ? (hasPendingChange ? t("runSwitch") : t("noChanges")) : t("configureAccounts")) : t("configureAccounts")))
15251578
: t("loginToRun");
15261579
const note = el("action-note");
15271580
note.textContent = state.auth.allowed
15281581
? (loadingConfig
15291582
? t("loadingConfigNote")
1530-
: (hasPrivateAccounts ? (hasValidStrategy ? t("readyNote") : t("invalidStrategyNote")) : t("missingConfigNote")))
1583+
: (hasPrivateAccounts
1584+
? (hasValidStrategy ? (hasPendingChange ? t("readyNote") : t("noChangesNote")) : t("invalidStrategyNote"))
1585+
: t("missingConfigNote")))
15311586
: t("readonlyNote");
1532-
note.classList.toggle("warning", state.auth.allowed && !loadingConfig && (!hasPrivateAccounts || !hasValidStrategy));
1587+
note.classList.toggle(
1588+
"warning",
1589+
state.auth.allowed && !loadingConfig && (!hasPrivateAccounts || !hasValidStrategy || !hasPendingChange),
1590+
);
15331591
}
15341592

15351593
function renderAppVisibility() {

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.

0 commit comments

Comments
 (0)