Skip to content

Commit eaf4c92

Browse files
authored
Merge pull request #205 from QuantStrategyLab/codex/us-lifecycle-gates-20260706
Align US strategy lifecycle gates
2 parents 8c0dbac + 58d2f00 commit eaf4c92

6 files changed

Lines changed: 34 additions & 10 deletions

File tree

docs/us_equity_strategy_status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ See the Chinese handbook for localized positioning text and default parameter ta
3737

3838
| Profile / track | Status | Why not live |
3939
| --- | --- | --- |
40-
| `tecl_xlk_trend_income` | `research_enabled` | Failed promotion versus live TQQQ and SOXL on overlapping windows (2024+ CAGR 24.8%, max drawdown -46.0%). Code and backtest tooling retained. Docs: [`UsEquitySnapshotPipelines/docs/tecl-xlk-optimization-research.md`](../../UsEquitySnapshotPipelines/docs/tecl-xlk-optimization-research.md). |
40+
| `tecl_xlk_trend_income` | `research_backtest_only` | Failed promotion versus live TQQQ and SOXL on overlapping windows (2024+ CAGR 24.8%, max drawdown -46.0%). Code and backtest tooling retained. Docs: [`UsEquitySnapshotPipelines/docs/tecl-xlk-optimization-research.md`](../../UsEquitySnapshotPipelines/docs/tecl-xlk-optimization-research.md). |
4141
| `tech_communication_pullback_enhancement` | research_only | Underperforms `russell_top50_leader_rotation` on return and drawdown. |
4242
| `QQQ` / `SPY` LEAPS growth overlay | research_only | Design: [`research/index_leaps_growth_overlay.md`](./research/index_leaps_growth_overlay.md). Proxy backtest module: `UsEquitySnapshotPipelines/docs/index-leaps-growth-overlay-research.md`. |
4343
| `us_equity_combo_leveraged_shadow_352045` | shadow_candidate | TQQQ 35% / SOXL 20% / BOXX 45% with `hard_defense_risk_exposure=0.0`. Backtest evidence: 22.21% CAGR, -33.88% max drawdown over 2010-01-05 to 2026-07-02 with 5 bps cost. Runtime config: `src/us_equity_strategies/configs/us_equity_combo_leveraged_shadow_352045.json`. |

docs/us_equity_strategy_status.zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ _更新日期:2026-06-28_
8282

8383
| 研究方向 | 当前状态 | 不直接部署的原因 |
8484
| --- | --- | --- |
85-
| `tecl_xlk_trend_income` | `research_enabled` | 重叠窗口未跑赢 live TQQQ / SOXL(2024+ CAGR 24.8%,最大回撤 -46.0%);保留策略实现与回测入口,不进入 runtime。研究文档见 [`UsEquitySnapshotPipelines/docs/tecl-xlk-optimization-research.zh-CN.md`](../../UsEquitySnapshotPipelines/docs/tecl-xlk-optimization-research.zh-CN.md)|
85+
| `tecl_xlk_trend_income` | `research_backtest_only` | 重叠窗口未跑赢 live TQQQ / SOXL(2024+ CAGR 24.8%,最大回撤 -46.0%);保留策略实现与回测入口,不进入 runtime。研究文档见 [`UsEquitySnapshotPipelines/docs/tecl-xlk-optimization-research.zh-CN.md`](../../UsEquitySnapshotPipelines/docs/tecl-xlk-optimization-research.zh-CN.md)|
8686
| `us_equity_combo_leveraged_shadow_352045` | shadow candidate | 组合型杠杆候选:`TQQQ 35% / SOXL 20% / BOXX 45%`,risk-off 归零风险腿转 `BOXX`。当前仅提供受控 runtime_config 与测试保护,不改变默认 live;需要平台 dry-run / shadow 周期证据后才可重新评估 live。 |
8787
| `us_equity_combo_leveraged_shadow_402040` | shadow candidate | 组合型杠杆候选:`TQQQ 40% / SOXL 20% / BOXX 40%`,risk-off 归零风险腿转 `BOXX`。这是 35%-40% 回撤预算下的下一轮 shadow 配置,不直接替换 live。 |
8888
| `crisis_response_shadow` 插件 | 可作为 `tqqq_growth_income``shadow` 插件候选,只写信号、日志和通知上下文。 | 现在是 defense-only 黑天鹅观察流,不下单、不改 allocation;需要稳定 shadow 日志后再做 evidence review。 |

src/us_equity_strategies/catalog.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ def _build_strategy_definition(
595595
asset_scope="technology_etf_plus_income",
596596
benchmark="XLK",
597597
role="sector_offensive_income",
598-
status="research_enabled",
598+
status="research_backtest_only",
599599
),
600600
RUSSELL_TOP50_LEADER_ROTATION_PROFILE: StrategyMetadata(
601601
canonical_profile=RUSSELL_TOP50_LEADER_ROTATION_PROFILE,
@@ -637,15 +637,15 @@ def _build_strategy_definition(
637637
canonical_profile=US_EQUITY_COMBO_PROFILE,
638638
display_name="US Equity Combo",
639639
description=(
640-
"Live US core combo: Russell Top50 leaders (40%) + Nasdaq/S&P ETF "
640+
"Shadow/internal US core combo: Russell Top50 leaders (40%) + Nasdaq/S&P ETF "
641641
"sleeve (40%) + BOXX cash defense (20%), with dynamic defense weights."
642642
),
643643
aliases=(),
644644
cadence="monthly review",
645645
asset_scope="us_equity_combo",
646646
benchmark="SPY",
647647
role="us_equity_combo",
648-
status="runtime_enabled",
648+
status="shadow_candidate",
649649
),
650650
US_EQUITY_COMBO_CORE_PROFILE: StrategyMetadata(
651651
canonical_profile=US_EQUITY_COMBO_CORE_PROFILE,
@@ -666,15 +666,15 @@ def _build_strategy_definition(
666666
canonical_profile=US_EQUITY_COMBO_LEVERAGED_PROFILE,
667667
display_name="US Equity Combo Leveraged",
668668
description=(
669-
"Leveraged US combo: TQQQ (40%) + SOXL (20%) + BOXX (40%) with "
669+
"Shadow leveraged US combo: TQQQ (40%) + SOXL (20%) + BOXX (40%) with "
670670
"SPY MA200 dynamic risk-off cut."
671671
),
672672
aliases=(),
673673
cadence="daily review",
674674
asset_scope="us_equity_combo_leveraged",
675675
benchmark="SPY",
676676
role="us_equity_combo_leveraged",
677-
status="runtime_enabled",
677+
status="shadow_candidate",
678678
),
679679
}
680680

src/us_equity_strategies/runtime_adapters.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@
108108
available_capabilities=frozenset({"fractional_share_execution"}),
109109
runtime_policy=StrategyRuntimePolicy(signal_effective_after_trading_days=0),
110110
),
111+
}
112+
113+
# Shadow/internal combo profiles remain describable for runtime contract and
114+
# test coverage, but they are not treated as base live adapters.
115+
SHADOW_RUNTIME_ADAPTERS: dict[str, StrategyRuntimeAdapter] = {
111116
US_EQUITY_COMBO_PROFILE: StrategyRuntimeAdapter(
112117
status_icon="\U0001f1fa\U0001f1f8",
113118
available_inputs=frozenset({"russell_snapshot", "current_holdings"}),
@@ -183,6 +188,18 @@ def _build_platform_runtime_adapter_map(platform_id: str) -> dict[str, StrategyR
183188
profile,
184189
platform_id=normalized_platform,
185190
)
191+
for profile, base_adapter in SHADOW_RUNTIME_ADAPTERS.items():
192+
definition = get_strategy_definition(profile)
193+
if normalized_platform not in definition.supported_platforms:
194+
continue
195+
adapters[profile] = validate_strategy_runtime_adapter(
196+
replace(
197+
base_adapter,
198+
available_inputs=frozenset(base_adapter.available_inputs or definition.required_inputs),
199+
portfolio_input_name=base_adapter.portfolio_input_name
200+
or ("portfolio_snapshot" if "portfolio_snapshot" in definition.required_inputs else None),
201+
)
202+
)
186203
return adapters
187204

188205

tests/test_catalog.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,14 @@ def test_metadata_map_exposes_display_names_and_roles(self):
264264
metadata_map[US_EQUITY_COMBO_CORE_PROFILE].status,
265265
"shadow_candidate",
266266
)
267+
self.assertEqual(
268+
metadata_map[US_EQUITY_COMBO_PROFILE].status,
269+
"shadow_candidate",
270+
)
271+
self.assertEqual(
272+
metadata_map[US_EQUITY_COMBO_LEVERAGED_PROFILE].status,
273+
"shadow_candidate",
274+
)
267275

268276
def test_option_overlay_defaults_are_enabled_but_live_gated(self):
269277
live_option_profiles = (
@@ -514,8 +522,6 @@ def test_runtime_enabled_profiles_are_the_live_catalog(self):
514522
RUSSELL_TOP50_LEADER_ROTATION_PROFILE,
515523
NASDAQ_SP500_SMART_DCA_PROFILE,
516524
IBIT_SMART_DCA_PROFILE,
517-
US_EQUITY_COMBO_PROFILE,
518-
US_EQUITY_COMBO_LEVERAGED_PROFILE,
519525
}
520526
),
521527
)

tests/test_contract_governance.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
LONGBRIDGE_PLATFORM,
1818
PAPER_SIGNAL_PLATFORM,
1919
PLATFORM_RUNTIME_ADAPTERS,
20+
SHADOW_RUNTIME_ADAPTERS,
2021
SCHWAB_PLATFORM,
2122
FIRSTRADE_PLATFORM,
2223
get_platform_runtime_adapter,
@@ -155,7 +156,7 @@ def test_runtime_adapter_map_matches_catalog_compatibility(self) -> None:
155156
supported_profiles = frozenset(
156157
profile
157158
for profile, platforms in compatibility_map.items()
158-
if platform_id in platforms and profile in BASE_RUNTIME_ADAPTERS
159+
if platform_id in platforms and profile in (BASE_RUNTIME_ADAPTERS | SHADOW_RUNTIME_ADAPTERS)
159160
)
160161
with self.subTest(platform_id=platform_id):
161162
self.assertEqual(frozenset(adapters), supported_profiles)

0 commit comments

Comments
 (0)