diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea2c5d4..1ed65d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: assert CallableNotificationPort assert CallablePortfolioPort assert resolve_canonical_profile("tech_communication_pullback_enhancement") == "tech_communication_pullback_enhancement" - assert resolve_hk_canonical_profile("hk_blue_chip_leader_rotation") == "hk_blue_chip_leader_rotation" + assert resolve_hk_canonical_profile("hk_listed_global_etf_rotation") == "hk_listed_global_etf_rotation" PY - name: Install editable shared repositories diff --git a/docs/hk_equity_runtime.md b/docs/hk_equity_runtime.md index fc08a5e..1b37309 100644 --- a/docs/hk_equity_runtime.md +++ b/docs/hk_equity_runtime.md @@ -14,9 +14,9 @@ QuantStrategyLab 现有平台仓库里,能做港股股票交易运行时接入 ## 运行时设计 -平台运行时已具备港股市场维度,并接入 `HkEquityStrategies` 的港股 profile 元数据:`hk_blue_chip_leader_rotation` 是架构占位,`hk_index_mean_reversion`、`hk_etf_regime_rotation` 是 `market_history` 研究候选,`hk_listed_global_etf_rotation` 已 runtime-enabled。生产 Cloud Run 仍保持原策略,除非单独变更 `RUNTIME_TARGET_JSON` / `STRATEGY_PROFILE`。整体仍沿用美股策略的分层方式: +平台运行时已具备港股市场维度,并接入 `HkEquityStrategies` 的港股 profile 元数据。当前平台可选港股 profile 只暴露 `runtime_enabled` 的 `hk_listed_global_etf_rotation`;`hk_blue_chip_leader_rotation` 是 snapshot 架构占位,`hk_index_mean_reversion`、`hk_etf_regime_rotation` 是 `market_history` 研究候选,均留在研究/快照仓库,不进入平台可选列表。生产 Cloud Run 仍保持原策略,除非单独变更 `RUNTIME_TARGET_JSON` / `STRATEGY_PROFILE`。整体仍沿用美股策略的分层方式: -1. [`HkEquityStrategies`](https://github.com/QuantStrategyLab/HkEquityStrategies) 提供 `hk_equity` 策略定义、运行入口和 LongBridge runtime adapter。 +1. [`HkEquityStrategies`](https://github.com/QuantStrategyLab/HkEquityStrategies) 提供非 snapshot `hk_equity` 策略定义、运行入口和 LongBridge runtime adapter。 2. [`HkEquitySnapshotPipelines`](https://github.com/QuantStrategyLab/HkEquitySnapshotPipelines) 产出 snapshot-backed profile 的特征快照、manifest、ranking 和 release summary。 3. 非 snapshot profile 使用平台 market-data feed 提供的 `market_history`,不需要 snapshot artifact。 4. LongBridge 只读取 `RUNTIME_TARGET_JSON`、策略 profile、snapshot/config 路径和平台 market scope。 @@ -28,10 +28,12 @@ QuantStrategyLab 现有平台仓库里,能做港股股票交易运行时接入 | Profile | Domain | Inputs | Target mode | Snapshot manifest | Status | | --- | --- | --- | --- | --- | --- | -| `hk_blue_chip_leader_rotation` | `hk_equity` | `feature_snapshot` | `weight` | required | eligible but disabled | -| `hk_index_mean_reversion` | `hk_equity` | `market_history` | `weight` | not required | eligible but disabled | -| `hk_etf_regime_rotation` | `hk_equity` | `market_history` | `weight` | not required | eligible but disabled | -| `hk_listed_global_etf_rotation` | `hk_equity` | `market_history` | `weight` | not required | runtime-enabled; not deployed by default | +| `hk_listed_global_etf_rotation` | `hk_equity` | `market_history` | `weight` | not required | runtime-enabled; platform-selectable | +| `hk_blue_chip_leader_rotation` | `hk_equity` | `feature_snapshot` | `weight` | required | snapshot scaffold; not platform-selectable | +| `hk_index_mean_reversion` | `hk_equity` | `market_history` | `weight` | not required | research/backtest only; not platform-selectable | +| `hk_etf_regime_rotation` | `hk_equity` | `market_history` | `weight` | not required | research/backtest only; not platform-selectable | + +`scripts/print_strategy_profile_status.py` 只显示平台可选 profile,因此只会列出 `hk_listed_global_etf_rotation` 这一条港股 profile。其他港股候选继续保留在研究文档和 snapshot pipeline,不应该出现在 Cloud Run switch plan 里。 未来启用 snapshot-backed profile 后的最小策略配置示例;当前不要写入 Cloud Run: @@ -135,6 +137,6 @@ gh workflow run sync-cloud-run-env.yml \ ## 风险和注意事项 - `XHKG` 是否可用取决于部署环境里的 `pandas_market_calendars` 版本;如不可用,可用 `LONGBRIDGE_MARKET_CALENDAR` 临时覆盖。 -- `hk_listed_global_etf_rotation` 已在策略包 runtime-enabled,但生产 Cloud Run 仍保持原配置;`hk_blue_chip_leader_rotation`、`hk_index_mean_reversion`、`hk_etf_regime_rotation` 仍未启用,不要写入生产 Cloud Run。 +- `hk_listed_global_etf_rotation` 已在策略包 runtime-enabled,但生产 Cloud Run 仍保持原配置;`hk_blue_chip_leader_rotation`、`hk_index_mean_reversion`、`hk_etf_regime_rotation` 不进入平台可选列表,不要写入生产 Cloud Run。 - 港股 `market_history` profile 投入生产前,需要先用 LongBridge HK 行情 feed 对 `02800`、`03033`、`02822`、`02840`、`03110`、`03188`、`02834`、`03175` 做 dry-run 校验,不提交真实订单。 - LongBridge 下单仍保持整数股规则;如果未来港股策略涉及碎股或特殊交易单位,需要在策略层明确 lot-size 约束后再扩展。 diff --git a/strategy_registry.py b/strategy_registry.py index d670fd0..959faf0 100644 --- a/strategy_registry.py +++ b/strategy_registry.py @@ -117,7 +117,7 @@ def describe_platform_runtime_requirements(profile: str | None, *, platform_id: ), supported_capabilities=frozenset(), ) -ELIGIBLE_STRATEGY_PROFILES = derive_eligible_profiles_for_platform( +_STRUCTURALLY_ELIGIBLE_STRATEGY_PROFILES = derive_eligible_profiles_for_platform( STRATEGY_CATALOG, capability_matrix=PLATFORM_CAPABILITY_MATRIX, runtime_adapter_loader=lambda profile: get_platform_runtime_adapter( @@ -125,6 +125,8 @@ def describe_platform_runtime_requirements(profile: str | None, *, platform_id: platform_id=LONGBRIDGE_PLATFORM, ), ) - frozenset({NASDAQ_SP500_SMART_DCA_PROFILE}) +# Keep research-only and snapshot-scaffold HK profiles out of platform switch/status output. +ELIGIBLE_STRATEGY_PROFILES = _STRUCTURALLY_ELIGIBLE_STRATEGY_PROFILES & LONGBRIDGE_ROLLOUT_ALLOWLIST LONGBRIDGE_ENABLED_PROFILES = derive_enabled_profiles_for_platform( STRATEGY_CATALOG, capability_matrix=PLATFORM_CAPABILITY_MATRIX, diff --git a/tests/test_runtime_config_support.py b/tests/test_runtime_config_support.py index 512394e..ee855ec 100644 --- a/tests/test_runtime_config_support.py +++ b/tests/test_runtime_config_support.py @@ -78,7 +78,7 @@ def expected_longbridge_enabled_profiles(actual_profiles) -> frozenset[str]: def expected_longbridge_profiles(actual_profiles) -> frozenset[str]: - return expected_longbridge_enabled_profiles(actual_profiles) | HK_DISABLED_PROFILES + return expected_longbridge_enabled_profiles(actual_profiles) def runtime_target_json( @@ -603,39 +603,6 @@ def test_platform_profile_status_matrix_matches_current_longbridge_rollout(self) by_profile["mega_cap_leader_rotation_top50_balanced"]["display_name"], "Mega Cap Leader Rotation Top50 Balanced", ) - self.assertEqual( - by_profile["hk_blue_chip_leader_rotation"], - { - "canonical_profile": "hk_blue_chip_leader_rotation", - "display_name": "HK Blue Chip Leader Rotation", - "domain": "hk_equity", - "eligible": True, - "enabled": False, - "platform": "longbridge", - }, - ) - self.assertEqual( - by_profile["hk_index_mean_reversion"], - { - "canonical_profile": "hk_index_mean_reversion", - "display_name": "HK Index Mean Reversion", - "domain": "hk_equity", - "eligible": True, - "enabled": False, - "platform": "longbridge", - }, - ) - self.assertEqual( - by_profile["hk_etf_regime_rotation"], - { - "canonical_profile": "hk_etf_regime_rotation", - "display_name": "HK ETF Regime Rotation", - "domain": "hk_equity", - "eligible": True, - "enabled": False, - "platform": "longbridge", - }, - ) self.assertEqual( by_profile["hk_listed_global_etf_rotation"], { @@ -647,6 +614,8 @@ def test_platform_profile_status_matrix_matches_current_longbridge_rollout(self) "platform": "longbridge", }, ) + for profile in HK_DISABLED_PROFILES: + self.assertNotIn(profile, by_profile) def test_loads_feature_snapshot_env_for_tech_profile(self): with patch.dict( @@ -768,12 +737,9 @@ def test_print_strategy_profile_status_json_matches_registry(self): self.assertEqual(by_profile["mega_cap_leader_rotation_top50_balanced"]["input_mode"], "feature_snapshot") self.assertTrue(by_profile["mega_cap_leader_rotation_top50_balanced"]["requires_snapshot_artifacts"]) self.assertFalse(by_profile["mega_cap_leader_rotation_top50_balanced"]["requires_strategy_config_path"]) - self.assertEqual(by_profile["hk_blue_chip_leader_rotation"]["profile_group"], "snapshot_backed") - self.assertEqual(by_profile["hk_blue_chip_leader_rotation"]["input_mode"], "feature_snapshot") - self.assertTrue(by_profile["hk_blue_chip_leader_rotation"]["requires_snapshot_artifacts"]) - self.assertTrue(by_profile["hk_blue_chip_leader_rotation"]["requires_snapshot_manifest_path"]) - self.assertFalse(by_profile["hk_blue_chip_leader_rotation"]["requires_strategy_config_path"]) - for profile in ("hk_index_mean_reversion", "hk_etf_regime_rotation", "hk_listed_global_etf_rotation"): + for profile in ("hk_index_mean_reversion", "hk_etf_regime_rotation", "hk_blue_chip_leader_rotation"): + self.assertNotIn(profile, by_profile) + for profile in ("hk_listed_global_etf_rotation",): self.assertEqual(by_profile[profile]["profile_group"], "direct_runtime_inputs") self.assertEqual(by_profile[profile]["input_mode"], "market_history") self.assertFalse(by_profile[profile]["requires_snapshot_artifacts"]) @@ -798,18 +764,15 @@ def test_print_strategy_profile_status_table_contains_expected_headers(self): self.assertIn("requires_snapshot_artifacts", result.stdout) self.assertIn("soxl_soxx_trend_income", result.stdout) self.assertIn("global_etf_rotation", result.stdout) - self.assertIn("hk_blue_chip_leader_rotation", result.stdout) - self.assertIn("hk_index_mean_reversion", result.stdout) - self.assertIn("hk_etf_regime_rotation", result.stdout) self.assertIn("hk_listed_global_etf_rotation", result.stdout) self.assertIn("russell_1000_multi_factor_defensive", result.stdout) self.assertIn("Global ETF Rotation", result.stdout) - self.assertIn("HK Blue Chip Leader Rotation", result.stdout) - self.assertIn("HK Index Mean Reversion", result.stdout) - self.assertIn("HK ETF Regime Rotation", result.stdout) self.assertIn("HK-listed Global ETF Rotation", result.stdout) self.assertIn("Russell 1000 Multi-Factor", result.stdout) self.assertIn("Tech/Communication Pullback Enhancement", result.stdout) + self.assertNotIn("hk_blue_chip_leader_rotation", result.stdout) + self.assertNotIn("hk_index_mean_reversion", result.stdout) + self.assertNotIn("hk_etf_regime_rotation", result.stdout) def test_print_strategy_switch_env_plan_for_global_etf_rotation(self): result = subprocess.run(