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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 9 additions & 7 deletions docs/hk_equity_runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -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。
Expand All @@ -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:

Expand Down Expand Up @@ -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 约束后再扩展。
4 changes: 3 additions & 1 deletion strategy_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,16 @@ 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(
profile,
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,
Expand Down
55 changes: 9 additions & 46 deletions tests/test_runtime_config_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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"],
{
Expand All @@ -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(
Expand Down Expand Up @@ -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"])
Expand All @@ -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(
Expand Down