Skip to content

Commit 9c575b5

Browse files
committed
Enable mega cap dynamic top20 profile
1 parent f7b86d8 commit 9c575b5

8 files changed

Lines changed: 113 additions & 8 deletions

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@ jobs:
5757
NOTIFY_LANG
5858
)
5959
60-
if [ "${STRATEGY_PROFILE:-}" = "russell_1000_multi_factor_defensive" ] || [ "${STRATEGY_PROFILE:-}" = "tech_communication_pullback_enhancement" ] || [ "${STRATEGY_PROFILE:-}" = "qqq_tech_enhancement" ]; then
60+
if [ "${STRATEGY_PROFILE:-}" = "russell_1000_multi_factor_defensive" ] || [ "${STRATEGY_PROFILE:-}" = "tech_communication_pullback_enhancement" ] || [ "${STRATEGY_PROFILE:-}" = "qqq_tech_enhancement" ] || [ "${STRATEGY_PROFILE:-}" = "mega_cap_leader_rotation_dynamic_top20" ]; then
6161
required_vars+=(IBKR_FEATURE_SNAPSHOT_PATH)
6262
fi
6363
64+
if [ "${STRATEGY_PROFILE:-}" = "tech_communication_pullback_enhancement" ] || [ "${STRATEGY_PROFILE:-}" = "qqq_tech_enhancement" ] || [ "${STRATEGY_PROFILE:-}" = "mega_cap_leader_rotation_dynamic_top20" ]; then
65+
required_vars+=(IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH)
66+
fi
67+
6468
if [ "${STRATEGY_PROFILE:-}" = "tech_communication_pullback_enhancement" ] || [ "${STRATEGY_PROFILE:-}" = "qqq_tech_enhancement" ]; then
65-
required_vars+=(IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH IBKR_STRATEGY_CONFIG_PATH IBKR_RECONCILIATION_OUTPUT_PATH)
69+
required_vars+=(IBKR_STRATEGY_CONFIG_PATH IBKR_RECONCILIATION_OUTPUT_PATH)
6670
fi
6771
6872
missing_vars=()

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ The mainline runtime now follows one path only:
3939
- `tqqq_growth_income`
4040
- `soxl_soxx_trend_income`
4141
- `tech_communication_pullback_enhancement`
42+
- `mega_cap_leader_rotation_dynamic_top20`
4243

4344

4445
**IBKR profile status**
@@ -50,6 +51,7 @@ The mainline runtime now follows one path only:
5051
| `tqqq_growth_income` | TQQQ Growth Income | Yes | Yes | No | No | `us_equity` | enabled value-mode alternative |
5152
| `soxl_soxx_trend_income` | SOXL/SOXX Semiconductor Trend Income | Yes | Yes | No | No | `us_equity` | current IBKR live line |
5253
| `tech_communication_pullback_enhancement` | Tech/Communication Pullback Enhancement | Yes | Yes | No | No | `us_equity` | enabled feature-snapshot alternative |
54+
| `mega_cap_leader_rotation_dynamic_top20` | Mega Cap Leader Rotation Dynamic Top20 | Yes | Yes | No | No | `us_equity` | enabled concentrated leader rotation |
5355

5456
Check the current matrix locally:
5557

@@ -94,9 +96,9 @@ The selected `ACCOUNT_GROUP` is now the runtime identity. Keep broker-specific i
9496
|----------|----------|-------------|
9597
| `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. |
9698
| `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. |
97-
| `STRATEGY_PROFILE` | Yes | Strategy profile selector. Supported `us_equity` values: `global_etf_rotation`, `russell_1000_multi_factor_defensive`, `tqqq_growth_income`, `soxl_soxx_trend_income`, `tech_communication_pullback_enhancement` |
99+
| `STRATEGY_PROFILE` | Yes | Strategy profile selector. Supported `us_equity` values: `global_etf_rotation`, `russell_1000_multi_factor_defensive`, `tqqq_growth_income`, `soxl_soxx_trend_income`, `tech_communication_pullback_enhancement`, `mega_cap_leader_rotation_dynamic_top20` |
98100
| `ACCOUNT_GROUP` | Yes | Account-group selector. No default fallback. |
99-
| `IBKR_FEATURE_SNAPSHOT_PATH` | Conditionally required | Required for snapshot-backed profiles such as `russell_1000_multi_factor_defensive` and `tech_communication_pullback_enhancement`. Path to the latest feature snapshot file (`.csv`, `.json`, `.jsonl`, `.parquet`). |
101+
| `IBKR_FEATURE_SNAPSHOT_PATH` | Conditionally required | Required for snapshot-backed profiles such as `russell_1000_multi_factor_defensive`, `tech_communication_pullback_enhancement`, and `mega_cap_leader_rotation_dynamic_top20`. Path to the latest feature snapshot file (`.csv`, `.json`, `.jsonl`, `.parquet`). |
100102
| `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME` | Yes for Cloud Run | Secret Manager secret name for account-group config JSON. Recommended production source. |
101103
| `IB_ACCOUNT_GROUP_CONFIG_JSON` | No | Local/dev JSON fallback for account-group config. Not recommended for production Cloud Run. |
102104
| `TELEGRAM_TOKEN` | Yes | Telegram bot token. For Cloud Run, prefer a Secret Manager reference instead of a literal env var. |
@@ -155,6 +157,16 @@ GLOBAL_TELEGRAM_CHAT_ID=<telegram-chat-id>
155157
NOTIFY_LANG=zh
156158
```
157159

160+
```bash
161+
STRATEGY_PROFILE=mega_cap_leader_rotation_dynamic_top20
162+
ACCOUNT_GROUP=default
163+
IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
164+
IBKR_FEATURE_SNAPSHOT_PATH=/var/data/mega_cap_leader_rotation_dynamic_top20_feature_snapshot_latest.csv
165+
IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=/var/manifests/mega_cap_leader_rotation_dynamic_top20_feature_snapshot_latest.csv.manifest.json
166+
GLOBAL_TELEGRAM_CHAT_ID=<telegram-chat-id>
167+
NOTIFY_LANG=zh
168+
```
169+
158170
This shared-config mode is only for the **IBKR pair** (`InteractiveBrokersPlatform` + `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`.
159171

160172
Recommended account-group config payload:
@@ -274,7 +286,7 @@ gcloud run services update ibkr-quant \
274286

275287
IBKR runtime 负责把共享的 `us_equity` 策略档位部署到 GCP Cloud Run,并连接 GCE 上的 IB Gateway 执行。策略逻辑、策略频率、标的池、参数和研究/回测说明都放在 `UsEquityStrategies`;这个仓库只维护 IBKR 运行时、账号组、Gateway 连接、下单和通知。
276288

277-
当前 `global_etf_rotation``russell_1000_multi_factor_defensive``tqqq_growth_income``soxl_soxx_trend_income``tech_communication_pullback_enhancement` 的策略实现都来自 `UsEquityStrategies`
289+
当前 `global_etf_rotation``russell_1000_multi_factor_defensive``tqqq_growth_income``soxl_soxx_trend_income``tech_communication_pullback_enhancement``mega_cap_leader_rotation_dynamic_top20` 的策略实现都来自 `UsEquityStrategies`
278290

279291
完整策略说明现在放在 [`UsEquityStrategies`](https://github.com/QuantStrategyLab/UsEquityStrategies)。这个 README 只保留 IBKR 运行时、profile 启用状态、部署和凭据说明。
280292

@@ -315,7 +327,7 @@ IBKR 账户
315327
|------|------|------|
316328
| `IB_GATEWAY_ZONE` | 可选过渡项 | GCE zone(如 `us-central1-a`)。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
317329
| `IB_GATEWAY_IP_MODE` | 可选过渡项 | `internal`(默认)或 `external`。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
318-
| `STRATEGY_PROFILE` || 策略档位选择。当前可用的 `us_equity` 值:`global_etf_rotation``russell_1000_multi_factor_defensive``tqqq_growth_income``soxl_soxx_trend_income``tech_communication_pullback_enhancement` |
330+
| `STRATEGY_PROFILE` || 策略档位选择。当前可用的 `us_equity` 值:`global_etf_rotation``russell_1000_multi_factor_defensive``tqqq_growth_income``soxl_soxx_trend_income``tech_communication_pullback_enhancement``mega_cap_leader_rotation_dynamic_top20` |
319331
| `ACCOUNT_GROUP` || 账号组选择器,不再提供默认回退。 |
320332
| `IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME` | Cloud Run 建议必填 | 账号组配置 JSON 在 Secret Manager 里的密钥名。生产环境推荐使用。 |
321333
| `IB_ACCOUNT_GROUP_CONFIG_JSON` || 本地开发用的账号组配置 JSON fallback。不建议在生产 Cloud Run 直接使用。 |
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# IBKR paper runtime switch: mega_cap_leader_rotation_dynamic_top20
2+
STRATEGY_PROFILE=mega_cap_leader_rotation_dynamic_top20
3+
ACCOUNT_GROUP=default
4+
IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
5+
IBKR_FEATURE_SNAPSHOT_PATH=/var/data/mega_cap_leader_rotation_dynamic_top20_feature_snapshot_latest.csv
6+
IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=/var/manifests/mega_cap_leader_rotation_dynamic_top20_feature_snapshot_latest.csv.manifest.json
7+
GLOBAL_TELEGRAM_CHAT_ID=<telegram-chat-id>
8+
NOTIFY_LANG=zh

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
flask
22
gunicorn
33
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.15
4-
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@v0.7.21
4+
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@v0.7.22
55
pandas
66
numpy
77
requests

strategy_registry.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"tech_communication_pullback_enhancement",
2727
"qqq_tech_enhancement",
2828
"global_etf_rotation",
29+
"mega_cap_leader_rotation_dynamic_top20",
2930
"russell_1000_multi_factor_defensive",
3031
"soxl_soxx_trend_income",
3132
"tqqq_growth_income",

tests/test_runtime_config_support.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def test_platform_supported_profiles_are_filtered_by_registry():
154154
"tqqq_growth_income",
155155
"tech_communication_pullback_enhancement",
156156
"global_etf_rotation",
157+
"mega_cap_leader_rotation_dynamic_top20",
157158
"russell_1000_multi_factor_defensive",
158159
}
159160
)
@@ -166,6 +167,7 @@ def test_platform_eligible_profiles_are_exposed_by_capability_matrix():
166167
"tqqq_growth_income",
167168
"tech_communication_pullback_enhancement",
168169
"global_etf_rotation",
170+
"mega_cap_leader_rotation_dynamic_top20",
169171
"russell_1000_multi_factor_defensive",
170172
}
171173
)
@@ -184,6 +186,23 @@ def test_load_platform_runtime_settings_accepts_tech_communication_pullback_enha
184186
assert settings.strategy_target_mode == "weight"
185187

186188

189+
def test_load_platform_runtime_settings_accepts_mega_cap_leader_rotation_dynamic_top20(monkeypatch):
190+
monkeypatch.setenv("STRATEGY_PROFILE", "mega_cap_leader_rotation_dynamic_top20")
191+
monkeypatch.setenv("ACCOUNT_GROUP", "default")
192+
monkeypatch.setenv("IB_ACCOUNT_GROUP_CONFIG_JSON", MINIMAL_GROUP_JSON)
193+
monkeypatch.setenv("IBKR_FEATURE_SNAPSHOT_PATH", "/tmp/mega.csv")
194+
monkeypatch.setenv("IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH", "/tmp/mega.csv.manifest.json")
195+
196+
settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
197+
198+
assert settings.strategy_profile == "mega_cap_leader_rotation_dynamic_top20"
199+
assert settings.strategy_display_name == "Mega Cap Leader Rotation Dynamic Top20"
200+
assert settings.strategy_target_mode == "weight"
201+
assert settings.feature_snapshot_path == "/tmp/mega.csv"
202+
assert settings.feature_snapshot_manifest_path == "/tmp/mega.csv.manifest.json"
203+
assert settings.strategy_config_path is None
204+
205+
187206
def test_load_platform_runtime_settings_accepts_tqqq_growth_income(monkeypatch):
188207
monkeypatch.setenv("STRATEGY_PROFILE", "tqqq_growth_income")
189208
monkeypatch.setenv("ACCOUNT_GROUP", "default")
@@ -224,6 +243,7 @@ def test_platform_profile_status_matrix_matches_current_ibkr_rollout():
224243
"soxl_soxx_trend_income",
225244
"tqqq_growth_income",
226245
"tech_communication_pullback_enhancement",
246+
"mega_cap_leader_rotation_dynamic_top20",
227247
}
228248
assert by_profile["global_etf_rotation"] == {
229249
"canonical_profile": "global_etf_rotation",
@@ -277,6 +297,10 @@ def test_print_strategy_profile_status_json_matches_registry():
277297
assert by_profile["tech_communication_pullback_enhancement"]["input_mode"] == "feature_snapshot"
278298
assert by_profile["tech_communication_pullback_enhancement"]["requires_snapshot_artifacts"] is True
279299
assert by_profile["tech_communication_pullback_enhancement"]["requires_strategy_config_path"] is True
300+
assert by_profile["mega_cap_leader_rotation_dynamic_top20"]["profile_group"] == "snapshot_backed"
301+
assert by_profile["mega_cap_leader_rotation_dynamic_top20"]["input_mode"] == "feature_snapshot"
302+
assert by_profile["mega_cap_leader_rotation_dynamic_top20"]["requires_snapshot_artifacts"] is True
303+
assert by_profile["mega_cap_leader_rotation_dynamic_top20"]["requires_strategy_config_path"] is False
280304
assert by_profile["russell_1000_multi_factor_defensive"]["requires_strategy_config_path"] is False
281305

282306

@@ -320,6 +344,26 @@ def test_print_strategy_switch_env_plan_for_tqqq_growth_income():
320344
assert "IBKR_FEATURE_SNAPSHOT_PATH" in plan["remove_if_present"]
321345

322346

347+
def test_print_strategy_switch_env_plan_for_mega_cap_feature_snapshot_profile():
348+
result = subprocess.run(
349+
[sys.executable, str(SWITCH_PLAN_SCRIPT_PATH), "--profile", "mega_cap_leader_rotation_dynamic_top20", "--json"],
350+
check=True,
351+
capture_output=True,
352+
text=True,
353+
)
354+
355+
plan = json.loads(result.stdout)
356+
assert plan["canonical_profile"] == "mega_cap_leader_rotation_dynamic_top20"
357+
assert plan["profile_group"] == "snapshot_backed"
358+
assert plan["input_mode"] == "feature_snapshot"
359+
assert plan["requires_snapshot_artifacts"] is True
360+
assert plan["requires_strategy_config_path"] is False
361+
assert plan["set_env"]["IBKR_FEATURE_SNAPSHOT_PATH"] == "<required>"
362+
assert plan["set_env"]["IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH"] == "<required>"
363+
assert "IBKR_STRATEGY_CONFIG_PATH" in plan["remove_if_present"]
364+
assert "IBKR_RECONCILIATION_OUTPUT_PATH" in plan["remove_if_present"]
365+
366+
323367
def test_print_strategy_switch_env_plan_for_feature_snapshot_profile():
324368
result = subprocess.run(
325369
[sys.executable, str(SWITCH_PLAN_SCRIPT_PATH), "--profile", "tech_communication_pullback_enhancement", "--json"],

tests/test_strategy_loader.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,23 @@ def test_load_strategy_entrypoint_for_profile_resolves_tqqq_growth_income(monkey
5353
assert entrypoint.manifest.default_config["benchmark_symbol"] == "QQQ"
5454

5555

56+
def test_load_strategy_entrypoint_for_profile_resolves_mega_cap_dynamic_top20(monkeypatch):
57+
try:
58+
import pandas # noqa: F401
59+
except ModuleNotFoundError:
60+
return
61+
62+
market_calendars_module = types.ModuleType("pandas_market_calendars")
63+
market_calendars_module.get_calendar = lambda name: None
64+
monkeypatch.setitem(sys.modules, "pandas_market_calendars", market_calendars_module)
65+
66+
entrypoint = load_strategy_entrypoint_for_profile("mega_cap_leader_rotation_dynamic_top20")
67+
68+
assert entrypoint.manifest.profile == "mega_cap_leader_rotation_dynamic_top20"
69+
assert entrypoint.manifest.required_inputs == frozenset({"feature_snapshot"})
70+
assert entrypoint.manifest.default_config["holdings_count"] == 4
71+
72+
5673
def test_load_strategy_entrypoint_for_profile_rejects_legacy_cash_buffer_profile(monkeypatch):
5774
try:
5875
import pandas # noqa: F401
@@ -85,6 +102,24 @@ def test_load_strategy_runtime_adapter_for_profile_resolves_tech_communication_p
85102
assert adapter.snapshot_contract_version == "tech_communication_pullback_enhancement.feature_snapshot.v1"
86103

87104

105+
def test_load_strategy_runtime_adapter_for_profile_resolves_mega_cap_dynamic_top20(monkeypatch):
106+
try:
107+
import pandas # noqa: F401
108+
except ModuleNotFoundError:
109+
return
110+
111+
market_calendars_module = types.ModuleType("pandas_market_calendars")
112+
market_calendars_module.get_calendar = lambda name: None
113+
monkeypatch.setitem(sys.modules, "pandas_market_calendars", market_calendars_module)
114+
115+
adapter = load_strategy_runtime_adapter_for_profile("mega_cap_leader_rotation_dynamic_top20")
116+
117+
assert adapter.status_icon == "👑"
118+
assert adapter.available_inputs == frozenset({"feature_snapshot"})
119+
assert adapter.require_snapshot_manifest is True
120+
assert adapter.snapshot_contract_version == "mega_cap_leader_rotation_dynamic_top20.feature_snapshot.v1"
121+
122+
88123
def test_load_strategy_runtime_adapter_for_profile_resolves_global_etf_rotation_inputs(monkeypatch):
89124
market_calendars_module = types.ModuleType("pandas_market_calendars")
90125
market_calendars_module.get_calendar = lambda name: None

tests/test_sync_cloud_run_env_workflow.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ grep -Fq 'STRATEGY_PROFILE=${STRATEGY_PROFILE}' "$workflow_file"
4343
grep -Fq 'ACCOUNT_GROUP=${ACCOUNT_GROUP}' "$workflow_file"
4444
grep -Fq 'IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=${IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME}' "$workflow_file"
4545
grep -Fq 'required_vars+=(IBKR_FEATURE_SNAPSHOT_PATH)' "$workflow_file"
46-
grep -Fq 'required_vars+=(IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH IBKR_STRATEGY_CONFIG_PATH IBKR_RECONCILIATION_OUTPUT_PATH)' "$workflow_file"
46+
grep -Fq 'required_vars+=(IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH)' "$workflow_file"
47+
grep -Fq 'required_vars+=(IBKR_STRATEGY_CONFIG_PATH IBKR_RECONCILIATION_OUTPUT_PATH)' "$workflow_file"
4748
grep -Fq 'env_pairs+=("IBKR_FEATURE_SNAPSHOT_PATH=${IBKR_FEATURE_SNAPSHOT_PATH}")' "$workflow_file"
4849
grep -Fq 'env_pairs+=("IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=${IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH}")' "$workflow_file"
4950
grep -Fq 'env_pairs+=("IBKR_STRATEGY_CONFIG_PATH=${IBKR_STRATEGY_CONFIG_PATH}")' "$workflow_file"

0 commit comments

Comments
 (0)