Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions docs/platform_runtime_inventory.md
Original file line number Diff line number Diff line change
@@ -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:

Expand Down Expand Up @@ -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=<runtime_enabled us_equity profile>`
Expand All @@ -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**
Expand Down Expand Up @@ -97,19 +104,27 @@ 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=<runtime_enabled us_equity profile>` per account service
- `STRATEGY_PROFILE=<runtime_enabled us_equity profile>` per account service
- `ACCOUNT_REGION=PAPER|HK|SG`
- `LONGPORT_SECRET_NAME=<account token secret>`
- **Runtime secrets**
- Secret Manager refs for LongPort app key / app secret
- 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.
Expand Down
20 changes: 17 additions & 3 deletions docs/platform_runtime_inventory.zh-CN.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 平台运行接线清单

_校验快照日期:2026-04-18_
_校验快照日期:2026-05-14_

这份文档记录公开仓库里可以保留的 runtime 接线信息,用来快速回答一个问题:

Expand Down Expand Up @@ -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=<runtime_enabled us_equity profile>`
Expand All @@ -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`
- **核心运行选择器**
Expand Down Expand Up @@ -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=<runtime_enabled us_equity profile>`
Expand All @@ -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 服务、triggerGitHub 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` 应使用上面的完整运行时服务名,不再使用旧短前缀。
Expand Down
4 changes: 2 additions & 2 deletions docs/platform_strategy_matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<runtime_enabled us_equity profile>` | Cloud Run | Yes - controlled by platform rollout config |
| Charles Schwab | `QuantStrategyLab/CharlesSchwabPlatform` | `us_equity` | `RUNTIME_TARGET_JSON` | `STRATEGY_PROFILE=<runtime_enabled us_equity profile>` | Cloud Run | Yes - controlled by platform rollout config |
| LongBridge | `QuantStrategyLab/LongBridgePlatform` | `us_equity` | `RUNTIME_TARGET_JSON` + `ACCOUNT_REGION` | `STRATEGY_PROFILE=<runtime_enabled us_equity 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=<runtime_enabled us_equity 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
Expand All @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion docs/platform_strategy_matrix.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ _核对时间:2026-04-18_
|---|---|---|---|---|---|
| IBKR | `QuantStrategyLab/InteractiveBrokersPlatform` | `us_equity` | `STRATEGY_PROFILE=<runtime_enabled us_equity profile>` | Cloud Run | 可以,按平台 rollout 配置启用 |
| Charles Schwab | `QuantStrategyLab/CharlesSchwabPlatform` | `us_equity` | `STRATEGY_PROFILE=<runtime_enabled us_equity profile>` | Cloud Run | 可以,按平台 rollout 配置启用 |
| LongBridge | `QuantStrategyLab/LongBridgePlatform` | `us_equity` | 每个区域服务配置 `STRATEGY_PROFILE=<runtime_enabled us_equity profile>` | Cloud Run | 可以,按平台 rollout 配置启用 |
| LongBridge | `QuantStrategyLab/LongBridgePlatform` | `us_equity` | 每个区域服务配置 `STRATEGY_PROFILE=<runtime_enabled us_equity profile>` | Cloud Run | 可以,按账户服务 rollout 配置启用 |
| Binance | `QuantStrategyLab/BinancePlatform` | `crypto` | `crypto_leader_rotation` | Oracle Cloud + self-hosted runner | 当前只支持这个 crypto profile |

## 这张表现在该怎么理解
Expand Down
8 changes: 8 additions & 0 deletions src/quant_platform_kit/common/runtime_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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(),
Expand All @@ -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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Deep-copy execution_windows before storing runtime target

build_runtime_target only does a top-level dict() copy for execution_windows, so nested structures remain shared with the caller. If the original nested mapping is mutated after construction, RuntimeTarget (declared frozen=True) silently changes, which can corrupt downstream logging/report context that expects the runtime target to be an immutable snapshot.

Useful? React with 👍 / 👎.

)


Expand Down Expand Up @@ -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,
Expand All @@ -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,
)
17 changes: 16 additions & 1 deletion tests/test_runtime_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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(
Expand All @@ -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"}}}'
)
},
)
Expand All @@ -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"):
Expand Down Expand Up @@ -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(
Expand All @@ -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)