Skip to content

Commit 2bd8bf1

Browse files
committed
Drop legacy IBKR env compatibility
1 parent b3758c9 commit 2bd8bf1

7 files changed

Lines changed: 28 additions & 62 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,5 @@ jobs:
8484
8585
gcloud run services update "${CLOUD_RUN_SERVICE}" \
8686
--region "${CLOUD_RUN_REGION}" \
87+
--remove-env-vars "IB_GATEWAY_HOST,IB_GATEWAY_PORT,TELEGRAM_CHAT_ID" \
8788
--update-env-vars "TELEGRAM_TOKEN=${TELEGRAM_TOKEN},GLOBAL_TELEGRAM_CHAT_ID=${GLOBAL_TELEGRAM_CHAT_ID},NOTIFY_LANG=${NOTIFY_LANG},IB_GATEWAY_INSTANCE_NAME=${IB_GATEWAY_INSTANCE_NAME},IB_GATEWAY_ZONE=${IB_GATEWAY_ZONE},IB_GATEWAY_MODE=${IB_GATEWAY_MODE},IB_GATEWAY_IP_MODE=${IB_GATEWAY_IP_MODE},IB_CLIENT_ID=${IB_CLIENT_ID}"

README.md

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -99,19 +99,16 @@ Equity: $2,100.00 | Buying Power: $50.00
9999

100100
| Variable | Required | Description |
101101
|----------|----------|-------------|
102-
| `IB_GATEWAY_HOST` | Conditional | Legacy explicit host or IP. If not set, the app falls back to `IB_GATEWAY_INSTANCE_NAME`. |
103-
| `IB_GATEWAY_INSTANCE_NAME` | Conditional | Recommended shared config name for the GCE instance. Useful when Gateway and Quant share the same GitHub-managed config. |
102+
| `IB_GATEWAY_INSTANCE_NAME` | Yes | GCE instance name for the IB gateway. |
104103
| `IB_GATEWAY_ZONE` | Yes | GCE zone (e.g. `us-central1-a`) |
105104
| `IB_GATEWAY_IP_MODE` | No | `internal` (default) or `external`; for Cloud Run, `internal` with Direct VPC egress is recommended |
106-
| `IB_GATEWAY_MODE` | No | Recommended shared mode flag. `live` maps to port `4001`, `paper` maps to port `4002`. |
107-
| `IB_GATEWAY_PORT` | No | Legacy explicit port override. If set together with `IB_GATEWAY_MODE`, it must match (`live`=`4001`, `paper`=`4002`). |
105+
| `IB_GATEWAY_MODE` | Yes | Required mode flag. `live` maps to port `4001`, `paper` maps to port `4002`. |
108106
| `IB_CLIENT_ID` | No | IB client ID (default: 1) |
109107
| `TELEGRAM_TOKEN` | Yes | Telegram bot token |
110-
| `TELEGRAM_CHAT_ID` | Conditional | Per-service Telegram chat ID. If not set, the app falls back to `GLOBAL_TELEGRAM_CHAT_ID`. |
111-
| `GLOBAL_TELEGRAM_CHAT_ID` | No | Optional shared Telegram chat ID for teams that keep one common destination across multiple quant services. |
108+
| `GLOBAL_TELEGRAM_CHAT_ID` | Yes | Telegram chat ID used by this service. |
112109
| `NOTIFY_LANG` | No | `en` (default) or `zh` |
113110

114-
`IB_GATEWAY_HOST` and `IB_GATEWAY_INSTANCE_NAME` are backward-compatible alternatives; one of them must be set. If you use instance-name resolution with `IB_GATEWAY_ZONE`, the service account needs `roles/compute.viewer`. The recommended deployment is Cloud Run with Direct VPC egress to the GCE private IP. Set `IB_GATEWAY_IP_MODE=external` only if you intentionally expose the gateway over a public IP and have locked down API access and firewall rules.
111+
If you use instance-name resolution with `IB_GATEWAY_ZONE`, the service account needs `roles/compute.viewer`. The recommended deployment is Cloud Run with Direct VPC egress to the GCE private IP. Set `IB_GATEWAY_IP_MODE=external` only if you intentionally expose the gateway over a public IP and have locked down API access and firewall rules.
115112

116113
**Recommended shared-config mode**
117114

@@ -126,8 +123,6 @@ IB_CLIENT_ID=1
126123
NOTIFY_LANG=zh
127124
```
128125

129-
In this mode, you do not need to set `IB_GATEWAY_PORT` manually; the app derives it from `IB_GATEWAY_MODE`.
130-
131126
This shared-config mode is only for the **IBKR pair** (`IBKRQuant` + `IBKRGatewayManager`). It is not meant to become a global secret bundle for unrelated quant repos. Across multiple quant projects, the only broadly reusable runtime settings are usually `GLOBAL_TELEGRAM_CHAT_ID` and `NOTIFY_LANG`.
132127

133128
### GitHub-managed Cloud Run env sync
@@ -152,18 +147,18 @@ Recommended setup:
152147
- `GLOBAL_TELEGRAM_CHAT_ID`
153148
- `NOTIFY_LANG`
154149

155-
On every push to `main`, the workflow updates the existing Cloud Run service with the values above. It does **not** remove legacy `IB_GATEWAY_HOST`, `IB_GATEWAY_PORT`, or `TELEGRAM_CHAT_ID`, so existing deployments keep working. Once you have confirmed the service is reading the new shared values as intended, you can remove the legacy Cloud Run env vars manually.
150+
On every push to `main`, the workflow updates the existing Cloud Run service with the values above and removes `IB_GATEWAY_HOST`, `IB_GATEWAY_PORT`, and `TELEGRAM_CHAT_ID`.
156151

157152
Important:
158153

159-
- 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 + manual Cloud Run env setup keeps working.
154+
- The workflow only becomes strict when `ENABLE_GITHUB_ENV_SYNC=true`. If this variable is unset, the sync job is skipped.
160155
- Here "shared config" still only means the **IBKR pair** (`IBKRQuant` + `IBKRGatewayManager`). `GCP_SA_KEY` and `TELEGRAM_TOKEN` remain repository-specific.
161156

162157
### Deployment
163158

164159
1. **GCE**: Set up IB Gateway (paper or live) on a GCE instance. Ensure API access is enabled, remote clients are allowed when needed, and use `4001` for `live` or `4002` for `paper`.
165160
2. **VPC / Subnet**: Put Cloud Run and GCE in the same VPC. For cleaner firewall rules, reserve a dedicated subnet for Cloud Run Direct VPC egress.
166-
3. **Cloud Run**: Deploy or update this Flask app with Direct VPC egress. You can either keep the legacy pair `IB_GATEWAY_HOST + IB_GATEWAY_PORT`, or use the shared-config pair `IB_GATEWAY_INSTANCE_NAME + IB_GATEWAY_MODE`. In both cases keep `IB_GATEWAY_ZONE` and `IB_GATEWAY_IP_MODE=internal`.
161+
3. **Cloud Run**: Deploy or update this Flask app with Direct VPC egress. Use `IB_GATEWAY_INSTANCE_NAME + IB_GATEWAY_MODE`, and keep `IB_GATEWAY_ZONE` and `IB_GATEWAY_IP_MODE=internal`.
167162
4. **Firewall**: Allow TCP `4001` (`live`) or `4002` (`paper`) from the Cloud Run egress subnet CIDR to the GCE instance.
168163
5. **Cloud Scheduler**: Create a job: `45 15 * * 1-5` (America/New_York), POST to the Cloud Run URL. The code handles market calendar checks internally.
169164
6. **Optional public-IP mode**: Only if you cannot use VPC, set `IB_GATEWAY_IP_MODE=external`, expose the GCE public IP deliberately, and restrict source ranges tightly. This is not the default path.
@@ -247,19 +242,16 @@ IBKR 账户
247242

248243
| 变量 | 必需 | 说明 |
249244
|------|------|------|
250-
| `IB_GATEWAY_HOST` | 条件必需 | 旧写法,直接填主机名或 IP。不填时会回退到 `IB_GATEWAY_INSTANCE_NAME`|
251-
| `IB_GATEWAY_INSTANCE_NAME` | 条件必需 | 推荐的共享配置写法,填 GCE 实例名。适合和 Gateway 共用一套 GitHub 配置。 |
245+
| `IB_GATEWAY_INSTANCE_NAME` || IB Gateway 所在 GCE 实例名。 |
252246
| `IB_GATEWAY_ZONE` || GCE zone (如 `us-central1-a`) |
253247
| `IB_GATEWAY_IP_MODE` || `internal`(默认)或 `external`;Cloud Run 推荐配合 Direct VPC egress 使用 `internal` |
254-
| `IB_GATEWAY_MODE` || 推荐的共享模式变量。`live` 会映射到 `4001``paper` 会映射到 `4002`|
255-
| `IB_GATEWAY_PORT` || 旧写法,直接指定端口。如果和 `IB_GATEWAY_MODE` 同时配置,必须一致。 |
248+
| `IB_GATEWAY_MODE` || 必需。`live` 会映射到 `4001``paper` 会映射到 `4002`|
256249
| `IB_CLIENT_ID` || IB 连接客户端 ID (默认: 1) |
257250
| `TELEGRAM_TOKEN` || Telegram 机器人 Token |
258-
| `TELEGRAM_CHAT_ID` | 条件必需 | 当前服务自己的 Telegram Chat ID。不填时会回退到 `GLOBAL_TELEGRAM_CHAT_ID`|
259-
| `GLOBAL_TELEGRAM_CHAT_ID` || 可选的共享 Telegram Chat ID。适合多个 quant 服务共用一个接收目标。 |
251+
| `GLOBAL_TELEGRAM_CHAT_ID` || 这个服务使用的 Telegram Chat ID。 |
260252
| `NOTIFY_LANG` || `en`(默认) 或 `zh` |
261253

262-
`IB_GATEWAY_HOST``IB_GATEWAY_INSTANCE_NAME` 是兼容关系,二选一即可。如果你配了 `IB_GATEWAY_ZONE` 让程序通过实例名解析内网 IP,Cloud Run service account 需要 `roles/compute.viewer` 权限。推荐做法是 Cloud Run 通过 Direct VPC egress 访问 GCE 内网地址。只有在你明确要走公网暴露的 GCE 时,才设置 `IB_GATEWAY_IP_MODE=external`
254+
如果你配了 `IB_GATEWAY_ZONE` 让程序通过实例名解析内网 IP,Cloud Run service account 需要 `roles/compute.viewer` 权限。推荐做法是 Cloud Run 通过 Direct VPC egress 访问 GCE 内网地址。只有在你明确要走公网暴露的 GCE 时,才设置 `IB_GATEWAY_IP_MODE=external`
263255

264256
**推荐的共享配置模式**
265257

@@ -274,8 +266,6 @@ IB_CLIENT_ID=1
274266
NOTIFY_LANG=zh
275267
```
276268

277-
这种写法下,不需要再手工维护 `IB_GATEWAY_PORT`,程序会按 `IB_GATEWAY_MODE` 自动推导。
278-
279269
这里说的“共享配置”只针对 **IBKR 这一组系统**,也就是 `IBKRQuant``IBKRGatewayManager` 之间共享。它不是让所有 quant 仓库都共用一套 secrets。对多个量化仓库来说,通常只有 `GLOBAL_TELEGRAM_CHAT_ID``NOTIFY_LANG` 适合做跨项目共享。
280270

281271
### GitHub 统一管理 Cloud Run 环境变量
@@ -300,18 +290,18 @@ NOTIFY_LANG=zh
300290
- `GLOBAL_TELEGRAM_CHAT_ID`
301291
- `NOTIFY_LANG`
302292

303-
每次 push 到 `main` 时,这个 workflow 会把上面这些值同步到现有 Cloud Run 服务里。它**不会主动删除**旧的 `IB_GATEWAY_HOST``IB_GATEWAY_PORT` `TELEGRAM_CHAT_ID`,这样现有部署不会被硬切断。等你确认服务已经按预期读取新变量后,再手动删除旧的 Cloud Run env 即可
293+
每次 push 到 `main` 时,这个 workflow 会把上面这些值同步到现有 Cloud Run 服务里,并删除旧的 `IB_GATEWAY_HOST``IB_GATEWAY_PORT` `TELEGRAM_CHAT_ID`
304294

305295
注意:
306296

307-
- 只有在 `ENABLE_GITHUB_ENV_SYNC=true` 时,这个 workflow 才会严格校验并执行同步。没打开时会直接跳过,不影响原来 Google Cloud Trigger + 手工 Cloud Run env 的老流程
297+
- 只有在 `ENABLE_GITHUB_ENV_SYNC=true` 时,这个 workflow 才会严格校验并执行同步。没打开时会直接跳过。
308298
- 这里说的“共享配置”仍然只针对 **IBKR 这一组系统**`GCP_SA_KEY``TELEGRAM_TOKEN` 依然是这个仓库自己的 secrets,不建议提升成所有 quant 共用的全局 secret。
309299

310300
### 部署
311301

312302
1. **GCE**: 部署 IB Gateway(模拟或实盘),确认 API 已开启、需要远程连接时已允许非 localhost 客户端,并确认 `live` 使用 `4001``paper` 使用 `4002`
313303
2. **VPC / 子网**: 让 Cloud Run 和 GCE 处于同一个 VPC。为了让防火墙规则更干净,建议给 Cloud Run Direct VPC egress 单独准备一个子网。
314-
3. **Cloud Run**: 部署此 Flask 应用时启用 Direct VPC egress。你可以继续用旧写法 `IB_GATEWAY_HOST + IB_GATEWAY_PORT`,也可以改成推荐的共享写法 `IB_GATEWAY_INSTANCE_NAME + IB_GATEWAY_MODE`。两种方式都要保留 `IB_GATEWAY_ZONE``IB_GATEWAY_IP_MODE=internal`。Service account 需要 `roles/compute.viewer` 权限。
304+
3. **Cloud Run**: 部署此 Flask 应用时启用 Direct VPC egress。使用 `IB_GATEWAY_INSTANCE_NAME + IB_GATEWAY_MODE`,并保留 `IB_GATEWAY_ZONE``IB_GATEWAY_IP_MODE=internal`。Service account 需要 `roles/compute.viewer` 权限。
315305
4. **防火墙**: 只允许 Cloud Run 出口子网访问 GCE 的 `TCP 4001``live`)或 `TCP 4002``paper`)。
316306
5. **Cloud Scheduler**: 创建定时任务 `45 15 * * 1-5`(America/New_York 时区),POST 到 Cloud Run URL。代码内部处理交易日判断。
317307
6. **可选公网模式**: 只有在不能走 VPC 时,才设置 `IB_GATEWAY_IP_MODE=external`,并且要明确开放 GCE 公网 IP,同时严格限制来源 IP 和防火墙规则。

main.py

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ def resolve_gce_instance_ip(instance_name, zone):
8181
def get_ib_host():
8282
"""
8383
Resolve IB Gateway host.
84-
- Prefer IB_GATEWAY_HOST, fallback to IB_GATEWAY_INSTANCE_NAME
84+
- Read IB_GATEWAY_INSTANCE_NAME only
8585
- If IB_GATEWAY_ZONE is set: resolve instance name via Compute API
86-
- If IB_GATEWAY_ZONE is not set: use the configured host directly
86+
- If IB_GATEWAY_ZONE is not set: use the configured instance name directly
8787
"""
88-
host = os.getenv("IB_GATEWAY_HOST") or os.getenv("IB_GATEWAY_INSTANCE_NAME")
88+
host = os.getenv("IB_GATEWAY_INSTANCE_NAME")
8989
if not host:
90-
raise EnvironmentError("IB_GATEWAY_HOST or IB_GATEWAY_INSTANCE_NAME is required")
90+
raise EnvironmentError("IB_GATEWAY_INSTANCE_NAME is required")
9191
zone = os.getenv("IB_GATEWAY_ZONE", "")
9292
if zone:
9393
return resolve_gce_instance_ip(host, zone)
@@ -97,29 +97,15 @@ def get_ib_host():
9797
def get_ib_gateway_mode():
9898
mode = os.getenv("IB_GATEWAY_MODE", "").strip().lower()
9999
if not mode:
100-
return ""
100+
raise EnvironmentError("IB_GATEWAY_MODE is required and must be either 'live' or 'paper'")
101101
if mode in {"live", "paper"}:
102102
return mode
103103
raise EnvironmentError("IB_GATEWAY_MODE must be either 'live' or 'paper'")
104104

105105

106106
def get_ib_port():
107-
configured_port = os.getenv("IB_GATEWAY_PORT", "").strip()
108107
mode = get_ib_gateway_mode()
109-
110-
if configured_port:
111-
port = int(configured_port)
112-
expected_port = {"live": 4001, "paper": 4002}.get(mode)
113-
if expected_port and port != expected_port:
114-
raise EnvironmentError(
115-
f"IB_GATEWAY_PORT={port} conflicts with IB_GATEWAY_MODE={mode} "
116-
f"(expected {expected_port})"
117-
)
118-
return port
119-
120-
if mode == "paper":
121-
return 4002
122-
return 4001
108+
return 4002 if mode == "paper" else 4001
123109

124110

125111
# ---------------------------------------------------------------------------
@@ -130,7 +116,7 @@ def get_ib_port():
130116
IB_CLIENT_ID = int(os.getenv("IB_CLIENT_ID", "1"))
131117

132118
TG_TOKEN = os.getenv("TELEGRAM_TOKEN")
133-
TG_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID") or os.getenv("GLOBAL_TELEGRAM_CHAT_ID")
119+
TG_CHAT_ID = os.getenv("GLOBAL_TELEGRAM_CHAT_ID")
134120
NOTIFY_LANG = os.getenv("NOTIFY_LANG", "en")
135121

136122
# Strategy parameters

tests/conftest.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,10 @@
1515
def strategy_module_factory(monkeypatch):
1616
def load_strategy_module(**env_overrides):
1717
defaults = {
18-
"IB_GATEWAY_HOST": None,
1918
"IB_GATEWAY_INSTANCE_NAME": "127.0.0.1",
2019
"IB_GATEWAY_ZONE": None,
2120
"IB_GATEWAY_MODE": "live",
22-
"IB_GATEWAY_PORT": None,
2321
"IB_CLIENT_ID": "1",
24-
"TELEGRAM_CHAT_ID": None,
2522
"GLOBAL_TELEGRAM_CHAT_ID": None,
2623
}
2724
defaults.update(env_overrides)

tests/test_event_loop.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ def test_instance_name_alias_is_used_as_host(strategy_module):
4343

4444

4545
def test_ib_gateway_mode_derives_paper_port(strategy_module_factory):
46-
module = strategy_module_factory(IB_GATEWAY_MODE="paper", IB_GATEWAY_PORT=None)
46+
module = strategy_module_factory(IB_GATEWAY_MODE="paper")
4747

4848
assert module.IB_PORT == 4002
4949

5050

51-
def test_explicit_port_must_match_gateway_mode(strategy_module_factory):
52-
with pytest.raises(EnvironmentError, match="conflicts with IB_GATEWAY_MODE=paper"):
53-
strategy_module_factory(IB_GATEWAY_MODE="paper", IB_GATEWAY_PORT="4001")
51+
def test_ib_gateway_mode_is_required(strategy_module_factory):
52+
with pytest.raises(EnvironmentError, match="IB_GATEWAY_MODE is required"):
53+
strategy_module_factory(IB_GATEWAY_MODE=None)
5454

5555

5656
def test_resolve_gce_instance_ip_prefers_internal_by_default(strategy_module, monkeypatch):

tests/test_request_handling.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,8 @@ class FakeResponse:
9797
assert "Telegram send failed with status 401: unauthorized" in captured.out
9898

9999

100-
def test_global_telegram_chat_id_is_used_as_fallback(strategy_module_factory):
100+
def test_global_telegram_chat_id_is_used(strategy_module_factory):
101101
module = strategy_module_factory(
102-
TELEGRAM_CHAT_ID=None,
103102
GLOBAL_TELEGRAM_CHAT_ID="shared-chat-id",
104103
)
105104

tests/test_sync_cloud_run_env_workflow.sh

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,4 @@ grep -Fq "Cloud Run env sync is enabled, but these values are missing:" "$workfl
2222
grep -Fq "if: steps.config.outputs.enabled == 'true'" "$workflow_file"
2323
grep -Fq 'IB_GATEWAY_INSTANCE_NAME=${IB_GATEWAY_INSTANCE_NAME}' "$workflow_file"
2424
grep -Fq 'IB_GATEWAY_MODE=${IB_GATEWAY_MODE}' "$workflow_file"
25-
if grep -Fq -- "--remove-env-vars IB_GATEWAY_HOST" "$workflow_file"; then
26-
echo "workflow should not force-remove legacy gateway vars; keep backward compatibility" >&2
27-
exit 1
28-
fi
29-
if grep -Fq -- "--remove-env-vars TELEGRAM_CHAT_ID" "$workflow_file"; then
30-
echo "workflow should not force-remove TELEGRAM_CHAT_ID; keep backward compatibility" >&2
31-
exit 1
32-
fi
25+
grep -Fq -- '--remove-env-vars "IB_GATEWAY_HOST,IB_GATEWAY_PORT,TELEGRAM_CHAT_ID"' "$workflow_file"

0 commit comments

Comments
 (0)