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
15 changes: 15 additions & 0 deletions docs/qsl_lifecycle_truth_v1.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,18 @@ blocked_live_reason is empty

缺少任意字段时都 fail closed。设置网站、Worker、配置生成器和后端验证不得从
catalog 名称、默认值或 inventory 状态推导 live 权限。

## 一次性迁移规则

- `platform-config.json`、设置网站 fallback 和生成的 profile asset 只写规范状态。
- Worker 可以在同步入口读取旧状态一次,但写入 KV 前必须转换为规范状态。
- 旧 `runtime_enabled` 只有同时带齐已有部署的显式 live 字段时才转换为
`live_enabled`;否则转换为 `live_candidate`。
- Python 后端暂时只读兼容旧 live stage,供未迁移 runtime 回滚;新配置生成器
不再产生旧值。
- 当前 QuantRuntimeSettings 的权威 `platform-config.json` 没有旧
`runtime_enabled` live entry,因此没有伪造迁移授权。此前设置页中的两个陈旧
fallback live 标记已按权威配置 fail closed 对齐为 `research_active`。

待所有 broker runtime 消费规范字段并完成回滚验证后,删除后端最后一层
`runtime_enabled` 只读兼容。
50 changes: 25 additions & 25 deletions platform-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@
"label_en": "NASDAQ Growth Income",
"domain": "us_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand Down Expand Up @@ -409,7 +409,7 @@
"label_en": "Semiconductor Trend Income",
"domain": "us_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand Down Expand Up @@ -449,7 +449,7 @@
"domain": "us_equity",
"scheduler_profile": "us_dca_month_end",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand All @@ -476,7 +476,7 @@
"ibit_zscore_exit": "us_daily"
},
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand All @@ -499,7 +499,7 @@
"label_en": "Global ETF Rotation",
"domain": "us_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand Down Expand Up @@ -544,7 +544,7 @@
"domain": "us_equity",
"scheduler_profile": "us_snapshot_month_start",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand Down Expand Up @@ -588,7 +588,7 @@
"label_en": "TECL/XLK Trend Income",
"domain": "us_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -606,7 +606,7 @@
"label_en": "US Core Combo",
"domain": "us_equity",
"runtime_enabled": false,
"lifecycle_stage": "shadow_candidate",
"lifecycle_stage": "shadow_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand Down Expand Up @@ -646,7 +646,7 @@
"label_en": "US Core Combo Shadow",
"domain": "us_equity",
"runtime_enabled": false,
"lifecycle_stage": "shadow_candidate",
"lifecycle_stage": "shadow_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand All @@ -666,7 +666,7 @@
"label_en": "US Alpha Combo",
"domain": "us_equity",
"runtime_enabled": false,
"lifecycle_stage": "shadow_candidate",
"lifecycle_stage": "shadow_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand All @@ -686,7 +686,7 @@
"label_en": "HK ETF Tactical Rotation",
"domain": "hk_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -705,7 +705,7 @@
"domain": "hk_equity",
"scheduler_profile": "hk_snapshot_month_start",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand All @@ -729,7 +729,7 @@
"label_en": "HK Core Combo",
"domain": "hk_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -748,7 +748,7 @@
"label_en": "CN Industry ETF Rotation",
"domain": "cn_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand Down Expand Up @@ -786,7 +786,7 @@
"label_en": "CN Index ETF Tactical Rotation",
"domain": "cn_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -804,7 +804,7 @@
"label_en": "CN ChiNext Tactical Rotation",
"domain": "cn_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -822,7 +822,7 @@
"label_en": "CN ChiNext Growth Momentum Quality",
"domain": "cn_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -841,7 +841,7 @@
"domain": "cn_equity",
"scheduler_profile": "cn_snapshot_month_start",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -860,7 +860,7 @@
"domain": "cn_equity",
"scheduler_profile": "cn_snapshot_month_start",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -878,7 +878,7 @@
"label_en": "CN STAR Growth Momentum Quality",
"domain": "cn_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -896,7 +896,7 @@
"label_en": "CN Alpha Combo",
"domain": "cn_equity",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -915,7 +915,7 @@
"label_en": "Crypto Live Pool Rotation",
"domain": "crypto",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand All @@ -934,7 +934,7 @@
"label_en": "BTC DCA",
"domain": "crypto",
"runtime_enabled": false,
"lifecycle_stage": "shadow_candidate",
"lifecycle_stage": "shadow_active",
"can_switch_live": false,
"allowed_execution_modes": [
"paper",
Expand All @@ -953,7 +953,7 @@
"label_en": "Altcoin Trend",
"domain": "crypto",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand All @@ -971,7 +971,7 @@
"label_en": "Crypto Core Combo",
"domain": "crypto",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"can_switch_live": false,
"allowed_execution_modes": [
"dry_run"
Expand Down
33 changes: 31 additions & 2 deletions python/tests/test_runtime_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ def test_build_config_strategy_to_json_compat_includes_strategy_gate_fields(self

def test_global_and_hk_global_etf_rotation_profiles_are_research_only(self):
expected = {
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"runtime_enabled": False,
"can_switch_live": False,
"allowed_execution_modes": ["dry_run"],
Expand Down Expand Up @@ -678,7 +678,7 @@ def test_not_evidenced_profiles_are_catalog_demoted_fail_closed(self):
expected = {
"runtime_enabled": False,
"can_switch_live": False,
"lifecycle_stage": "research_backtest_only",
"lifecycle_stage": "research_active",
"allowed_execution_modes": ["paper", "dry_run"],
"blocked_live_reason": "missing_current_promotion_evidence_and_preauthorized_autonomy_policy",
}
Expand Down Expand Up @@ -775,6 +775,35 @@ def test_build_platform_config_build_strategy_profile_entries_defaults_gate_fiel
self.assertEqual(profile["allowed_execution_modes"], ["paper", "dry_run"])
self.assertEqual(profile["blocked_live_reason"], "research_active")

def test_published_strategy_lifecycle_fields_use_only_canonical_states(self):
canonical = {
"research_active",
"shadow_active",
"paper_active",
"live_candidate",
"live_enabled",
}
config_entries = build_config.load_config()["strategies"].values()
generated_entries = json.loads(
(
ROOT
/ "web/strategy-switch-console/strategy-profiles.example.json"
).read_text(encoding="utf-8")
)
app_source = (
ROOT / "web/strategy-switch-console/app.js"
).read_text(encoding="utf-8")
fallback_match = re.search(
r"const defaultStrategyProfiles = window\.__DEFAULT_STRATEGY_PROFILES__ \|\| (\[.*?\n \]);",
app_source,
re.DOTALL,
)
self.assertIsNotNone(fallback_match)
fallback_entries = json.loads(fallback_match.group(1))

for entry in [*config_entries, *generated_entries, *fallback_entries]:
self.assertIn(entry["lifecycle_stage"], canonical)

def test_assignment_payload_can_redact_values(self):
_, target = self.load_target("examples/targets/longbridge/sg.example.json")
assignment = next(
Expand Down
22 changes: 21 additions & 1 deletion tests/strategy_switch_worker_validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,24 @@ const strategyProfiles = __test.normalizeStrategyProfilesPayload(
domain: "us_equity",
runtime_enabled: true,
},
{
profile: "legacy_shadow_profile",
label: "Legacy Shadow",
domain: "us_equity",
runtime_enabled: false,
lifecycle_stage: "shadow_candidate",
can_switch_live: false,
allowed_execution_modes: ["paper"],
},
{
profile: "legacy_live_profile",
label: "Legacy Live",
domain: "us_equity",
runtime_enabled: true,
lifecycle_stage: "runtime_enabled",
can_switch_live: true,
allowed_execution_modes: ["live", "paper"],
},
],
"test_strategy_profiles",
);
Expand Down Expand Up @@ -399,7 +417,7 @@ assert.equal(strategyProfiles[0].option_growth_overlay_nav_budget_ratio, "0.03")
assert.equal(strategyProfiles[0].option_income_overlay_enabled, false);
assert.equal(strategyProfiles[0].latest_evidence_status, "live_allowed");
assert.equal(strategyProfiles[0].plugin_gate_status, "live_allowed");
assert.equal(strategyProfiles[2].lifecycle_stage, "research");
assert.equal(strategyProfiles[2].lifecycle_stage, "research_active");
assert.equal(strategyProfiles[2].can_switch_live, false);
assert.deepEqual(strategyProfiles[2].allowed_execution_modes, ["paper"]);
assert.equal(strategyProfiles[2].blocked_live_reason, "promotion_required");
Expand All @@ -408,6 +426,8 @@ assert.equal(strategyProfiles[2].plugin_gate_status, "blocked");
assert.equal(strategyProfiles[3].dca_enabled, true);
assert.equal(strategyProfiles[3].dca_default_mode, "fixed");
assert.equal(strategyProfiles[3].dca_default_base_investment_usd, "1000");
assert.equal(strategyProfiles[4].lifecycle_stage, "shadow_active");
assert.equal(strategyProfiles[5].lifecycle_stage, "live_enabled");

assert.doesNotThrow(() =>
__test.assertStrategyAllowedForAccount(
Expand Down
Loading