From 8f61679d65bed1602f7d24ffcf8da48770582671 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:40:56 +0800 Subject: [PATCH 1/2] docs: add open-source README overview --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 9a8a1e9..f2b2059 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,36 @@ # InteractiveBrokersPlatform + + +> ⚠️ 投资有风险,不构成投资建议,仅供学习交流用途。 +> ⚠️ Investing involves risk. This project does not provide investment advice and is for educational and research purposes only. + +## Open-source overview / 开源项目入口 + +| Item | Description | +| --- | --- | +| Project type | execution platform | +| What it does | Interactive Brokers execution platform for QuantStrategyLab US/HK equity strategies with Cloud Run dispatch and dry-run controls. | +| 中文说明 | IBKR 执行平台,负责加载策略、处理账户/市场输入,并通过 Cloud Run/调度执行 dry-run 或实盘路径。 | +| Current status | Execution platform. Treat all live credentials, account IDs and order paths as production-sensitive. | + +### Quick start + +- `python -m pip install -e '.[test]'` +- `python -m pytest -q` + +### Deploy / operate safely + +Start from dry-run GitHub Actions/Cloud Run deployment, verify secrets and account group config, then enable live execution only per account. + +### Strategy performance / evidence boundary + +Strategy performance is not owned here; review UsEquityStrategies, HkEquityStrategies and their snapshot pipeline evidence before enabling a profile. + +> Detailed runbooks, migration notes, workflow internals, and historical decisions are kept below. Start with this overview before using the lower-level operational sections. + + + > Risk warning: this project is not investment advice and is provided for study and engineering validation only. ![Python](https://img.shields.io/badge/Python-3.9%2B-blue) From 05c05ca44167d622f37222418ace8d2d2b8fed49 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Wed, 3 Jun 2026 16:18:31 +0800 Subject: [PATCH 2/2] Align IBKR live profiles with current strategy rollout --- README.md | 12 ++-- README.zh-CN.md | 4 +- strategy_registry.py | 6 +- tests/test_decision_mapper.py | 4 +- tests/test_runtime_config_support.py | 87 ++++++++----------------- tests/test_snapshot_strategy_runtime.py | 5 ++ tests/test_strategy_loader.py | 18 ++--- 7 files changed, 53 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index f2b2059..b117fdc 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,6 @@ translation. - `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_blue_chip_leader_rotation` (architecture scaffold only; eligible but disabled) @@ -91,7 +90,6 @@ translation. | `russell_1000_multi_factor_defensive` | Russell 1000 Multi-Factor | Yes | Yes | `us_equity` | defensive stock baseline | | `tqqq_growth_income` | TQQQ Growth Income | Yes | Yes | `us_equity` | enabled value-mode alternative | | `soxl_soxx_trend_income` | SOXL/SOXX Semiconductor Trend Income | Yes | Yes | `us_equity` | current IBKR live line | -| `tech_communication_pullback_enhancement` | Tech/Communication Pullback Enhancement | Yes | Yes | `us_equity` | enabled feature-snapshot alternative | | `mega_cap_leader_rotation_top50_balanced` | Mega Cap Leader Rotation Top50 Balanced | Yes | Yes | `us_equity` | enabled balanced Top50 leader rotation | | `nasdaq_sp500_smart_dca` | Nasdaq/S&P 500 Smart DCA | Yes | Yes | `us_equity` | buy-only cash-deployment profile | | `hk_blue_chip_leader_rotation` | HK Blue Chip Leader Rotation | Yes | No | `hk_equity` | architecture scaffold only; not runtime-enabled | @@ -156,7 +154,7 @@ For IBKR, keep `paper` as a single account-group entry. If you later add live ac | `IBKR_CONNECT_ATTEMPTS` | No | Number of IBKR connection attempts before failing the cycle. Defaults to `3`. | | `IBKR_CONNECT_RETRY_DELAY_SECONDS` | No | Delay between failed IBKR connection attempts. Defaults to `5`. | | `IBKR_CLIENT_ID_RETRY_OFFSET` | No | Offset added to the configured `ib_client_id` on each retry, so a timed-out API handshake can retry with a fresh client id. Defaults to `100`. | -| `STRATEGY_PROFILE` | Yes | Strategy profile selector. Enabled values: `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`, and `hk_etf_regime_rotation` are not runtime-enabled and are not selectable by the platform status/switch tooling. Cloud Run uses the values configured on the selected service. | +| `STRATEGY_PROFILE` | Yes | Strategy profile selector. Enabled values: `global_etf_rotation`, `russell_1000_multi_factor_defensive`, `tqqq_growth_income`, `soxl_soxx_trend_income`, `mega_cap_leader_rotation_top50_balanced`, `nasdaq_sp500_smart_dca`, `hk_listed_global_etf_rotation`. Tech/Communication and the HK scaffold profiles are not runtime-enabled and are not selectable by the platform status/switch tooling. Cloud Run uses the values configured on the selected service. | | `ACCOUNT_GROUP` | Yes | Account-group selector. Set explicitly for each deployment. | | `IBKR_MARKET` | No | Market scope. Defaults to `HK` when `ACCOUNT_GROUP` contains `hk`, otherwise `US`. | | `IBKR_MARKET_CALENDAR` | No | Market calendar. Defaults to `XHKG` for HK and `NYSE` for US. | @@ -164,7 +162,7 @@ For IBKR, keep `paper` as a single account-group entry. If you later add live ac | `IBKR_MARKET_EXCHANGE` | No | Stock contract exchange. Defaults to `SEHK` for HK and `SMART` for US. | | `IBKR_MARKET_CURRENCY` | No | Stock contract currency and portfolio currency scope. Defaults to `HKD` for HK and `USD` for US. | | `IBKR_MARKET_DATA_SYMBOL_SUFFIX` | No | Suffix used only for yfinance fallback symbols. Defaults to `.HK` for HK and empty for US. | -| `IBKR_FEATURE_SNAPSHOT_PATH` | Conditionally required | Required for enabled snapshot-backed profiles such as `russell_1000_multi_factor_defensive`, `tech_communication_pullback_enhancement`, and `mega_cap_leader_rotation_top50_balanced`. The HK scaffold will also require this after promotion. Path to the latest feature snapshot file (`.csv`, `.json`, `.jsonl`, `.parquet`). | +| `IBKR_FEATURE_SNAPSHOT_PATH` | Conditionally required | Required for enabled snapshot-backed profiles such as `russell_1000_multi_factor_defensive` and `mega_cap_leader_rotation_top50_balanced`. The HK scaffold will also require this after promotion. Path to the latest feature snapshot file (`.csv`, `.json`, `.jsonl`, `.parquet`). | | `IBKR_STRATEGY_PLUGIN_MOUNTS_JSON` | No | Optional IBKR-side strategy plugin mount JSON. The plugin artifact controls mode; platform config must not set `mode`. | | `IBKR_MIN_ORDER_NOTIONAL_USD` | No | Minimum buy notional for limit buys; defaults to `50.0`. | | `IBKR_MIN_RESERVED_CASH_USD` | No | Platform-level minimum cash reserve in USD. Defaults to `0`; the effective reserve is the max of this floor and the effective cash reserve ratio. | @@ -230,11 +228,11 @@ NOTIFY_LANG=zh ``` ```bash -STRATEGY_PROFILE=tech_communication_pullback_enhancement +STRATEGY_PROFILE=mega_cap_leader_rotation_top50_balanced ACCOUNT_GROUP=paper IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups -IBKR_FEATURE_SNAPSHOT_PATH=/var/data/tech_communication_pullback_enhancement_feature_snapshot_latest.csv -IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=/var/manifests/tech_communication_pullback_enhancement_feature_snapshot_latest.csv.manifest.json +IBKR_FEATURE_SNAPSHOT_PATH=/var/data/mega_cap_leader_rotation_top50_balanced_feature_snapshot_latest.csv +IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=/var/manifests/mega_cap_leader_rotation_top50_balanced_feature_snapshot_latest.csv.manifest.json # IBKR_STRATEGY_CONFIG_PATH is optional; the bundled canonical default is used when unset. IBKR_DRY_RUN_ONLY=true # IBKR orders run on whole shares only. diff --git a/README.zh-CN.md b/README.zh-CN.md index 5964a11..149c610 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -74,7 +74,7 @@ IBKR 账户 | `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 使用当前服务上配置的取值 | +| `STRATEGY_PROFILE` | 是 | 策略档位选择。当前已启用值:`global_etf_rotation`、`russell_1000_multi_factor_defensive`、`tqqq_growth_income`、`soxl_soxx_trend_income`、`mega_cap_leader_rotation_top50_balanced`、`nasdaq_sp500_smart_dca`、`hk_listed_global_etf_rotation`。Tech/Communication 和港股 scaffold profiles 不是 runtime-enabled,不会被平台状态/切换工具列为可选;Cloud Run 使用当前服务上配置的取值 | | `ACCOUNT_GROUP` | 是 | 账号组选择器,每个部署都要显式设置。 | | `IBKR_MARKET` | 否 | 市场范围。`ACCOUNT_GROUP` 包含 `hk` 时默认 `HK`,其他情况默认 `US`。 | | `IBKR_MARKET_CALENDAR` | 否 | 市场日历。港股默认 `XHKG`,美股默认 `NYSE`。 | @@ -82,7 +82,7 @@ IBKR 账户 | `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_FEATURE_SNAPSHOT_PATH` | 条件必填 | `russell_1000_multi_factor_defensive`、`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`;实际预留取该下限和有效预留现金比例中的最大值。 | diff --git a/strategy_registry.py b/strategy_registry.py index cdefd65..b664766 100644 --- a/strategy_registry.py +++ b/strategy_registry.py @@ -35,6 +35,7 @@ IBKR_PLATFORM = "ibkr" HK_EQUITY_DOMAIN = "hk_equity" +TECH_COMMUNICATION_PULLBACK_PROFILE = "tech_communication_pullback_enhancement" PLATFORM_SUPPORTED_DOMAINS: dict[str, frozenset[str]] = { IBKR_PLATFORM: frozenset({US_EQUITY_DOMAIN, HK_EQUITY_DOMAIN}), @@ -95,7 +96,10 @@ def describe_platform_runtime_requirements(profile: str | None, *, platform_id: STRATEGY_CATALOG = _merge_strategy_catalogs(US_STRATEGY_CATALOG, HK_STRATEGY_CATALOG) US_STRATEGY_PROFILES = frozenset(US_STRATEGY_CATALOG.definitions) HK_STRATEGY_PROFILES = frozenset(HK_STRATEGY_CATALOG.definitions) -IBKR_ROLLOUT_ALLOWLIST = get_us_runtime_enabled_profiles() | get_hk_runtime_enabled_profiles() +IBKR_EXCLUDED_LIVE_PROFILES = frozenset({TECH_COMMUNICATION_PULLBACK_PROFILE}) +IBKR_ROLLOUT_ALLOWLIST = ( + get_us_runtime_enabled_profiles() | get_hk_runtime_enabled_profiles() +) - IBKR_EXCLUDED_LIVE_PROFILES PLATFORM_CAPABILITY_MATRIX = PlatformCapabilityMatrix( platform_id=IBKR_PLATFORM, supported_domains=PLATFORM_SUPPORTED_DOMAINS[IBKR_PLATFORM], diff --git a/tests/test_decision_mapper.py b/tests/test_decision_mapper.py index 4c17e51..3e9ff0d 100644 --- a/tests/test_decision_mapper.py +++ b/tests/test_decision_mapper.py @@ -18,7 +18,7 @@ def test_map_strategy_decision_maps_weight_positions_and_safe_haven(): target_weights, signal_desc, is_emergency, status_desc, metadata = map_strategy_decision( decision, - strategy_profile="tech_communication_pullback_enhancement", + strategy_profile="mega_cap_leader_rotation_top50_balanced", runtime_metadata={"status_icon": "🧲", "dry_run_only": True}, ) @@ -49,7 +49,7 @@ def test_map_strategy_decision_returns_noop_when_flagged_no_execute(): target_weights, signal_desc, is_emergency, status_desc, metadata = map_strategy_decision( decision, - strategy_profile="tech_communication_pullback_enhancement", + strategy_profile="mega_cap_leader_rotation_top50_balanced", runtime_metadata={ "status_icon": "🛑", "snapshot_guard_decision": "fail_closed", diff --git a/tests/test_runtime_config_support.py b/tests/test_runtime_config_support.py index 6062afc..653d75d 100644 --- a/tests/test_runtime_config_support.py +++ b/tests/test_runtime_config_support.py @@ -61,7 +61,6 @@ "nasdaq_sp500_smart_dca", "russell_1000_multi_factor_defensive", "soxl_soxx_trend_income", - "tech_communication_pullback_enhancement", "tqqq_growth_income", "hk_high_dividend_low_vol_trend", "hk_listed_global_etf_rotation", @@ -233,7 +232,7 @@ def test_load_platform_runtime_settings_prefers_runtime_target_json(monkeypatch) monkeypatch.setenv( "RUNTIME_TARGET_JSON", runtime_target_json( - "tech_communication_pullback_enhancement", + "tqqq_growth_income", dry_run_only=True, account_selector=["U999"], service_name="interactive-brokers-paper-service", @@ -242,8 +241,8 @@ def test_load_platform_runtime_settings_prefers_runtime_target_json(monkeypatch) settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1") - assert settings.strategy_profile == "tech_communication_pullback_enhancement" - assert settings.runtime_target.strategy_profile == "tech_communication_pullback_enhancement" + assert settings.strategy_profile == "tqqq_growth_income" + assert settings.runtime_target.strategy_profile == "tqqq_growth_income" assert settings.runtime_target.platform_id == "ibkr" assert settings.runtime_target.dry_run_only is True assert settings.runtime_target.execution_mode == "paper" @@ -573,20 +572,18 @@ def test_platform_eligible_profiles_are_exposed_by_capability_matrix(): assert get_eligible_profiles_for_platform(IBKR_PLATFORM) == EXPECTED_IBKR_PROFILES -def test_load_platform_runtime_settings_accepts_tech_communication_pullback_enhancement(monkeypatch): +def test_load_platform_runtime_settings_rejects_research_only_tech_communication_pullback(monkeypatch): monkeypatch.setenv( "RUNTIME_TARGET_JSON", runtime_target_json("tech_communication_pullback_enhancement"), ) monkeypatch.setenv("ACCOUNT_GROUP", "paper") 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") + monkeypatch.setenv("IBKR_FEATURE_SNAPSHOT_PATH", "/tmp/tech.csv") + monkeypatch.setenv("IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH", "/tmp/tech.csv.manifest.json") - assert settings.strategy_profile == "tech_communication_pullback_enhancement" - assert settings.strategy_display_name == "Tech/Communication Pullback Enhancement" - assert settings.strategy_target_mode == "weight" + with pytest.raises(ValueError, match="Unsupported STRATEGY_PROFILE"): + load_platform_runtime_settings(project_id_resolver=lambda: "project-1") @pytest.mark.parametrize("profile", sorted(HK_DISABLED_PROFILES)) @@ -678,7 +675,7 @@ def test_platform_profile_matrix_exposes_profiles_without_selection_roles(): by_profile = {row["canonical_profile"]: row for row in rows} assert "is_default" not in by_profile["global_etf_rotation"] assert "is_rollback" not in by_profile["global_etf_rotation"] - assert by_profile["tech_communication_pullback_enhancement"]["display_name"] == "Tech/Communication Pullback Enhancement" + assert "tech_communication_pullback_enhancement" not in by_profile def test_platform_profile_status_matrix_matches_current_ibkr_rollout(): @@ -746,10 +743,7 @@ def test_print_strategy_profile_status_json_matches_registry(): assert by_profile["nasdaq_sp500_smart_dca"]["profile_group"] == "direct_runtime_inputs" assert by_profile["nasdaq_sp500_smart_dca"]["input_mode"] == "market_history+portfolio_snapshot" assert by_profile["nasdaq_sp500_smart_dca"]["requires_snapshot_artifacts"] is False - assert by_profile["tech_communication_pullback_enhancement"]["profile_group"] == "snapshot_backed" - assert by_profile["tech_communication_pullback_enhancement"]["input_mode"] == "feature_snapshot" - assert by_profile["tech_communication_pullback_enhancement"]["requires_snapshot_artifacts"] is True - assert by_profile["tech_communication_pullback_enhancement"]["requires_strategy_config_path"] is True + assert "tech_communication_pullback_enhancement" not in by_profile assert by_profile["mega_cap_leader_rotation_top50_balanced"]["profile_group"] == "snapshot_backed" assert by_profile["mega_cap_leader_rotation_top50_balanced"]["input_mode"] == "feature_snapshot" assert by_profile["mega_cap_leader_rotation_top50_balanced"]["requires_snapshot_artifacts"] is True @@ -780,7 +774,8 @@ def test_print_strategy_profile_status_table_contains_expected_headers(): assert "requires_snapshot_artifacts" in result.stdout assert "global_etf_rotation" in result.stdout assert "hk_listed_global_etf_rotation" in result.stdout - assert "Tech/Communication Pullback Enhancement" in result.stdout + assert "Mega Cap Leader Rotation Top50 Balanced" in result.stdout + assert "Tech/Communication Pullback Enhancement" not in result.stdout assert "HK-listed Global ETF Rotation" in result.stdout assert "TQQQ Growth Income" in result.stdout assert "hk_blue_chip_leader_rotation" not in result.stdout @@ -1110,24 +1105,22 @@ def test_print_strategy_switch_env_plan_rejects_hk_disabled_profiles(profile): def test_print_strategy_switch_env_plan_for_feature_snapshot_profile(): result = subprocess.run( - [sys.executable, str(SWITCH_PLAN_SCRIPT_PATH), "--profile", "tech_communication_pullback_enhancement", "--json"], + [sys.executable, str(SWITCH_PLAN_SCRIPT_PATH), "--profile", "mega_cap_leader_rotation_top50_balanced", "--json"], check=True, capture_output=True, text=True, ) plan = json.loads(result.stdout) - assert plan["canonical_profile"] == "tech_communication_pullback_enhancement" + assert plan["canonical_profile"] == "mega_cap_leader_rotation_top50_balanced" assert plan["profile_group"] == "snapshot_backed" assert plan["input_mode"] == "feature_snapshot" assert plan["requires_snapshot_artifacts"] is True - assert plan["requires_strategy_config_path"] is True - assert plan["config_source_policy"] == "bundled_or_env" + assert plan["requires_strategy_config_path"] is False assert plan["set_env"]["IBKR_FEATURE_SNAPSHOT_PATH"] == "" assert plan["set_env"]["IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH"] == "" assert "IBKR_STRATEGY_CONFIG_PATH" not in plan["set_env"] assert "IBKR_STRATEGY_CONFIG_PATH" in plan["remove_if_present"] - assert "IBKR_RECONCILIATION_OUTPUT_PATH" in plan["optional_env"] def test_print_strategy_switch_env_plan_uses_manifest_contract_policy(): @@ -1168,7 +1161,7 @@ def test_load_platform_runtime_settings_reads_feature_snapshot_path(monkeypatch) assert settings.feature_snapshot_path == "/tmp/r1000-latest.csv" -def test_load_platform_runtime_settings_reads_tech_pullback_runtime_config(monkeypatch): +def test_load_platform_runtime_settings_rejects_tech_pullback_runtime_config(monkeypatch): monkeypatch.setenv( "RUNTIME_TARGET_JSON", runtime_target_json("tech_communication_pullback_enhancement"), @@ -1179,40 +1172,15 @@ def test_load_platform_runtime_settings_reads_tech_pullback_runtime_config(monke monkeypatch.setenv("IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH", "/tmp/cash-buffer.csv.manifest.json") monkeypatch.setenv("IBKR_STRATEGY_CONFIG_PATH", "/tmp/cash-buffer-config.json") monkeypatch.setenv("IBKR_RECONCILIATION_OUTPUT_PATH", "/tmp/reconciliation.json") - monkeypatch.setenv("IBKR_DRY_RUN_ONLY", "true") - - settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1") - - assert settings.feature_snapshot_path == "/tmp/cash-buffer.csv" - assert settings.feature_snapshot_manifest_path == "/tmp/cash-buffer.csv.manifest.json" - assert settings.strategy_config_path == "/tmp/cash-buffer-config.json" - assert settings.strategy_config_source == "env" - assert settings.reconciliation_output_path == "/tmp/reconciliation.json" - assert settings.dry_run_only is True - - -def test_load_platform_runtime_settings_uses_bundled_tech_pullback_config_when_env_missing(monkeypatch): - monkeypatch.setenv( - "RUNTIME_TARGET_JSON", - runtime_target_json("tech_communication_pullback_enhancement"), - ) - monkeypatch.setenv("ACCOUNT_GROUP", "paper") - monkeypatch.setenv("IB_ACCOUNT_GROUP_CONFIG_JSON", MINIMAL_GROUP_JSON) - monkeypatch.setenv("IBKR_FEATURE_SNAPSHOT_PATH", "/tmp/cash-buffer.csv") - monkeypatch.delenv("IBKR_STRATEGY_CONFIG_PATH", raising=False) - monkeypatch.delenv("STRATEGY_CONFIG_PATH", raising=False) - settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1") - - assert settings.strategy_config_path is not None - assert settings.strategy_config_path.endswith("tech_communication_pullback_enhancement.json") - assert settings.strategy_config_source == "bundled_canonical_default" + with pytest.raises(ValueError, match="Unsupported STRATEGY_PROFILE"): + load_platform_runtime_settings(project_id_resolver=lambda: "project-1") def test_load_platform_runtime_settings_derives_artifact_paths_from_root(monkeypatch, tmp_path): monkeypatch.setenv( "RUNTIME_TARGET_JSON", - runtime_target_json("tech_communication_pullback_enhancement"), + runtime_target_json("mega_cap_leader_rotation_top50_balanced"), ) monkeypatch.setenv("ACCOUNT_GROUP", "paper") monkeypatch.setenv("IB_ACCOUNT_GROUP_CONFIG_JSON", MINIMAL_GROUP_JSON) @@ -1226,21 +1194,22 @@ def test_load_platform_runtime_settings_derives_artifact_paths_from_root(monkeyp settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1") assert settings.strategy_artifact_root == str(tmp_path) - assert settings.strategy_artifact_dir == str(tmp_path / "tech_communication_pullback_enhancement") + assert settings.strategy_artifact_dir == str(tmp_path / "mega_cap_leader_rotation_top50_balanced") assert settings.feature_snapshot_path == str( - tmp_path / "tech_communication_pullback_enhancement" / "tech_communication_pullback_enhancement_feature_snapshot_latest.csv" + tmp_path + / "mega_cap_leader_rotation_top50_balanced" + / "mega_cap_leader_rotation_top50_balanced_feature_snapshot_latest.csv" ) assert settings.feature_snapshot_manifest_path == str( tmp_path - / "tech_communication_pullback_enhancement" - / "tech_communication_pullback_enhancement_feature_snapshot_latest.csv.manifest.json" + / "mega_cap_leader_rotation_top50_balanced" + / "mega_cap_leader_rotation_top50_balanced_feature_snapshot_latest.csv.manifest.json" ) assert settings.reconciliation_output_path == str( - tmp_path / "tech_communication_pullback_enhancement" / "reconciliation" + tmp_path / "mega_cap_leader_rotation_top50_balanced" / "reconciliation" ) - assert settings.strategy_config_path is not None - assert settings.strategy_config_path.endswith("tech_communication_pullback_enhancement.json") - assert settings.strategy_config_source == "bundled_canonical_default" + assert settings.strategy_config_path is None + assert settings.strategy_config_source is None diff --git a/tests/test_snapshot_strategy_runtime.py b/tests/test_snapshot_strategy_runtime.py index 2c969b6..3118b6c 100644 --- a/tests/test_snapshot_strategy_runtime.py +++ b/tests/test_snapshot_strategy_runtime.py @@ -87,6 +87,7 @@ def fake_load_feature_snapshot_guarded(path, **_kwargs): assert result[4]["snapshot_guard_decision"] == "proceed" +@pytest.mark.skip(reason="Tech/Communication is research-only and no longer an IBKR live profile") def test_compute_signals_loads_tech_communication_pullback_enhancement_runtime(strategy_module_factory, monkeypatch, tmp_path): pytest.importorskip("pandas") @@ -154,6 +155,7 @@ def test_compute_signals_loads_tech_communication_pullback_enhancement_runtime(s assert module.CASH_RESERVE_RATIO == pytest.approx(0.0) +@pytest.mark.skip(reason="Tech/Communication is research-only and no longer an IBKR live profile") def test_compute_signals_fail_closes_when_snapshot_missing(strategy_module_factory): module = strategy_module_factory( STRATEGY_PROFILE="tech_communication_pullback_enhancement", @@ -168,6 +170,7 @@ def test_compute_signals_fail_closes_when_snapshot_missing(strategy_module_facto assert "feature_snapshot_missing" in result[4]["fail_reason"] +@pytest.mark.skip(reason="Tech/Communication is research-only and no longer an IBKR live profile") def test_compute_signals_fail_closes_when_snapshot_is_stale(strategy_module_factory, tmp_path): snapshot_path = tmp_path / "stale_snapshot.csv" snapshot_path.write_text( @@ -195,6 +198,7 @@ def test_compute_signals_fail_closes_when_snapshot_is_stale(strategy_module_fact assert "feature_snapshot_stale" in result[4]["fail_reason"] +@pytest.mark.skip(reason="Tech/Communication is research-only and no longer an IBKR live profile") def test_compute_signals_fail_closes_when_manifest_missing(strategy_module_factory, tmp_path): snapshot_path = tmp_path / "snapshot.csv" config_path = tmp_path / "tech_communication_pullback_enhancement.json" @@ -266,6 +270,7 @@ def test_platform_reserved_cash_policy_can_raise_strategy_cash_reserve(strategy_ assert module.CASH_RESERVE_FLOOR_USD == pytest.approx(250.0) +@pytest.mark.skip(reason="Tech/Communication is research-only and no longer an IBKR live profile") def test_compute_signals_exposes_dry_run_price_fallbacks(strategy_module_factory, tmp_path): pytest.importorskip("pandas") diff --git a/tests/test_strategy_loader.py b/tests/test_strategy_loader.py index 7b51b0e..3ae9d2e 100644 --- a/tests/test_strategy_loader.py +++ b/tests/test_strategy_loader.py @@ -20,7 +20,7 @@ def test_load_strategy_entrypoint_for_profile_resolves_global_etf_rotation(monke assert "market_history" in entrypoint.manifest.required_inputs -def test_load_strategy_entrypoint_for_profile_resolves_tech_communication_pullback_enhancement(monkeypatch): +def test_load_strategy_entrypoint_for_profile_rejects_research_only_tech_communication_pullback(monkeypatch): try: import pandas # noqa: F401 except ModuleNotFoundError: @@ -30,10 +30,8 @@ def test_load_strategy_entrypoint_for_profile_resolves_tech_communication_pullba market_calendars_module.get_calendar = lambda name: None monkeypatch.setitem(sys.modules, "pandas_market_calendars", market_calendars_module) - entrypoint = load_strategy_entrypoint_for_profile("tech_communication_pullback_enhancement") - - assert entrypoint.manifest.profile == "tech_communication_pullback_enhancement" - assert entrypoint.manifest.default_config["safe_haven"] == "BOXX" + with pytest.raises(ValueError, match="Unsupported STRATEGY_PROFILE"): + load_strategy_entrypoint_for_profile("tech_communication_pullback_enhancement") def test_load_strategy_entrypoint_for_profile_resolves_tqqq_growth_income(monkeypatch): @@ -92,7 +90,7 @@ def test_load_strategy_entrypoint_for_profile_rejects_legacy_cash_buffer_profile load_strategy_entrypoint_for_profile("tech_pullback_cash_buffer") -def test_load_strategy_runtime_adapter_for_profile_resolves_tech_communication_pullback_enhancement(monkeypatch): +def test_load_strategy_runtime_adapter_for_profile_rejects_research_only_tech_communication_pullback(monkeypatch): try: import pandas # noqa: F401 except ModuleNotFoundError: @@ -102,12 +100,8 @@ def test_load_strategy_runtime_adapter_for_profile_resolves_tech_communication_p market_calendars_module.get_calendar = lambda name: None monkeypatch.setitem(sys.modules, "pandas_market_calendars", market_calendars_module) - adapter = load_strategy_runtime_adapter_for_profile("tech_communication_pullback_enhancement") - - assert adapter.status_icon == "🧲" - assert adapter.available_inputs == frozenset({"feature_snapshot"}) - assert adapter.require_snapshot_manifest is True - assert adapter.snapshot_contract_version == "tech_communication_pullback_enhancement.feature_snapshot.v1" + with pytest.raises(ValueError, match="Unsupported STRATEGY_PROFILE"): + load_strategy_runtime_adapter_for_profile("tech_communication_pullback_enhancement") def test_load_strategy_runtime_adapter_for_profile_rejects_archived_mega_cap_dynamic_top20(monkeypatch):