From 2b7e121674f850a56e9bad4d3428999cf72a261e Mon Sep 17 00:00:00 2001
From: Pigbibi <20649888+Pigbibi@users.noreply.github.com>
Date: Wed, 3 Jun 2026 13:12:43 +0800
Subject: [PATCH] Split English and Chinese README docs
---
README.md | 301 ++---------------------------------------------
README.zh-CN.md | 302 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 312 insertions(+), 291 deletions(-)
create mode 100644 README.zh-CN.md
diff --git a/README.md b/README.md
index 78eb1c1..9a8a1e9 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,16 @@
# InteractiveBrokersPlatform
-> ⚠️ 投资有风险,不构成投资建议,仅供学习交流用途。
+> Risk warning: this project is not investment advice and is provided for study and engineering validation only.




-[English](#english) | [中文](#中文)
+Language: [English](README.md) | [中文](README.zh-CN.md)
---
-
-## English
-
IBKR runtime for shared `us_equity` profiles from `UsEquityStrategies` and `hk_equity` profiles from `HkEquityStrategies`. Strategy logic, cadence, asset universes, parameters, and research/backtest notes live in the strategy repositories.
The runtime carries a structured `RuntimeTarget` / `RUNTIME_TARGET_JSON` for the running service identity. Strategy-owned defaults come from `UsEquityStrategies` and `HkEquityStrategies`; platform variables are only explicit overrides.
@@ -30,6 +27,14 @@ The mainline runtime now follows one path only:
`main.py` no longer reads private strategy constants or platform-only fields from strategy return payloads.
+### Execution safety
+
+IBKR order routing expects weight targets. Value-target decisions are translated
+to weights using `portfolio_total_equity` from runtime metadata. If a new or
+empty account reports non-positive total equity, the mapper marks the decision
+as `no_execute` and omits the allocation payload instead of attempting
+translation.
+
### Strategy profile support
**Supported `STRATEGY_PROFILE` values**
@@ -447,289 +452,3 @@ gcloud run services update ibkr-quant \
--vpc-egress private-ranges-only \
--update-env-vars IB_GATEWAY_IP_MODE=internal
```
-
----
-
-
-## 中文
-
-IBKR runtime 负责把共享的 `us_equity` / `hk_equity` 策略档位部署到 GCP Cloud Run,并连接 GCE 上的 IB Gateway 执行。策略逻辑、策略频率、标的池、参数和研究/回测说明放在策略仓库;这个仓库只维护 IBKR 运行时、账号组、Gateway 连接、下单和通知。
-
-策略说明放在 [`UsEquityStrategies`](https://github.com/QuantStrategyLab/UsEquityStrategies) 和 [`HkEquityStrategies`](https://github.com/QuantStrategyLab/HkEquityStrategies);港股 snapshot artifact 由 [`HkEquitySnapshotPipelines`](https://github.com/QuantStrategyLab/HkEquitySnapshotPipelines) 生成。这个 README 只保留 IBKR 运行时、profile 启用状态、部署和凭据说明。
-
-### 执行边界
-
-当前主线运行路径已经统一为:
-
-- `main.py` 负责把平台输入组装成 `StrategyContext`
-- `strategy_runtime.py` 负责加载统一策略入口
-- `entrypoint.evaluate(ctx)` 返回共享的 `StrategyDecision`
-- `decision_mapper.py` 再把决策映射成 IBKR 订单、通知和运行时更新
-
-`main.py` 已经不再直接读取策略私有常量,也不再依赖策略返回里的平台专属字段。
-
-### 策略输入边界
-
-feature-snapshot 类策略使用 `UsEquitySnapshotPipelines` 或 `HkEquitySnapshotPipelines` 发布的上游 artifact。这个运行时只需要 artifact 的位置,例如 `IBKR_FEATURE_SNAPSHOT_PATH`;策略逻辑、策略频率、特征定义和 snapshot schema 说明放在策略/快照仓库。
-
-港股运行时范围、平台矩阵和环境变量默认值见 [`docs/hk_equity_runtime.md`](docs/hk_equity_runtime.md)。
-
-港股 Cloud Run 部署或环境复核先打印切换计划;如需部署或重新同步独立 HK dry-run 服务,再手动触发 `Deploy Cloud Run` workflow 的 `target=hk-verify`:
-
-```bash
-python scripts/print_strategy_switch_env_plan.py --profile hk_listed_global_etf_rotation --dry-run-only --deployment-selector hk-verify --account-scope hk-verify --account-group hk-verify --service-name interactive-brokers-hk-verify-service --json
-gh workflow run sync-cloud-run-env.yml --repo QuantStrategyLab/InteractiveBrokersPlatform -f target=hk-verify -f cloud_run_region= -f cloud_run_service=interactive-brokers-hk-verify-service -f account_group=hk-verify -f account_group_config_secret_name=ibkr-account-groups -f deploy_image=true -f sync_env=true
-```
-
-### 架构
-
-```
-Cloud Scheduler(cron 以所选策略的策略层频率为准)
- ↓ HTTP POST
-Cloud Run (Flask: 策略计算 + 编排)
- ↓ 共享平台适配层
-QuantPlatformKit (IBKR adapter)
- ↓ ib_insync TCP
-GCE (IB Gateway 常驻)
- ↓
-IBKR 账户
-```
-
-### 运行时环境变量
-
-现在 `ACCOUNT_GROUP` 就是运行身份选择器。broker 侧身份信息应该放在账号组配置 JSON 里,不要继续把这部分主配置塞回 Cloud Run env。
-
-| 变量 | 必需 | 说明 |
-|------|------|------|
-| `IB_GATEWAY_ZONE` | 可选过渡项 | GCE zone(如 `us-central1-a`)。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
-| `IB_GATEWAY_IP_MODE` | 可选过渡项 | `internal`(默认)或 `external`。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
-| `IBKR_CONNECT_TIMEOUT_SECONDS` | 否 | IB API 握手超时时间,单位秒。默认 `60`;只有 Gateway 远程 API 启动持续偏慢时才需要调高。 |
-| `IBKR_CONNECT_ATTEMPTS` | 否 | IBKR 连接失败前最多尝试次数。默认 `3`。 |
-| `IBKR_CONNECT_RETRY_DELAY_SECONDS` | 否 | IBKR 连接重试间隔,单位秒。默认 `5`。 |
-| `IBKR_CLIENT_ID_RETRY_OFFSET` | 否 | 每次重试时加到 `ib_client_id` 上的偏移量,用新的 client id 避开超时握手留下的卡住会话。默认 `100`。 |
-| `STRATEGY_PROFILE` | 是 | 策略档位选择。当前已启用值:`global_etf_rotation`、`russell_1000_multi_factor_defensive`、`tqqq_growth_income`、`soxl_soxx_trend_income`、`tech_communication_pullback_enhancement`、`mega_cap_leader_rotation_top50_balanced`、`nasdaq_sp500_smart_dca`、`hk_listed_global_etf_rotation`。`hk_blue_chip_leader_rotation`、`hk_index_mean_reversion`、`hk_etf_regime_rotation` 不是 runtime-enabled,不会被平台状态/切换工具列为可选;Cloud Run 使用当前服务上配置的取值 |
-| `ACCOUNT_GROUP` | 是 | 账号组选择器,每个部署都要显式设置。 |
-| `IBKR_MARKET` | 否 | 市场范围。`ACCOUNT_GROUP` 包含 `hk` 时默认 `HK`,其他情况默认 `US`。 |
-| `IBKR_MARKET_CALENDAR` | 否 | 市场日历。港股默认 `XHKG`,美股默认 `NYSE`。 |
-| `IBKR_MARKET_TIMEZONE` | 否 | 市场时区。港股默认 `Asia/Hong_Kong`,美股默认 `America/New_York`。 |
-| `IBKR_MARKET_EXCHANGE` | 否 | 股票合约交易所。港股默认 `SEHK`,美股默认 `SMART`。 |
-| `IBKR_MARKET_CURRENCY` | 否 | 股票合约币种和组合现金口径。港股默认 `HKD`,美股默认 `USD`。 |
-| `IBKR_MARKET_DATA_SYMBOL_SUFFIX` | 否 | 仅用于 yfinance fallback 的标的后缀。港股默认 `.HK`,美股默认空。 |
-| `IBKR_FEATURE_SNAPSHOT_PATH` | 条件必填 | `russell_1000_multi_factor_defensive`、`tech_communication_pullback_enhancement`、`mega_cap_leader_rotation_top50_balanced` 等已启用快照策略需要;港股架构占位后续启用时也会需要。指向最新特征快照文件(`.csv`、`.json`、`.jsonl`、`.parquet`)。 |
-| `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` | 否 | 可选的 IBKR 侧策略插件挂载 JSON。插件 artifact 自带模式;平台配置不要设置 `mode`。 |
-| `IBKR_MIN_ORDER_NOTIONAL_USD` | 否 | 限价买入的最小名义金额;默认 `50.0`。 |
-| `IBKR_MIN_RESERVED_CASH_USD` | 否 | 平台级最低预留现金 USD。默认 `0`;实际预留取该下限和有效预留现金比例中的最大值。 |
-| `IBKR_RESERVED_CASH_RATIO` | 否 | 平台级最低预留现金比例,取值 `[0,1]`。不设置时沿用策略/运行配置里的 `execution_cash_reserve_ratio`;设置后只会抬高,不会降低策略比例。 |
-| `IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD` | 否 | `BOXX`/`BIL` 等避险标的目标金额低于该 USD 门槛时保留现金,不买入。默认 `1000.0`。 |
-| `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME` | Cloud Run 建议必填 | 账号组配置 JSON 在 Secret Manager 里的密钥名。生产环境推荐使用。 |
-| `IB_ACCOUNT_GROUP_CONFIG_JSON` | 否 | 本地开发用的账号组配置 JSON fallback。不建议在生产 Cloud Run 直接使用。 |
-| `TELEGRAM_TOKEN` | 是 | Telegram 机器人 Token。Cloud Run 上更推荐走 Secret Manager 引用,不要直接写成明文 env。 |
-| `GLOBAL_TELEGRAM_CHAT_ID` | 是 | 这个服务使用的 Telegram Chat ID。 |
-| `NOTIFY_LANG` | 否 | `en`(默认)或 `zh` |
-| `CRISIS_ALERT_CHANNELS` | 否 | 可选危机告警通道列表:`email`、`sms`、`push` 和/或 `telegram`。 |
-| `CRISIS_ALERT_EMAIL_RECIPIENTS` | 否 | 通知收件邮箱。普通邮箱只收邮件;关联 Google Voice 的邮箱/地址会额外触发 Google Voice 提醒。支持逗号、分号或换行分隔。 |
-| `CRISIS_ALERT_EMAIL_SENDER_EMAIL` | 否 | 邮件通知的发送方邮箱。默认传输走 Gmail SMTP,但命名不绑定 Gmail。 |
-| `CRISIS_ALERT_EMAIL_SENDER_PASSWORD` | 否 | 发送方 SMTP 密码或 app password。Cloud Run env sync 建议配置 `CRISIS_ALERT_EMAIL_SENDER_PASSWORD_SECRET_NAME`。 |
-| `CRISIS_ALERT_EMAIL_SMTP_HOST` | 否 | 可选 SMTP host 覆盖。不设置时默认 Gmail SMTP。 |
-| `CRISIS_ALERT_EMAIL_SMTP_PORT` | 否 | 可选 SMTP port 覆盖。不设置时默认 `465`。 |
-| `CRISIS_ALERT_EMAIL_SMTP_SECURITY` | 否 | 可选 SMTP 加密方式:`ssl`、`starttls` 或 `none`。不设置时默认 `ssl`。 |
-| `CRISIS_ALERT_TELEGRAM_CHAT_IDS` | 否 | 危机告警专用 Telegram chat ID,和常规策略周期 Telegram 分开。 |
-| `CRISIS_ALERT_TELEGRAM_BOT_TOKEN` | 否 | 危机告警专用 Telegram bot token。Cloud Run env sync 建议配置 `CRISIS_ALERT_TELEGRAM_BOT_TOKEN_SECRET_NAME`。 |
-
-默认 Gateway 执行后端下,选中的账号组配置里至少要有:
-
-- `execution_backend`(可选;默认 `gateway`)
-- `ib_gateway_instance_name`
-- `ib_gateway_mode`
-- `ib_client_id`
-
-按当前推荐的 Cloud Run 部署方式,最好再一起放上:
-
-- `ib_gateway_zone`
-- `ib_gateway_port`(同一台 VM 上有多个 Gateway 时填写;不填则 live 默认 `4001`,paper 默认 `4002`)
-- `ib_gateway_ip_mode`(或者直接走默认 `internal`)
-
-如果你配置了 `ib_gateway_zone` 让程序通过实例名解析内网 IP,Cloud Run runtime service account 需要 `roles/compute.viewer`。如果账号组配置来源是 Secret Manager,同一个 runtime service account 还需要对 `ibkr-account-groups` 具备 `roles/secretmanager.secretAccessor`。
-
-账号组也可以把 `execution_backend` 设为 `quantconnect`。这个模式不再要求 Gateway 主机、端口和 client id,当前 Cloud Run 服务也会 fail-fast,避免误连 Gateway。QuantConnect 路径是部署/算法后端:真实账号映射、QuantConnect project/node/compile 标识、API token 和 IBKR 券商凭证都必须留在私有运行配置里,并且需要 QuantConnect 算法项目消费同一套策略或目标配置后才能实盘下单。
-
-**推荐的共享配置模式**
-
-当前第一步,建议让 GitHub / Cloud Run 只维护服务级变量:
-
-```bash
-STRATEGY_PROFILE=soxl_soxx_trend_income
-ACCOUNT_GROUP=paper
-IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
-GLOBAL_TELEGRAM_CHAT_ID=
-NOTIFY_LANG=zh
-
-# 仅作为过渡 fallback:
-IB_GATEWAY_ZONE=us-central1-c
-IB_GATEWAY_IP_MODE=internal
-```
-
-这里说的“共享配置”只针对 **IBKR 这一组系统**,也就是 `InteractiveBrokersPlatform` 和 `IBKRGatewayManager` 之间共享。它不是让所有 quant 仓库都共用一套 secrets。对多个量化仓库来说,`GLOBAL_TELEGRAM_CHAT_ID`、`NOTIFY_LANG`、`CRISIS_ALERT_CHANNELS`,以及同一套危机告警策略下的 `CRISIS_ALERT_EMAIL_*`/`CRISIS_ALERT_PUSH_*` 适合提升到组织级配置;告警 token 和密码仍应放在 GitHub Secret 或 GCP Secret Manager。
-
-推荐的账号组配置 JSON:
-
-```json
-{
- "groups": {
- "paper": {
- "execution_backend": "gateway",
- "ib_gateway_instance_name": "interactive-brokers-quant-instance",
- "ib_gateway_zone": "us-central1-c",
- "ib_gateway_mode": "paper",
- "ib_gateway_port": 4002,
- "ib_gateway_ip_mode": "internal",
- "ib_client_id": 1,
- "service_name": "interactive-brokers-quant-service",
- "account_ids": ["DU1234567"]
- }
- }
-}
-```
-
-仓库里也提供了一个可以直接改的起始样例:[`docs/examples/ibkr-account-groups.paper.json`](docs/examples/ibkr-account-groups.paper.json)。如果你要按 `ACCOUNT_GROUP=paper` 先落地,直接看 [`docs/ibkr_runtime_rollout.md`](docs/ibkr_runtime_rollout.md)。
-
-实盘多账户建议一个 UID 对应一个 Cloud Run 服务和一个账号组。每个实盘账号组只放一个 `account_ids` 值;运行时会用它过滤持仓、pending/fill 检查,并把同一个 UID 写进 IBKR 订单的 `order.account`。
-
-如果 IB Gateway 登录用户名本身能访问多个 linked IBKR 账户,仍然建议把这种更宽的登录权限留在 Gateway 层,每个 Cloud Run 服务只通过自己选中的账号组 `account_ids` 限定一个交易账户。服务连接成功后会校验该账号是否出现在 IBKR `managedAccounts` 里;如果不可见,会在读取组合或提交订单之前失败。开源仓库只保留通用示例,私有实盘映射放在 Secret Manager 等运行配置里。
-
-如果 IBKR 的每个副用户名只能看到一个 linked 账户,就按“一个用户名一个 Gateway session”部署;每个账号组配置自己的 `ib_gateway_port` 和 `ib_client_id`。多个 Gateway 可以在同一台 VM 上运行,只要 Gateway 容器暴露到不同 host port。
-
-当前行为改成了 fail-fast:
-
-- 没有 `STRATEGY_PROFILE` → 启动直接报错
-- 没有 `ACCOUNT_GROUP` → 启动直接报错
-- 没有账号组配置来源 → 启动直接报错
-- `gateway` 账号组缺少关键字段(`ib_gateway_instance_name`、`ib_gateway_mode`、`ib_client_id`)→ 启动直接报错
-- `execution_backend=quantconnect` 时直接请求 Gateway 执行 → 启动/执行前直接报错,不会尝试连接 Gateway
-
-如果 `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` 挂载了 `crisis_response_shadow` 插件,常规策略周期 Telegram 仍会包含插件摘要行。当插件信号升级到非 `no_action`(例如 `canonical_route=true_crisis`、`suggested_action=defend`/`blocked`,或 `would_trade_if_enabled=true`)时,服务还会按 `CRISIS_ALERT_CHANNELS` 配置额外发送独立危机通知。
-告警结果会写入 runtime report。重复发送抑制使用稳定的插件告警 key;如配置了 `STRATEGY_PLUGIN_ALERT_STATE_GCS_URI` 则写入该前缀,否则复用 `EXECUTION_REPORT_GCS_URI`,并有本地 `/tmp` marker fallback。
-
-### GitHub 统一管理 Cloud Run 部署和环境变量
-
-这个仓库提供 `.github/workflows/sync-cloud-run-env.yml` 作为 GitHub 管理 Cloud Run 的入口。设置 `ENABLE_GITHUB_CLOUD_RUN_DEPLOY=true` 时,GitHub Actions 会构建并发布容器镜像;设置 `ENABLE_GITHUB_ENV_SYNC=true` 时,GitHub Actions 会同步运行时环境变量。迁移期间两个开关可以独立启用,旧的 Google Cloud Trigger 也可以先保留。
-
-`push main` 使用 `ENABLE_MAIN_PUSH_CLOUD_RUN_AUTOMATION` 自动化开关。需要 main 分支 push 也触发 Cloud Run 自动化时,把它设为 `true`;手动 `workflow_dispatch` 仍按上面的部署/同步开关执行。
-
-推荐配置方式:
-
-- **仓库级 Variables**
- - `ENABLE_GITHUB_CLOUD_RUN_DEPLOY` = `true`(让 GitHub Actions 负责 build/push/deploy)
- - `ENABLE_GITHUB_ENV_SYNC` = `true`
- - `CLOUD_RUN_REGION`
- - `CLOUD_RUN_SERVICES`(逗号、分号或换行分隔的 Cloud Run 服务名;slot 部署优先用这个)
- - `CLOUD_RUN_SERVICE`(没设置 `CLOUD_RUN_SERVICES` 时的单服务兼容入口)
- - `CLOUD_RUN_SERVICE_TARGETS_JSON`(全量同步 slot 时优先用这个,见下面示例)
- - 可选:`GCP_ARTIFACT_REGISTRY_HOSTNAME`(Artifact Registry 不在 Cloud Run region 时才需要;默认 `-docker.pkg.dev`)
- - 可选:`CLOUD_RUN_ENV_SYNC_WAIT_FOR_COMMIT=false`(当目标服务由另一个部署链路管理、不会在同步前更新 `commit-sha` label 时使用)
- - `TELEGRAM_TOKEN_SECRET_NAME`(如果 Cloud Run 上的 `TELEGRAM_TOKEN` 已经改成 Secret Manager,建议配置)
- - `STRATEGY_PROFILE`(显式设置为任一已启用 profile,例如 `soxl_soxx_trend_income`)
- - `ACCOUNT_GROUP`(建议设为 `paper`)
- - `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`
- - 可选:`IBKR_MARKET`、`IBKR_MARKET_CALENDAR`、`IBKR_MARKET_CURRENCY`、`IBKR_MARKET_DATA_SYMBOL_SUFFIX`、`IBKR_MARKET_EXCHANGE`、`IBKR_MARKET_TIMEZONE`、`IBKR_STRATEGY_PLUGIN_MOUNTS_JSON`、`IBKR_MIN_RESERVED_CASH_USD`、`IBKR_RESERVED_CASH_RATIO`、`IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD`
- - 可选:`CRISIS_ALERT_EMAIL_RECIPIENTS`、`CRISIS_ALERT_EMAIL_SENDER_EMAIL`、`CRISIS_ALERT_EMAIL_SENDER_PASSWORD_SECRET_NAME`
- - 可选:`CRISIS_ALERT_EMAIL_SMTP_HOST`、`CRISIS_ALERT_EMAIL_SMTP_PORT`、`CRISIS_ALERT_EMAIL_SMTP_SECURITY`
- - `GLOBAL_TELEGRAM_CHAT_ID`
- - `NOTIFY_LANG`
-- **仓库级 Secrets**
- - `TELEGRAM_TOKEN`(仅在没设置 `TELEGRAM_TOKEN_SECRET_NAME` 时作为 fallback)
- - `CRISIS_ALERT_EMAIL_SENDER_PASSWORD`(仅在没设置 `CRISIS_ALERT_EMAIL_SENDER_PASSWORD_SECRET_NAME` 时作为 fallback)
-- **可选过渡 Variables**
- - `IB_GATEWAY_ZONE`
- - `IB_GATEWAY_IP_MODE`
-
-每次 push 到 `main` 时,这个 workflow 可以先构建一份容器镜像并部署到一个或多个 Cloud Run 服务,再生成 Cloud Run sync plan,把目标值同步到配置的服务里,并清掉已经转移到账号组配置里的旧 env(`IB_CLIENT_ID`、`IB_GATEWAY_INSTANCE_NAME`、`IB_GATEWAY_MODE`)以及更早的传输层 env(`IB_GATEWAY_HOST`、`IB_GATEWAY_PORT`、`TELEGRAM_CHAT_ID`)。如果目标 sync 配置里没有 `IB_GATEWAY_ZONE` 或 `IB_GATEWAY_IP_MODE`,workflow 也会把 Cloud Run 上这两个旧值一起删除,避免双配置源漂移。
-
-`STRATEGY_PROFILE` 由平台能力矩阵和从 `runtime_enabled` 策略元数据派生的 rollout allowlist 一起决定。当前策略域是 `us_equity` 和 `hk_equity`:`eligible` 表示平台理论上能跑,`enabled` 表示当前 rollout 真正放开。`ACCOUNT_GROUP` 是严格必填项,并会选中一份账号组配置。运行身份不完整时,服务会直接失败,不再静默回退。
-
-注意:
-
-- 只有在 `ENABLE_GITHUB_ENV_SYNC=true` 时,这个 workflow 才会严格校验并执行同步。没打开时会直接跳过。打开后,它会用 `scripts/build_cloud_run_env_sync_plan.py` 生成 per-service plan,并从策略状态矩阵动态解析每个目标策略需要的 snapshot/config 输入,不再维护硬编码策略名列表。
-- 只有在 `ENABLE_GITHUB_CLOUD_RUN_DEPLOY=true` 时,GitHub Actions 才会接管代码部署;没打开时,旧的 Cloud Build trigger 仍可继续负责发布。
-- 全量同步 slot 时应配置 `CLOUD_RUN_SERVICE_TARGETS_JSON`。`CLOUD_RUN_SERVICES` 只适合旧模式,也就是多个服务确实要收到同一份 runtime env。
-- 这里说的“共享配置”仍然只针对 **IBKR 这一组系统**。`TELEGRAM_TOKEN` 和 `TELEGRAM_TOKEN_SECRET_NAME` 都还是这个仓库自己的配置,不建议提升成所有 quant 共用的全局配置。危机告警如果确实跨平台共用同一套收件人和发送方,可以用 GitHub Organization Variables/Secrets 管理 `CRISIS_ALERT_CHANNELS`、`CRISIS_ALERT_EMAIL_*` 和 `CRISIS_ALERT_PUSH_*`。
-- 如果设置了 `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`,Cloud Run 运行时还需要有对应 Secret 的访问权限。
-- GitHub 现在通过 OIDC + Workload Identity Federation 登录 Google Cloud,这个 workflow 不再需要 `GCP_SA_KEY`。
-- GitHub 部署路径使用仓库里的 Dockerfile 和 Artifact Registry。部署服务账号需要 Artifact Registry 写入、Cloud Run 管理,以及对 runtime service account 的 service-account user 权限。
-
-### Runtime Guard 告警
-
-`.github/workflows/runtime-guard.yml` 是 IBKR Flask handler 之外的第二层通知。它只读取
-Cloud Logging 中最近的 Cloud Scheduler 错误和 Cloud Run 请求/运行失败,然后直接通过
-`CRISIS_ALERT_TELEGRAM_BOT_TOKEN` + `CRISIS_ALERT_TELEGRAM_CHAT_IDS` 或 fallback 的
-`TELEGRAM_TOKEN` + `GLOBAL_TELEGRAM_CHAT_ID` 发 Telegram。
-
-这个 guard 不会调用 Cloud Run 的交易路由,主要覆盖 Scheduler 没打到服务、
-OIDC/IAM/audience 配错、Cloud Run 返回 4xx/5xx、或容器在 app-level Telegram fallback
-执行前就失败的情况。
-
-需要的配置:
-
-- `CLOUD_RUN_SERVICES`、`CLOUD_RUN_SERVICE`、`CLOUD_RUN_SERVICE_TARGETS_JSON` 或
- `RUNTIME_GUARD_CLOUD_RUN_SERVICES` 中至少有要监控的服务名
-- GitHub deploy service account 需要 `interactivebrokersquant` 项目级 `roles/logging.viewer`
-- GitHub 中继续配置 Telegram chat/token 变量或 secrets
-- 可选设置 `RUNTIME_GUARD_SCHEDULER_JOB_PATTERN`,用正则把 Scheduler 日志限制到本部署的 job
-
-默认计划每 30 分钟检查一次。若要做 missed-run 心跳,设置
-`RUNTIME_GUARD_REQUIRE_SUCCESS=true`,并把 `RUNTIME_GUARD_LOOKBACK_MINUTES` 设成覆盖预期
-Scheduler 运行时间的窗口。默认不强制心跳,避免非交易窗口误报。
-
-更严格的完成检查是 `Execution Report Heartbeat`
-(`.github/workflows/execution-report-heartbeat.yml`)。它会在工作日预期市场窗口后检查
-`EXECUTION_REPORT_GCS_URI` 下最近的 runtime report JSON,读取 `status/stage/errors`,
-如果没有近期 report 或 report 状态为 `error` 等失败状态就发 Telegram。GitHub deploy
-service account 需要对 report bucket 有对象读取/列举权限。
-slot 部署会从 `CLOUD_RUN_SERVICE_TARGETS_JSON` 解析每个 service,并要求每个 service 都有近期
-可接受 report;如果只想监控部分服务,设置 `RUNTIME_HEARTBEAT_REQUIRED_SERVICES`。
-
-### 部署单元和命名建议
-
-- `QuantPlatformKit` 只是共享依赖,不单独部署;Cloud Run 现在部署的是 `InteractiveBrokersPlatform`。
-- 推荐 Cloud Run 服务名:`interactive-brokers-quant-service`。
-- 后续如果扩到多账户,建议按 `ACCOUNT_GROUP` 拆成多个 Cloud Run 服务,并让每个服务在运行时选中自己的账号组配置。
-- 如果后面改 GitHub 仓库名或再次迁组织,Cloud Build / Cloud Run 里的 GitHub 来源需要重新选择,不要假设旧绑定会自动跟过去。
-- 统一部署模型和触发器迁移清单见 [`QuantPlatformKit/docs/deployment_model.md`](../QuantPlatformKit/docs/deployment_model.md)。
-
-### 部署
-
-1. **GCE**: 部署 IB Gateway(模拟或实盘),确认 API 已开启、需要远程连接时已允许非 localhost 客户端,并确认 `live` 使用 `4001`、`paper` 使用 `4002`。
-2. **VPC / 子网**: 让 Cloud Run 和 GCE 处于同一个 VPC。为了让防火墙规则更干净,建议给 Cloud Run Direct VPC egress 单独准备一个子网。
-3. **Cloud Run**: 部署此 Flask 应用时启用 Direct VPC egress。设置 `STRATEGY_PROFILE`、`ACCOUNT_GROUP`、`IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`;只有在账号组配置里还没放 `ib_gateway_zone` / `ib_gateway_ip_mode` 时,才临时保留 `IB_GATEWAY_ZONE` / `IB_GATEWAY_IP_MODE` 作为过渡 fallback。runtime service account 需要 `roles/secretmanager.secretAccessor`,若走实例名解析,还需要 `roles/compute.viewer`。
- - 如果使用 Cloud Run source deploy,还要给 `gs://run-sources-${PROJECT_ID}-${REGION}` 这个 bucket 授权 `roles/storage.objectViewer`,对象是 build service account、deploy service account,以及 `${PROJECT_NUMBER}-compute@developer.gserviceaccount.com`。
-4. **防火墙**: 只允许 Cloud Run 出口子网访问 GCE 的 `TCP 4001`(`live`)或 `TCP 4002`(`paper`)。
-5. **Cloud Scheduler**: 创建定时任务,POST 到 Cloud Run URL。cron 频率以所选策略仓库里的策略层 cadence 为准;美股日频 profile 可使用临近收盘的工作日计划,例如 `45 15 * * 1-5`(America/New_York),港股 profile 应按 XHKG 和 `Asia/Hong_Kong` 设置。
-6. **可选公网模式**: 只有在不能走 VPC 时,才设置 `IB_GATEWAY_IP_MODE=external`,并且要明确开放 GCE 公网 IP,同时严格限制来源 IP 和防火墙规则。
-
-示例部署命令:
-
-```bash
-gcloud run deploy interactive-brokers-quant-service \
- --source . \
- --region us-central1 \
- --service-account ibkr-platform-runtime@PROJECT_ID.iam.gserviceaccount.com \
- --concurrency 1 \
- --max-instances 1 \
- --network default \
- --subnet cloudrun-direct-egress \
- --vpc-egress private-ranges-only \
- --set-env-vars STRATEGY_PROFILE=global_etf_rotation,ACCOUNT_GROUP=paper,IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups,GLOBAL_TELEGRAM_CHAT_ID=123456789,NOTIFY_LANG=zh
-```
-
-如果服务已经存在,而你们的 CI 只是更新代码/镜像,可以单独补一次网络配置:
-
-```bash
-gcloud run services update ibkr-quant \
- --region us-central1 \
- --concurrency 1 \
- --max-instances 1 \
- --network default \
- --subnet cloudrun-direct-egress \
- --vpc-egress private-ranges-only \
- --update-env-vars IB_GATEWAY_IP_MODE=internal
-```
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 0000000..5964a11
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,302 @@
+# InteractiveBrokersPlatform
+
+> ⚠️ 投资有风险,不构成投资建议,仅供学习交流用途。
+
+
+
+
+
+
+语言: [中文](README.zh-CN.md) | [English](README.md)
+
+---
+
+## 中文
+
+IBKR runtime 负责把共享的 `us_equity` / `hk_equity` 策略档位部署到 GCP Cloud Run,并连接 GCE 上的 IB Gateway 执行。策略逻辑、策略频率、标的池、参数和研究/回测说明放在策略仓库;这个仓库只维护 IBKR 运行时、账号组、Gateway 连接、下单和通知。
+
+策略说明放在 [`UsEquityStrategies`](https://github.com/QuantStrategyLab/UsEquityStrategies) 和 [`HkEquityStrategies`](https://github.com/QuantStrategyLab/HkEquityStrategies);港股 snapshot artifact 由 [`HkEquitySnapshotPipelines`](https://github.com/QuantStrategyLab/HkEquitySnapshotPipelines) 生成。这个 README 只保留 IBKR 运行时、profile 启用状态、部署和凭据说明。
+
+### 执行边界
+
+当前主线运行路径已经统一为:
+
+- `main.py` 负责把平台输入组装成 `StrategyContext`
+- `strategy_runtime.py` 负责加载统一策略入口
+- `entrypoint.evaluate(ctx)` 返回共享的 `StrategyDecision`
+- `decision_mapper.py` 再把决策映射成 IBKR 订单、通知和运行时更新
+
+`main.py` 已经不再直接读取策略私有常量,也不再依赖策略返回里的平台专属字段。
+
+### 执行安全
+
+IBKR 下单链路需要 weight targets。value-target 决策会用 runtime metadata 里的
+`portfolio_total_equity` 换算成权重。如果新账户或空账户返回的 total equity 非正数,
+mapper 现在会把决策标记为 `no_execute`,并省略 allocation payload,而不是继续进入
+翻译并抛出校验错误。
+
+### 策略输入边界
+
+feature-snapshot 类策略使用 `UsEquitySnapshotPipelines` 或 `HkEquitySnapshotPipelines` 发布的上游 artifact。这个运行时只需要 artifact 的位置,例如 `IBKR_FEATURE_SNAPSHOT_PATH`;策略逻辑、策略频率、特征定义和 snapshot schema 说明放在策略/快照仓库。
+
+港股运行时范围、平台矩阵和环境变量默认值见 [`docs/hk_equity_runtime.md`](docs/hk_equity_runtime.md)。
+
+港股 Cloud Run 部署或环境复核先打印切换计划;如需部署或重新同步独立 HK dry-run 服务,再手动触发 `Deploy Cloud Run` workflow 的 `target=hk-verify`:
+
+```bash
+python scripts/print_strategy_switch_env_plan.py --profile hk_listed_global_etf_rotation --dry-run-only --deployment-selector hk-verify --account-scope hk-verify --account-group hk-verify --service-name interactive-brokers-hk-verify-service --json
+gh workflow run sync-cloud-run-env.yml --repo QuantStrategyLab/InteractiveBrokersPlatform -f target=hk-verify -f cloud_run_region= -f cloud_run_service=interactive-brokers-hk-verify-service -f account_group=hk-verify -f account_group_config_secret_name=ibkr-account-groups -f deploy_image=true -f sync_env=true
+```
+
+### 架构
+
+```
+Cloud Scheduler(cron 以所选策略的策略层频率为准)
+ ↓ HTTP POST
+Cloud Run (Flask: 策略计算 + 编排)
+ ↓ 共享平台适配层
+QuantPlatformKit (IBKR adapter)
+ ↓ ib_insync TCP
+GCE (IB Gateway 常驻)
+ ↓
+IBKR 账户
+```
+
+### 运行时环境变量
+
+现在 `ACCOUNT_GROUP` 就是运行身份选择器。broker 侧身份信息应该放在账号组配置 JSON 里,不要继续把这部分主配置塞回 Cloud Run env。
+
+| 变量 | 必需 | 说明 |
+|------|------|------|
+| `IB_GATEWAY_ZONE` | 可选过渡项 | GCE zone(如 `us-central1-a`)。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
+| `IB_GATEWAY_IP_MODE` | 可选过渡项 | `internal`(默认)或 `external`。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
+| `IBKR_CONNECT_TIMEOUT_SECONDS` | 否 | IB API 握手超时时间,单位秒。默认 `60`;只有 Gateway 远程 API 启动持续偏慢时才需要调高。 |
+| `IBKR_CONNECT_ATTEMPTS` | 否 | IBKR 连接失败前最多尝试次数。默认 `3`。 |
+| `IBKR_CONNECT_RETRY_DELAY_SECONDS` | 否 | IBKR 连接重试间隔,单位秒。默认 `5`。 |
+| `IBKR_CLIENT_ID_RETRY_OFFSET` | 否 | 每次重试时加到 `ib_client_id` 上的偏移量,用新的 client id 避开超时握手留下的卡住会话。默认 `100`。 |
+| `STRATEGY_PROFILE` | 是 | 策略档位选择。当前已启用值:`global_etf_rotation`、`russell_1000_multi_factor_defensive`、`tqqq_growth_income`、`soxl_soxx_trend_income`、`tech_communication_pullback_enhancement`、`mega_cap_leader_rotation_top50_balanced`、`nasdaq_sp500_smart_dca`、`hk_listed_global_etf_rotation`。`hk_blue_chip_leader_rotation`、`hk_index_mean_reversion`、`hk_etf_regime_rotation` 不是 runtime-enabled,不会被平台状态/切换工具列为可选;Cloud Run 使用当前服务上配置的取值 |
+| `ACCOUNT_GROUP` | 是 | 账号组选择器,每个部署都要显式设置。 |
+| `IBKR_MARKET` | 否 | 市场范围。`ACCOUNT_GROUP` 包含 `hk` 时默认 `HK`,其他情况默认 `US`。 |
+| `IBKR_MARKET_CALENDAR` | 否 | 市场日历。港股默认 `XHKG`,美股默认 `NYSE`。 |
+| `IBKR_MARKET_TIMEZONE` | 否 | 市场时区。港股默认 `Asia/Hong_Kong`,美股默认 `America/New_York`。 |
+| `IBKR_MARKET_EXCHANGE` | 否 | 股票合约交易所。港股默认 `SEHK`,美股默认 `SMART`。 |
+| `IBKR_MARKET_CURRENCY` | 否 | 股票合约币种和组合现金口径。港股默认 `HKD`,美股默认 `USD`。 |
+| `IBKR_MARKET_DATA_SYMBOL_SUFFIX` | 否 | 仅用于 yfinance fallback 的标的后缀。港股默认 `.HK`,美股默认空。 |
+| `IBKR_FEATURE_SNAPSHOT_PATH` | 条件必填 | `russell_1000_multi_factor_defensive`、`tech_communication_pullback_enhancement`、`mega_cap_leader_rotation_top50_balanced` 等已启用快照策略需要;港股架构占位后续启用时也会需要。指向最新特征快照文件(`.csv`、`.json`、`.jsonl`、`.parquet`)。 |
+| `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` | 否 | 可选的 IBKR 侧策略插件挂载 JSON。插件 artifact 自带模式;平台配置不要设置 `mode`。 |
+| `IBKR_MIN_ORDER_NOTIONAL_USD` | 否 | 限价买入的最小名义金额;默认 `50.0`。 |
+| `IBKR_MIN_RESERVED_CASH_USD` | 否 | 平台级最低预留现金 USD。默认 `0`;实际预留取该下限和有效预留现金比例中的最大值。 |
+| `IBKR_RESERVED_CASH_RATIO` | 否 | 平台级最低预留现金比例,取值 `[0,1]`。不设置时沿用策略/运行配置里的 `execution_cash_reserve_ratio`;设置后只会抬高,不会降低策略比例。 |
+| `IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD` | 否 | `BOXX`/`BIL` 等避险标的目标金额低于该 USD 门槛时保留现金,不买入。默认 `1000.0`。 |
+| `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME` | Cloud Run 建议必填 | 账号组配置 JSON 在 Secret Manager 里的密钥名。生产环境推荐使用。 |
+| `IB_ACCOUNT_GROUP_CONFIG_JSON` | 否 | 本地开发用的账号组配置 JSON fallback。不建议在生产 Cloud Run 直接使用。 |
+| `TELEGRAM_TOKEN` | 是 | Telegram 机器人 Token。Cloud Run 上更推荐走 Secret Manager 引用,不要直接写成明文 env。 |
+| `GLOBAL_TELEGRAM_CHAT_ID` | 是 | 这个服务使用的 Telegram Chat ID。 |
+| `NOTIFY_LANG` | 否 | `en`(默认)或 `zh` |
+| `CRISIS_ALERT_CHANNELS` | 否 | 可选危机告警通道列表:`email`、`sms`、`push` 和/或 `telegram`。 |
+| `CRISIS_ALERT_EMAIL_RECIPIENTS` | 否 | 通知收件邮箱。普通邮箱只收邮件;关联 Google Voice 的邮箱/地址会额外触发 Google Voice 提醒。支持逗号、分号或换行分隔。 |
+| `CRISIS_ALERT_EMAIL_SENDER_EMAIL` | 否 | 邮件通知的发送方邮箱。默认传输走 Gmail SMTP,但命名不绑定 Gmail。 |
+| `CRISIS_ALERT_EMAIL_SENDER_PASSWORD` | 否 | 发送方 SMTP 密码或 app password。Cloud Run env sync 建议配置 `CRISIS_ALERT_EMAIL_SENDER_PASSWORD_SECRET_NAME`。 |
+| `CRISIS_ALERT_EMAIL_SMTP_HOST` | 否 | 可选 SMTP host 覆盖。不设置时默认 Gmail SMTP。 |
+| `CRISIS_ALERT_EMAIL_SMTP_PORT` | 否 | 可选 SMTP port 覆盖。不设置时默认 `465`。 |
+| `CRISIS_ALERT_EMAIL_SMTP_SECURITY` | 否 | 可选 SMTP 加密方式:`ssl`、`starttls` 或 `none`。不设置时默认 `ssl`。 |
+| `CRISIS_ALERT_TELEGRAM_CHAT_IDS` | 否 | 危机告警专用 Telegram chat ID,和常规策略周期 Telegram 分开。 |
+| `CRISIS_ALERT_TELEGRAM_BOT_TOKEN` | 否 | 危机告警专用 Telegram bot token。Cloud Run env sync 建议配置 `CRISIS_ALERT_TELEGRAM_BOT_TOKEN_SECRET_NAME`。 |
+
+默认 Gateway 执行后端下,选中的账号组配置里至少要有:
+
+- `execution_backend`(可选;默认 `gateway`)
+- `ib_gateway_instance_name`
+- `ib_gateway_mode`
+- `ib_client_id`
+
+按当前推荐的 Cloud Run 部署方式,最好再一起放上:
+
+- `ib_gateway_zone`
+- `ib_gateway_port`(同一台 VM 上有多个 Gateway 时填写;不填则 live 默认 `4001`,paper 默认 `4002`)
+- `ib_gateway_ip_mode`(或者直接走默认 `internal`)
+
+如果你配置了 `ib_gateway_zone` 让程序通过实例名解析内网 IP,Cloud Run runtime service account 需要 `roles/compute.viewer`。如果账号组配置来源是 Secret Manager,同一个 runtime service account 还需要对 `ibkr-account-groups` 具备 `roles/secretmanager.secretAccessor`。
+
+账号组也可以把 `execution_backend` 设为 `quantconnect`。这个模式不再要求 Gateway 主机、端口和 client id,当前 Cloud Run 服务也会 fail-fast,避免误连 Gateway。QuantConnect 路径是部署/算法后端:真实账号映射、QuantConnect project/node/compile 标识、API token 和 IBKR 券商凭证都必须留在私有运行配置里,并且需要 QuantConnect 算法项目消费同一套策略或目标配置后才能实盘下单。
+
+**推荐的共享配置模式**
+
+当前第一步,建议让 GitHub / Cloud Run 只维护服务级变量:
+
+```bash
+STRATEGY_PROFILE=soxl_soxx_trend_income
+ACCOUNT_GROUP=paper
+IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
+GLOBAL_TELEGRAM_CHAT_ID=
+NOTIFY_LANG=zh
+
+# 仅作为过渡 fallback:
+IB_GATEWAY_ZONE=us-central1-c
+IB_GATEWAY_IP_MODE=internal
+```
+
+这里说的“共享配置”只针对 **IBKR 这一组系统**,也就是 `InteractiveBrokersPlatform` 和 `IBKRGatewayManager` 之间共享。它不是让所有 quant 仓库都共用一套 secrets。对多个量化仓库来说,`GLOBAL_TELEGRAM_CHAT_ID`、`NOTIFY_LANG`、`CRISIS_ALERT_CHANNELS`,以及同一套危机告警策略下的 `CRISIS_ALERT_EMAIL_*`/`CRISIS_ALERT_PUSH_*` 适合提升到组织级配置;告警 token 和密码仍应放在 GitHub Secret 或 GCP Secret Manager。
+
+推荐的账号组配置 JSON:
+
+```json
+{
+ "groups": {
+ "paper": {
+ "execution_backend": "gateway",
+ "ib_gateway_instance_name": "interactive-brokers-quant-instance",
+ "ib_gateway_zone": "us-central1-c",
+ "ib_gateway_mode": "paper",
+ "ib_gateway_port": 4002,
+ "ib_gateway_ip_mode": "internal",
+ "ib_client_id": 1,
+ "service_name": "interactive-brokers-quant-service",
+ "account_ids": ["DU1234567"]
+ }
+ }
+}
+```
+
+仓库里也提供了一个可以直接改的起始样例:[`docs/examples/ibkr-account-groups.paper.json`](docs/examples/ibkr-account-groups.paper.json)。如果你要按 `ACCOUNT_GROUP=paper` 先落地,直接看 [`docs/ibkr_runtime_rollout.md`](docs/ibkr_runtime_rollout.md)。
+
+实盘多账户建议一个 UID 对应一个 Cloud Run 服务和一个账号组。每个实盘账号组只放一个 `account_ids` 值;运行时会用它过滤持仓、pending/fill 检查,并把同一个 UID 写进 IBKR 订单的 `order.account`。
+
+如果 IB Gateway 登录用户名本身能访问多个 linked IBKR 账户,仍然建议把这种更宽的登录权限留在 Gateway 层,每个 Cloud Run 服务只通过自己选中的账号组 `account_ids` 限定一个交易账户。服务连接成功后会校验该账号是否出现在 IBKR `managedAccounts` 里;如果不可见,会在读取组合或提交订单之前失败。开源仓库只保留通用示例,私有实盘映射放在 Secret Manager 等运行配置里。
+
+如果 IBKR 的每个副用户名只能看到一个 linked 账户,就按“一个用户名一个 Gateway session”部署;每个账号组配置自己的 `ib_gateway_port` 和 `ib_client_id`。多个 Gateway 可以在同一台 VM 上运行,只要 Gateway 容器暴露到不同 host port。
+
+当前行为改成了 fail-fast:
+
+- 没有 `STRATEGY_PROFILE` → 启动直接报错
+- 没有 `ACCOUNT_GROUP` → 启动直接报错
+- 没有账号组配置来源 → 启动直接报错
+- `gateway` 账号组缺少关键字段(`ib_gateway_instance_name`、`ib_gateway_mode`、`ib_client_id`)→ 启动直接报错
+- `execution_backend=quantconnect` 时直接请求 Gateway 执行 → 启动/执行前直接报错,不会尝试连接 Gateway
+
+如果 `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` 挂载了 `crisis_response_shadow` 插件,常规策略周期 Telegram 仍会包含插件摘要行。当插件信号升级到非 `no_action`(例如 `canonical_route=true_crisis`、`suggested_action=defend`/`blocked`,或 `would_trade_if_enabled=true`)时,服务还会按 `CRISIS_ALERT_CHANNELS` 配置额外发送独立危机通知。
+告警结果会写入 runtime report。重复发送抑制使用稳定的插件告警 key;如配置了 `STRATEGY_PLUGIN_ALERT_STATE_GCS_URI` 则写入该前缀,否则复用 `EXECUTION_REPORT_GCS_URI`,并有本地 `/tmp` marker fallback。
+
+### GitHub 统一管理 Cloud Run 部署和环境变量
+
+这个仓库提供 `.github/workflows/sync-cloud-run-env.yml` 作为 GitHub 管理 Cloud Run 的入口。设置 `ENABLE_GITHUB_CLOUD_RUN_DEPLOY=true` 时,GitHub Actions 会构建并发布容器镜像;设置 `ENABLE_GITHUB_ENV_SYNC=true` 时,GitHub Actions 会同步运行时环境变量。迁移期间两个开关可以独立启用,旧的 Google Cloud Trigger 也可以先保留。
+
+`push main` 使用 `ENABLE_MAIN_PUSH_CLOUD_RUN_AUTOMATION` 自动化开关。需要 main 分支 push 也触发 Cloud Run 自动化时,把它设为 `true`;手动 `workflow_dispatch` 仍按上面的部署/同步开关执行。
+
+推荐配置方式:
+
+- **仓库级 Variables**
+ - `ENABLE_GITHUB_CLOUD_RUN_DEPLOY` = `true`(让 GitHub Actions 负责 build/push/deploy)
+ - `ENABLE_GITHUB_ENV_SYNC` = `true`
+ - `CLOUD_RUN_REGION`
+ - `CLOUD_RUN_SERVICES`(逗号、分号或换行分隔的 Cloud Run 服务名;slot 部署优先用这个)
+ - `CLOUD_RUN_SERVICE`(没设置 `CLOUD_RUN_SERVICES` 时的单服务兼容入口)
+ - `CLOUD_RUN_SERVICE_TARGETS_JSON`(全量同步 slot 时优先用这个,见下面示例)
+ - 可选:`GCP_ARTIFACT_REGISTRY_HOSTNAME`(Artifact Registry 不在 Cloud Run region 时才需要;默认 `-docker.pkg.dev`)
+ - 可选:`CLOUD_RUN_ENV_SYNC_WAIT_FOR_COMMIT=false`(当目标服务由另一个部署链路管理、不会在同步前更新 `commit-sha` label 时使用)
+ - `TELEGRAM_TOKEN_SECRET_NAME`(如果 Cloud Run 上的 `TELEGRAM_TOKEN` 已经改成 Secret Manager,建议配置)
+ - `STRATEGY_PROFILE`(显式设置为任一已启用 profile,例如 `soxl_soxx_trend_income`)
+ - `ACCOUNT_GROUP`(建议设为 `paper`)
+ - `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`
+ - 可选:`IBKR_MARKET`、`IBKR_MARKET_CALENDAR`、`IBKR_MARKET_CURRENCY`、`IBKR_MARKET_DATA_SYMBOL_SUFFIX`、`IBKR_MARKET_EXCHANGE`、`IBKR_MARKET_TIMEZONE`、`IBKR_STRATEGY_PLUGIN_MOUNTS_JSON`、`IBKR_MIN_RESERVED_CASH_USD`、`IBKR_RESERVED_CASH_RATIO`、`IBKR_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD`
+ - 可选:`CRISIS_ALERT_EMAIL_RECIPIENTS`、`CRISIS_ALERT_EMAIL_SENDER_EMAIL`、`CRISIS_ALERT_EMAIL_SENDER_PASSWORD_SECRET_NAME`
+ - 可选:`CRISIS_ALERT_EMAIL_SMTP_HOST`、`CRISIS_ALERT_EMAIL_SMTP_PORT`、`CRISIS_ALERT_EMAIL_SMTP_SECURITY`
+ - `GLOBAL_TELEGRAM_CHAT_ID`
+ - `NOTIFY_LANG`
+- **仓库级 Secrets**
+ - `TELEGRAM_TOKEN`(仅在没设置 `TELEGRAM_TOKEN_SECRET_NAME` 时作为 fallback)
+ - `CRISIS_ALERT_EMAIL_SENDER_PASSWORD`(仅在没设置 `CRISIS_ALERT_EMAIL_SENDER_PASSWORD_SECRET_NAME` 时作为 fallback)
+- **可选过渡 Variables**
+ - `IB_GATEWAY_ZONE`
+ - `IB_GATEWAY_IP_MODE`
+
+每次 push 到 `main` 时,这个 workflow 可以先构建一份容器镜像并部署到一个或多个 Cloud Run 服务,再生成 Cloud Run sync plan,把目标值同步到配置的服务里,并清掉已经转移到账号组配置里的旧 env(`IB_CLIENT_ID`、`IB_GATEWAY_INSTANCE_NAME`、`IB_GATEWAY_MODE`)以及更早的传输层 env(`IB_GATEWAY_HOST`、`IB_GATEWAY_PORT`、`TELEGRAM_CHAT_ID`)。如果目标 sync 配置里没有 `IB_GATEWAY_ZONE` 或 `IB_GATEWAY_IP_MODE`,workflow 也会把 Cloud Run 上这两个旧值一起删除,避免双配置源漂移。
+
+`STRATEGY_PROFILE` 由平台能力矩阵和从 `runtime_enabled` 策略元数据派生的 rollout allowlist 一起决定。当前策略域是 `us_equity` 和 `hk_equity`:`eligible` 表示平台理论上能跑,`enabled` 表示当前 rollout 真正放开。`ACCOUNT_GROUP` 是严格必填项,并会选中一份账号组配置。运行身份不完整时,服务会直接失败,不再静默回退。
+
+注意:
+
+- 只有在 `ENABLE_GITHUB_ENV_SYNC=true` 时,这个 workflow 才会严格校验并执行同步。没打开时会直接跳过。打开后,它会用 `scripts/build_cloud_run_env_sync_plan.py` 生成 per-service plan,并从策略状态矩阵动态解析每个目标策略需要的 snapshot/config 输入,不再维护硬编码策略名列表。
+- 只有在 `ENABLE_GITHUB_CLOUD_RUN_DEPLOY=true` 时,GitHub Actions 才会接管代码部署;没打开时,旧的 Cloud Build trigger 仍可继续负责发布。
+- 全量同步 slot 时应配置 `CLOUD_RUN_SERVICE_TARGETS_JSON`。`CLOUD_RUN_SERVICES` 只适合旧模式,也就是多个服务确实要收到同一份 runtime env。
+- 这里说的“共享配置”仍然只针对 **IBKR 这一组系统**。`TELEGRAM_TOKEN` 和 `TELEGRAM_TOKEN_SECRET_NAME` 都还是这个仓库自己的配置,不建议提升成所有 quant 共用的全局配置。危机告警如果确实跨平台共用同一套收件人和发送方,可以用 GitHub Organization Variables/Secrets 管理 `CRISIS_ALERT_CHANNELS`、`CRISIS_ALERT_EMAIL_*` 和 `CRISIS_ALERT_PUSH_*`。
+- 如果设置了 `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`,Cloud Run 运行时还需要有对应 Secret 的访问权限。
+- GitHub 现在通过 OIDC + Workload Identity Federation 登录 Google Cloud,这个 workflow 不再需要 `GCP_SA_KEY`。
+- GitHub 部署路径使用仓库里的 Dockerfile 和 Artifact Registry。部署服务账号需要 Artifact Registry 写入、Cloud Run 管理,以及对 runtime service account 的 service-account user 权限。
+
+### Runtime Guard 告警
+
+`.github/workflows/runtime-guard.yml` 是 IBKR Flask handler 之外的第二层通知。它只读取
+Cloud Logging 中最近的 Cloud Scheduler 错误和 Cloud Run 请求/运行失败,然后直接通过
+`CRISIS_ALERT_TELEGRAM_BOT_TOKEN` + `CRISIS_ALERT_TELEGRAM_CHAT_IDS` 或 fallback 的
+`TELEGRAM_TOKEN` + `GLOBAL_TELEGRAM_CHAT_ID` 发 Telegram。
+
+这个 guard 不会调用 Cloud Run 的交易路由,主要覆盖 Scheduler 没打到服务、
+OIDC/IAM/audience 配错、Cloud Run 返回 4xx/5xx、或容器在 app-level Telegram fallback
+执行前就失败的情况。
+
+需要的配置:
+
+- `CLOUD_RUN_SERVICES`、`CLOUD_RUN_SERVICE`、`CLOUD_RUN_SERVICE_TARGETS_JSON` 或
+ `RUNTIME_GUARD_CLOUD_RUN_SERVICES` 中至少有要监控的服务名
+- GitHub deploy service account 需要 `interactivebrokersquant` 项目级 `roles/logging.viewer`
+- GitHub 中继续配置 Telegram chat/token 变量或 secrets
+- 可选设置 `RUNTIME_GUARD_SCHEDULER_JOB_PATTERN`,用正则把 Scheduler 日志限制到本部署的 job
+
+默认计划每 30 分钟检查一次。若要做 missed-run 心跳,设置
+`RUNTIME_GUARD_REQUIRE_SUCCESS=true`,并把 `RUNTIME_GUARD_LOOKBACK_MINUTES` 设成覆盖预期
+Scheduler 运行时间的窗口。默认不强制心跳,避免非交易窗口误报。
+
+更严格的完成检查是 `Execution Report Heartbeat`
+(`.github/workflows/execution-report-heartbeat.yml`)。它会在工作日预期市场窗口后检查
+`EXECUTION_REPORT_GCS_URI` 下最近的 runtime report JSON,读取 `status/stage/errors`,
+如果没有近期 report 或 report 状态为 `error` 等失败状态就发 Telegram。GitHub deploy
+service account 需要对 report bucket 有对象读取/列举权限。
+slot 部署会从 `CLOUD_RUN_SERVICE_TARGETS_JSON` 解析每个 service,并要求每个 service 都有近期
+可接受 report;如果只想监控部分服务,设置 `RUNTIME_HEARTBEAT_REQUIRED_SERVICES`。
+
+### 部署单元和命名建议
+
+- `QuantPlatformKit` 只是共享依赖,不单独部署;Cloud Run 现在部署的是 `InteractiveBrokersPlatform`。
+- 推荐 Cloud Run 服务名:`interactive-brokers-quant-service`。
+- 后续如果扩到多账户,建议按 `ACCOUNT_GROUP` 拆成多个 Cloud Run 服务,并让每个服务在运行时选中自己的账号组配置。
+- 如果后面改 GitHub 仓库名或再次迁组织,Cloud Build / Cloud Run 里的 GitHub 来源需要重新选择,不要假设旧绑定会自动跟过去。
+- 统一部署模型和触发器迁移清单见 [`QuantPlatformKit/docs/deployment_model.md`](../QuantPlatformKit/docs/deployment_model.md)。
+
+### 部署
+
+1. **GCE**: 部署 IB Gateway(模拟或实盘),确认 API 已开启、需要远程连接时已允许非 localhost 客户端,并确认 `live` 使用 `4001`、`paper` 使用 `4002`。
+2. **VPC / 子网**: 让 Cloud Run 和 GCE 处于同一个 VPC。为了让防火墙规则更干净,建议给 Cloud Run Direct VPC egress 单独准备一个子网。
+3. **Cloud Run**: 部署此 Flask 应用时启用 Direct VPC egress。设置 `STRATEGY_PROFILE`、`ACCOUNT_GROUP`、`IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`;只有在账号组配置里还没放 `ib_gateway_zone` / `ib_gateway_ip_mode` 时,才临时保留 `IB_GATEWAY_ZONE` / `IB_GATEWAY_IP_MODE` 作为过渡 fallback。runtime service account 需要 `roles/secretmanager.secretAccessor`,若走实例名解析,还需要 `roles/compute.viewer`。
+ - 如果使用 Cloud Run source deploy,还要给 `gs://run-sources-${PROJECT_ID}-${REGION}` 这个 bucket 授权 `roles/storage.objectViewer`,对象是 build service account、deploy service account,以及 `${PROJECT_NUMBER}-compute@developer.gserviceaccount.com`。
+4. **防火墙**: 只允许 Cloud Run 出口子网访问 GCE 的 `TCP 4001`(`live`)或 `TCP 4002`(`paper`)。
+5. **Cloud Scheduler**: 创建定时任务,POST 到 Cloud Run URL。cron 频率以所选策略仓库里的策略层 cadence 为准;美股日频 profile 可使用临近收盘的工作日计划,例如 `45 15 * * 1-5`(America/New_York),港股 profile 应按 XHKG 和 `Asia/Hong_Kong` 设置。
+6. **可选公网模式**: 只有在不能走 VPC 时,才设置 `IB_GATEWAY_IP_MODE=external`,并且要明确开放 GCE 公网 IP,同时严格限制来源 IP 和防火墙规则。
+
+示例部署命令:
+
+```bash
+gcloud run deploy interactive-brokers-quant-service \
+ --source . \
+ --region us-central1 \
+ --service-account ibkr-platform-runtime@PROJECT_ID.iam.gserviceaccount.com \
+ --concurrency 1 \
+ --max-instances 1 \
+ --network default \
+ --subnet cloudrun-direct-egress \
+ --vpc-egress private-ranges-only \
+ --set-env-vars STRATEGY_PROFILE=global_etf_rotation,ACCOUNT_GROUP=paper,IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups,GLOBAL_TELEGRAM_CHAT_ID=123456789,NOTIFY_LANG=zh
+```
+
+如果服务已经存在,而你们的 CI 只是更新代码/镜像,可以单独补一次网络配置:
+
+```bash
+gcloud run services update ibkr-quant \
+ --region us-central1 \
+ --concurrency 1 \
+ --max-instances 1 \
+ --network default \
+ --subnet cloudrun-direct-egress \
+ --vpc-egress private-ranges-only \
+ --update-env-vars IB_GATEWAY_IP_MODE=internal
+```