Skip to content

Commit 2ab6bee

Browse files
authored
Merge pull request #98 from QuantStrategyLab/fix/console-review-followups
Strategy switch console review follow-ups
2 parents 4cef125 + 15f4d1d commit 2ab6bee

3 files changed

Lines changed: 106 additions & 48 deletions

File tree

tests/strategy_switch_worker_validation.mjs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,15 @@ assert.ok(indexHtml.includes('qmt: { label: "QMT"'));
4545
assert.ok(indexHtml.includes('cn_industry_etf_rotation'));
4646
assert.ok(indexHtml.includes('id="income-layer-section"'));
4747
assert.ok(indexHtml.includes('id="option-overlay-section"'));
48-
assert.ok(indexHtml.includes('id="margin-policy-stack"'));
49-
assert.ok(indexHtml.includes('class="margin-policy-stack"'));
5048
assert.ok(indexHtml.includes('id="reserve-policy-stack"'));
5149
assert.ok(indexHtml.includes('class="reserve-policy-stack"'));
50+
assert.equal(indexHtml.includes('id="margin-policy-stack"'), false);
51+
assert.ok(indexHtml.includes("cash_only_execution_mode: item.cash_only_execution_mode"));
52+
assert.ok(indexHtml.includes("function incomeLayerFieldsConfigured("));
53+
assert.ok(indexHtml.includes("function effectiveIncomeLayerForAccount("));
54+
assert.ok(indexHtml.includes('class="summary-list" id="summary-list" role="list"'));
55+
assert.ok(indexHtml.includes('labelNode.className = "summary-label"'));
56+
assert.equal(indexHtml.includes("noChangesNote"), false);
5257
assert.equal(indexHtml.match(/class="form-section dca-section"/g)?.length, 1);
5358
assert.ok(indexHtml.includes('qmtDryRunOnlyNote'));
5459
assert.ok(indexHtml.includes('optionOverlayDefaultSimple: "策略默认:开启"'));
@@ -141,7 +146,6 @@ assert.equal(
141146
indexHtml.includes('state.auth.allowed && !loadingConfig && (!hasPrivateAccounts || !hasValidStrategy || !hasPendingChange)'),
142147
false,
143148
);
144-
assert.ok(indexHtml.includes('noChangesNote'));
145149
assert.equal(indexHtml.includes('placeholder="150"'), false);
146150
assert.equal(indexHtml.includes('placeholder="0.03"'), false);
147151
assert.equal(indexHtml.includes("ibkr-primary"), false);
@@ -384,6 +388,21 @@ assert.deepEqual(accountOptions.longbridge[1].supported_domains, ["us_equity", "
384388
assert.deepEqual(accountOptions.ibkr[0].supported_domains, ["us_equity", "hk_equity"]);
385389
assert.equal(accountOptions.longbridge[0].cash_currency, "HKD");
386390

391+
const accountOptionsWithCashOnlyMode = __test.normalizeAccountOptionsPayload(
392+
{
393+
longbridge: [
394+
{
395+
key: "sg",
396+
label: "sg",
397+
target_name: "sg",
398+
cash_only_execution_mode: "enabled",
399+
},
400+
],
401+
},
402+
"test_account_options",
403+
);
404+
assert.equal(accountOptionsWithCashOnlyMode.longbridge[0].cash_only_execution_mode, "enabled");
405+
387406
const kvUnboundSyncResponse = await worker.fetch(
388407
new Request("https://switch.example/api/internal/sync-account-default", {
389408
method: "POST",
@@ -899,6 +918,7 @@ globalThis.fetch = async (url) => {
899918
ACCOUNT_GROUP: "demo-ibkr-tqqq",
900919
IBKR_MIN_RESERVED_CASH_USD: "150",
901920
IBKR_RESERVED_CASH_RATIO: "0.03",
921+
IBKR_CASH_ONLY_EXECUTION: "false",
902922
INCOME_LAYER_START_USD: "250000",
903923
INCOME_LAYER_MAX_RATIO: "0.55",
904924
OPTION_OVERLAY_ENABLED: "true",
@@ -930,6 +950,7 @@ try {
930950
assert.equal(currentStrategies.ibkr["ibkr-primary"].strategy_profile, "ibit_smart_dca");
931951
assert.equal(currentStrategies.ibkr["ibkr-primary"].min_reserved_cash_usd, "150");
932952
assert.equal(currentStrategies.ibkr["ibkr-primary"].reserved_cash_ratio, "0.03");
953+
assert.equal(currentStrategies.ibkr["ibkr-primary"].cash_only_execution, false);
933954
assert.equal(currentStrategies.ibkr["ibkr-primary"].income_layer_start_usd, "250000");
934955
assert.equal(currentStrategies.ibkr["ibkr-primary"].income_layer_max_ratio, "0.55");
935956
assert.equal(currentStrategies.ibkr["ibkr-primary"].option_overlay_enabled, true);

web/strategy-switch-console/index.html

Lines changed: 81 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -352,12 +352,6 @@
352352
min-width: 0;
353353
}
354354

355-
.margin-policy-stack {
356-
display: grid;
357-
gap: 14px;
358-
min-width: 0;
359-
}
360-
361355
.reserve-policy-stack.policy-stack-muted {
362356
opacity: 0.58;
363357
}
@@ -628,6 +622,7 @@
628622
margin: 0;
629623
padding: 8px 16px;
630624
display: grid;
625+
gap: 0;
631626
}
632627

633628
.summary-row {
@@ -650,14 +645,14 @@
650645
background: color-mix(in srgb, var(--platform-color, var(--accent)) 6%, #ffffff);
651646
}
652647

653-
.summary-row dt {
648+
.summary-label {
654649
color: var(--muted);
655650
font-size: 12px;
656651
line-height: 1.4;
657652
font-weight: 760;
658653
}
659654

660-
.summary-row dd {
655+
.summary-value {
661656
margin: 0;
662657
min-width: 0;
663658
color: var(--ink);
@@ -667,7 +662,7 @@
667662
overflow-wrap: anywhere;
668663
}
669664

670-
.summary-row.pending dd {
665+
.summary-row.pending .summary-value {
671666
color: var(--platform-color, var(--accent));
672667
font-weight: 800;
673668
}
@@ -1136,13 +1131,13 @@
11361131
border-bottom-color: rgba(229, 236, 242, 0.96);
11371132
}
11381133

1139-
.summary-row dt {
1134+
.summary-label {
11401135
color: #66758a;
11411136
font-size: 12px;
11421137
font-weight: 790;
11431138
}
11441139

1145-
.summary-row dd {
1140+
.summary-value {
11461141
color: #263244;
11471142
font-size: 13px;
11481143
font-weight: 700;
@@ -1409,19 +1404,11 @@ <h3 class="execution-cash-policy-heading" data-i18n="executionCashPolicyTitle">
14091404
<p class="execution-cash-policy-note" id="execution-cash-policy-note" data-i18n="executionCashPolicyNote">允许融资与预留现金覆盖不能同时生效。</p>
14101405
<p class="execution-cash-policy-note qmt-platform-cash-note" id="qmt-platform-cash-note" hidden data-i18n="qmtPlatformCashNote">A 股 QMT 不使用 margin / 平台预留现金;现金约束在策略参数 execution_cash_reserve_ratio 内配置。</p>
14111406
<div class="execution-cash-policy-grid" id="execution-cash-policy-grid">
1412-
<div class="margin-policy-stack" id="margin-policy-stack">
1413-
<label class="control-block cash-only-mode-block policy-block" id="cash-only-policy-block">
1414-
<span data-i18n="cashOnlyExecutionMode">允许融资</span>
1415-
<select id="cash-only-execution-mode-select"></select>
1416-
<span class="selection-meta" id="cash-only-execution-mode-meta"></span>
1417-
</label>
1418-
1419-
<label class="control-block reserve-ratio-block policy-block" id="reserve-ratio-block">
1420-
<span data-i18n="reservedCashRatio">预留现金比例</span>
1421-
<input id="reserved-cash-ratio-input" type="number" inputmode="decimal" min="0" max="1" step="0.01">
1422-
<span class="selection-meta" data-i18n="reservedCashRatioMeta">例如 0.03 表示 3%。</span>
1423-
</label>
1424-
</div>
1407+
<label class="control-block cash-only-mode-block policy-block" id="cash-only-policy-block">
1408+
<span data-i18n="cashOnlyExecutionMode">允许融资</span>
1409+
<select id="cash-only-execution-mode-select"></select>
1410+
<span class="selection-meta" id="cash-only-execution-mode-meta"></span>
1411+
</label>
14251412

14261413
<div class="reserve-policy-stack" id="reserve-policy-stack">
14271414
<label class="control-block reserve-policy-block policy-block" id="reserve-policy-block">
@@ -1435,6 +1422,12 @@ <h3 class="execution-cash-policy-heading" data-i18n="executionCashPolicyTitle">
14351422
<input id="min-reserved-cash-input" type="number" inputmode="decimal" min="0" step="1">
14361423
<span class="selection-meta" data-i18n="reservedCashMeta">固定金额下限,可单独设置或与比例取较大值。</span>
14371424
</label>
1425+
1426+
<label class="control-block reserve-ratio-block policy-block" id="reserve-ratio-block">
1427+
<span data-i18n="reservedCashRatio">预留现金比例</span>
1428+
<input id="reserved-cash-ratio-input" type="number" inputmode="decimal" min="0" max="1" step="0.01">
1429+
<span class="selection-meta" data-i18n="reservedCashRatioMeta">例如 0.03 表示 3%。</span>
1430+
</label>
14381431
</div>
14391432
</div>
14401433
</div>
@@ -1466,9 +1459,9 @@ <h3 class="execution-cash-policy-heading" data-i18n="executionCashPolicyTitle">
14661459
<h2 data-i18n="summary">切换摘要</h2>
14671460
<span class="pill" id="mode-pill">live</span>
14681461
</div>
1469-
<dl class="summary-list" id="summary-list"></dl>
1462+
<div class="summary-list" id="summary-list" role="list"></div>
14701463
<div class="summary-actions">
1471-
<button class="btn" id="copy-button" type="button" data-i18n="copySummary">复制摘要</button>
1464+
<button class="btn" id="copy-button" type="button" data-i18n="copySummary">复制状态</button>
14721465
</div>
14731466
</aside>
14741467
</section>
@@ -1736,7 +1729,6 @@ <h2 data-i18n="summary">切换摘要</h2>
17361729
loadingConfigNote: "正在读取账号配置和当前状态。",
17371730
missingConfigNote: "账号配置未加载,暂时不能执行。",
17381731
readyNote: "点击后会触发工作流,并同步目标平台服务。",
1739-
noChangesNote: "当前选择与已读取配置一致。",
17401732
invalidStrategyNote: "当前账号没有可执行策略,暂时不能切换。",
17411733
invalidReservePolicyNote: "请为当前预留现金策略填写有效金额或比例。",
17421734
invalidIncomeLayerNote: "请填写有效的收入层起始金额和最高比例。",
@@ -1902,7 +1894,6 @@ <h2 data-i18n="summary">切换摘要</h2>
19021894
loadingConfigNote: "Reading account config and current state.",
19031895
missingConfigNote: "Account config is not loaded, so switching is disabled.",
19041896
readyNote: "This dispatches the workflow and syncs the target platform service.",
1905-
noChangesNote: "The current selection matches the readable config.",
19061897
invalidStrategyNote: "This account has no runnable strategy, so switching is disabled.",
19071898
invalidReservePolicyNote: "Enter a valid amount or ratio for the selected reserved-cash policy.",
19081899
invalidIncomeLayerNote: "Enter a valid income layer start amount and max ratio.",
@@ -2550,6 +2541,31 @@ <h2 data-i18n="summary">切换摘要</h2>
25502541
};
25512542
}
25522543

2544+
function incomeLayerFieldsConfigured(entry) {
2545+
const current = incomeLayerFromEntry(entry);
2546+
return current.enabled !== null || Boolean(current.startUsd || current.maxRatio);
2547+
}
2548+
2549+
function effectiveIncomeLayerForAccount(platform, account, profile = state.forms[platform]?.strategy) {
2550+
const defaults = incomeLayerDefaultForStrategy(profile);
2551+
if (!defaults) return null;
2552+
const entry = currentEntryForAccount(platform, account);
2553+
if (!entry) return null;
2554+
const current = incomeLayerFromEntry(entry);
2555+
if (!incomeLayerFieldsConfigured(entry)) {
2556+
return {
2557+
enabled: true,
2558+
startUsd: String(defaults.startUsd),
2559+
maxRatio: defaults.maxRatio,
2560+
};
2561+
}
2562+
return {
2563+
enabled: current.enabled ?? true,
2564+
startUsd: current.startUsd || String(defaults.startUsd),
2565+
maxRatio: current.maxRatio || defaults.maxRatio,
2566+
};
2567+
}
2568+
25532569
function currentDcaForAccount(platform, account, profile = state.forms[platform]?.strategy) {
25542570
const defaults = dcaConfigForStrategy(profile);
25552571
if (!defaults) return { supported: false, mode: "", baseInvestmentUsd: "" };
@@ -3131,15 +3147,17 @@ <h2 data-i18n="summary">切换摘要</h2>
31313147
function currentIncomeLayerText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {
31323148
const defaults = incomeLayerDefaultForStrategy(profile);
31333149
if (!defaults) return t("incomeLayerNotSupported");
3134-
const current = currentIncomeLayerForAccount(platform, account);
3135-
const enabled = current.enabled ?? true;
3136-
const startUsd = current.startUsd || String(defaults.startUsd);
3137-
const ratio = current.maxRatio || defaults.maxRatio;
3138-
if (!current.startUsd && !current.maxRatio && current.enabled === null) {
3150+
const entry = currentEntryForAccount(platform, account);
3151+
if (!entry) return t("notRead");
3152+
const current = incomeLayerFromEntry(entry);
3153+
if (!incomeLayerFieldsConfigured(entry)) {
31393154
return t("incomeLayerDefault")
31403155
.replace("{start}", formatUsd(defaults.startUsd))
31413156
.replace("{ratio}", formatRatioPercent(defaults.maxRatio));
31423157
}
3158+
const enabled = current.enabled ?? true;
3159+
const startUsd = current.startUsd || String(defaults.startUsd);
3160+
const ratio = current.maxRatio || defaults.maxRatio;
31433161
return enabled
31443162
? t("incomeLayerOn")
31453163
.replace("{start}", formatUsd(startUsd))
@@ -3243,25 +3261,38 @@ <h2 data-i18n="summary">切换摘要</h2>
32433261
const defaults = incomeLayerDefaultForStrategy(profile);
32443262
if (!defaults) return { supported: false, changed: false, inputs: {} };
32453263
const mode = normalizeIncomeLayerMode(inputs.income_layer_mode);
3246-
const current = currentIncomeLayerForAccount(platform, account);
3247-
const currentEnabled = current.enabled ?? true;
3248-
const currentStartUsd = current.startUsd || String(defaults.startUsd);
3249-
const currentRatio = current.maxRatio || defaults.maxRatio;
3264+
const entry = currentEntryForAccount(platform, account);
3265+
const rawCurrent = currentIncomeLayerForAccount(platform, account);
3266+
const effective = effectiveIncomeLayerForAccount(platform, account, profile);
3267+
const currentEnabled = effective?.enabled ?? true;
3268+
const currentStartUsd = effective?.startUsd ?? String(defaults.startUsd);
3269+
const currentRatio = effective?.maxRatio ?? defaults.maxRatio;
32503270
if (mode === "current") {
32513271
return {
32523272
supported: true,
32533273
changed: false,
32543274
inputs: {
3255-
income_layer_enabled: currentEnabled,
3256-
income_layer_start_usd: currentStartUsd,
3257-
income_layer_max_ratio: currentRatio,
3275+
income_layer_enabled: rawCurrent.enabled,
3276+
income_layer_start_usd: rawCurrent.startUsd,
3277+
income_layer_max_ratio: rawCurrent.maxRatio,
32583278
},
32593279
};
32603280
}
32613281
if (mode === "disabled") {
3282+
if (!entry) {
3283+
return {
3284+
supported: true,
3285+
changed: false,
3286+
inputs: {
3287+
income_layer_enabled: false,
3288+
income_layer_start_usd: "",
3289+
income_layer_max_ratio: "",
3290+
},
3291+
};
3292+
}
32623293
return {
32633294
supported: true,
3264-
changed: currentEnabled !== false || Boolean(current.startUsd || current.maxRatio),
3295+
changed: currentEnabled !== false || Boolean(rawCurrent.startUsd || rawCurrent.maxRatio),
32653296
inputs: {
32663297
income_layer_enabled: false,
32673298
income_layer_start_usd: "",
@@ -3273,7 +3304,7 @@ <h2 data-i18n="summary">切换摘要</h2>
32733304
const nextRatio = cleanDisplayRatio(inputs.income_layer_max_ratio || defaults.maxRatio);
32743305
return {
32753306
supported: true,
3276-
changed: Boolean(currentEnabled !== true || nextStartUsd !== currentStartUsd || nextRatio !== currentRatio),
3307+
changed: !entry || Boolean(currentEnabled !== true || nextStartUsd !== currentStartUsd || nextRatio !== currentRatio),
32773308
inputs: {
32783309
income_layer_enabled: true,
32793310
income_layer_start_usd: nextStartUsd,
@@ -3852,10 +3883,13 @@ <h2 data-i18n="summary">切换摘要</h2>
38523883
for (const [label, value, rowClass, valueTone] of summaryRows(inputs)) {
38533884
const row = document.createElement("div");
38543885
row.className = "summary-row";
3886+
row.setAttribute("role", "listitem");
38553887
if (rowClass) row.classList.add(rowClass);
3856-
const labelNode = document.createElement("dt");
3888+
const labelNode = document.createElement("div");
3889+
labelNode.className = "summary-label";
38573890
labelNode.textContent = label;
3858-
const valueNode = document.createElement("dd");
3891+
const valueNode = document.createElement("div");
3892+
valueNode.className = "summary-value";
38593893
if (valueTone) {
38603894
const badge = document.createElement("span");
38613895
badge.className = `summary-status ${valueTone}`;
@@ -4046,6 +4080,9 @@ <h2 data-i18n="summary">切换摘要</h2>
40464080
variable_scope: item.variable_scope ? String(item.variable_scope) : "",
40474081
plugin_mode: item.plugin_mode ? String(item.plugin_mode) : "",
40484082
option_overlay_mode: item.option_overlay_mode ? normalizeOptionOverlayMode(item.option_overlay_mode) : "",
4083+
cash_only_execution_mode: item.cash_only_execution_mode
4084+
? normalizeCashOnlyExecutionMode(item.cash_only_execution_mode)
4085+
: "",
40494086
dca_mode: item.dca_mode ? normalizeDcaMode(item.dca_mode) : "",
40504087
dca_base_investment_usd: cleanDisplayPositiveNumber(item.dca_base_investment_usd),
40514088
ibit_zscore_exit_mode: cleanIbitZscoreExitMode(item.ibit_zscore_exit_mode),

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)