diff --git a/README.md b/README.md index 3352dcd..4e7c604 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ This repository uses `QuantPlatformKit` for LongPort token handling, context boo The `semiconductor_rotation_income` allocation logic is loaded 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#semiconductor_rotation_income). The sections below focus on execution-side defaults and runtime behavior. +This runtime matrix is the authoritative enablement source for LongBridge. `UsEquityStrategies` only carries strategy-layer compatibility and metadata. + + +**LongBridge profile matrix** + +| Canonical profile | Display name | Alias | Enabled | Default | Rollback | Domain | Runtime note | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `semiconductor_rotation_income` | Semiconductor Trend Income | `semiconductor_trend_income` | Yes | Yes | Yes | `us_equity` | current LongBridge default | **Layers** @@ -86,7 +94,7 @@ BOXX: $34,000.00 Cash: $10,000.00 | `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`) | | `SERVICE_NAME` | No | Alert/log prefix for service identity (default: `longbridge-quant-semiconductor-rotation-income`) | -| `STRATEGY_PROFILE` | No | Strategy profile selector (default: `semiconductor_rotation_income`; supported value: `semiconductor_rotation_income`) | +| `STRATEGY_PROFILE` | No | Strategy profile selector (default: `semiconductor_rotation_income`; supported canonical value: `semiconductor_rotation_income`; alias: `semiconductor_trend_income`) | | `ACCOUNT_REGION` | No | Account region marker for platform-style deployment (e.g. `HK`, `SG`; defaults to `ACCOUNT_PREFIX` / service-name suffix / `DEFAULT`) | | `NOTIFY_LANG` | No | Notification language: `en` (English, default) or `zh` (Chinese) | | `GOOGLE_CLOUD_PROJECT` | No | GCP project ID (defaults to ADC project when unset) | @@ -110,7 +118,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/SERVICE_NAME]`) - `SERVICE_NAME`: e.g. `longbridge-quant-semiconductor-rotation-income-hk`, `longbridge-quant-semiconductor-rotation-income-sg` -- `STRATEGY_PROFILE`: use `semiconductor_rotation_income` for the current LongBridge strategy profile +- `STRATEGY_PROFILE`: use canonical `semiconductor_rotation_income` for the current LongBridge strategy profile; alias `semiconductor_trend_income` resolves to the same strategy - Current strategy domain is `us_equity`. The repo now keeps a small strategy registry so future strategy switching can grow by domain + profile, instead of mixing platform and strategy in one layer. - `ACCOUNT_REGION`: explicitly mark the deployed account region (`HK` / `SG`); if unset, the app falls back to `ACCOUNT_PREFIX` or the `-hk` / `-sg` service-name suffix - `NOTIFY_LANG`: set `en` or `zh` per deployment @@ -257,7 +265,7 @@ BOXX: $34,000.00 现金: $10,000.00 | `LONGPORT_SECRET_NAME` | 否 | Secret Manager 中的密钥名称(默认: `longport_token_hk`) | | `ACCOUNT_PREFIX` | 否 | 通知/日志前缀,区分账户环境(默认: `DEFAULT`) | | `SERVICE_NAME` | 否 | 通知/日志前缀,区分服务(默认: `longbridge-quant-semiconductor-rotation-income`) | -| `STRATEGY_PROFILE` | 否 | 策略档位选择(默认: `semiconductor_rotation_income`;当前支持值: `semiconductor_rotation_income`) | +| `STRATEGY_PROFILE` | 否 | 策略档位选择(默认: `semiconductor_rotation_income`;当前 canonical 值: `semiconductor_rotation_income`;alias: `semiconductor_trend_income`) | | `ACCOUNT_REGION` | 否 | 平台化部署时的账户区域标记(如 `HK`、`SG`;默认按 `ACCOUNT_PREFIX` / 服务名后缀 / `DEFAULT` 推断) | | `NOTIFY_LANG` | 否 | 通知语言: `en`(英文,默认)或 `zh`(中文) | | `GOOGLE_CLOUD_PROJECT` | 否 | GCP 项目 ID(未设置时使用 ADC 默认项目) | @@ -281,7 +289,7 @@ Secret Manager 中需存在 `LONGPORT_SECRET_NAME` 指定的密钥(默认: `lo - `LONGPORT_SECRET_NAME`: 指向不同密钥(如 `longport_token_hk`、`longport_token_sg`) - `ACCOUNT_PREFIX`: 如 `HK`、`SG`(所有通知/日志将包含 `[ACCOUNT_PREFIX/SERVICE_NAME]`) - `SERVICE_NAME`: 如 `longbridge-quant-semiconductor-rotation-income-hk`、`longbridge-quant-semiconductor-rotation-income-sg` -- `STRATEGY_PROFILE`: 当前 LongBridge 策略档位使用 `semiconductor_rotation_income` +- `STRATEGY_PROFILE`: 当前 LongBridge 策略档位使用 canonical `semiconductor_rotation_income`;alias `semiconductor_trend_income` 会解析到同一条策略 - 当前策略域是 `us_equity`。本地策略注册表只用于域和 profile 校验。 - `ACCOUNT_REGION`: 显式标记部署账户区域(`HK` / `SG`);未设置时会回退到 `ACCOUNT_PREFIX` 或服务名里的 `-hk` / `-sg` 后缀 - `NOTIFY_LANG`: 每个部署可独立设置 `en` 或 `zh` diff --git a/requirements.txt b/requirements.txt index 9c2b929..3e811f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ flask gunicorn quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.6.0 -us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@v0.6.0 +us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@17fa78c22463070b88ba4a884c84e7a761835ccb pandas requests pytz diff --git a/strategy_registry.py b/strategy_registry.py index a64b2d8..d10bea3 100644 --- a/strategy_registry.py +++ b/strategy_registry.py @@ -1,33 +1,41 @@ from __future__ import annotations -from us_equity_strategies import get_strategy_definitions as get_us_equity_strategy_definitions - -from quant_platform_kit.common.strategies import ( - StrategyDefinition, - US_EQUITY_DOMAIN, - get_supported_profiles_for_platform as qpk_get_supported_profiles_for_platform, - resolve_strategy_definition as qpk_resolve_strategy_definition, +from us_equity_strategies.platform_registry_support import ( + build_platform_profile_matrix, + get_enabled_profiles_for_platform, + resolve_platform_strategy_definition, ) -LONGBRIDGE_PLATFORM = "longbridge" +from quant_platform_kit.common.strategies import StrategyDefinition, US_EQUITY_DOMAIN +LONGBRIDGE_PLATFORM = "longbridge" DEFAULT_STRATEGY_PROFILE = "semiconductor_rotation_income" +ROLLBACK_STRATEGY_PROFILE = DEFAULT_STRATEGY_PROFILE -STRATEGY_DEFINITIONS = get_us_equity_strategy_definitions() +LONGBRIDGE_ENABLED_PROFILES = frozenset({"semiconductor_rotation_income"}) PLATFORM_SUPPORTED_DOMAINS: dict[str, frozenset[str]] = { LONGBRIDGE_PLATFORM: frozenset({US_EQUITY_DOMAIN}), } -SUPPORTED_STRATEGY_PROFILES = frozenset(STRATEGY_DEFINITIONS) +SUPPORTED_STRATEGY_PROFILES = LONGBRIDGE_ENABLED_PROFILES def get_supported_profiles_for_platform(platform_id: str) -> frozenset[str]: - return qpk_get_supported_profiles_for_platform( - STRATEGY_DEFINITIONS, - PLATFORM_SUPPORTED_DOMAINS, - platform_id=platform_id, + return get_enabled_profiles_for_platform( + platform_id, + expected_platform_id=LONGBRIDGE_PLATFORM, + enabled_profiles=LONGBRIDGE_ENABLED_PROFILES, + ) + + +def get_platform_profile_matrix() -> list[dict[str, object]]: + return build_platform_profile_matrix( + platform_id=LONGBRIDGE_PLATFORM, + enabled_profiles=LONGBRIDGE_ENABLED_PROFILES, + default_profile=DEFAULT_STRATEGY_PROFILE, + rollback_profile=ROLLBACK_STRATEGY_PROFILE, ) @@ -36,10 +44,11 @@ def resolve_strategy_definition( *, platform_id: str, ) -> StrategyDefinition: - return qpk_resolve_strategy_definition( + return resolve_platform_strategy_definition( raw_value, platform_id=platform_id, - strategy_definitions=STRATEGY_DEFINITIONS, + expected_platform_id=LONGBRIDGE_PLATFORM, + enabled_profiles=LONGBRIDGE_ENABLED_PROFILES, platform_supported_domains=PLATFORM_SUPPORTED_DOMAINS, default_profile=DEFAULT_STRATEGY_PROFILE, ) diff --git a/tests/test_runtime_config_support.py b/tests/test_runtime_config_support.py index b8eb027..5c3fcee 100644 --- a/tests/test_runtime_config_support.py +++ b/tests/test_runtime_config_support.py @@ -19,7 +19,7 @@ infer_account_region, load_platform_runtime_settings, ) -from strategy_registry import LONGBRIDGE_PLATFORM, US_EQUITY_DOMAIN, get_supported_profiles_for_platform +from strategy_registry import LONGBRIDGE_PLATFORM, US_EQUITY_DOMAIN, get_platform_profile_matrix, get_supported_profiles_for_platform class RuntimeConfigSupportTests(unittest.TestCase): @@ -44,6 +44,12 @@ def test_platform_supported_profiles_are_filtered_by_registry(self): frozenset({DEFAULT_STRATEGY_PROFILE}), ) + def test_accepts_human_readable_alias(self): + with patch.dict(os.environ, {"STRATEGY_PROFILE": "semiconductor_trend_income"}, clear=True): + settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1") + + self.assertEqual(settings.strategy_profile, DEFAULT_STRATEGY_PROFILE) + def test_account_region_prefers_explicit_env(self): region = infer_account_region( "sg", @@ -73,6 +79,13 @@ 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): + rows = get_platform_profile_matrix() + self.assertEqual(rows[0]["canonical_profile"], DEFAULT_STRATEGY_PROFILE) + self.assertEqual(rows[0]["display_name"], "Semiconductor Trend Income") + self.assertTrue(rows[0]["is_default"]) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_strategy_loader.py b/tests/test_strategy_loader.py index e30f90e..059b0e8 100644 --- a/tests/test_strategy_loader.py +++ b/tests/test_strategy_loader.py @@ -17,6 +17,21 @@ def test_load_allocation_module_resolves_semiconductor_rotation_income(self): "us_equity_strategies.strategies.semiconductor_rotation_income", ) + def test_load_allocation_module_resolves_semiconductor_rotation_income_alias(self): + try: + from strategy_loader import load_allocation_module + + module = load_allocation_module("semiconductor_trend_income") + except ModuleNotFoundError as exc: + if exc.name in {"numpy", "pandas"}: + self.skipTest(f"{exc.name} is not installed") + raise + + self.assertEqual( + module.__name__, + "us_equity_strategies.strategies.semiconductor_rotation_income", + ) + if __name__ == "__main__": unittest.main()