diff --git a/README.md b/README.md
index c3bffb6..c20aa5d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# IBKR Global ETF Rotation
+# InteractiveBrokersPlatform


-
+

[English](#english) | [中文](#中文)
@@ -12,18 +12,38 @@
## English
-Quarterly momentum rotation across 22 global ETFs (international markets, commodities, US sectors, US broad market, tech, and semiconductors) with daily canary emergency check. Designed to stay more stable than high-beta tech strategies while still allowing major tech leadership to enter the rotation. Deployed on GCP Cloud Run, connecting to IB Gateway on GCE.
+IBKR runtime for shared `us_equity` strategy profiles from `UsEquityStrategies`. Today it supports:
-This runtime now supports multiple `us_equity` profiles sourced from `UsEquityStrategies`.
+- `global_etf_rotation` (`Global ETF Rotation Defense`): quarterly ETF momentum rotation with daily canary defense
+- `russell_1000_multi_factor_defensive` (`Russell 1000 Multi-Factor Defensive`): monthly stock-selection strategy that consumes a precomputed feature snapshot
+- `cash_buffer_branch_default` (`Tech Pullback Cash Buffer`, alias `tech_pullback_cash_buffer`): monthly tech-heavy stock-selection branch with explicit BOXX cash buffer
-Current runtime-facing profiles:
-- `global_etf_rotation` — quarterly ETF rotation (current rollback line)
-- `cash_buffer_branch_default` — monthly stock-selection branch with an explicit `80%` stock cap and `BOXX` parking
+Current strategy implementations are sourced from `UsEquityStrategies`.
-Full strategy documentation lives in `UsEquityStrategies`; the sections here stay focused on execution and deployment.
+Full strategy documentation now lives in [`UsEquityStrategies`](https://github.com/QuantStrategyLab/UsEquityStrategies). The strategy section below is kept as an execution-side summary.
+This runtime matrix is the authoritative enablement source for IBKR. `UsEquityStrategies` only describes strategy-layer compatibility and human-readable metadata.
### Strategy
+**Supported `STRATEGY_PROFILE` values**
+
+- `global_etf_rotation`
+- `russell_1000_multi_factor_defensive`
+- `cash_buffer_branch_default`
+
+Human-readable alias is available for config review and future migration:
+
+- `tech_pullback_cash_buffer` -> `cash_buffer_branch_default`
+
+
+**IBKR profile matrix**
+
+| Canonical profile | Display name | Alias | Enabled | Default | Rollback | Domain | Runtime note |
+| --- | --- | --- | --- | --- | --- | --- | --- |
+| `global_etf_rotation` | Global ETF Rotation Defense | `global_macro_etf_rotation` | Yes | Yes | Yes | `us_equity` | current rollback line |
+| `russell_1000_multi_factor_defensive` | Russell 1000 Multi-Factor Defensive | `r1000_multifactor_defensive` | Yes | No | No | `us_equity` | defensive stock baseline |
+| `cash_buffer_branch_default` | Tech Pullback Cash Buffer | `tech_pullback_cash_buffer` | Yes | No | No | `us_equity` | current IBKR paper dry-run candidate |
+
**Pool (22 ETFs + 1 safe haven):**
| Category | Tickers |
@@ -55,6 +75,31 @@ Full strategy documentation lives in `UsEquityStrategies`; the sections here sta
- 2023+ CAGR: 29.2% | Max Drawdown: 20.9%
- Legacy non-tech baseline and prior `QQQ` default remain available in the research script for comparison
+### Snapshot-based stock profile (`russell_1000_multi_factor_defensive`)
+
+- Signal source: precomputed feature snapshot file
+- Default benchmark: `SPY`
+- Default safe haven: `BOXX`
+- Runtime expectation:
+ - the feature snapshot is produced upstream
+ - Cloud Run / local runtime reads the latest file from `IBKR_FEATURE_SNAPSHOT_PATH`
+- Required snapshot columns:
+ - `symbol`, `sector`, `mom_6_1`, `mom_12_1`, `sma200_gap`, `vol_63`, `maxdd_126`
+ - optional passthrough columns such as `as_of`, `close`, `volume`, `adv20_usd`, `history_days`, `eligible`
+
+Recommended upstream task:
+
+```bash
+PYTHONPATH=src:. python3 scripts/run_russell_1000_snapshot_task.py
+```
+
+Then point this runtime at the generated file:
+
+```bash
+STRATEGY_PROFILE=russell_1000_multi_factor_defensive
+IBKR_FEATURE_SNAPSHOT_PATH=/var/data/r1000_feature_snapshot.csv
+```
+
### Architecture
```
@@ -113,18 +158,14 @@ The selected `ACCOUNT_GROUP` is now the runtime identity. Keep broker-specific i
|----------|----------|-------------|
| `IB_GATEWAY_ZONE` | Optional fallback | GCE zone (for example `us-central1-a`). Recommended to keep in the selected account-group entry; this env var is only a transition fallback. |
| `IB_GATEWAY_IP_MODE` | Optional fallback | `internal` (default) or `external`. Recommended to keep in the selected account-group entry; this env var is only a transition fallback. |
-| `STRATEGY_PROFILE` | Yes | Strategy profile selector. Supported `us_equity` values: `global_etf_rotation`, `cash_buffer_branch_default` |
+| `STRATEGY_PROFILE` | Yes | Strategy profile selector. Supported `us_equity` values: `global_etf_rotation`, `russell_1000_multi_factor_defensive`, `cash_buffer_branch_default` (alias: `tech_pullback_cash_buffer`) |
| `ACCOUNT_GROUP` | Yes | Account-group selector. No default fallback. |
+| `IBKR_FEATURE_SNAPSHOT_PATH` | Conditionally required | Required when `STRATEGY_PROFILE=russell_1000_multi_factor_defensive`. Path to the latest feature snapshot file (`.csv`, `.json`, `.jsonl`, `.parquet`). |
| `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME` | Yes for Cloud Run | Secret Manager secret name for account-group config JSON. Recommended production source. |
| `IB_ACCOUNT_GROUP_CONFIG_JSON` | No | Local/dev JSON fallback for account-group config. Not recommended for production Cloud Run. |
| `TELEGRAM_TOKEN` | Yes | Telegram bot token. For Cloud Run, prefer a Secret Manager reference instead of a literal env var. |
| `GLOBAL_TELEGRAM_CHAT_ID` | Yes | Telegram chat ID used by this service. |
| `NOTIFY_LANG` | No | `en` (default) or `zh` |
-| `IBKR_FEATURE_SNAPSHOT_PATH` | Required for snapshot-based profiles | Latest feature snapshot file path. Required for `cash_buffer_branch_default`. |
-| `IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH` | Required for `cash_buffer_branch_default` | Sidecar manifest path used by runtime freshness / contract checks. |
-| `IBKR_STRATEGY_CONFIG_PATH` | Required for `cash_buffer_branch_default` | Canonical runtime config path used for manifest/config matching. |
-| `IBKR_RECONCILIATION_OUTPUT_PATH` | No | Optional structured reconciliation output path for dry-run / paper execution logs. |
-| `IBKR_DRY_RUN_ONLY` | No | `true` to block order submission and only emit planned actions; `false` for real paper orders. |
The selected account-group entry must provide at least:
@@ -144,27 +185,34 @@ If you use instance-name resolution with `ib_gateway_zone`, the Cloud Run runtim
For the current first rollout, keep GitHub / Cloud Run focused on service-level values:
```bash
-# rollback / legacy ETF line
STRATEGY_PROFILE=global_etf_rotation
ACCOUNT_GROUP=default
IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
GLOBAL_TELEGRAM_CHAT_ID=
NOTIFY_LANG=zh
-# optional transition fallback only:
+# Optional transition fallback only:
IB_GATEWAY_ZONE=us-central1-c
IB_GATEWAY_IP_MODE=internal
```
+For the snapshot-based stock profiles:
+
+```bash
+STRATEGY_PROFILE=russell_1000_multi_factor_defensive
+ACCOUNT_GROUP=default
+IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
+IBKR_FEATURE_SNAPSHOT_PATH=/var/data/r1000_feature_snapshot.csv
+GLOBAL_TELEGRAM_CHAT_ID=
+NOTIFY_LANG=zh
+```
+
```bash
-# snapshot-based stock paper branch
STRATEGY_PROFILE=cash_buffer_branch_default
ACCOUNT_GROUP=default
IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
IBKR_FEATURE_SNAPSHOT_PATH=/var/data/cash_buffer_branch_feature_snapshot_latest.csv
IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=/var/data/cash_buffer_branch_feature_snapshot_latest.csv.manifest.json
-IBKR_STRATEGY_CONFIG_PATH=/app/research/configs/growth_pullback_cash_buffer_branch_default.json
-IBKR_RECONCILIATION_OUTPUT_PATH=/var/log/ibkr_cash_buffer_branch_reconciliation.json
IBKR_DRY_RUN_ONLY=true
GLOBAL_TELEGRAM_CHAT_ID=
NOTIFY_LANG=zh
@@ -219,7 +267,7 @@ Recommended setup:
- `CLOUD_RUN_REGION`
- `CLOUD_RUN_SERVICE`
- `TELEGRAM_TOKEN_SECRET_NAME` (recommended when Cloud Run already uses Secret Manager for `TELEGRAM_TOKEN`)
- - `STRATEGY_PROFILE` (recommended: `global_etf_rotation` for rollback, or `cash_buffer_branch_default` for the snapshot-based paper branch)
+ - `STRATEGY_PROFILE` (recommended: `global_etf_rotation`)
- `ACCOUNT_GROUP` (recommended: `default`)
- `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`
- `GLOBAL_TELEGRAM_CHAT_ID`
@@ -232,7 +280,7 @@ Recommended setup:
On every push to `main`, the workflow updates the existing Cloud Run service with the values above and removes legacy env vars that should now live in the account-group config (`IB_CLIENT_ID`, `IB_GATEWAY_INSTANCE_NAME`, `IB_GATEWAY_MODE`) plus the older transport vars (`IB_GATEWAY_HOST`, `IB_GATEWAY_PORT`, `TELEGRAM_CHAT_ID`). If `IB_GATEWAY_ZONE` or `IB_GATEWAY_IP_MODE` are blank in GitHub, the workflow also removes them from Cloud Run to avoid drift.
-`STRATEGY_PROFILE` now supports both `global_etf_rotation` and `cash_buffer_branch_default` under the shared `us_equity` domain. `ACCOUNT_GROUP` selects one account-group config entry, and the service still fails fast if that runtime identity is incomplete.
+For now, `STRATEGY_PROFILE` still only supports one strategy profile. The current strategy domain is `us_equity`, and the repo now keeps a thin strategy registry so future expansion can grow by domain + profile instead of mixing strategy and platform in one layer. `ACCOUNT_GROUP` now selects one account-group config entry, and the service fails fast if that runtime identity is incomplete.
Important:
@@ -289,13 +337,9 @@ gcloud run services update ibkr-quant \
基于 IBKR 的全球 ETF 季度轮动策略(国际市场、商品、美股行业、美股宽基、科技和半导体),含每日金丝雀应急机制。定位上比 `TQQQ`、`SOXL` 这类高弹性科技策略更稳健,但不再把科技完全排除在外。部署在 GCP Cloud Run,连接 GCE 上的 IB Gateway。
-当前这个 runtime 已经可以承载多个来自 `UsEquityStrategies` 的 `us_equity` profile。
-
-当前运行侧最相关的两个 profile:
-- `global_etf_rotation`:季度 ETF 轮动,也是当前 rollback 线
-- `cash_buffer_branch_default`:月频个股分支,`risk_on` 明确只上 `80%` 股票,其余停在 `BOXX`
+当前 `global_etf_rotation`、`russell_1000_multi_factor_defensive` 和 `cash_buffer_branch_default` 的策略实现都来自 `UsEquityStrategies`。
-完整策略说明放在 `UsEquityStrategies`;这里主要保留执行和部署侧摘要。
+完整策略说明现在放在 [`UsEquityStrategies`](https://github.com/QuantStrategyLab/UsEquityStrategies#global_etf_rotation)。下面的策略章节主要保留执行侧摘要。
### 策略
@@ -352,18 +396,13 @@ IBKR 账户
|------|------|------|
| `IB_GATEWAY_ZONE` | 可选过渡项 | GCE zone(如 `us-central1-a`)。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
| `IB_GATEWAY_IP_MODE` | 可选过渡项 | `internal`(默认)或 `external`。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
-| `STRATEGY_PROFILE` | 是 | 策略档位选择。当前支持的 `us_equity` 值:`global_etf_rotation`、`cash_buffer_branch_default` |
+| `STRATEGY_PROFILE` | 是 | 策略档位选择。当前可用的 `us_equity` 值:`global_etf_rotation`、`russell_1000_multi_factor_defensive`、`cash_buffer_branch_default`(alias:`tech_pullback_cash_buffer`) |
| `ACCOUNT_GROUP` | 是 | 账号组选择器,不再提供默认回退。 |
| `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` |
-| `IBKR_FEATURE_SNAPSHOT_PATH` | snapshot 型策略必填 | 最新 feature snapshot 文件路径。`cash_buffer_branch_default` 必填。 |
-| `IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH` | `cash_buffer_branch_default` 必填 | snapshot sidecar manifest 路径,用于 freshness / contract 检查。 |
-| `IBKR_STRATEGY_CONFIG_PATH` | `cash_buffer_branch_default` 必填 | runtime 侧 canonical config 路径,用于 manifest/config 匹配。 |
-| `IBKR_RECONCILIATION_OUTPUT_PATH` | 否 | dry-run / paper 执行后的结构化对账输出路径。 |
-| `IBKR_DRY_RUN_ONLY` | 否 | `true` 时只输出计划动作不下单;`false` 时允许真正的 paper 下单。 |
选中的账号组配置里,至少要有:
@@ -443,7 +482,7 @@ IB_GATEWAY_IP_MODE=internal
- `CLOUD_RUN_REGION`
- `CLOUD_RUN_SERVICE`
- `TELEGRAM_TOKEN_SECRET_NAME`(如果 Cloud Run 上的 `TELEGRAM_TOKEN` 已经改成 Secret Manager,建议配置)
- - `STRATEGY_PROFILE`(rollback 建议用 `global_etf_rotation`,snapshot 个股 paper 分支用 `cash_buffer_branch_default`)
+ - `STRATEGY_PROFILE`(建议设为 `global_etf_rotation`)
- `ACCOUNT_GROUP`(建议设为 `default`)
- `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME`
- `GLOBAL_TELEGRAM_CHAT_ID`
@@ -456,7 +495,7 @@ IB_GATEWAY_IP_MODE=internal
每次 push 到 `main` 时,这个 workflow 会把上面这些值同步到现有 Cloud Run 服务里,并清掉已经转移到账号组配置里的旧 env(`IB_CLIENT_ID`、`IB_GATEWAY_INSTANCE_NAME`、`IB_GATEWAY_MODE`)以及更早的传输层 env(`IB_GATEWAY_HOST`、`IB_GATEWAY_PORT`、`TELEGRAM_CHAT_ID`)。如果 GitHub 里没有配置 `IB_GATEWAY_ZONE` 或 `IB_GATEWAY_IP_MODE`,workflow 也会把 Cloud Run 上这两个旧值一起删除,避免双配置源漂移。
-`STRATEGY_PROFILE` 现在支持 `global_etf_rotation` 和 `cash_buffer_branch_default` 两个 `us_equity` profile。`ACCOUNT_GROUP` 仍然是严格必填项,并会选中一份账号组配置;运行身份不完整时,服务会直接失败,不再静默回退。
+`STRATEGY_PROFILE` 当前只有一个可用值;当前策略域是 `us_equity`,本地策略注册表只用于域和 profile 校验。`ACCOUNT_GROUP` 是严格必填项,并会选中一份账号组配置。运行身份不完整时,服务会直接失败,不再静默回退。
注意:
diff --git a/requirements.txt b/requirements.txt
index f190f4d..08bbd42 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
flask
gunicorn
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.6.0
-us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@5c40d49927b7a86e27feee23b82da81338320404
+us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@17fa78c22463070b88ba4a884c84e7a761835ccb
pandas
numpy
requests
diff --git a/strategy_registry.py b/strategy_registry.py
index 64fc203..40bf2a8 100644
--- a/strategy_registry.py
+++ b/strategy_registry.py
@@ -1,33 +1,48 @@
from __future__ import annotations
-from us_equity_strategies import get_strategy_definitions as get_us_equity_strategy_definitions
-
-from quant_platform_kit.common.strategies import (
- StrategyDefinition,
- US_EQUITY_DOMAIN,
- get_supported_profiles_for_platform as qpk_get_supported_profiles_for_platform,
- resolve_strategy_definition as qpk_resolve_strategy_definition,
+from us_equity_strategies.platform_registry_support import (
+ build_platform_profile_matrix,
+ get_enabled_profiles_for_platform,
+ resolve_platform_strategy_definition,
)
-IBKR_PLATFORM = "ibkr"
+from quant_platform_kit.common.strategies import StrategyDefinition, US_EQUITY_DOMAIN
+IBKR_PLATFORM = "ibkr"
DEFAULT_STRATEGY_PROFILE = "global_etf_rotation"
-
-STRATEGY_DEFINITIONS = get_us_equity_strategy_definitions()
+ROLLBACK_STRATEGY_PROFILE = DEFAULT_STRATEGY_PROFILE
+
+# 平台启用状态从策略定义层拆出来;当前只让 IBKR 明确启用这三条。
+IBKR_ENABLED_PROFILES = frozenset(
+ {
+ "cash_buffer_branch_default",
+ "global_etf_rotation",
+ "russell_1000_multi_factor_defensive",
+ }
+)
PLATFORM_SUPPORTED_DOMAINS: dict[str, frozenset[str]] = {
IBKR_PLATFORM: frozenset({US_EQUITY_DOMAIN}),
}
-SUPPORTED_STRATEGY_PROFILES = frozenset(STRATEGY_DEFINITIONS)
+SUPPORTED_STRATEGY_PROFILES = IBKR_ENABLED_PROFILES
def get_supported_profiles_for_platform(platform_id: str) -> frozenset[str]:
- return qpk_get_supported_profiles_for_platform(
- STRATEGY_DEFINITIONS,
- PLATFORM_SUPPORTED_DOMAINS,
- platform_id=platform_id,
+ return get_enabled_profiles_for_platform(
+ platform_id,
+ expected_platform_id=IBKR_PLATFORM,
+ enabled_profiles=IBKR_ENABLED_PROFILES,
+ )
+
+
+def get_platform_profile_matrix() -> list[dict[str, object]]:
+ return build_platform_profile_matrix(
+ platform_id=IBKR_PLATFORM,
+ enabled_profiles=IBKR_ENABLED_PROFILES,
+ default_profile=DEFAULT_STRATEGY_PROFILE,
+ rollback_profile=ROLLBACK_STRATEGY_PROFILE,
)
@@ -36,10 +51,11 @@ def resolve_strategy_definition(
*,
platform_id: str,
) -> StrategyDefinition:
- return qpk_resolve_strategy_definition(
+ return resolve_platform_strategy_definition(
raw_value,
platform_id=platform_id,
- strategy_definitions=STRATEGY_DEFINITIONS,
+ expected_platform_id=IBKR_PLATFORM,
+ enabled_profiles=IBKR_ENABLED_PROFILES,
platform_supported_domains=PLATFORM_SUPPORTED_DOMAINS,
require_explicit=True,
)
diff --git a/tests/test_runtime_config_support.py b/tests/test_runtime_config_support.py
index 7a888ad..5abf14e 100644
--- a/tests/test_runtime_config_support.py
+++ b/tests/test_runtime_config_support.py
@@ -5,7 +5,7 @@
load_platform_runtime_settings,
parse_account_group_configs,
)
-from strategy_registry import IBKR_PLATFORM, US_EQUITY_DOMAIN, get_supported_profiles_for_platform
+from strategy_registry import IBKR_PLATFORM, US_EQUITY_DOMAIN, get_platform_profile_matrix, get_supported_profiles_for_platform
MINIMAL_GROUP_JSON = (
@@ -137,6 +137,26 @@ def test_platform_supported_profiles_are_filtered_by_registry():
)
+def test_load_platform_runtime_settings_accepts_cash_buffer_alias(monkeypatch):
+ monkeypatch.setenv("STRATEGY_PROFILE", "tech_pullback_cash_buffer")
+ monkeypatch.setenv("ACCOUNT_GROUP", "default")
+ monkeypatch.setenv("IB_ACCOUNT_GROUP_CONFIG_JSON", MINIMAL_GROUP_JSON)
+ monkeypatch.setenv("IBKR_FEATURE_SNAPSHOT_PATH", "/tmp/cash-buffer.csv")
+
+ settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
+
+ assert settings.strategy_profile == "cash_buffer_branch_default"
+
+
+def test_platform_profile_matrix_marks_default_and_rollback():
+ rows = get_platform_profile_matrix()
+ by_profile = {row["canonical_profile"]: row for row in rows}
+ assert by_profile["global_etf_rotation"]["is_default"] is True
+ assert by_profile["global_etf_rotation"]["is_rollback"] is True
+ assert by_profile["cash_buffer_branch_default"]["display_name"] == "Tech Pullback Cash Buffer"
+
+
+
def test_load_platform_runtime_settings_reads_feature_snapshot_path(monkeypatch):
monkeypatch.setenv("STRATEGY_PROFILE", "russell_1000_multi_factor_defensive")
monkeypatch.setenv("ACCOUNT_GROUP", "default")
diff --git a/tests/test_strategy_loader.py b/tests/test_strategy_loader.py
index bc36170..5213392 100644
--- a/tests/test_strategy_loader.py
+++ b/tests/test_strategy_loader.py
@@ -43,3 +43,20 @@ def test_load_signal_logic_module_resolves_cash_buffer_branch_default(monkeypatc
assert module.__name__ == "us_equity_strategies.strategies.cash_buffer_branch_default"
assert module.SIGNAL_SOURCE == "feature_snapshot"
+
+
+def test_load_signal_logic_module_resolves_cash_buffer_alias(monkeypatch):
+ try:
+ import pandas # noqa: F401
+ except ModuleNotFoundError:
+ return
+
+ market_calendars_module = types.ModuleType("pandas_market_calendars")
+ market_calendars_module.get_calendar = lambda name: None
+ monkeypatch.setitem(sys.modules, "pandas_market_calendars", market_calendars_module)
+ sys.modules.pop("us_equity_strategies.strategies.cash_buffer_branch_default", None)
+
+ module = load_signal_logic_module("tech_pullback_cash_buffer")
+
+ assert module.__name__ == "us_equity_strategies.strategies.cash_buffer_branch_default"
+ assert module.SIGNAL_SOURCE == "feature_snapshot"