Skip to content

Commit 7835ad9

Browse files
committed
Adopt runtime target assembly
1 parent ff4b92a commit 7835ad9

14 files changed

Lines changed: 232 additions & 228 deletions

.github/workflows/sync-cloud-run-env.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,19 @@ jobs:
114114
output.write(
115115
f"config_source_policy={str(selected.get('config_source_policy') or 'none')}\n"
116116
)
117+
normalized_region = os.environ.get("ACCOUNT_REGION", "").strip().upper()
118+
dry_run_only = os.environ.get("LONGBRIDGE_DRY_RUN_ONLY", "").strip().lower() == "true"
119+
runtime_target = {
120+
"platform_id": "longbridge",
121+
"strategy_profile": canonical_profile,
122+
"dry_run_only": dry_run_only,
123+
"deployment_selector": normalized_region or None,
124+
"account_selector": [normalized_region] if normalized_region else [],
125+
"account_scope": normalized_region or None,
126+
"service_name": os.environ.get("CLOUD_RUN_SERVICE", "").strip() or None,
127+
"execution_mode": "paper" if dry_run_only else "live",
128+
}
129+
output.write(f"runtime_target_json={json.dumps(runtime_target, sort_keys=True)}\n")
117130
PY
118131
119132
- name: Validate HK env sync inputs
@@ -123,6 +136,7 @@ jobs:
123136
REQUIRES_SNAPSHOT_MANIFEST_PATH: ${{ steps.strategy_requirements.outputs.requires_snapshot_manifest_path }}
124137
REQUIRES_STRATEGY_CONFIG_PATH: ${{ steps.strategy_requirements.outputs.requires_strategy_config_path }}
125138
CONFIG_SOURCE_POLICY: ${{ steps.strategy_requirements.outputs.config_source_policy }}
139+
RUNTIME_TARGET_JSON: ${{ steps.strategy_requirements.outputs.runtime_target_json }}
126140
run: |
127141
set -euo pipefail
128142
@@ -146,6 +160,10 @@ jobs:
146160
missing_vars+=("TELEGRAM_TOKEN_SECRET_NAME or TELEGRAM_TOKEN")
147161
fi
148162
163+
if [ -z "${RUNTIME_TARGET_JSON:-}" ]; then
164+
missing_vars+=("RUNTIME_TARGET_JSON")
165+
fi
166+
149167
if [ -z "${LONGPORT_APP_KEY_SECRET_NAME:-}" ]; then
150168
missing_vars+=("LONGPORT_APP_KEY_SECRET_NAME")
151169
fi
@@ -227,6 +245,7 @@ jobs:
227245
"ACCOUNT_PREFIX=${ACCOUNT_PREFIX}"
228246
"STRATEGY_PROFILE=${STRATEGY_PROFILE}"
229247
"ACCOUNT_REGION=${ACCOUNT_REGION}"
248+
"RUNTIME_TARGET_JSON=${RUNTIME_TARGET_JSON}"
230249
)
231250
secret_pairs=()
232251
remove_env_vars=(
@@ -420,6 +439,19 @@ jobs:
420439
output.write(
421440
f"config_source_policy={str(selected.get('config_source_policy') or 'none')}\n"
422441
)
442+
normalized_region = os.environ.get("ACCOUNT_REGION", "").strip().upper()
443+
dry_run_only = os.environ.get("LONGBRIDGE_DRY_RUN_ONLY", "").strip().lower() == "true"
444+
runtime_target = {
445+
"platform_id": "longbridge",
446+
"strategy_profile": canonical_profile,
447+
"dry_run_only": dry_run_only,
448+
"deployment_selector": normalized_region or None,
449+
"account_selector": [normalized_region] if normalized_region else [],
450+
"account_scope": normalized_region or None,
451+
"service_name": os.environ.get("CLOUD_RUN_SERVICE", "").strip() or None,
452+
"execution_mode": "paper" if dry_run_only else "live",
453+
}
454+
output.write(f"runtime_target_json={json.dumps(runtime_target, sort_keys=True)}\n")
423455
PY
424456
425457
- name: Validate SG env sync inputs
@@ -429,6 +461,7 @@ jobs:
429461
REQUIRES_SNAPSHOT_MANIFEST_PATH: ${{ steps.strategy_requirements.outputs.requires_snapshot_manifest_path }}
430462
REQUIRES_STRATEGY_CONFIG_PATH: ${{ steps.strategy_requirements.outputs.requires_strategy_config_path }}
431463
CONFIG_SOURCE_POLICY: ${{ steps.strategy_requirements.outputs.config_source_policy }}
464+
RUNTIME_TARGET_JSON: ${{ steps.strategy_requirements.outputs.runtime_target_json }}
432465
run: |
433466
set -euo pipefail
434467
@@ -452,6 +485,10 @@ jobs:
452485
missing_vars+=("TELEGRAM_TOKEN_SECRET_NAME or TELEGRAM_TOKEN")
453486
fi
454487
488+
if [ -z "${RUNTIME_TARGET_JSON:-}" ]; then
489+
missing_vars+=("RUNTIME_TARGET_JSON")
490+
fi
491+
455492
if [ -z "${LONGPORT_APP_KEY_SECRET_NAME:-}" ]; then
456493
missing_vars+=("LONGPORT_APP_KEY_SECRET_NAME")
457494
fi
@@ -533,6 +570,7 @@ jobs:
533570
"ACCOUNT_PREFIX=${ACCOUNT_PREFIX}"
534571
"STRATEGY_PROFILE=${STRATEGY_PROFILE}"
535572
"ACCOUNT_REGION=${ACCOUNT_REGION}"
573+
"RUNTIME_TARGET_JSON=${RUNTIME_TARGET_JSON}"
536574
)
537575
secret_pairs=()
538576
remove_env_vars=(

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
Quant system on LongPort OpenAPI and Google Cloud Run.
1111

1212
This repository uses `QuantPlatformKit` for LongPort token handling, context bootstrap, account snapshot access, market data, and order submission. Cloud Run deploys this repository directly.
13+
The runtime now carries a structured `RuntimeTarget` / `RUNTIME_TARGET_JSON` alongside the compatibility `STRATEGY_PROFILE` selector.
1314
The LongBridge runtime can execute the seven current `runtime_enabled` `us_equity` profiles from `UsEquityStrategies`; `LongBridgePlatform` keeps the LongPort runtime, token refresh, execution, and notification flow.
15+
`STRATEGY_PROFILE` remains the compatibility selector for strategy routing, while `RuntimeTarget` describes the running service identity.
1416

1517
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.
1618
This runtime matrix is the authoritative enablement source for LongBridge. `UsEquityStrategies` carries strategy-layer logic, cadence, compatibility, and metadata.
@@ -63,7 +65,7 @@ Telegram notifications include structured execution and heartbeat messages, with
6365
| `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` |
6466
| `LONGPORT_SECRET_NAME` | No | Secret Manager secret name for LongPort token (default: `longport_token_hk`) |
6567
| `ACCOUNT_PREFIX` | No | Alert/log prefix for account/environment (default: `DEFAULT`) |
66-
| `STRATEGY_PROFILE` | Yes | Strategy profile selector. Set explicitly per deployment; enabled values include `global_etf_confidence_vol_gate`, `global_etf_rotation`, `mega_cap_leader_rotation_top50_balanced`, `russell_1000_multi_factor_defensive`, `soxl_soxx_trend_income`, `tech_communication_pullback_enhancement`, and `tqqq_growth_income` |
68+
| `STRATEGY_PROFILE` | Yes | Strategy profile selector for compatibility and strategy routing. Set explicitly per deployment; enabled values include `global_etf_confidence_vol_gate`, `global_etf_rotation`, `mega_cap_leader_rotation_top50_balanced`, `russell_1000_multi_factor_defensive`, `soxl_soxx_trend_income`, `tech_communication_pullback_enhancement`, and `tqqq_growth_income`. The structured runtime target is carried separately as `RUNTIME_TARGET_JSON`. |
6769
| `ACCOUNT_REGION` | No | Account region marker for platform-style deployment (e.g. `HK`, `SG`; defaults to `ACCOUNT_PREFIX` / `DEFAULT`) |
6870
| `LONGBRIDGE_DRY_RUN_ONLY` | No | Set to `true` to keep the selected deployment in dry-run mode. |
6971
| `LONGBRIDGE_FRACTIONAL_LIMIT_BUY_FALLBACK_TO_MARKET` | No | Set to `true` to convert fractional `limit buy` orders to `market buy` orders instead of skipping them. |
@@ -93,8 +95,8 @@ Deploy the same codebase as multiple Cloud Run services (e.g. `HK` and `SG`) by
9395

9496
- `LONGPORT_SECRET_NAME`: point to different secrets (e.g. `longport_token_hk`, `longport_token_sg`)
9597
- `ACCOUNT_PREFIX`: e.g. `HK`, `SG` (all Telegram/log alerts will include `[ACCOUNT_PREFIX]`)
96-
- `STRATEGY_PROFILE`: set per service; current live examples are `tech_communication_pullback_enhancement` on HK and `soxl_soxx_trend_income` on SG
97-
- 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.
98+
- `STRATEGY_PROFILE`: set per service; current live examples are `tech_communication_pullback_enhancement` on HK and `soxl_soxx_trend_income` on SG. The deployment control plane now also carries `RUNTIME_TARGET_JSON`; treat `STRATEGY_PROFILE` as a compatibility input that still selects the strategy implementation, not the only identity key.
99+
- Current strategy domain is `us_equity`. `STRATEGY_PROFILE` still 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.
98100
- `ACCOUNT_REGION`: explicitly mark the deployed account region (`HK` / `SG`); if unset, the app falls back to `ACCOUNT_PREFIX` or `DEFAULT`
99101
- `LONGBRIDGE_DRY_RUN_ONLY`: set per service when that deployment should stay dry-run
100102
- `NOTIFY_LANG`: set `en` or `zh` per deployment
@@ -130,6 +132,7 @@ Important:
130132
- `CLOUD_RUN_REGION` should be set on each GitHub Environment, not as one shared repository variable. This lets `HK` and `SG` live in different Cloud Run regions.
131133
- `LONGPORT_APP_KEY_SECRET_NAME` and `LONGPORT_APP_SECRET_SECRET_NAME` should also be set on each GitHub Environment. Do not keep one repository-level default for them when `HK` and `SG` use different LongPort credentials.
132134
- The workflow only becomes strict when `ENABLE_GITHUB_ENV_SYNC=true`. If this variable is unset, the sync job is skipped and the old Google Cloud Trigger-only setup keeps working. Once you set it to `true`, missing env-sync values become a hard failure so you do not get a false green deployment. The selected profile's snapshot/config requirements are resolved from `scripts/print_strategy_profile_status.py --json` instead of a hard-coded strategy-name list.
135+
- The workflow now also emits `RUNTIME_TARGET_JSON` so Cloud Run receives a structured runtime target alongside the legacy `STRATEGY_PROFILE` input.
133136
- GitHub now authenticates to Google Cloud with OIDC + Workload Identity Federation, so `GCP_SA_KEY` is no longer required for this workflow.
134137
- If you deploy with `gcloud run deploy --source` or a Cloud Run source trigger, also grant `roles/storage.objectViewer` on `gs://run-sources-<project>-<region>` to the build service account, the deploy service account, and the default compute service account. Otherwise source deploy can fail before Cloud Build starts with `storage.objects.get` denied.
135138
- Here "shared" only means **shared inside this repository** between the `HK` and `SG` Cloud Run services. The Telegram token can still be shared, but LongPort app credentials should live in Secret Manager and be referenced by per-environment secret-name variables; they are not meant to be a global secret set reused by unrelated quant repos.
@@ -139,7 +142,7 @@ Important:
139142

140143
- `QuantPlatformKit` is only a shared dependency; Cloud Run still deploys `LongBridgePlatform` itself.
141144
- Recommended Cloud Run service names: `longbridge-quant-hk-service` and `longbridge-quant-sg-service`.
142-
- Keep using two triggers and two GitHub Environments. The split key is still `CLOUD_RUN_SERVICE + CLOUD_RUN_REGION`, and the runtime identity is now explicit through `STRATEGY_PROFILE + ACCOUNT_REGION`.
145+
- Keep using two triggers and two GitHub Environments. The split key is still `CLOUD_RUN_SERVICE + CLOUD_RUN_REGION`, and the runtime identity is now explicit through `RUNTIME_TARGET_JSON` with `STRATEGY_PROFILE + ACCOUNT_REGION` kept for compatibility.
143146
- If you later rename or move this repository, rebuild the GitHub source binding in Google Cloud for both triggers instead of assuming the existing source binding will follow the rename.
144147
- For the shared deployment model and trigger migration checklist, see [`QuantPlatformKit/docs/deployment_model.md`](../QuantPlatformKit/docs/deployment_model.md).
145148

@@ -242,7 +245,7 @@ Secret Manager 中需存在 `LONGPORT_SECRET_NAME` 指定的密钥(默认: `lo
242245

243246
- `LONGPORT_SECRET_NAME`: 指向不同密钥(如 `longport_token_hk``longport_token_sg`
244247
- `ACCOUNT_PREFIX`: 如 `HK``SG`(所有通知/日志将包含 `[ACCOUNT_PREFIX]`
245-
- `STRATEGY_PROFILE`: 按服务分别设置;当前线上 HK 用 `tech_communication_pullback_enhancement`,SG 用 `soxl_soxx_trend_income`
248+
- `STRATEGY_PROFILE`: 按服务分别设置;当前线上 HK 用 `tech_communication_pullback_enhancement`,SG 用 `soxl_soxx_trend_income`。控制面会另外携带 `RUNTIME_TARGET_JSON``STRATEGY_PROFILE` 继续只作为兼容选择器。
246249
- 当前策略域是 `us_equity``STRATEGY_PROFILE` 现在会先经过平台能力矩阵,再经过从 `runtime_enabled` 策略元数据派生的 rollout allowlist:`eligible` 表示平台理论可跑,`enabled` 表示当前 rollout 真正放开。
247250
- `ACCOUNT_REGION`: 显式标记部署账户区域(`HK` / `SG`);未设置时会回退到 `ACCOUNT_PREFIX``DEFAULT`
248251
- `LONGBRIDGE_DRY_RUN_ONLY`: 需要保持模拟运行时按服务单独设置

application/runtime_composer.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from application.runtime_dependencies import LongBridgeRebalanceConfig, LongBridgeRebalanceRuntime
1111
from application.runtime_notification_adapters import build_runtime_notification_adapters
1212
from application.runtime_reporting_adapters import build_runtime_reporting_adapters
13+
from quant_platform_kit.common import build_runtime_assembly, build_runtime_context_fields
14+
from quant_platform_kit.common.runtime_target import RuntimeTarget
1315
from notifications.telegram import build_prefixer, build_sender
1416

1517

@@ -49,6 +51,7 @@ class LongBridgeRuntimeComposer:
4951
report_builder: Callable[..., dict[str, Any]] | None = None
5052
report_persister: Callable[..., Any] | None = None
5153
translator: Callable[..., str] | None = None
54+
runtime_target: RuntimeTarget | None = None
5255
prefixer_builder: Callable[..., Callable[[str], str]] = build_prefixer
5356
sender_builder: Callable[..., Callable[[str], None]] = build_sender
5457
env_reader: Callable[[str, str], str | None] | None = None
@@ -104,21 +107,27 @@ def build_notification_adapters(self):
104107
)
105108

106109
def build_reporting_adapters(self):
107-
return self.reporting_adapter_builder(
110+
runtime_assembly = build_runtime_assembly(
108111
platform="longbridge",
109112
deploy_target="cloud_run",
110113
service_name=self.env_reader("K_SERVICE", "longbridge-platform"),
111114
strategy_profile=self.strategy_profile,
112-
strategy_domain=self.strategy_domain,
115+
runtime_target=self.runtime_target,
113116
account_scope=self.account_region,
114117
account_region=self.account_region,
115118
project_id=self.project_id,
116-
extra_context_fields={
117-
"account_prefix": self.account_prefix,
118-
"strategy_display_name": self.strategy_display_name,
119-
"strategy_display_name_localized": self.strategy_display_name_localized,
120-
**dict(self.extra_reporting_fields),
121-
},
119+
extra_context_fields=build_runtime_context_fields(
120+
{
121+
"account_prefix": self.account_prefix,
122+
"strategy_display_name": self.strategy_display_name,
123+
"strategy_display_name_localized": self.strategy_display_name_localized,
124+
**dict(self.extra_reporting_fields),
125+
},
126+
),
127+
)
128+
return self.reporting_adapter_builder(
129+
runtime_assembly=runtime_assembly,
130+
strategy_domain=self.strategy_domain,
122131
managed_symbols=self.managed_symbols,
123132
account_prefix=self.account_prefix,
124133
benchmark_symbol=self.benchmark_symbol,
@@ -210,6 +219,7 @@ def build_runtime_composer(
210219
report_builder: Callable[..., dict[str, Any]],
211220
report_persister: Callable[..., Any],
212221
translator: Callable[..., str],
222+
runtime_target: RuntimeTarget | None,
213223
env_reader: Callable[[str, str], str | None],
214224
sleeper: Callable[[float], None],
215225
printer: Callable[..., Any] = print,
@@ -250,6 +260,7 @@ def build_runtime_composer(
250260
report_builder=report_builder,
251261
report_persister=report_persister,
252262
translator=translator,
263+
runtime_target=runtime_target,
253264
env_reader=env_reader,
254265
sleeper=sleeper,
255266
printer=printer,

0 commit comments

Comments
 (0)