Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions internal_dependency_matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@
"path": "requirements.txt",
"package": "us-equity-strategies",
"source_repo": "UsEquityStrategies",
"ref": "c8df5f9659340965bd7f53998892ed1018ed4254"
"ref": "6568c315ce3be6f7ae5b799374cf7fb44232c170"
},
{
"consumer_repo": "CharlesSchwabPlatform",
"path": "requirements.txt",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "d786c1140967f0e96e35599d057f0655e5a9ba25"
"ref": "b0eacd2fe4884f7f2447b704a232e9a121f396c4"
},
{
"consumer_repo": "CnEquitySnapshotPipelines",
Expand All @@ -55,21 +55,21 @@
"path": "pyproject.toml",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "d786c1140967f0e96e35599d057f0655e5a9ba25"
"ref": "b0eacd2fe4884f7f2447b704a232e9a121f396c4"
},
{
"consumer_repo": "FirstradePlatform",
"path": "requirements.txt",
"package": "us-equity-strategies",
"source_repo": "UsEquityStrategies",
"ref": "c8df5f9659340965bd7f53998892ed1018ed4254"
"ref": "6568c315ce3be6f7ae5b799374cf7fb44232c170"
},
{
"consumer_repo": "FirstradePlatform",
"path": "requirements.txt",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "d786c1140967f0e96e35599d057f0655e5a9ba25"
"ref": "b0eacd2fe4884f7f2447b704a232e9a121f396c4"
},
{
"consumer_repo": "HkEquitySnapshotPipelines",
Expand All @@ -83,42 +83,42 @@
"path": "requirements.txt",
"package": "hk-equity-strategies",
"source_repo": "HkEquityStrategies",
"ref": "700c8b19c46336d3d8fcba687e58553afcf0235f"
"ref": "e9e3058c1eaf3f43b25d50df5eb14442816e568e"
},
{
"consumer_repo": "InteractiveBrokersPlatform",
"path": "requirements.txt",
"package": "us-equity-strategies",
"source_repo": "UsEquityStrategies",
"ref": "c8df5f9659340965bd7f53998892ed1018ed4254"
"ref": "6568c315ce3be6f7ae5b799374cf7fb44232c170"
},
{
"consumer_repo": "InteractiveBrokersPlatform",
"path": "requirements.txt",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "d786c1140967f0e96e35599d057f0655e5a9ba25"
"ref": "b0eacd2fe4884f7f2447b704a232e9a121f396c4"
},
{
"consumer_repo": "LongBridgePlatform",
"path": "requirements.txt",
"package": "hk-equity-strategies",
"source_repo": "HkEquityStrategies",
"ref": "700c8b19c46336d3d8fcba687e58553afcf0235f"
"ref": "e9e3058c1eaf3f43b25d50df5eb14442816e568e"
},
{
"consumer_repo": "LongBridgePlatform",
"path": "requirements.txt",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "d786c1140967f0e96e35599d057f0655e5a9ba25"
"ref": "b0eacd2fe4884f7f2447b704a232e9a121f396c4"
},
{
"consumer_repo": "LongBridgePlatform",
"path": "requirements.txt",
"package": "us-equity-strategies",
"source_repo": "UsEquityStrategies",
"ref": "c8df5f9659340965bd7f53998892ed1018ed4254"
"ref": "6568c315ce3be6f7ae5b799374cf7fb44232c170"
},
{
"consumer_repo": "QmtPlatform",
Expand Down
2 changes: 1 addition & 1 deletion platform-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"reserved_cash": true,
"income_layer": true,
"option_overlay": true,
"dca": false
"dca": true
},
"default_account": {
"key": "preview",
Expand Down
5 changes: 3 additions & 2 deletions python/scripts/build_runtime_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
"ibit_smart_dca",
}
)
DCA_SUPPORTED_PLATFORMS = frozenset({"firstrade"})
DCA_SUPPORTED_PLATFORMS = frozenset({"longbridge", "ibkr", "schwab", "firstrade"})
DCA_MODES = frozenset({"fixed", "smart"})
DCA_MODE_VARIABLE = "DCA_MODE"
DCA_BASE_INVESTMENT_VARIABLE = "DCA_BASE_INVESTMENT_USD"
Expand Down Expand Up @@ -334,8 +334,9 @@ def _normalize_dca_mode(value: str) -> str:
def _validate_dca_platform(platform: str, strategy_profile: str) -> None:
profile = str(strategy_profile or "").strip().lower()
if profile in DCA_PROFILES and platform not in DCA_SUPPORTED_PLATFORMS:
supported = ", ".join(sorted(DCA_SUPPORTED_PLATFORMS))
raise ValueError(
"DCA strategy profiles are only supported on firstrade; "
f"DCA strategy profiles are only supported on {supported}; "
f"got platform={platform!r}, strategy_profile={profile!r}"
)

Expand Down
8 changes: 5 additions & 3 deletions python/tests/test_runtime_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ def test_build_switch_target_rejects_dca_settings_for_non_dca_profile(self):
with self.assertRaisesRegex(ValueError, "DCA settings are only supported"):
build_runtime_switch.build_switch_target(args)

def test_build_switch_target_rejects_dca_profile_on_non_firstrade_platform(self):
def test_build_switch_target_accepts_dca_profile_on_ibkr(self):
parser = build_runtime_switch.build_parser()
args = parser.parse_args(
[
Expand All @@ -788,8 +788,10 @@ def test_build_switch_target_rejects_dca_profile_on_non_firstrade_platform(self)
]
)

with self.assertRaisesRegex(ValueError, "DCA strategy profiles are only supported on firstrade"):
build_runtime_switch.build_switch_target(args)
target = build_runtime_switch.build_switch_target(args)

self.assertEqual(target["runtime_target"]["strategy_profile"], "nasdaq_sp500_smart_dca")
self.assertEqual(target["runtime_target"]["platform_id"], "ibkr")

def test_build_switch_target_rejects_direct_dca_extra_variables(self):
parser = build_runtime_switch.build_parser()
Expand Down
51 changes: 27 additions & 24 deletions tests/strategy_switch_worker_validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -574,18 +574,20 @@ assert.deepEqual(JSON.parse(normalizedDcaInputs.extra_variables_json), {
dca_base_investment_usd: "500",
cash_only_execution_mode: "enabled",
});
assert.throws(
() => __test.normalizeSwitchInputs({
platform: "ibkr",
target_name: "ibkr-primary",
strategy_profile: "nasdaq_sp500_smart_dca",
execution_mode: "live",
plugin_mode: "auto",
dca_mode: "smart",
dca_base_investment_usd: "500",
}),
/DCA strategy profiles are not supported on ibkr/,
);
const normalizedIbkrDcaInputs = __test.normalizeSwitchInputs({
platform: "ibkr",
target_name: "ibkr-primary",
strategy_profile: "nasdaq_sp500_smart_dca",
execution_mode: "live",
plugin_mode: "auto",
dca_mode: "smart",
dca_base_investment_usd: "500",
});
assert.deepEqual(JSON.parse(normalizedIbkrDcaInputs.extra_variables_json), {
dca_mode: "smart",
dca_base_investment_usd: "500",
cash_only_execution_mode: "enabled",
});
const normalizedDcaJsonInputs = __test.normalizeSwitchInputs({
platform: "firstrade",
target_name: "default",
Expand Down Expand Up @@ -614,25 +616,26 @@ assert.deepEqual(JSON.parse(normalizedIbitZscoreInputs.extra_variables_json), {
ibit_zscore_exit_mode: "live",
cash_only_execution_mode: "enabled",
});
assert.throws(
() => __test.normalizeSwitchInputs({
platform: "ibkr",
target_name: "ibit-primary",
strategy_profile: "ibit_smart_dca",
execution_mode: "live",
plugin_mode: "auto",
ibit_zscore_exit_mode: "live",
}),
/DCA strategy profiles are not supported on ibkr/,
);
const normalizedIbkrIbitZscoreInputs = __test.normalizeSwitchInputs({
platform: "ibkr",
target_name: "ibit-primary",
strategy_profile: "ibit_smart_dca",
execution_mode: "live",
plugin_mode: "auto",
ibit_zscore_exit_mode: "live",
});
assert.deepEqual(JSON.parse(normalizedIbkrIbitZscoreInputs.extra_variables_json), {
ibit_zscore_exit_mode: "live",
cash_only_execution_mode: "enabled",
});
assert.throws(
() => __test.normalizeSwitchInputs({
platform: "ibkr",
target_name: "ibkr-primary",
strategy_profile: "nasdaq_sp500_smart_dca",
ibit_zscore_exit_mode: "live",
}),
/DCA strategy profiles are not supported on ibkr/,
/IBIT Z-Score exit settings/,
);
assert.throws(
() => __test.normalizeSwitchInputs({
Expand Down
41 changes: 41 additions & 0 deletions tests/test_cash_financing.js
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,34 @@ function syncRuntimeTargetForAccount(state, form, platform, account) {
form.runtimeTargetMode = target.known ? (target.enabled ? "enabled" : "disabled") : "current";
}

function normalizeRuntimeTargetMode(value) {
return ["current", "enabled", "disabled"].includes(value) ? value : "current";
}

function runtimeTargetEnabledForAccount(state, platform, account) {
return cleanOptionalBoolean(currentEntryForAccount(state, platform, account)?.runtime_target_enabled);
}

function pendingRuntimeTarget(state, inputs, platform, account) {
const mode = normalizeRuntimeTargetMode(inputs.runtime_target_enabled_mode);
if (mode === "current") {
return {
changed: false,
inputs: {
runtime_target_enabled: runtimeTargetEnabledForAccount(state, platform, account) ?? true,
},
};
}
const current = runtimeTargetEnabledForAccount(state, platform, account);
const currentEnabled = current ?? true;
const nextEnabled = mode === "enabled";
const entry = currentEntryForAccount(state, platform, account);
return {
changed: Boolean(entry && current !== null && currentEnabled !== nextEnabled),
inputs: { runtime_target_enabled: nextEnabled },
};
}

// --- 测试 ---
console.log("\n=== 9. syncRuntimeTargetForAccount (解析为具体值) ===\n");

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

// 9e: summary/pending should not mark "current" as a disable change
{
const state = { currentStrategies: makeCurrentStrategies("ibkr", { extra: { runtime_target_enabled: true } }) };
const pending = pendingRuntimeTarget(
state,
{ runtime_target_enabled_mode: "current" },
"ibkr",
makeAccount("preview"),
);
assert(pending.changed === false, "9e: current runtime target mode → unchanged");
assert(pending.inputs.runtime_target_enabled === true, "9e: current runtime target keeps enabled value");
}

// ============================================================
// 10. syncIncomeLayerForAccount (解析为具体值)
// ============================================================
Expand Down
8 changes: 7 additions & 1 deletion web/strategy-switch-console/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
reserved_cash: true,
income_layer: true,
option_overlay: true,
dca: false,
dca: true,
execution_mode: "live",
service_name: "",
default_execution_mode: "live"
Expand Down Expand Up @@ -1942,6 +1942,12 @@ function hasValidStrategySelection(platform = state.selected) {

function pendingRuntimeTarget(inputs, platform = state.selected, account = selectedAccount(platform)) {
const mode = normalizeRuntimeTargetMode(inputs.runtime_target_enabled_mode);
if (mode === "current") {
return {
changed: false,
inputs: { runtime_target_enabled: runtimeTargetEnabledForAccount(platform, account) ?? true },
};
}
const current = runtimeTargetEnabledForAccount(platform, account);
const currentEnabled = current ?? true;
const nextEnabled = mode === "enabled";
Expand Down
2 changes: 1 addition & 1 deletion web/strategy-switch-console/app_js.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions web/strategy-switch-console/config.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading