Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/sync-cloud-run-env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ jobs:
NOTIFY_LANG
)

if [ "${STRATEGY_PROFILE:-}" = "russell_1000_multi_factor_defensive" ] || [ "${STRATEGY_PROFILE:-}" = "cash_buffer_branch_default" ]; then
if [ "${STRATEGY_PROFILE:-}" = "russell_1000_multi_factor_defensive" ] || [ "${STRATEGY_PROFILE:-}" = "tech_pullback_cash_buffer" ]; then
required_vars+=(IBKR_FEATURE_SNAPSHOT_PATH)
fi

if [ "${STRATEGY_PROFILE:-}" = "cash_buffer_branch_default" ]; then
if [ "${STRATEGY_PROFILE:-}" = "tech_pullback_cash_buffer" ]; then
required_vars+=(IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH IBKR_STRATEGY_CONFIG_PATH IBKR_RECONCILIATION_OUTPUT_PATH)
fi

Expand Down
22 changes: 9 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ IBKR runtime for shared `us_equity` strategy profiles 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
- `tech_pullback_cash_buffer` (`Tech Pullback Cash Buffer`): monthly tech-heavy stock-selection branch with explicit BOXX cash buffer

Current strategy implementations are sourced from `UsEquityStrategies`.

Expand All @@ -29,11 +29,7 @@ This runtime matrix is the authoritative enablement source for IBKR. `UsEquitySt

- `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`
- `tech_pullback_cash_buffer`


**IBKR profile matrix**
Expand All @@ -42,7 +38,7 @@ Human-readable alias is available for config review and future migration:
| --- | --- | --- | --- | --- | --- | --- | --- |
| `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 |
| `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):**

Expand Down Expand Up @@ -158,7 +154,7 @@ 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`, `russell_1000_multi_factor_defensive`, `cash_buffer_branch_default` (alias: `tech_pullback_cash_buffer`) |
| `STRATEGY_PROFILE` | Yes | Strategy profile selector. Supported `us_equity` values: `global_etf_rotation`, `russell_1000_multi_factor_defensive`, `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. |
Expand Down Expand Up @@ -208,11 +204,11 @@ NOTIFY_LANG=zh
```

```bash
STRATEGY_PROFILE=cash_buffer_branch_default
STRATEGY_PROFILE=tech_pullback_cash_buffer
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_FEATURE_SNAPSHOT_PATH=/var/data/tech_pullback_cash_buffer_feature_snapshot_latest.csv
IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=/var/manifests/tech_pullback_cash_buffer_feature_snapshot_latest.csv.manifest.json
IBKR_DRY_RUN_ONLY=true
GLOBAL_TELEGRAM_CHAT_ID=<telegram-chat-id>
NOTIFY_LANG=zh
Expand Down Expand Up @@ -337,7 +333,7 @@ gcloud run services update ibkr-quant \

基于 IBKR 的全球 ETF 季度轮动策略(国际市场、商品、美股行业、美股宽基、科技和半导体),含每日金丝雀应急机制。定位上比 `TQQQ`、`SOXL` 这类高弹性科技策略更稳健,但不再把科技完全排除在外。部署在 GCP Cloud Run,连接 GCE 上的 IB Gateway。

当前 `global_etf_rotation`、`russell_1000_multi_factor_defensive` 和 `cash_buffer_branch_default` 的策略实现都来自 `UsEquityStrategies`。
当前 `global_etf_rotation`、`russell_1000_multi_factor_defensive` 和 `tech_pullback_cash_buffer` 的策略实现都来自 `UsEquityStrategies`。

完整策略说明现在放在 [`UsEquityStrategies`](https://github.com/QuantStrategyLab/UsEquityStrategies#global_etf_rotation)。下面的策略章节主要保留执行侧摘要。

Expand Down Expand Up @@ -396,7 +392,7 @@ IBKR 账户
|------|------|------|
| `IB_GATEWAY_ZONE` | 可选过渡项 | GCE zone(如 `us-central1-a`)。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
| `IB_GATEWAY_IP_MODE` | 可选过渡项 | `internal`(默认)或 `external`。推荐直接放进选中的账号组配置里;这里只保留过渡 fallback。 |
| `STRATEGY_PROFILE` | 是 | 策略档位选择。当前可用的 `us_equity` 值:`global_etf_rotation`、`russell_1000_multi_factor_defensive`、`cash_buffer_branch_default`(alias:`tech_pullback_cash_buffer` |
| `STRATEGY_PROFILE` | 是 | 策略档位选择。当前可用的 `us_equity` 值:`global_etf_rotation`、`russell_1000_multi_factor_defensive`、`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 直接使用。 |
Expand Down
11 changes: 0 additions & 11 deletions configs/paper_cash_buffer_branch.dryrun.env.example

This file was deleted.

11 changes: 0 additions & 11 deletions configs/paper_cash_buffer_branch.env.example

This file was deleted.

11 changes: 0 additions & 11 deletions configs/paper_cash_buffer_branch.paper.env.example

This file was deleted.

2 changes: 1 addition & 1 deletion configs/paper_global_etf_rotation.rollback.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ACCOUNT_GROUP=default
IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
GLOBAL_TELEGRAM_CHAT_ID=<telegram-chat-id>
NOTIFY_LANG=zh
# remove if previously set for cash_buffer_branch_default:
# remove if previously set for tech_pullback_cash_buffer:
# IBKR_FEATURE_SNAPSHOT_PATH
# IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH
# IBKR_STRATEGY_CONFIG_PATH
Expand Down
11 changes: 11 additions & 0 deletions configs/paper_tech_pullback_cash_buffer.dryrun.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# IBKR paper runtime switch: tech_pullback_cash_buffer (dry-run)
STRATEGY_PROFILE=tech_pullback_cash_buffer
ACCOUNT_GROUP=default
IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
IBKR_FEATURE_SNAPSHOT_PATH=/var/data/tech_pullback_cash_buffer_feature_snapshot_latest.csv
IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=/var/manifests/tech_pullback_cash_buffer_feature_snapshot_latest.csv.manifest.json
IBKR_STRATEGY_CONFIG_PATH=/workspace/research/configs/growth_pullback_tech_pullback_cash_buffer.json
IBKR_RECONCILIATION_OUTPUT_PATH=/var/log/ibkr_tech_pullback_cash_buffer_reconciliation.json
IBKR_DRY_RUN_ONLY=true
GLOBAL_TELEGRAM_CHAT_ID=<telegram-chat-id>
NOTIFY_LANG=zh
11 changes: 11 additions & 0 deletions configs/paper_tech_pullback_cash_buffer.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# IBKR paper runtime switch: tech_pullback_cash_buffer
STRATEGY_PROFILE=tech_pullback_cash_buffer
ACCOUNT_GROUP=default
IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
IBKR_FEATURE_SNAPSHOT_PATH=/var/data/tech_pullback_cash_buffer_feature_snapshot_latest.csv
IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=/var/manifests/tech_pullback_cash_buffer_feature_snapshot_latest.csv.manifest.json
IBKR_STRATEGY_CONFIG_PATH=/workspace/research/configs/growth_pullback_tech_pullback_cash_buffer.json
IBKR_RECONCILIATION_OUTPUT_PATH=/var/log/ibkr_tech_pullback_cash_buffer_reconciliation.json
IBKR_DRY_RUN_ONLY=true
GLOBAL_TELEGRAM_CHAT_ID=<telegram-chat-id>
NOTIFY_LANG=zh
11 changes: 11 additions & 0 deletions configs/paper_tech_pullback_cash_buffer.paper.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# IBKR paper runtime switch: tech_pullback_cash_buffer (paper ordering)
STRATEGY_PROFILE=tech_pullback_cash_buffer
ACCOUNT_GROUP=default
IB_ACCOUNT_GROUP_CONFIG_SECRET_NAME=ibkr-account-groups
IBKR_FEATURE_SNAPSHOT_PATH=/var/data/tech_pullback_cash_buffer_feature_snapshot_latest.csv
IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH=/var/manifests/tech_pullback_cash_buffer_feature_snapshot_latest.csv.manifest.json
IBKR_STRATEGY_CONFIG_PATH=/workspace/research/configs/growth_pullback_tech_pullback_cash_buffer.json
IBKR_RECONCILIATION_OUTPUT_PATH=/var/log/ibkr_tech_pullback_cash_buffer_reconciliation.json
IBKR_DRY_RUN_ONLY=false
GLOBAL_TELEGRAM_CHAT_ID=<telegram-chat-id>
NOTIFY_LANG=zh
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
flask
gunicorn
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@6e8cc058b821aea8a54015d4b39e02fbdd3dc198
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@8b0c59f2ecd7c74cc2d350f1870f124a2560b205
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@53f996ef489700f325a5b750355d644f4aed7270
pandas
numpy
requests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,10 @@ def build_recommendation(
level = "cash_buffer_branch_candidate"
reason = "cash-buffer branch direction still exists, but the refined candidates do not keep enough relative-to-QQQ edge"
elif honest_ok and occupancy_ok and concentration_ok and best_is_current_default and (stable_margin or no_material_gain_over_current):
level = "cash_buffer_branch_default_frozen"
level = "tech_pullback_cash_buffer_frozen"
reason = "current honest cash-buffer branch is already good enough; small refinements do not justify further spec churn"
elif honest_ok and performance_ok:
level = "cash_buffer_branch_default"
level = "tech_pullback_cash_buffer"
reason = "a refined honest cash-buffer candidate is strong enough to act as the branch default, but the spec is not fully frozen yet"
else:
level = "cash_buffer_branch_candidate"
Expand Down
20 changes: 10 additions & 10 deletions research/backtest_growth_pullback_v1_5_freeze_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
DEFAULT_RESULTS_DIR = SCRIPT_DIR / "results"
DEFAULT_CONFIGS_DIR = SCRIPT_DIR / "configs"
MAIN_COST_BPS = 5.0
CANONICAL_CONFIG_FILENAME = "growth_pullback_cash_buffer_branch_default.json"
CANONICAL_CONFIG_FILENAME = "growth_pullback_tech_pullback_cash_buffer.json"
V14_SUMMARY_FILENAME = "growth_pullback_v1_4_cash_buffer_refinement.csv"
V14_RECOMMENDATION_FILENAME = "growth_pullback_v1_4_recommendation.json"

Expand Down Expand Up @@ -72,15 +72,15 @@ def resolve_paths(configs_dir: Path, results_dir: Path) -> dict[str, Path]:
def build_canonical_spec(center_cfg: gp.GrowthPullbackConfig, previous_candidate_name: str) -> CanonicalBranchSpec:
config = replace(
center_cfg,
name="cash_buffer_branch_default",
name="tech_pullback_cash_buffer",
holdings_count=8,
single_name_cap=0.10,
sector_cap=0.40,
hold_bonus=0.10,
exposures=gp.suite.ExposureConfig("80_60_0", 0.60, 0.00),
)
return CanonicalBranchSpec(
name="cash_buffer_branch_default",
name="tech_pullback_cash_buffer",
previous_candidate_name=previous_candidate_name,
config=config,
risk_on_exposure=0.80,
Expand All @@ -95,7 +95,7 @@ def build_canonical_spec(center_cfg: gp.GrowthPullbackConfig, previous_candidate

def canonical_spec_to_dict(spec: CanonicalBranchSpec) -> dict[str, object]:
return {
"role": "cash_buffer_branch_default",
"role": "tech_pullback_cash_buffer",
"status": "research_only",
"strategy": "growth_pullback_systematic_v1",
"branch_name": "cash_buffer_branch",
Expand Down Expand Up @@ -295,7 +295,7 @@ def build_consistency_checks(
},
{
"check": "recommendation_matches_branch_default_role",
"passed": recommendation_level == "cash_buffer_branch_default",
"passed": recommendation_level == "tech_pullback_cash_buffer",
"detail": f"recommendation={recommendation_level}",
},
{
Expand Down Expand Up @@ -390,7 +390,7 @@ def build_role_table(
qqq_full = v12.extract_period_row(qqq_reference_rows, "QQQ", MAIN_COST_BPS, "Full Sample")
rows.extend([
{
"strategy": "cash_buffer_branch_default",
"strategy": "tech_pullback_cash_buffer",
"role": "cash-buffered parallel branch",
"full_cagr": canonical_summary["full_cagr"],
"oos_cagr": canonical_summary["oos_cagr"],
Expand Down Expand Up @@ -486,15 +486,15 @@ def build_freeze_recommendation(
level = "candidate"
reason = "branch role is valid, but consistency or performance checks are not strong enough for default status"
elif frozen_blockers:
level = "cash_buffer_branch_default"
level = "tech_pullback_cash_buffer"
reason = "canonical naming and branch role are now clear, but avg names / 2022 profile / recent micro-adjustment mean it should stop at default, not frozen"
else:
level = "cash_buffer_branch_default_frozen"
level = "tech_pullback_cash_buffer_frozen"
reason = "branch semantics, deployment consistency, and behavior are all stable enough to freeze"

return {
"research_recommendation": level,
"branch_name": "cash_buffer_branch_default",
"branch_name": "tech_pullback_cash_buffer",
"previous_candidate_name": "cash_buffer_a__hb10__base__adv50",
"reason": reason,
"frozen_blockers": frozen_blockers,
Expand Down Expand Up @@ -638,7 +638,7 @@ def main() -> None:
"information_ratio_vs_qqq_oos": float(v12.extract_period_row(full_rows_df, "coherent_full_deployment_branch", MAIN_COST_BPS, "OOS Sample")["Information Ratio vs QQQ"]),
})

preliminary_recommendation_level = "cash_buffer_branch_default"
preliminary_recommendation_level = "tech_pullback_cash_buffer"
manifest = build_manifest(
spec=spec,
canonical_summary=canonical_summary,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Research-only daily overlay backtest for cash_buffer_branch_default."""
"""Research-only daily overlay backtest for tech_pullback_cash_buffer."""

from __future__ import annotations

Expand All @@ -25,7 +25,7 @@

DEFAULT_RESULTS_DIR = SCRIPT_DIR / "results"
DEFAULT_CONFIGS_DIR = SCRIPT_DIR / "configs"
DEFAULT_BASELINE_CONFIG_PATH = DEFAULT_CONFIGS_DIR / "growth_pullback_cash_buffer_branch_default.json"
DEFAULT_BASELINE_CONFIG_PATH = DEFAULT_CONFIGS_DIR / "growth_pullback_tech_pullback_cash_buffer.json"
DEFAULT_QQQ_PLUS_RESULTS_PATH = DEFAULT_RESULTS_DIR / "stock_alpha_v1_1_spec_lock.csv"
QQQ_PLUS_CURRENT_DEFAULT_SCENARIO = "v11_liquid_50m_norm_universe_cross_sectional_h12_cap8_sector30_hold10"
MAIN_COST_BPS = 5.0
Expand Down Expand Up @@ -107,7 +107,7 @@ def load_baseline_spec(path: Path) -> BaselineSpec:
return BaselineSpec(
config=config,
risk_on_exposure=float(payload["exposures"]["risk_on"]),
name="cash_buffer_branch_default_monthly_baseline",
name="tech_pullback_cash_buffer_monthly_baseline",
config_payload=payload,
)

Expand All @@ -117,7 +117,7 @@ def build_baseline_candidate(spec: BaselineSpec) -> v12.GeometryCandidate:
spec.name,
spec.config,
risk_on_exposure=spec.risk_on_exposure,
note="Monthly baseline from canonical cash_buffer_branch_default; month-start chooses names, non-rebalance days are no-op.",
note="Monthly baseline from canonical tech_pullback_cash_buffer; month-start chooses names, non-rebalance days are no-op.",
)


Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"role": "cash_buffer_branch_default",
"role": "tech_pullback_cash_buffer",
"status": "research_only",
"strategy": "growth_pullback_systematic_v1",
"branch_name": "cash_buffer_branch",
"name": "cash_buffer_branch_default",
"name": "tech_pullback_cash_buffer",
"previous_candidate_name": "cash_buffer_a__hb10__base__adv50",
"family": "tech_heavy_pullback",
"universe": "tech_heavy",
Expand Down
4 changes: 2 additions & 2 deletions research/results/growth_pullback_branch_manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"branch_name": "cash_buffer_branch_default",
"branch_name": "tech_pullback_cash_buffer",
"previous_candidate_name": "cash_buffer_a__hb10__base__adv50",
"role": "cash-buffered parallel branch",
"strategy_family": "tech_heavy_pullback",
Expand Down Expand Up @@ -50,7 +50,7 @@
"OOS CAGR and IR improved with a simple hold_bonus increase from 0.05 to 0.10.",
"Deployment semantics remain perfectly aligned with the written spec."
],
"recommendation": "cash_buffer_branch_default",
"recommendation": "tech_pullback_cash_buffer",
"oos_cagr": 0.34068001857447316,
"oos_cagr_minus_qqq": 0.23873167970694698,
"oos_max_drawdown": -0.19176289938838953,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
| QQQ | 2023+ | 0.282076 | -0.227683 | 0.000000 | 1.000000 | 1.000000 | 1.000000 | -0.000000 | | 1.000000 | 1.000000 |

## Recommendation
- research_recommendation=cash_buffer_branch_default
- research_recommendation=tech_pullback_cash_buffer
- selected_refined_default=cash_buffer_a__hb10__base__adv50
- keep_parallel_branch=True
- role_vs_qqq_plus_current_default=并行分支
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"current_default": "coherent_cash_buffer_branch",
"selected_refined_default": "cash_buffer_a__hb10__base__adv50",
"full_deployment_reference": "coherent_full_deployment_branch",
"research_recommendation": "cash_buffer_branch_default",
"research_recommendation": "tech_pullback_cash_buffer",
"keep_parallel_branch": true,
"role_vs_qqq_plus_current_default": "并行分支",
"reason": "a refined honest cash-buffer candidate is strong enough to act as the branch default, but the spec is not fully frozen yet",
Expand Down
Loading