Skip to content

Commit 00fbd53

Browse files
authored
feat: 融资切换时保留/恢复预留现金配置 (#114)
feat: 融资切换时保留/恢复预留现金配置
2 parents 8881d36 + 8419de4 commit 00fbd53

3 files changed

Lines changed: 163 additions & 10 deletions

File tree

tests/test_cash_financing.js

Lines changed: 143 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,38 @@ function executionCashPolicyConflict(form) {
8383
return allowMarginExplicitlySelected(form) && reserveCashOverrideActive(form);
8484
}
8585

86-
// --- 修复后的 reconcileExecutionCashPolicy ---
86+
// --- 修复后的 reconcileExecutionCashPolicy (save/restore) ---
8787
function reconcileExecutionCashPolicy(form, changed) {
8888
if (!form) return;
8989
if (changed === "margin" && allowMarginExplicitlySelected(form)) {
90+
// Save current reserve state so it can be restored if margin is later disabled
91+
if (form.reservePolicyMode !== "none" && (form.minReservedCashUsd || form.reservedCashRatio)) {
92+
form._prevReserve = {
93+
mode: form.reservePolicyMode,
94+
floor: form.minReservedCashUsd,
95+
ratio: form.reservedCashRatio,
96+
};
97+
}
9098
form.reservePolicyMode = "none";
9199
form.reservedCashTouched = true;
92-
form.minReservedCashUsd = "";
93-
form.reservedCashRatio = "";
100+
// Keep minReservedCashUsd and reservedCashRatio — will be restored if margin toggled back
94101
} else if (changed === "reserve" && reserveCashOverrideActive(form)) {
95102
form.cashOnlyExecutionMode = "enabled";
96103
form.cashOnlyExecutionTouched = true;
97104
}
98105
}
99106

107+
// --- cash-only change handler restore logic (extracted for testing) ---
108+
function restoreReserveAfterMarginDisabled(form) {
109+
if (!allowMarginExplicitlySelected(form) && form._prevReserve) {
110+
form.reservePolicyMode = form._prevReserve.mode;
111+
form.minReservedCashUsd = form._prevReserve.floor;
112+
form.reservedCashRatio = form._prevReserve.ratio;
113+
form.reservedCashTouched = true;
114+
delete form._prevReserve;
115+
}
116+
}
117+
100118
// --- 数据辅助函数 ---
101119
function cleanOptionalBoolean(value) {
102120
if (value === true || value === "true" || value === "1" || value === 1) return true;
@@ -289,9 +307,12 @@ console.log("\n=== 3. reconcileExecutionCashPolicy (修复后) ===\n");
289307
};
290308
reconcileExecutionCashPolicy(form, "margin");
291309
assert(form.reservePolicyMode === "none", "3a: margin=yes → reservePolicyMode='none'");
292-
assert(form.minReservedCashUsd === "", "3a: margin=yes → minReservedCashUsd cleared");
293-
assert(form.reservedCashRatio === "", "3a: margin=yes → reservedCashRatio cleared");
310+
assert(form.minReservedCashUsd === "10000", "3a: margin=yes → minReservedCashUsd PRESERVED");
311+
assert(form.reservedCashRatio === "0.05", "3a: margin=yes → reservedCashRatio PRESERVED");
294312
assert(form.reservedCashTouched === true, "3a: margin=yes → reservedCashTouched=true");
313+
assert(form._prevReserve.mode === "current", "3a: margin=yes → _prevReserve saved");
314+
assert(form._prevReserve.floor === "10000", "3a: margin=yes → _prevReserve.floor saved");
315+
assert(form._prevReserve.ratio === "0.05", "3a: margin=yes → _prevReserve.ratio saved");
295316
}
296317

297318
// 3b: 选择"允许融资: 是",当前 reserve=current,无值
@@ -305,7 +326,8 @@ console.log("\n=== 3. reconcileExecutionCashPolicy (修复后) ===\n");
305326
};
306327
reconcileExecutionCashPolicy(form, "margin");
307328
assert(form.reservePolicyMode === "none", "3b: margin=yes (no values) → still 'none'");
308-
assert(form.minReservedCashUsd === "", "3b: margin=yes (no values) → floor cleared");
329+
assert(form.minReservedCashUsd === "", "3b: margin=yes (no values) → floor empty");
330+
assert(form._prevReserve === undefined, "3b: no values → _prevReserve NOT saved");
309331
}
310332

311333
// 3c: 选择"允许融资: 否" → 不影响预留现金
@@ -669,7 +691,7 @@ console.log("\n=== 8. 回归测试:旧行为不应出现 ===\n");
669691
// 新代码:直接执行
670692
reconcileExecutionCashPolicy(form, "margin");
671693
assert(form.reservePolicyMode === "none", "8a: new code → reserve cleared regardless");
672-
assert(form.minReservedCashUsd === "", "8a: new code → values cleared");
694+
assert(form.minReservedCashUsd === "10000", "8a: new code → values preserved for restore");
673695
}
674696

675697
// 8b: 旧 bug — 初始加载 margin=enabled 时 reserve 仍显示值
@@ -979,5 +1001,119 @@ console.log("\n=== 12. 互斥 UI 不再禁用选项 ===\n");
9791001
assert(executionCashPolicyConflict(form) === false, "12b: no conflict after reconciliation");
9801002
}
9811003

1004+
// ============================================================
1005+
// 13. 融资切换 save/restore 预留现金配置
1006+
// ============================================================
1007+
1008+
console.log("\n=== 13. Save/Restore 预留现金配置 ===\n");
1009+
1010+
// 13a: 用户开融资 → 值保留 → 关融资 → 值恢复
1011+
{
1012+
const form = {
1013+
cashOnlyExecutionMode: "enabled", // 当前:不允许融资
1014+
reservePolicyMode: "ratio",
1015+
minReservedCashUsd: "",
1016+
reservedCashRatio: "0.05",
1017+
reservedCashTouched: true,
1018+
cashOnlyExecutionTouched: false,
1019+
};
1020+
// 用户选"允许融资: 是"
1021+
form.cashOnlyExecutionMode = "disabled";
1022+
form.cashOnlyExecutionTouched = true;
1023+
reconcileExecutionCashPolicy(form, "margin");
1024+
assert(form.reservePolicyMode === "none", "13a-step1: reserve cleared to 'none'");
1025+
assert(form.reservedCashRatio === "0.05", "13a-step1: ratio value preserved");
1026+
assert(form._prevReserve.mode === "ratio", "13a-step1: prev mode saved");
1027+
assert(form._prevReserve.ratio === "0.05", "13a-step1: prev ratio saved");
1028+
1029+
// 用户选"允许融资: 否" → 恢复
1030+
form.cashOnlyExecutionMode = "enabled";
1031+
restoreReserveAfterMarginDisabled(form);
1032+
assert(form.reservePolicyMode === "ratio", "13a-step2: reserve restored to 'ratio'");
1033+
assert(form.reservedCashRatio === "0.05", "13a-step2: ratio restored");
1034+
assert(form._prevReserve === undefined, "13a-step2: _prevReserve cleaned up");
1035+
}
1036+
1037+
// 13b: 用户开融资 → 关融资 → 手动改 reserve → 再开再关不复原旧值
1038+
{
1039+
const form = {
1040+
cashOnlyExecutionMode: "enabled",
1041+
reservePolicyMode: "max",
1042+
minReservedCashUsd: "10000",
1043+
reservedCashRatio: "0.03",
1044+
reservedCashTouched: true,
1045+
cashOnlyExecutionTouched: false,
1046+
};
1047+
// 开融资
1048+
form.cashOnlyExecutionMode = "disabled";
1049+
reconcileExecutionCashPolicy(form, "margin");
1050+
assert(form.reservePolicyMode === "none", "13b-step1: cleared");
1051+
assert(form._prevReserve.mode === "max", "13b-step1: prev 'max' saved");
1052+
1053+
// 关融资 → 恢复
1054+
form.cashOnlyExecutionMode = "enabled";
1055+
restoreReserveAfterMarginDisabled(form);
1056+
assert(form.reservePolicyMode === "max", "13b-step2: restored to 'max'");
1057+
1058+
// 用户手动改为 floor
1059+
form.reservePolicyMode = "floor";
1060+
form.minReservedCashUsd = "5000";
1061+
form.reservedCashRatio = "";
1062+
delete form._prevReserve;
1063+
1064+
// 再开融资
1065+
form.cashOnlyExecutionMode = "disabled";
1066+
reconcileExecutionCashPolicy(form, "margin");
1067+
assert(form._prevReserve.mode === "floor", "13b-step3: prev 'floor' saved");
1068+
assert(form._prevReserve.floor === "5000", "13b-step3: prev floor saved");
1069+
1070+
// 再关融资 → 恢复 floor
1071+
form.cashOnlyExecutionMode = "enabled";
1072+
restoreReserveAfterMarginDisabled(form);
1073+
assert(form.reservePolicyMode === "floor", "13b-step4: restored to 'floor'");
1074+
assert(form.minReservedCashUsd === "5000", "13b-step4: floor restored");
1075+
}
1076+
1077+
// 13c: reserve=none 时开融资不应保存 _prevReserve
1078+
{
1079+
const form = {
1080+
cashOnlyExecutionMode: "enabled",
1081+
reservePolicyMode: "none",
1082+
minReservedCashUsd: "",
1083+
reservedCashRatio: "",
1084+
reservedCashTouched: false,
1085+
};
1086+
form.cashOnlyExecutionMode = "disabled";
1087+
reconcileExecutionCashPolicy(form, "margin");
1088+
assert(form._prevReserve === undefined, "13c: reserve=none → no _prevReserve saved");
1089+
}
1090+
1091+
// 13d: margin 切到 "enabled" 但无 _prevReserve → 不动
1092+
{
1093+
const form = {
1094+
cashOnlyExecutionMode: "disabled",
1095+
reservePolicyMode: "none",
1096+
minReservedCashUsd: "",
1097+
reservedCashRatio: "",
1098+
_prevReserve: undefined,
1099+
};
1100+
form.cashOnlyExecutionMode = "enabled";
1101+
restoreReserveAfterMarginDisabled(form);
1102+
assert(form.reservePolicyMode === "none", "13d: no _prevReserve → stays 'none'");
1103+
}
1104+
1105+
// 13e: account switch 清除 _prevReserve
1106+
{
1107+
const form = {
1108+
cashOnlyExecutionMode: "disabled",
1109+
reservePolicyMode: "none",
1110+
minReservedCashUsd: "10000",
1111+
reservedCashRatio: "0.05",
1112+
_prevReserve: { mode: "max", floor: "10000", ratio: "0.05" },
1113+
};
1114+
delete form._prevReserve; // simulate account switch
1115+
assert(form._prevReserve === undefined, "13e: account switch clears _prevReserve");
1116+
}
1117+
9821118
// ============================================================
9831119
summary();

web/strategy-switch-console/index.html

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2455,10 +2455,17 @@ <h2 data-i18n="summary">切换摘要</h2>
24552455
function reconcileExecutionCashPolicy(form, changed) {
24562456
if (!form) return;
24572457
if (changed === "margin" && allowMarginExplicitlySelected(form)) {
2458+
// Save current reserve state so it can be restored if margin is later disabled
2459+
if (form.reservePolicyMode !== "none" && (form.minReservedCashUsd || form.reservedCashRatio)) {
2460+
form._prevReserve = {
2461+
mode: form.reservePolicyMode,
2462+
floor: form.minReservedCashUsd,
2463+
ratio: form.reservedCashRatio,
2464+
};
2465+
}
24582466
form.reservePolicyMode = "none";
24592467
form.reservedCashTouched = true;
2460-
form.minReservedCashUsd = "";
2461-
form.reservedCashRatio = "";
2468+
// Keep minReservedCashUsd and reservedCashRatio — they'll be restored if margin is toggled back
24622469
} else if (changed === "reserve" && reserveCashOverrideActive(form)) {
24632470
form.cashOnlyExecutionMode = "enabled";
24642471
form.cashOnlyExecutionTouched = true;
@@ -3244,6 +3251,7 @@ <h2 data-i18n="summary">切换摘要</h2>
32443251
state.forms[platform].cashOnlyExecutionTouched = false;
32453252
state.forms[platform].dcaTouched = false;
32463253
state.forms[platform].ibitZscoreExitTouched = false;
3254+
delete state.forms[platform]._prevReserve;
32473255
state.forms[platform].strategy = defaultStrategyForAccount(platform, options[0], state.forms[platform].strategy);
32483256
state.forms[platform].pluginMode = currentPluginModeForAccount(platform, options[0]);
32493257
syncRuntimeTargetForAccount(platform);
@@ -4650,6 +4658,7 @@ <h2 data-i18n="summary">切换摘要</h2>
46504658
state.forms[state.selected].cashOnlyExecutionTouched = false;
46514659
state.forms[state.selected].dcaTouched = false;
46524660
state.forms[state.selected].ibitZscoreExitTouched = false;
4661+
delete state.forms[state.selected]._prevReserve;
46534662
syncStrategyForAccount(state.selected);
46544663
render();
46554664
});
@@ -4735,6 +4744,14 @@ <h2 data-i18n="summary">切换摘要</h2>
47354744
const form = state.forms[state.selected];
47364745
form.cashOnlyExecutionMode = normalizeCashOnlyExecutionMode(el("cash-only-execution-mode-select").value);
47374746
form.cashOnlyExecutionTouched = form.cashOnlyExecutionMode !== "current";
4747+
// Restore saved reserve state when disabling margin
4748+
if (!allowMarginExplicitlySelected(form) && form._prevReserve) {
4749+
form.reservePolicyMode = form._prevReserve.mode;
4750+
form.minReservedCashUsd = form._prevReserve.floor;
4751+
form.reservedCashRatio = form._prevReserve.ratio;
4752+
form.reservedCashTouched = true;
4753+
delete form._prevReserve;
4754+
}
47384755
reconcileExecutionCashPolicy(form, "margin");
47394756
render();
47404757
});

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)