From 5eafd86b14884ffccf7d68638fcfe15d2fe895f1 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:53:47 +0800 Subject: [PATCH 1/2] Require explicit strategy profile selection --- README.md | 54 ++++++++++++------------ runtime_config_support.py | 2 - scripts/print_strategy_profile_status.py | 2 - strategy_registry.py | 31 +++++++++----- tests/test_runtime_config_support.py | 38 +++++++++++------ 5 files changed, 74 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index c3042e4..35a8b93 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Quant system on LongPort OpenAPI and Google Cloud Run. This repository uses `QuantPlatformKit` for LongPort token handling, context bootstrap, account snapshot access, market data, and order submission. Cloud Run deploys this repository directly. -The LongBridge runtime can execute all seven live `us_equity` profiles from `UsEquityStrategies`; `LongBridgePlatform` keeps the LongPort runtime, token refresh, execution, and notification flow. +The LongBridge runtime can execute all eight live `us_equity` profiles from `UsEquityStrategies`; `LongBridgePlatform` keeps the LongPort runtime, token refresh, execution, and notification flow. Full strategy documentation now lives in [`UsEquityStrategies`](https://github.com/QuantStrategyLab/UsEquityStrategies). The sections below focus on LongBridge runtime behavior, profile enablement, deployment, and credentials. This runtime matrix is the authoritative enablement source for LongBridge. `UsEquityStrategies` carries strategy-layer logic, cadence, compatibility, and metadata. @@ -29,15 +29,16 @@ Platform execution no longer depends on `strategy/allocation.py` or hard-coded s **LongBridge profile status** -| Canonical profile | Display name | Eligible | Enabled | Default | Rollback | Domain | Runtime note | -| --- | --- | --- | --- | --- | --- | --- | --- | -| `global_etf_rotation` | Global ETF Rotation | Yes | Yes | No | No | `us_equity` | enabled weight-mode rotation line | -| `russell_1000_multi_factor_defensive` | Russell 1000 Multi-Factor | Yes | Yes | No | No | `us_equity` | enabled feature-snapshot stock baseline | -| `mega_cap_leader_rotation_dynamic_top20` | Mega Cap Leader Rotation Dynamic Top20 | Yes | Yes | No | No | `us_equity` | selectable monthly feature-snapshot leader rotation | -| `dynamic_mega_leveraged_pullback` | Dynamic Mega Leveraged Pullback | Yes | Yes | No | No | `us_equity` | selectable 2x mega-cap pullback line | -| `soxl_soxx_trend_income` | SOXL/SOXX Semiconductor Trend Income | Yes | Yes | Yes | Yes | `us_equity` | current LongBridge default | -| `tqqq_growth_income` | TQQQ Growth Income | Yes | Yes | No | No | `us_equity` | current SG dry-run line | -| `tech_communication_pullback_enhancement` | Tech/Communication Pullback Enhancement | Yes | Yes | No | No | `us_equity` | current HK feature-snapshot line | +| Canonical profile | Display name | Eligible | Enabled | Domain | Runtime note | +| --- | --- | --- | --- | --- | --- | +| `global_etf_rotation` | Global ETF Rotation | Yes | Yes | `us_equity` | enabled weight-mode rotation line | +| `russell_1000_multi_factor_defensive` | Russell 1000 Multi-Factor | Yes | Yes | `us_equity` | enabled feature-snapshot stock baseline | +| `mega_cap_leader_rotation_aggressive` | Mega Cap Leader Rotation Aggressive | Yes | Yes | `us_equity` | selectable aggressive monthly feature-snapshot leader rotation | +| `mega_cap_leader_rotation_dynamic_top20` | Mega Cap Leader Rotation Dynamic Top20 | Yes | Yes | `us_equity` | selectable monthly feature-snapshot leader rotation | +| `dynamic_mega_leveraged_pullback` | Dynamic Mega Leveraged Pullback | Yes | Yes | `us_equity` | selectable 2x mega-cap pullback line | +| `soxl_soxx_trend_income` | SOXL/SOXX Semiconductor Trend Income | Yes | Yes | `us_equity` | current SG deployment | +| `tqqq_growth_income` | TQQQ Growth Income | Yes | Yes | `us_equity` | selectable growth line | +| `tech_communication_pullback_enhancement` | Tech/Communication Pullback Enhancement | Yes | Yes | `us_equity` | current HK feature-snapshot line | Check the current matrix locally: @@ -63,7 +64,7 @@ Telegram notifications include structured execution and heartbeat messages, with | `LONGPORT_APP_SECRET` | Yes | LongPort OpenAPI app secret (for token refresh); recommended to inject from the region-specific Secret Manager secret for this deployment, such as `longport-app-secret-hk` / `longport-app-secret-sg` | | `LONGPORT_SECRET_NAME` | No | Secret Manager secret name for LongPort token (default: `longport_token_hk`) | | `ACCOUNT_PREFIX` | No | Alert/log prefix for account/environment (default: `DEFAULT`) | -| `STRATEGY_PROFILE` | No | Strategy profile selector (default: `soxl_soxx_trend_income`; enabled values include `dynamic_mega_leveraged_pullback`, `global_etf_rotation`, `mega_cap_leader_rotation_dynamic_top20`, `russell_1000_multi_factor_defensive`, `soxl_soxx_trend_income`, `tech_communication_pullback_enhancement`, and `tqqq_growth_income`) | +| `STRATEGY_PROFILE` | Yes | Strategy profile selector. Set explicitly per deployment; enabled values include `dynamic_mega_leveraged_pullback`, `global_etf_rotation`, `mega_cap_leader_rotation_aggressive`, `mega_cap_leader_rotation_dynamic_top20`, `russell_1000_multi_factor_defensive`, `soxl_soxx_trend_income`, `tech_communication_pullback_enhancement`, and `tqqq_growth_income` | | `ACCOUNT_REGION` | No | Account region marker for platform-style deployment (e.g. `HK`, `SG`; defaults to `ACCOUNT_PREFIX` / `DEFAULT`) | | `LONGBRIDGE_DRY_RUN_ONLY` | No | Set to `true` to keep the selected deployment in dry-run mode. | | `INCOME_THRESHOLD_USD` | No | Optional override for the `tqqq_growth_income` income-layer threshold. Leave unset to use the strategy package default. | @@ -89,7 +90,7 @@ Deploy the same codebase as multiple Cloud Run services (e.g. `HK` and `SG`) by - `LONGPORT_SECRET_NAME`: point to different secrets (e.g. `longport_token_hk`, `longport_token_sg`) - `ACCOUNT_PREFIX`: e.g. `HK`, `SG` (all Telegram/log alerts will include `[ACCOUNT_PREFIX]`) -- `STRATEGY_PROFILE`: set per service; current live examples are `tech_communication_pullback_enhancement` on HK and `tqqq_growth_income` on SG +- `STRATEGY_PROFILE`: set per service; current live examples are `tech_communication_pullback_enhancement` on HK and `soxl_soxx_trend_income` on SG - Current strategy domain is `us_equity`. `STRATEGY_PROFILE` now goes through a platform capability matrix plus a rollout allowlist derived from `runtime_enabled` strategy metadata: `eligible` means the platform can run it in theory, `enabled` means the current rollout really allows it. - `ACCOUNT_REGION`: explicitly mark the deployed account region (`HK` / `SG`); if unset, the app falls back to `ACCOUNT_PREFIX` or `DEFAULT` - `LONGBRIDGE_DRY_RUN_ONLY`: set per service when that deployment should stay dry-run @@ -116,7 +117,7 @@ Recommended setup: - **GitHub Environment: `longbridge-sg`** - Variables: `CLOUD_RUN_REGION`, `CLOUD_RUN_SERVICE`, `ACCOUNT_PREFIX`, `ACCOUNT_REGION`, `STRATEGY_PROFILE`, `LONGPORT_SECRET_NAME`, `LONGPORT_APP_KEY_SECRET_NAME`, `LONGPORT_APP_SECRET_SECRET_NAME` - Optional variables: `LONGBRIDGE_FEATURE_SNAPSHOT_PATH`, `LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH`, `LONGBRIDGE_STRATEGY_CONFIG_PATH`, `LONGBRIDGE_DRY_RUN_ONLY`, `INCOME_THRESHOLD_USD`, `QQQI_INCOME_RATIO` - - Current live example: `STRATEGY_PROFILE=tqqq_growth_income` + - Current live example: `STRATEGY_PROFILE=soxl_soxx_trend_income` - Recommended secret-name values: `longport-app-key-sg`, `longport-app-secret-sg` On every push to `main`, the workflow updates both Cloud Run services with the shared and per-environment values above, and removes `TELEGRAM_CHAT_ID` from each Cloud Run service. @@ -157,7 +158,7 @@ IAM: the Cloud Run service account needs **Secret Manager Admin** (or Secret Acc 基于 LongPort OpenAPI 和 Google Cloud Run 的量化交易系统。 这个仓库通过 `QuantPlatformKit` 复用 LongPort token 处理、上下文初始化、账户快照、行情读取和下单逻辑。Cloud Run 直接部署这个仓库。 -`LongBridgePlatform` 现在可直接执行 `UsEquityStrategies` 里的全部 7 条 live `us_equity` 策略:`dynamic_mega_leveraged_pullback`、`global_etf_rotation`、`mega_cap_leader_rotation_dynamic_top20`、`russell_1000_multi_factor_defensive`、`soxl_soxx_trend_income`、`tqqq_growth_income` 和 `tech_communication_pullback_enhancement`;仓库本身继续保留 LongPort 运行时、token 刷新、执行和通知流程。 +`LongBridgePlatform` 现在可直接执行 `UsEquityStrategies` 里的全部 8 条 live `us_equity` 策略:`dynamic_mega_leveraged_pullback`、`global_etf_rotation`、`mega_cap_leader_rotation_aggressive`、`mega_cap_leader_rotation_dynamic_top20`、`russell_1000_multi_factor_defensive`、`soxl_soxx_trend_income`、`tqqq_growth_income` 和 `tech_communication_pullback_enhancement`;仓库本身继续保留 LongPort 运行时、token 刷新、执行和通知流程。 完整策略说明现在放在 [`UsEquityStrategies`](https://github.com/QuantStrategyLab/UsEquityStrategies)。下面这些章节只保留 LongBridge 运行时、profile 启用状态、部署和凭据说明。 @@ -174,15 +175,16 @@ IAM: the Cloud Run service account needs **Secret Manager Admin** (or Secret Acc **LongBridge profile status** -| Canonical profile | Display name | Eligible | Enabled | Default | Rollback | Domain | Runtime note | -| --- | --- | --- | --- | --- | --- | --- | --- | -| `global_etf_rotation` | Global ETF Rotation | Yes | Yes | No | No | `us_equity` | 已启用的 weight-mode 轮动线 | -| `russell_1000_multi_factor_defensive` | Russell 1000 Multi-Factor | Yes | Yes | No | No | `us_equity` | 已启用的 feature-snapshot 个股基线 | -| `mega_cap_leader_rotation_dynamic_top20` | Mega Cap Leader Rotation Dynamic Top20 | Yes | Yes | No | No | `us_equity` | 可选的月度 feature-snapshot 龙头轮动线 | -| `dynamic_mega_leveraged_pullback` | Dynamic Mega Leveraged Pullback | Yes | Yes | No | No | `us_equity` | 可选的 2x 龙头回调线 | -| `soxl_soxx_trend_income` | SOXL/SOXX 半导体趋势收益 | Yes | Yes | Yes | Yes | `us_equity` | 当前 LongBridge 默认回退线 | -| `tqqq_growth_income` | TQQQ 增长收益 | Yes | Yes | No | No | `us_equity` | 当前 SG dry-run 线路 | -| `tech_communication_pullback_enhancement` | 科技通信回调增强 | Yes | Yes | No | No | `us_equity` | 当前 HK feature-snapshot 线路 | +| Canonical profile | Display name | Eligible | Enabled | Domain | Runtime note | +| --- | --- | --- | --- | --- | --- | +| `global_etf_rotation` | Global ETF Rotation | Yes | Yes | `us_equity` | 已启用的 weight-mode 轮动线 | +| `russell_1000_multi_factor_defensive` | Russell 1000 Multi-Factor | Yes | Yes | `us_equity` | 已启用的 feature-snapshot 个股基线 | +| `mega_cap_leader_rotation_aggressive` | Mega Cap Leader Rotation Aggressive | Yes | Yes | `us_equity` | 可选的激进月度 feature-snapshot 龙头轮动线 | +| `mega_cap_leader_rotation_dynamic_top20` | Mega Cap Leader Rotation Dynamic Top20 | Yes | Yes | `us_equity` | 可选的月度 feature-snapshot 龙头轮动线 | +| `dynamic_mega_leveraged_pullback` | Dynamic Mega Leveraged Pullback | Yes | Yes | `us_equity` | 可选的 2x 龙头回调线 | +| `soxl_soxx_trend_income` | SOXL/SOXX 半导体趋势收益 | Yes | Yes | `us_equity` | 当前 SG 部署线路 | +| `tqqq_growth_income` | TQQQ 增长收益 | Yes | Yes | `us_equity` | 可选增长线路 | +| `tech_communication_pullback_enhancement` | 科技通信回调增强 | Yes | Yes | `us_equity` | 当前 HK feature-snapshot 线路 | 本地可直接查看当前矩阵: @@ -208,7 +210,7 @@ Telegram 通知包含结构化的调仓和心跳消息,支持中英文切换 | `LONGPORT_APP_SECRET` | 是 | LongPort OpenAPI 应用密钥(用于刷新 Token);建议从当前部署对应区域的 Secret Manager 密钥注入,例如 `longport-app-secret-hk` / `longport-app-secret-sg` | | `LONGPORT_SECRET_NAME` | 否 | Secret Manager 中的密钥名称(默认: `longport_token_hk`) | | `ACCOUNT_PREFIX` | 否 | 通知/日志前缀,区分账户环境(默认: `DEFAULT`) | -| `STRATEGY_PROFILE` | 否 | 策略档位选择(默认: `soxl_soxx_trend_income`;已启用值还包括 `dynamic_mega_leveraged_pullback`、`global_etf_rotation`、`mega_cap_leader_rotation_dynamic_top20`、`russell_1000_multi_factor_defensive`、`soxl_soxx_trend_income`、`tech_communication_pullback_enhancement` 和 `tqqq_growth_income`) | +| `STRATEGY_PROFILE` | 是 | 策略档位选择。每个部署都要显式设置;已启用值包括 `dynamic_mega_leveraged_pullback`、`global_etf_rotation`、`mega_cap_leader_rotation_aggressive`、`mega_cap_leader_rotation_dynamic_top20`、`russell_1000_multi_factor_defensive`、`soxl_soxx_trend_income`、`tech_communication_pullback_enhancement` 和 `tqqq_growth_income` | | `ACCOUNT_REGION` | 否 | 平台化部署时的账户区域标记(如 `HK`、`SG`;默认按 `ACCOUNT_PREFIX` / `DEFAULT` 推断) | | `LONGBRIDGE_DRY_RUN_ONLY` | 否 | 设为 `true` 时,该部署保持 dry-run。 | | `INCOME_THRESHOLD_USD` | 否 | 可选的 `tqqq_growth_income` 收入层启动阈值覆盖。不填时使用策略包默认值。 | @@ -234,7 +236,7 @@ Secret Manager 中需存在 `LONGPORT_SECRET_NAME` 指定的密钥(默认: `lo - `LONGPORT_SECRET_NAME`: 指向不同密钥(如 `longport_token_hk`、`longport_token_sg`) - `ACCOUNT_PREFIX`: 如 `HK`、`SG`(所有通知/日志将包含 `[ACCOUNT_PREFIX]`) -- `STRATEGY_PROFILE`: 按服务分别设置;当前线上 HK 用 `tech_communication_pullback_enhancement`,SG 用 `tqqq_growth_income` +- `STRATEGY_PROFILE`: 按服务分别设置;当前线上 HK 用 `tech_communication_pullback_enhancement`,SG 用 `soxl_soxx_trend_income` - 当前策略域是 `us_equity`。`STRATEGY_PROFILE` 现在会先经过平台能力矩阵,再经过从 `runtime_enabled` 策略元数据派生的 rollout allowlist:`eligible` 表示平台理论可跑,`enabled` 表示当前 rollout 真正放开。 - `ACCOUNT_REGION`: 显式标记部署账户区域(`HK` / `SG`);未设置时会回退到 `ACCOUNT_PREFIX` 或 `DEFAULT` - `LONGBRIDGE_DRY_RUN_ONLY`: 需要保持模拟运行时按服务单独设置 @@ -261,7 +263,7 @@ Secret Manager 中需存在 `LONGPORT_SECRET_NAME` 指定的密钥(默认: `lo - **GitHub Environment: `longbridge-sg`** - Variables: `CLOUD_RUN_REGION`、`CLOUD_RUN_SERVICE`、`ACCOUNT_PREFIX`、`ACCOUNT_REGION`、`STRATEGY_PROFILE`、`LONGPORT_SECRET_NAME`、`LONGPORT_APP_KEY_SECRET_NAME`、`LONGPORT_APP_SECRET_SECRET_NAME` - 可选 Variables: `LONGBRIDGE_FEATURE_SNAPSHOT_PATH`、`LONGBRIDGE_FEATURE_SNAPSHOT_MANIFEST_PATH`、`LONGBRIDGE_STRATEGY_CONFIG_PATH`、`LONGBRIDGE_DRY_RUN_ONLY`、`INCOME_THRESHOLD_USD`、`QQQI_INCOME_RATIO` - - 当前线上示例:`STRATEGY_PROFILE=tqqq_growth_income` + - 当前线上示例:`STRATEGY_PROFILE=soxl_soxx_trend_income` - 建议的 secret-name 值:`longport-app-key-sg`、`longport-app-secret-sg` 每次 push 到 `main` 时,这个 workflow 会分别更新两个 Cloud Run 服务,把共享和各自隔离的变量同步进去,并删除旧的 `TELEGRAM_CHAT_ID`。 diff --git a/runtime_config_support.py b/runtime_config_support.py index b5417e2..57fe1b3 100644 --- a/runtime_config_support.py +++ b/runtime_config_support.py @@ -7,7 +7,6 @@ from quant_platform_kit.common.strategies import derive_strategy_artifact_paths from strategy_registry import ( - DEFAULT_STRATEGY_PROFILE as PLATFORM_DEFAULT_STRATEGY_PROFILE, LONGBRIDGE_PLATFORM, resolve_strategy_definition, resolve_strategy_metadata, @@ -15,7 +14,6 @@ from us_equity_strategies import get_strategy_catalog DEFAULT_ACCOUNT_REGION = "DEFAULT" -DEFAULT_STRATEGY_PROFILE = PLATFORM_DEFAULT_STRATEGY_PROFILE DEFAULT_LONGPORT_SECRET_NAME = "longport_token_hk" diff --git a/scripts/print_strategy_profile_status.py b/scripts/print_strategy_profile_status.py index 06122e1..a3af964 100644 --- a/scripts/print_strategy_profile_status.py +++ b/scripts/print_strategy_profile_status.py @@ -43,8 +43,6 @@ def _print_table(rows: list[dict[str, object]]) -> None: "requires_strategy_config_path", "eligible", "enabled", - "is_default", - "is_rollback", "domain", ) widths = { diff --git a/strategy_registry.py b/strategy_registry.py index c42181c..fb341a7 100644 --- a/strategy_registry.py +++ b/strategy_registry.py @@ -23,9 +23,6 @@ LONGBRIDGE_PLATFORM = "longbridge" -DEFAULT_STRATEGY_PROFILE = "soxl_soxx_trend_income" -ROLLBACK_STRATEGY_PROFILE = DEFAULT_STRATEGY_PROFILE - LONGBRIDGE_ROLLOUT_ALLOWLIST = get_runtime_enabled_profiles() PLATFORM_SUPPORTED_DOMAINS: dict[str, frozenset[str]] = { @@ -71,11 +68,17 @@ platform_id=LONGBRIDGE_PLATFORM, supported_domains=PLATFORM_SUPPORTED_DOMAINS[LONGBRIDGE_PLATFORM], enabled_profiles=LONGBRIDGE_ENABLED_PROFILES, - default_profile=DEFAULT_STRATEGY_PROFILE, - rollback_profile=ROLLBACK_STRATEGY_PROFILE, + default_profile="", + rollback_profile="", + require_explicit_profile=True, ) SUPPORTED_STRATEGY_PROFILES = LONGBRIDGE_ENABLED_PROFILES +_SELECTION_ROLE_FIELDS = frozenset({"is_default", "is_rollback"}) + + +def _without_selection_role_fields(row: dict[str, object]) -> dict[str, object]: + return {key: value for key, value in row.items() if key not in _SELECTION_ROLE_FIELDS} def get_eligible_profiles_for_platform(platform_id: str) -> frozenset[str]: @@ -89,15 +92,21 @@ def get_supported_profiles_for_platform(platform_id: str) -> frozenset[str]: def get_platform_profile_matrix() -> list[dict[str, object]]: - return build_platform_profile_matrix(STRATEGY_CATALOG, policy=PLATFORM_POLICY) + return [ + _without_selection_role_fields(row) + for row in build_platform_profile_matrix(STRATEGY_CATALOG, policy=PLATFORM_POLICY) + ] def get_platform_profile_status_matrix() -> list[dict[str, object]]: - return build_platform_profile_status_matrix( - STRATEGY_CATALOG, - policy=PLATFORM_POLICY, - eligible_profiles=ELIGIBLE_STRATEGY_PROFILES, - ) + return [ + _without_selection_role_fields(row) + for row in build_platform_profile_status_matrix( + STRATEGY_CATALOG, + policy=PLATFORM_POLICY, + eligible_profiles=ELIGIBLE_STRATEGY_PROFILES, + ) + ] def resolve_strategy_definition( diff --git a/tests/test_runtime_config_support.py b/tests/test_runtime_config_support.py index ff2bfde..00f9fcb 100644 --- a/tests/test_runtime_config_support.py +++ b/tests/test_runtime_config_support.py @@ -20,7 +20,6 @@ from runtime_config_support import ( DEFAULT_ACCOUNT_REGION, DEFAULT_LONGPORT_SECRET_NAME, - DEFAULT_STRATEGY_PROFILE, infer_account_region, load_platform_runtime_settings, ) @@ -34,15 +33,18 @@ ) +SAMPLE_STRATEGY_PROFILE = "soxl_soxx_trend_income" + + class RuntimeConfigSupportTests(unittest.TestCase): - def test_load_platform_runtime_settings_uses_defaults(self): - with patch.dict(os.environ, {}, clear=True): + def test_load_platform_runtime_settings_uses_defaults_with_explicit_strategy_profile(self): + with patch.dict(os.environ, {"STRATEGY_PROFILE": SAMPLE_STRATEGY_PROFILE}, clear=True): settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1") self.assertEqual(settings.project_id, "project-1") self.assertEqual(settings.secret_name, DEFAULT_LONGPORT_SECRET_NAME) self.assertEqual(settings.account_prefix, "DEFAULT") - self.assertEqual(settings.strategy_profile, DEFAULT_STRATEGY_PROFILE) + self.assertEqual(settings.strategy_profile, SAMPLE_STRATEGY_PROFILE) self.assertEqual(settings.strategy_display_name, "SOXL/SOXX Semiconductor Trend Income") self.assertEqual(settings.strategy_domain, US_EQUITY_DOMAIN) self.assertEqual(settings.account_region, DEFAULT_ACCOUNT_REGION) @@ -55,6 +57,11 @@ def test_load_platform_runtime_settings_uses_defaults(self): self.assertIsNone(settings.feature_snapshot_path) self.assertIsNone(settings.strategy_config_path) + def test_load_platform_runtime_settings_requires_strategy_profile(self): + with patch.dict(os.environ, {}, clear=True): + with self.assertRaisesRegex(EnvironmentError, "STRATEGY_PROFILE is required"): + load_platform_runtime_settings(project_id_resolver=lambda: "project-1") + def test_platform_supported_profiles_are_filtered_by_registry(self): self.assertEqual( get_supported_profiles_for_platform(LONGBRIDGE_PLATFORM), @@ -62,6 +69,7 @@ def test_platform_supported_profiles_are_filtered_by_registry(self): { "dynamic_mega_leveraged_pullback", "global_etf_rotation", + "mega_cap_leader_rotation_aggressive", "mega_cap_leader_rotation_dynamic_top20", "russell_1000_multi_factor_defensive", "tqqq_growth_income", @@ -78,6 +86,7 @@ def test_platform_eligible_profiles_are_exposed_by_capability_matrix(self): { "dynamic_mega_leveraged_pullback", "global_etf_rotation", + "mega_cap_leader_rotation_aggressive", "mega_cap_leader_rotation_dynamic_top20", "russell_1000_multi_factor_defensive", "tqqq_growth_income", @@ -88,7 +97,11 @@ def test_platform_eligible_profiles_are_exposed_by_capability_matrix(self): ) def test_dry_run_only_is_loaded_from_env(self): - with patch.dict(os.environ, {"LONGBRIDGE_DRY_RUN_ONLY": "true"}, clear=True): + with patch.dict( + os.environ, + {"STRATEGY_PROFILE": SAMPLE_STRATEGY_PROFILE, "LONGBRIDGE_DRY_RUN_ONLY": "true"}, + clear=True, + ): settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1") self.assertTrue(settings.dry_run_only) @@ -149,11 +162,12 @@ def test_unsupported_strategy_profile_fails_fast(self): with self.assertRaisesRegex(ValueError, "Unsupported STRATEGY_PROFILE"): load_platform_runtime_settings(project_id_resolver=lambda: "project-1") - def test_platform_profile_matrix_marks_default(self): + def test_platform_profile_matrix_exposes_profiles_without_selection_roles(self): rows = get_platform_profile_matrix() by_profile = {row["canonical_profile"]: row for row in rows} - self.assertEqual(by_profile[DEFAULT_STRATEGY_PROFILE]["display_name"], "SOXL/SOXX Semiconductor Trend Income") - self.assertTrue(by_profile[DEFAULT_STRATEGY_PROFILE]["is_default"]) + self.assertEqual(by_profile[SAMPLE_STRATEGY_PROFILE]["display_name"], "SOXL/SOXX Semiconductor Trend Income") + self.assertNotIn("is_default", by_profile[SAMPLE_STRATEGY_PROFILE]) + self.assertNotIn("is_rollback", by_profile[SAMPLE_STRATEGY_PROFILE]) def test_platform_profile_status_matrix_matches_current_longbridge_rollout(self): rows = get_platform_profile_status_matrix() @@ -164,6 +178,7 @@ def test_platform_profile_status_matrix_matches_current_longbridge_rollout(self) { "global_etf_rotation", "dynamic_mega_leveraged_pullback", + "mega_cap_leader_rotation_aggressive", "mega_cap_leader_rotation_dynamic_top20", "russell_1000_multi_factor_defensive", "tqqq_growth_income", @@ -179,8 +194,6 @@ def test_platform_profile_status_matrix_matches_current_longbridge_rollout(self) "domain": "us_equity", "eligible": True, "enabled": True, - "is_default": True, - "is_rollback": True, "platform": "longbridge", }, ) @@ -200,6 +213,9 @@ def test_platform_profile_status_matrix_matches_current_longbridge_rollout(self) self.assertTrue(by_profile["mega_cap_leader_rotation_dynamic_top20"]["eligible"]) self.assertTrue(by_profile["mega_cap_leader_rotation_dynamic_top20"]["enabled"]) self.assertEqual(by_profile["mega_cap_leader_rotation_dynamic_top20"]["display_name"], "Mega Cap Leader Rotation Dynamic Top20") + self.assertTrue(by_profile["mega_cap_leader_rotation_aggressive"]["eligible"]) + self.assertTrue(by_profile["mega_cap_leader_rotation_aggressive"]["enabled"]) + self.assertEqual(by_profile["mega_cap_leader_rotation_aggressive"]["display_name"], "Mega Cap Leader Rotation Aggressive") self.assertTrue(by_profile["dynamic_mega_leveraged_pullback"]["eligible"]) self.assertTrue(by_profile["dynamic_mega_leveraged_pullback"]["enabled"]) self.assertEqual(by_profile["dynamic_mega_leveraged_pullback"]["display_name"], "Dynamic Mega Leveraged Pullback") @@ -264,8 +280,6 @@ def test_print_strategy_profile_status_json_matches_registry(self): "domain", "eligible", "enabled", - "is_default", - "is_rollback", "platform", ) } From b2b4e0edb30d01356d50eb2a17a6d2d5dd8b8710 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:22:26 +0800 Subject: [PATCH 2/2] Set explicit profile in shared chat fallback test --- tests/test_shared_chat_id_fallback.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_shared_chat_id_fallback.py b/tests/test_shared_chat_id_fallback.py index 2de0cc6..4225b84 100644 --- a/tests/test_shared_chat_id_fallback.py +++ b/tests/test_shared_chat_id_fallback.py @@ -137,6 +137,7 @@ def test_global_telegram_chat_id_is_used(self): os.environ, { "GLOBAL_TELEGRAM_CHAT_ID": "shared-chat-id", + "STRATEGY_PROFILE": "soxl_soxx_trend_income", }, clear=False, ):