diff --git a/README.zh-CN.md b/README.zh-CN.md index e8519d5..47edee8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -49,7 +49,7 @@ python3 -m unittest discover -s python/tests -v 推荐流程: 1. 第一次运行保持 `apply=false`,只看 preview。 -2. 确认 `repository`、`environment`、`strategy_profile`、`service_name`、`execution_mode` 和插件挂载正确。 +2. 确认 `repository`、`environment`、`strategy_profile`、`service_name`、`execution_mode`,以及由 `strategy_profile` 派生的 `scheduler` / 插件挂载正确。 3. 再运行 `apply=true`,并填写 `confirm_apply=APPLY`,写入目标仓库变量。 4. 如果要让平台仓同步 Cloud Run 环境,额外设置 `trigger_platform_sync=true`,并填写 `confirm_apply=APPLY_AND_SYNC`。 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ea240c9..c4c5b1f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -4,6 +4,8 @@ QuantRuntimeSettings is a **config-driven** runtime settings package that serves as the central control plane for QuantStrategyLab deployments. It defines versioned strategy-to-platform assignments and hosts a Cloudflare Workers-based strategy switch console. +The generated `RUNTIME_TARGET_JSON` payload is the canonical desired-state contract for a switch. `scheduler` and plugin mount outputs are derived from `strategy_profile`, while `execution_mode` is part of the target and validated against strategy-profile policy. + The repository has a **three-tier architecture** built around a single source of truth: ``` diff --git a/python/tests/test_runtime_settings.py b/python/tests/test_runtime_settings.py index 024768a..7d4a056 100644 --- a/python/tests/test_runtime_settings.py +++ b/python/tests/test_runtime_settings.py @@ -710,6 +710,7 @@ def test_build_switch_target_defaults_longbridge_sg_tqqq(self): "tqqq_growth_income/plugins/market_regime_control/latest_signal.json", ) self.assertEqual(plugin_payload["strategy_plugins"][0]["expected_schema_version"], "market_regime_control.v1") + self.assertEqual(runtime_settings.validate_target(target), []) def test_build_switch_target_uses_fork_repository_overrides(self): parser = build_runtime_switch.build_parser() @@ -1405,6 +1406,24 @@ def test_runtime_target_scheduler_rejects_invalid_cron_shape(self): runtime_settings.validate_target(target), ) + def test_runtime_target_scheduler_rejects_extra_fields(self): + _, target = self.load_target("examples/targets/longbridge/hk_combo.example.json") + target["runtime_target"]["scheduler"]["offset_minutes"] = 10 + + self.assertIn( + "runtime_target.scheduler.offset_minutes is unsupported", + runtime_settings.validate_target(target), + ) + + def test_runtime_target_scheduler_requires_timezone(self): + _, target = self.load_target("examples/targets/longbridge/hk_combo.example.json") + target["runtime_target"]["scheduler"].pop("timezone") + + self.assertIn( + "runtime_target.scheduler.timezone must be a non-empty string", + runtime_settings.validate_target(target), + ) + def test_build_switch_target_rejects_secret_extra_variable(self): parser = build_runtime_switch.build_parser() args = parser.parse_args( diff --git a/schemas/runtime-target.schema.json b/schemas/runtime-target.schema.json index d31d7c9..cea415f 100644 --- a/schemas/runtime-target.schema.json +++ b/schemas/runtime-target.schema.json @@ -77,6 +77,24 @@ "type": "string", "enum": ["live", "paper", "dry_run"] }, + "scheduler": { + "type": "object", + "additionalProperties": false, + "properties": { + "timezone": { + "type": "string" + }, + "main_time": { + "type": "string" + }, + "probe_time": { + "type": "string" + }, + "precheck_time": { + "type": "string" + } + } + }, "execution_windows": { "type": "object", "additionalProperties": false,