Skip to content

Commit 8ce4fe2

Browse files
authored
refactor longbridge strategy registry metadata (#5)
1 parent 7185070 commit 8ce4fe2

5 files changed

Lines changed: 67 additions & 22 deletions

File tree

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ This repository uses `QuantPlatformKit` for LongPort token handling, context boo
1313
The `semiconductor_rotation_income` allocation logic is loaded from `UsEquityStrategies`. `LongBridgePlatform` keeps the LongPort runtime, token refresh, execution, and notification flow.
1414

1515
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.
16+
This runtime matrix is the authoritative enablement source for LongBridge. `UsEquityStrategies` only carries strategy-layer compatibility and metadata.
17+
18+
19+
**LongBridge profile matrix**
20+
21+
| Canonical profile | Display name | Alias | Enabled | Default | Rollback | Domain | Runtime note |
22+
| --- | --- | --- | --- | --- | --- | --- | --- |
23+
| `semiconductor_rotation_income` | Semiconductor Trend Income | `semiconductor_trend_income` | Yes | Yes | Yes | `us_equity` | current LongBridge default |
1624

1725
**Layers**
1826

@@ -86,7 +94,7 @@ BOXX: $34,000.00 Cash: $10,000.00
8694
| `LONGPORT_SECRET_NAME` | No | Secret Manager secret name for LongPort token (default: `longport_token_hk`) |
8795
| `ACCOUNT_PREFIX` | No | Alert/log prefix for account/environment (default: `DEFAULT`) |
8896
| `SERVICE_NAME` | No | Alert/log prefix for service identity (default: `longbridge-quant-semiconductor-rotation-income`) |
89-
| `STRATEGY_PROFILE` | No | Strategy profile selector (default: `semiconductor_rotation_income`; supported value: `semiconductor_rotation_income`) |
97+
| `STRATEGY_PROFILE` | No | Strategy profile selector (default: `semiconductor_rotation_income`; supported canonical value: `semiconductor_rotation_income`; alias: `semiconductor_trend_income`) |
9098
| `ACCOUNT_REGION` | No | Account region marker for platform-style deployment (e.g. `HK`, `SG`; defaults to `ACCOUNT_PREFIX` / service-name suffix / `DEFAULT`) |
9199
| `NOTIFY_LANG` | No | Notification language: `en` (English, default) or `zh` (Chinese) |
92100
| `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
110118
- `LONGPORT_SECRET_NAME`: point to different secrets (e.g. `longport_token_hk`, `longport_token_sg`)
111119
- `ACCOUNT_PREFIX`: e.g. `HK`, `SG` (all Telegram/log alerts will include `[ACCOUNT_PREFIX/SERVICE_NAME]`)
112120
- `SERVICE_NAME`: e.g. `longbridge-quant-semiconductor-rotation-income-hk`, `longbridge-quant-semiconductor-rotation-income-sg`
113-
- `STRATEGY_PROFILE`: use `semiconductor_rotation_income` for the current LongBridge strategy profile
121+
- `STRATEGY_PROFILE`: use canonical `semiconductor_rotation_income` for the current LongBridge strategy profile; alias `semiconductor_trend_income` resolves to the same strategy
114122
- 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.
115123
- `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
116124
- `NOTIFY_LANG`: set `en` or `zh` per deployment
@@ -257,7 +265,7 @@ BOXX: $34,000.00 现金: $10,000.00
257265
| `LONGPORT_SECRET_NAME` || Secret Manager 中的密钥名称(默认: `longport_token_hk`|
258266
| `ACCOUNT_PREFIX` || 通知/日志前缀,区分账户环境(默认: `DEFAULT`|
259267
| `SERVICE_NAME` || 通知/日志前缀,区分服务(默认: `longbridge-quant-semiconductor-rotation-income`|
260-
| `STRATEGY_PROFILE` || 策略档位选择(默认: `semiconductor_rotation_income`当前支持值: `semiconductor_rotation_income`|
268+
| `STRATEGY_PROFILE` || 策略档位选择(默认: `semiconductor_rotation_income`当前 canonical 值: `semiconductor_rotation_income`;alias: `semiconductor_trend_income`|
261269
| `ACCOUNT_REGION` || 平台化部署时的账户区域标记(如 `HK``SG`;默认按 `ACCOUNT_PREFIX` / 服务名后缀 / `DEFAULT` 推断) |
262270
| `NOTIFY_LANG` || 通知语言: `en`(英文,默认)或 `zh`(中文) |
263271
| `GOOGLE_CLOUD_PROJECT` || GCP 项目 ID(未设置时使用 ADC 默认项目) |
@@ -281,7 +289,7 @@ Secret Manager 中需存在 `LONGPORT_SECRET_NAME` 指定的密钥(默认: `lo
281289
- `LONGPORT_SECRET_NAME`: 指向不同密钥(如 `longport_token_hk``longport_token_sg`
282290
- `ACCOUNT_PREFIX`: 如 `HK``SG`(所有通知/日志将包含 `[ACCOUNT_PREFIX/SERVICE_NAME]`
283291
- `SERVICE_NAME`: 如 `longbridge-quant-semiconductor-rotation-income-hk``longbridge-quant-semiconductor-rotation-income-sg`
284-
- `STRATEGY_PROFILE`: 当前 LongBridge 策略档位使用 `semiconductor_rotation_income`
292+
- `STRATEGY_PROFILE`: 当前 LongBridge 策略档位使用 canonical `semiconductor_rotation_income`;alias `semiconductor_trend_income` 会解析到同一条策略
285293
- 当前策略域是 `us_equity`。本地策略注册表只用于域和 profile 校验。
286294
- `ACCOUNT_REGION`: 显式标记部署账户区域(`HK` / `SG`);未设置时会回退到 `ACCOUNT_PREFIX` 或服务名里的 `-hk` / `-sg` 后缀
287295
- `NOTIFY_LANG`: 每个部署可独立设置 `en``zh`

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
flask
22
gunicorn
33
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.6.0
4-
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@v0.6.0
4+
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@17fa78c22463070b88ba4a884c84e7a761835ccb
55
pandas
66
requests
77
pytz

strategy_registry.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
11
from __future__ import annotations
22

3-
from us_equity_strategies import get_strategy_definitions as get_us_equity_strategy_definitions
4-
5-
from quant_platform_kit.common.strategies import (
6-
StrategyDefinition,
7-
US_EQUITY_DOMAIN,
8-
get_supported_profiles_for_platform as qpk_get_supported_profiles_for_platform,
9-
resolve_strategy_definition as qpk_resolve_strategy_definition,
3+
from us_equity_strategies.platform_registry_support import (
4+
build_platform_profile_matrix,
5+
get_enabled_profiles_for_platform,
6+
resolve_platform_strategy_definition,
107
)
118

12-
LONGBRIDGE_PLATFORM = "longbridge"
9+
from quant_platform_kit.common.strategies import StrategyDefinition, US_EQUITY_DOMAIN
1310

11+
LONGBRIDGE_PLATFORM = "longbridge"
1412

1513
DEFAULT_STRATEGY_PROFILE = "semiconductor_rotation_income"
14+
ROLLBACK_STRATEGY_PROFILE = DEFAULT_STRATEGY_PROFILE
1615

17-
STRATEGY_DEFINITIONS = get_us_equity_strategy_definitions()
16+
LONGBRIDGE_ENABLED_PROFILES = frozenset({"semiconductor_rotation_income"})
1817

1918
PLATFORM_SUPPORTED_DOMAINS: dict[str, frozenset[str]] = {
2019
LONGBRIDGE_PLATFORM: frozenset({US_EQUITY_DOMAIN}),
2120
}
2221

23-
SUPPORTED_STRATEGY_PROFILES = frozenset(STRATEGY_DEFINITIONS)
22+
SUPPORTED_STRATEGY_PROFILES = LONGBRIDGE_ENABLED_PROFILES
2423

2524

2625
def get_supported_profiles_for_platform(platform_id: str) -> frozenset[str]:
27-
return qpk_get_supported_profiles_for_platform(
28-
STRATEGY_DEFINITIONS,
29-
PLATFORM_SUPPORTED_DOMAINS,
30-
platform_id=platform_id,
26+
return get_enabled_profiles_for_platform(
27+
platform_id,
28+
expected_platform_id=LONGBRIDGE_PLATFORM,
29+
enabled_profiles=LONGBRIDGE_ENABLED_PROFILES,
30+
)
31+
32+
33+
def get_platform_profile_matrix() -> list[dict[str, object]]:
34+
return build_platform_profile_matrix(
35+
platform_id=LONGBRIDGE_PLATFORM,
36+
enabled_profiles=LONGBRIDGE_ENABLED_PROFILES,
37+
default_profile=DEFAULT_STRATEGY_PROFILE,
38+
rollback_profile=ROLLBACK_STRATEGY_PROFILE,
3139
)
3240

3341

@@ -36,10 +44,11 @@ def resolve_strategy_definition(
3644
*,
3745
platform_id: str,
3846
) -> StrategyDefinition:
39-
return qpk_resolve_strategy_definition(
47+
return resolve_platform_strategy_definition(
4048
raw_value,
4149
platform_id=platform_id,
42-
strategy_definitions=STRATEGY_DEFINITIONS,
50+
expected_platform_id=LONGBRIDGE_PLATFORM,
51+
enabled_profiles=LONGBRIDGE_ENABLED_PROFILES,
4352
platform_supported_domains=PLATFORM_SUPPORTED_DOMAINS,
4453
default_profile=DEFAULT_STRATEGY_PROFILE,
4554
)

tests/test_runtime_config_support.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
infer_account_region,
2020
load_platform_runtime_settings,
2121
)
22-
from strategy_registry import LONGBRIDGE_PLATFORM, US_EQUITY_DOMAIN, get_supported_profiles_for_platform
22+
from strategy_registry import LONGBRIDGE_PLATFORM, US_EQUITY_DOMAIN, get_platform_profile_matrix, get_supported_profiles_for_platform
2323

2424

2525
class RuntimeConfigSupportTests(unittest.TestCase):
@@ -44,6 +44,12 @@ def test_platform_supported_profiles_are_filtered_by_registry(self):
4444
frozenset({DEFAULT_STRATEGY_PROFILE}),
4545
)
4646

47+
def test_accepts_human_readable_alias(self):
48+
with patch.dict(os.environ, {"STRATEGY_PROFILE": "semiconductor_trend_income"}, clear=True):
49+
settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
50+
51+
self.assertEqual(settings.strategy_profile, DEFAULT_STRATEGY_PROFILE)
52+
4753
def test_account_region_prefers_explicit_env(self):
4854
region = infer_account_region(
4955
"sg",
@@ -73,6 +79,13 @@ def test_unsupported_strategy_profile_fails_fast(self):
7379
with self.assertRaisesRegex(ValueError, "Unsupported STRATEGY_PROFILE"):
7480
load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
7581

82+
def test_platform_profile_matrix_marks_default(self):
83+
rows = get_platform_profile_matrix()
84+
self.assertEqual(rows[0]["canonical_profile"], DEFAULT_STRATEGY_PROFILE)
85+
self.assertEqual(rows[0]["display_name"], "Semiconductor Trend Income")
86+
self.assertTrue(rows[0]["is_default"])
87+
88+
7689

7790
if __name__ == "__main__":
7891
unittest.main()

tests/test_strategy_loader.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ def test_load_allocation_module_resolves_semiconductor_rotation_income(self):
1717
"us_equity_strategies.strategies.semiconductor_rotation_income",
1818
)
1919

20+
def test_load_allocation_module_resolves_semiconductor_rotation_income_alias(self):
21+
try:
22+
from strategy_loader import load_allocation_module
23+
24+
module = load_allocation_module("semiconductor_trend_income")
25+
except ModuleNotFoundError as exc:
26+
if exc.name in {"numpy", "pandas"}:
27+
self.skipTest(f"{exc.name} is not installed")
28+
raise
29+
30+
self.assertEqual(
31+
module.__name__,
32+
"us_equity_strategies.strategies.semiconductor_rotation_income",
33+
)
34+
2035

2136
if __name__ == "__main__":
2237
unittest.main()

0 commit comments

Comments
 (0)