diff --git a/docs/platform_runtime_inventory.md b/docs/platform_runtime_inventory.md index 03f1c4a..a291291 100644 --- a/docs/platform_runtime_inventory.md +++ b/docs/platform_runtime_inventory.md @@ -1,6 +1,6 @@ # Platform Runtime Inventory -_Verified snapshot: 2026-04-18_ +_Verified snapshot: 2026-05-14_ This document records the public runtime wiring inventory across platform repositories and deployment projects. It is meant to answer one question quickly: @@ -45,7 +45,12 @@ For the platform / strategy-domain / configurable-profile matrix, see [`platform - **Runtime service account** - `ibkr-platform-runtime@interactivebrokersquant.iam.gserviceaccount.com` - **Scheduler** - - `interactive-brokers-quant-service-scheduler` + - `interactive-brokers-live-slot-a-precheck-scheduler` + - `interactive-brokers-live-slot-a-probe-scheduler` + - `interactive-brokers-live-slot-a-scheduler` + - `interactive-brokers-live-slot-b-precheck-scheduler` + - `interactive-brokers-live-slot-b-probe-scheduler` + - `interactive-brokers-live-slot-b-scheduler` - region: `us-central1` - **Core runtime selectors** - `STRATEGY_PROFILE=` @@ -69,6 +74,8 @@ For the platform / strategy-domain / configurable-profile matrix, see [`platform - **Runtime service account** - `schwab-platform-runtime@charlesschwabquant.iam.gserviceaccount.com` - **Scheduler** + - `charles-schwab-quant-service-precheck-scheduler` + - `charles-schwab-quant-service-probe-scheduler` - `charles-schwab-quant-service-scheduler` - region: `us-central1` - **Core runtime selectors** @@ -97,11 +104,17 @@ For the platform / strategy-domain / configurable-profile matrix, see [`platform - **Runtime service account** - `longbridge-platform-runtime@longbridgequant.iam.gserviceaccount.com` - **Schedulers** + - `longbridge-quant-paper-service-precheck-scheduler` in `asia-east2` + - `longbridge-quant-paper-service-probe-scheduler` in `asia-east2` - `longbridge-quant-paper-service-scheduler` in `asia-east2` - - HK: reserved / not yet wired + - `longbridge-quant-hk-service-precheck-scheduler` in `asia-east2` + - `longbridge-quant-hk-service-probe-scheduler` in `asia-east2` + - `longbridge-quant-hk-service-scheduler` in `asia-east2` + - `longbridge-quant-sg-service-precheck-scheduler` in `asia-southeast1` + - `longbridge-quant-sg-service-probe-scheduler` in `asia-southeast1` - `longbridge-quant-sg-service-scheduler` in `asia-southeast1` - **Core runtime selectors** - - `STRATEGY_PROFILE=` per account service +- `STRATEGY_PROFILE=` per account service - `ACCOUNT_REGION=PAPER|HK|SG` - `LONGPORT_SECRET_NAME=` - **Runtime secrets** @@ -109,7 +122,9 @@ For the platform / strategy-domain / configurable-profile matrix, see [`platform - account token secrets selected by `LONGPORT_SECRET_NAME` - runtime Telegram token secret - **Runtime notes** - - PAPER and SG are live today; HK keeps the same deployment pattern when it is added. Each account identity gets its own Cloud Run service, trigger, and GitHub Environment. + - Each account identity gets its own Cloud Run service, trigger, and GitHub Environment. HK can keep the same deployment pattern when it is promoted; rollout state is controlled outside the public docs. +- Each live account identity now has three Cloud Scheduler triggers: `precheck` after the open window, `probe` at open+30 minutes, and the root execution trigger near the close window. +- For Cloud Scheduler HTTP targets, keep the OIDC audience on the Cloud Run service root URL; do not append `precheck` or `probe` to the audience. - Snapshot-backed profiles require feature snapshot path / manifest envs; direct-runtime profiles do not. - App key / secret are account-specific Secret Manager refs; Telegram token is shared inside the LongBridge project. - `SERVICE_NAME` should use the full runtime-facing service names above, not older short prefixes. diff --git a/docs/platform_runtime_inventory.zh-CN.md b/docs/platform_runtime_inventory.zh-CN.md index c1384b3..6474277 100644 --- a/docs/platform_runtime_inventory.zh-CN.md +++ b/docs/platform_runtime_inventory.zh-CN.md @@ -1,6 +1,6 @@ # 平台运行接线清单 -_校验快照日期:2026-04-18_ +_校验快照日期:2026-05-14_ 这份文档记录公开仓库里可以保留的 runtime 接线信息,用来快速回答一个问题: @@ -44,7 +44,12 @@ _校验快照日期:2026-04-18_ - **runtime service account** - `ibkr-platform-runtime@interactivebrokersquant.iam.gserviceaccount.com` - **Scheduler** - - `interactive-brokers-quant-service-scheduler` + - `interactive-brokers-live-slot-a-precheck-scheduler` + - `interactive-brokers-live-slot-a-probe-scheduler` + - `interactive-brokers-live-slot-a-scheduler` + - `interactive-brokers-live-slot-b-precheck-scheduler` + - `interactive-brokers-live-slot-b-probe-scheduler` + - `interactive-brokers-live-slot-b-scheduler` - region:`us-central1` - **核心运行选择器** - `STRATEGY_PROFILE=` @@ -68,6 +73,8 @@ _校验快照日期:2026-04-18_ - **runtime service account** - `schwab-platform-runtime@charlesschwabquant.iam.gserviceaccount.com` - **Scheduler** + - `charles-schwab-quant-service-precheck-scheduler` + - `charles-schwab-quant-service-probe-scheduler` - `charles-schwab-quant-service-scheduler` - region:`us-central1` - **核心运行选择器** @@ -95,7 +102,14 @@ _校验快照日期:2026-04-18_ - **runtime service account** - `longbridge-platform-runtime@longbridgequant.iam.gserviceaccount.com` - **Scheduler** + - `longbridge-quant-paper-service-precheck-scheduler`(`asia-east2`) + - `longbridge-quant-paper-service-probe-scheduler`(`asia-east2`) + - `longbridge-quant-paper-service-scheduler`(`asia-east2`) + - `longbridge-quant-hk-service-precheck-scheduler`(`asia-east2`) + - `longbridge-quant-hk-service-probe-scheduler`(`asia-east2`) - `longbridge-quant-hk-service-scheduler`(`asia-east2`) + - `longbridge-quant-sg-service-precheck-scheduler`(`asia-southeast1`) + - `longbridge-quant-sg-service-probe-scheduler`(`asia-southeast1`) - `longbridge-quant-sg-service-scheduler`(`asia-southeast1`) - **核心运行选择器** - 每个区域服务设置 `STRATEGY_PROFILE=` @@ -106,7 +120,7 @@ _校验快照日期:2026-04-18_ - 由 `LONGPORT_SECRET_NAME` 选择的区域 token secret - runtime Telegram token secret - **运行说明** - - HK / SG 继续保持两个 Cloud Run 服务、两个 trigger、两个 GitHub Environment。 + - 每个账户身份都对应自己的 Cloud Run 服务、trigger 和 GitHub Environment;公开文档不记录当前哪几个账户处于实盘。每个账户都有三条 Cloud Scheduler 触发:开盘后 `precheck`,开盘后 `probe`(+30 分钟),临近收盘执行。Scheduler 的 OIDC audience 也要指向 Cloud Run 服务根 URL,不要拼到 `precheck` 或 `probe` 路径。 - snapshot 驱动策略需要 feature snapshot path / manifest env;直接运行输入策略不需要。 - App key / secret 使用分区域的 Secret Manager 引用;Telegram token 在 LongBridge 项目内共享。 - `SERVICE_NAME` 应使用上面的完整运行时服务名,不再使用旧短前缀。 diff --git a/docs/platform_strategy_matrix.md b/docs/platform_strategy_matrix.md index 2ca2aed..a3ce69b 100644 --- a/docs/platform_strategy_matrix.md +++ b/docs/platform_strategy_matrix.md @@ -29,7 +29,7 @@ For strategy behavior, research status, and archived backtest evidence, see |---|---|---|---|---|---|---| | IBKR | `QuantStrategyLab/InteractiveBrokersPlatform` | `us_equity` | `RUNTIME_TARGET_JSON` + `ACCOUNT_GROUP` | `STRATEGY_PROFILE=` | Cloud Run | Yes - controlled by platform rollout config | | Charles Schwab | `QuantStrategyLab/CharlesSchwabPlatform` | `us_equity` | `RUNTIME_TARGET_JSON` | `STRATEGY_PROFILE=` | Cloud Run | Yes - controlled by platform rollout config | -| LongBridge | `QuantStrategyLab/LongBridgePlatform` | `us_equity` | `RUNTIME_TARGET_JSON` + `ACCOUNT_REGION` | `STRATEGY_PROFILE=` per account service | Cloud Run | Yes - paper and SG today; HK later | +| LongBridge | `QuantStrategyLab/LongBridgePlatform` | `us_equity` | `RUNTIME_TARGET_JSON` + `ACCOUNT_REGION` | `STRATEGY_PROFILE=` per account service | Cloud Run | Yes - controlled by per-account rollout config | | Binance | `QuantStrategyLab/BinancePlatform` | `crypto` | `RUNTIME_TARGET_JSON` (workflow-local) | `crypto_leader_rotation` | Oracle Cloud + self-hosted runner | No - only this profile is supported today | ## What this means right now @@ -42,7 +42,7 @@ Platforms currently in this domain: - `CharlesSchwabPlatform` - `LongBridgePlatform` -LongBridge account identities are modeled as `paper`, `HK`, and `SG`; today the live services use `paper` and `SG`, and `HK` follows the same contract when it is introduced. +LongBridge account identities are modeled as `paper`, `HK`, and `SG`; each account service follows the same contract, with deployment-specific rollout control deciding which ones are active. Important limitation: diff --git a/docs/platform_strategy_matrix.zh-CN.md b/docs/platform_strategy_matrix.zh-CN.md index dafc394..68eb9fa 100644 --- a/docs/platform_strategy_matrix.zh-CN.md +++ b/docs/platform_strategy_matrix.zh-CN.md @@ -29,7 +29,7 @@ _核对时间:2026-04-18_ |---|---|---|---|---|---| | IBKR | `QuantStrategyLab/InteractiveBrokersPlatform` | `us_equity` | `STRATEGY_PROFILE=` | Cloud Run | 可以,按平台 rollout 配置启用 | | Charles Schwab | `QuantStrategyLab/CharlesSchwabPlatform` | `us_equity` | `STRATEGY_PROFILE=` | Cloud Run | 可以,按平台 rollout 配置启用 | -| LongBridge | `QuantStrategyLab/LongBridgePlatform` | `us_equity` | 每个区域服务配置 `STRATEGY_PROFILE=` | Cloud Run | 可以,按平台 rollout 配置启用 | +| LongBridge | `QuantStrategyLab/LongBridgePlatform` | `us_equity` | 每个区域服务配置 `STRATEGY_PROFILE=` | Cloud Run | 可以,按账户服务 rollout 配置启用 | | Binance | `QuantStrategyLab/BinancePlatform` | `crypto` | `crypto_leader_rotation` | Oracle Cloud + self-hosted runner | 当前只支持这个 crypto profile | ## 这张表现在该怎么理解 diff --git a/src/quant_platform_kit/common/runtime_target.py b/src/quant_platform_kit/common/runtime_target.py index ad3c656..d0a955a 100644 --- a/src/quant_platform_kit/common/runtime_target.py +++ b/src/quant_platform_kit/common/runtime_target.py @@ -15,6 +15,7 @@ class RuntimeTarget: account_selector: tuple[str, ...] = () account_scope: str | None = None service_name: str | None = None + execution_windows: dict[str, Any] | None = None @property def execution_mode(self) -> str: @@ -67,6 +68,7 @@ def build_runtime_target( account_selector: Iterable[str] | str | None = None, account_scope: str | None = None, service_name: str | None = None, + execution_windows: Mapping[str, Any] | None = None, ) -> RuntimeTarget: return RuntimeTarget( platform_id=str(platform_id).strip(), @@ -76,6 +78,7 @@ def build_runtime_target( account_selector=_normalize_account_selector(account_selector), account_scope=_normalize_optional_string(account_scope), service_name=_normalize_optional_string(service_name), + execution_windows=dict(execution_windows) if execution_windows is not None else None, ) @@ -137,6 +140,10 @@ def resolve_runtime_target_from_env( "RUNTIME_TARGET_JSON.execution_mode does not match dry_run_only" ) + execution_windows = payload.get("execution_windows") + if execution_windows is not None and not isinstance(execution_windows, dict): + raise ValueError("RUNTIME_TARGET_JSON.execution_windows must be an object when present") + return build_runtime_target( platform_id=resolved_platform_id, strategy_profile=resolved_strategy_profile, @@ -145,4 +152,5 @@ def resolve_runtime_target_from_env( account_selector=payload.get("account_selector"), account_scope=payload.get("account_scope"), service_name=payload.get("service_name"), + execution_windows=execution_windows, ) diff --git a/tests/test_runtime_target.py b/tests/test_runtime_target.py index c72e67b..18918db 100644 --- a/tests/test_runtime_target.py +++ b/tests/test_runtime_target.py @@ -19,6 +19,10 @@ def test_build_runtime_target_normalizes_selectors_and_mode(self) -> None: account_selector=(" U123 ", "", None), account_scope=" hk ", service_name=" longbridge-quant-hk-service ", + execution_windows={ + "precheck": {"enabled": True, "offset_minutes": 15, "mode": "notify_only"}, + "execution": {"enabled": True, "offset_minutes": 15, "mode": "paper"}, + }, ) self.assertEqual(target.platform_id, "longbridge") @@ -28,6 +32,8 @@ def test_build_runtime_target_normalizes_selectors_and_mode(self) -> None: self.assertEqual(target.account_selector, ("U123",)) self.assertEqual(target.account_scope, "hk") self.assertEqual(target.service_name, "longbridge-quant-hk-service") + self.assertEqual(target.execution_windows["precheck"]["offset_minutes"], 15) + self.assertEqual(target.execution_windows["execution"]["mode"], "paper") def test_build_runtime_target_supports_live_mode_without_account_selector(self) -> None: target = build_runtime_target( @@ -50,7 +56,9 @@ def test_resolve_runtime_target_from_env_prefers_structured_json(self) -> None: '{"platform_id":"longbridge","strategy_profile":"global_etf_rotation",' '"dry_run_only":true,"deployment_selector":"HK","account_selector":["HK"],' '"account_scope":"HK","service_name":"longbridge-quant-hk-service",' - '"execution_mode":"paper"}' + '"execution_mode":"paper","execution_windows":{"precheck":{"enabled":true,' + '"offset_minutes":15,"mode":"notify_only"},"execution":{"enabled":true,' + '"offset_minutes":15,"mode":"paper"}}}' ) }, ) @@ -63,6 +71,8 @@ def test_resolve_runtime_target_from_env_prefers_structured_json(self) -> None: self.assertEqual(target.account_selector, ("HK",)) self.assertEqual(target.account_scope, "HK") self.assertEqual(target.service_name, "longbridge-quant-hk-service") + self.assertTrue(target.execution_windows["precheck"]["enabled"]) + self.assertEqual(target.execution_windows["execution"]["offset_minutes"], 15) def test_resolve_runtime_target_from_env_rejects_mismatched_execution_mode(self) -> None: with self.assertRaisesRegex(ValueError, "execution_mode does not match dry_run_only"): @@ -101,6 +111,10 @@ def test_build_runtime_context_fields_merges_runtime_target_without_overwriting_ strategy_profile="soxl_soxx_trend_income", dry_run_only=True, service_name="longbridge-platform", + execution_windows={ + "precheck": {"enabled": True, "offset_minutes": 15, "mode": "notify_only"}, + "execution": {"enabled": True, "offset_minutes": 15, "mode": "paper"}, + }, ) fields = build_runtime_context_fields( @@ -115,3 +129,4 @@ def test_build_runtime_context_fields_merges_runtime_target_without_overwriting_ self.assertEqual(fields["account_scope"], "HK") self.assertEqual(fields["runtime_target"]["platform_id"], "longbridge") self.assertEqual(fields["runtime_target"]["execution_mode"], "paper") + self.assertEqual(fields["runtime_target"]["execution_windows"]["precheck"]["enabled"], True)