Skip to content

Commit 42531bd

Browse files
authored
Merge branch 'main' into codex/audit-fix-20260702-1615-clean
2 parents 1b35662 + ad6117a commit 42531bd

8 files changed

Lines changed: 87 additions & 34 deletions

File tree

platform-config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
"reserved_cash": true,
8383
"income_layer": true,
8484
"option_overlay": true,
85-
"dca": false
85+
"dca": true
8686
},
8787
"default_account": {
8888
"key": "preview",

python/scripts/build_runtime_switch.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
"ibit_smart_dca",
177177
}
178178
)
179-
DCA_SUPPORTED_PLATFORMS = frozenset({"firstrade"})
179+
DCA_SUPPORTED_PLATFORMS = frozenset({"longbridge", "ibkr", "schwab", "firstrade"})
180180
DCA_MODES = frozenset({"fixed", "smart"})
181181
DCA_MODE_VARIABLE = "DCA_MODE"
182182
DCA_BASE_INVESTMENT_VARIABLE = "DCA_BASE_INVESTMENT_USD"
@@ -334,8 +334,9 @@ def _normalize_dca_mode(value: str) -> str:
334334
def _validate_dca_platform(platform: str, strategy_profile: str) -> None:
335335
profile = str(strategy_profile or "").strip().lower()
336336
if profile in DCA_PROFILES and platform not in DCA_SUPPORTED_PLATFORMS:
337+
supported = ", ".join(sorted(DCA_SUPPORTED_PLATFORMS))
337338
raise ValueError(
338-
"DCA strategy profiles are only supported on firstrade; "
339+
f"DCA strategy profiles are only supported on {supported}; "
339340
f"got platform={platform!r}, strategy_profile={profile!r}"
340341
)
341342

python/tests/test_runtime_settings.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -773,7 +773,7 @@ def test_build_switch_target_rejects_dca_settings_for_non_dca_profile(self):
773773
with self.assertRaisesRegex(ValueError, "DCA settings are only supported"):
774774
build_runtime_switch.build_switch_target(args)
775775

776-
def test_build_switch_target_rejects_dca_profile_on_non_firstrade_platform(self):
776+
def test_build_switch_target_accepts_dca_profile_on_ibkr(self):
777777
parser = build_runtime_switch.build_parser()
778778
args = parser.parse_args(
779779
[
@@ -788,8 +788,10 @@ def test_build_switch_target_rejects_dca_profile_on_non_firstrade_platform(self)
788788
]
789789
)
790790

791-
with self.assertRaisesRegex(ValueError, "DCA strategy profiles are only supported on firstrade"):
792-
build_runtime_switch.build_switch_target(args)
791+
target = build_runtime_switch.build_switch_target(args)
792+
793+
self.assertEqual(target["runtime_target"]["strategy_profile"], "nasdaq_sp500_smart_dca")
794+
self.assertEqual(target["runtime_target"]["platform_id"], "ibkr")
793795

794796
def test_build_switch_target_rejects_direct_dca_extra_variables(self):
795797
parser = build_runtime_switch.build_parser()

tests/strategy_switch_worker_validation.mjs

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -574,18 +574,20 @@ assert.deepEqual(JSON.parse(normalizedDcaInputs.extra_variables_json), {
574574
dca_base_investment_usd: "500",
575575
cash_only_execution_mode: "enabled",
576576
});
577-
assert.throws(
578-
() => __test.normalizeSwitchInputs({
579-
platform: "ibkr",
580-
target_name: "ibkr-primary",
581-
strategy_profile: "nasdaq_sp500_smart_dca",
582-
execution_mode: "live",
583-
plugin_mode: "auto",
584-
dca_mode: "smart",
585-
dca_base_investment_usd: "500",
586-
}),
587-
/DCA strategy profiles are not supported on ibkr/,
588-
);
577+
const normalizedIbkrDcaInputs = __test.normalizeSwitchInputs({
578+
platform: "ibkr",
579+
target_name: "ibkr-primary",
580+
strategy_profile: "nasdaq_sp500_smart_dca",
581+
execution_mode: "live",
582+
plugin_mode: "auto",
583+
dca_mode: "smart",
584+
dca_base_investment_usd: "500",
585+
});
586+
assert.deepEqual(JSON.parse(normalizedIbkrDcaInputs.extra_variables_json), {
587+
dca_mode: "smart",
588+
dca_base_investment_usd: "500",
589+
cash_only_execution_mode: "enabled",
590+
});
589591
const normalizedDcaJsonInputs = __test.normalizeSwitchInputs({
590592
platform: "firstrade",
591593
target_name: "default",
@@ -614,25 +616,26 @@ assert.deepEqual(JSON.parse(normalizedIbitZscoreInputs.extra_variables_json), {
614616
ibit_zscore_exit_mode: "live",
615617
cash_only_execution_mode: "enabled",
616618
});
617-
assert.throws(
618-
() => __test.normalizeSwitchInputs({
619-
platform: "ibkr",
620-
target_name: "ibit-primary",
621-
strategy_profile: "ibit_smart_dca",
622-
execution_mode: "live",
623-
plugin_mode: "auto",
624-
ibit_zscore_exit_mode: "live",
625-
}),
626-
/DCA strategy profiles are not supported on ibkr/,
627-
);
619+
const normalizedIbkrIbitZscoreInputs = __test.normalizeSwitchInputs({
620+
platform: "ibkr",
621+
target_name: "ibit-primary",
622+
strategy_profile: "ibit_smart_dca",
623+
execution_mode: "live",
624+
plugin_mode: "auto",
625+
ibit_zscore_exit_mode: "live",
626+
});
627+
assert.deepEqual(JSON.parse(normalizedIbkrIbitZscoreInputs.extra_variables_json), {
628+
ibit_zscore_exit_mode: "live",
629+
cash_only_execution_mode: "enabled",
630+
});
628631
assert.throws(
629632
() => __test.normalizeSwitchInputs({
630633
platform: "ibkr",
631634
target_name: "ibkr-primary",
632635
strategy_profile: "nasdaq_sp500_smart_dca",
633636
ibit_zscore_exit_mode: "live",
634637
}),
635-
/DCA strategy profiles are not supported on ibkr/,
638+
/IBIT Z-Score exit settings/,
636639
);
637640
assert.throws(
638641
() => __test.normalizeSwitchInputs({

tests/test_cash_financing.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,34 @@ function syncRuntimeTargetForAccount(state, form, platform, account) {
745745
form.runtimeTargetMode = target.known ? (target.enabled ? "enabled" : "disabled") : "current";
746746
}
747747

748+
function normalizeRuntimeTargetMode(value) {
749+
return ["current", "enabled", "disabled"].includes(value) ? value : "current";
750+
}
751+
752+
function runtimeTargetEnabledForAccount(state, platform, account) {
753+
return cleanOptionalBoolean(currentEntryForAccount(state, platform, account)?.runtime_target_enabled);
754+
}
755+
756+
function pendingRuntimeTarget(state, inputs, platform, account) {
757+
const mode = normalizeRuntimeTargetMode(inputs.runtime_target_enabled_mode);
758+
if (mode === "current") {
759+
return {
760+
changed: false,
761+
inputs: {
762+
runtime_target_enabled: runtimeTargetEnabledForAccount(state, platform, account) ?? true,
763+
},
764+
};
765+
}
766+
const current = runtimeTargetEnabledForAccount(state, platform, account);
767+
const currentEnabled = current ?? true;
768+
const nextEnabled = mode === "enabled";
769+
const entry = currentEntryForAccount(state, platform, account);
770+
return {
771+
changed: Boolean(entry && current !== null && currentEnabled !== nextEnabled),
772+
inputs: { runtime_target_enabled: nextEnabled },
773+
};
774+
}
775+
748776
// --- 测试 ---
749777
console.log("\n=== 9. syncRuntimeTargetForAccount (解析为具体值) ===\n");
750778

@@ -780,6 +808,19 @@ console.log("\n=== 9. syncRuntimeTargetForAccount (解析为具体值) ===\n");
780808
assert(form.runtimeTargetMode === "enabled", "9d: touched → not overwritten");
781809
}
782810

811+
// 9e: summary/pending should not mark "current" as a disable change
812+
{
813+
const state = { currentStrategies: makeCurrentStrategies("ibkr", { extra: { runtime_target_enabled: true } }) };
814+
const pending = pendingRuntimeTarget(
815+
state,
816+
{ runtime_target_enabled_mode: "current" },
817+
"ibkr",
818+
makeAccount("preview"),
819+
);
820+
assert(pending.changed === false, "9e: current runtime target mode → unchanged");
821+
assert(pending.inputs.runtime_target_enabled === true, "9e: current runtime target keeps enabled value");
822+
}
823+
783824
// ============================================================
784825
// 10. syncIncomeLayerForAccount (解析为具体值)
785826
// ============================================================

web/strategy-switch-console/app.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
reserved_cash: true,
6666
income_layer: true,
6767
option_overlay: true,
68-
dca: false,
68+
dca: true,
6969
execution_mode: "live",
7070
service_name: "",
7171
default_execution_mode: "live"
@@ -1942,6 +1942,12 @@ function hasValidStrategySelection(platform = state.selected) {
19421942

19431943
function pendingRuntimeTarget(inputs, platform = state.selected, account = selectedAccount(platform)) {
19441944
const mode = normalizeRuntimeTargetMode(inputs.runtime_target_enabled_mode);
1945+
if (mode === "current") {
1946+
return {
1947+
changed: false,
1948+
inputs: { runtime_target_enabled: runtimeTargetEnabledForAccount(platform, account) ?? true },
1949+
};
1950+
}
19451951
const current = runtimeTargetEnabledForAccount(platform, account);
19461952
const currentEnabled = current ?? true;
19471953
const nextEnabled = mode === "enabled";

web/strategy-switch-console/app_js.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/config.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)