Skip to content

Commit 07b38a0

Browse files
committed
Rename IBKR research modules to canonical strategy names
1 parent d306f81 commit 07b38a0

14 files changed

Lines changed: 53 additions & 94 deletions

research/README_stock_alpha.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
1. 复用 `russell_1000_multi_factor_defensive` 的现有真实数据代理回测结果。
66
2. 公平比较它和两个高弹性 ETF 策略:
7-
- `hybrid_growth_income`
8-
- `semiconductor_rotation_income`
7+
- `tqqq_growth_income`
8+
- `soxl_soxx_trend_income`
99
3. 研究一个新的 price-only 进攻型个股候选:`qqq_plus_stock_alpha_v1`
1010

1111
## 代码位置

research/backtest_tech_pullback_cash_buffer_daily_overlay.py renamed to research/backtest_qqq_tech_enhancement_daily_overlay.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
"""Research-only daily overlay backtest for tech_pullback_cash_buffer."""
2+
"""Research-only daily overlay backtest for qqq_tech_enhancement."""
33

44
from __future__ import annotations
55

@@ -107,7 +107,7 @@ def load_baseline_spec(path: Path) -> BaselineSpec:
107107
return BaselineSpec(
108108
config=config,
109109
risk_on_exposure=float(payload["exposures"]["risk_on"]),
110-
name="tech_pullback_cash_buffer_monthly_baseline",
110+
name="qqq_tech_enhancement_monthly_baseline",
111111
config_payload=payload,
112112
)
113113

@@ -117,7 +117,7 @@ def build_baseline_candidate(spec: BaselineSpec) -> v12.GeometryCandidate:
117117
spec.name,
118118
spec.config,
119119
risk_on_exposure=spec.risk_on_exposure,
120-
note="Monthly baseline from canonical tech_pullback_cash_buffer; month-start chooses names, non-rebalance days are no-op.",
120+
note="Monthly baseline from canonical qqq_tech_enhancement; month-start chooses names, non-rebalance days are no-op.",
121121
)
122122

123123

@@ -901,7 +901,7 @@ def write_summary_markdown(
901901
best_name = family_scores.loc[family_scores["family"] == "daily_name_level_trim_overlay"].head(1)
902902
best_combo = family_scores.loc[family_scores["family"] == "daily_portfolio_plus_name_overlay"].head(1)
903903
lines = [
904-
"# cash_buffer_branch daily overlay research",
904+
"# qqq_tech_enhancement daily overlay research",
905905
"",
906906
"## Baseline",
907907
f"- strategy={baseline_payload['name']}",

research/backtest_stock_alpha_suite.py

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
"""
33
Cross-strategy comparison for:
44
- russell_1000_multi_factor_defensive
5-
- hybrid_growth_income
6-
- semiconductor_rotation_income
5+
- tqqq_growth_income
6+
- soxl_soxx_trend_income
77
- qqq_plus_stock_alpha_v1 (research-only)
88
99
The script keeps the defensive baseline frozen, compares it fairly against the
@@ -62,14 +62,14 @@
6262

6363

6464
SAFE_HAVEN = "BOXX"
65-
HYBRID_SAFE_CASH = "CASH"
65+
TQQQ_GROWTH_SAFE_CASH = "CASH"
6666
OFFENSIVE_NAME = "qqq_plus_stock_alpha_v1"
6767

6868
DEFENSIVE_NAME = "russell_1000_multi_factor_defensive"
69-
HYBRID_FULL_NAME = "hybrid_growth_income"
70-
HYBRID_NORMALIZED_NAME = "hybrid_growth_income_no_income"
71-
SEMICONDUCTOR_FULL_NAME = "semiconductor_rotation_income"
72-
SEMICONDUCTOR_NORMALIZED_NAME = "semiconductor_rotation_income_no_income"
69+
TQQQ_GROWTH_FULL_NAME = "tqqq_growth_income"
70+
TQQQ_GROWTH_NORMALIZED_NAME = "tqqq_growth_income_no_income"
71+
SOXL_SOXX_TREND_FULL_NAME = "soxl_soxx_trend_income"
72+
SOXL_SOXX_TREND_NORMALIZED_NAME = "soxl_soxx_trend_income_no_income"
7373

7474
FULL_COMPARISON_LAYER = "full_strategy"
7575
NORMALIZED_COMPARISON_LAYER = "normalized"
@@ -573,7 +573,7 @@ def build_period_summary_rows(
573573
benchmark_returns,
574574
start=start,
575575
end=end,
576-
safe_haven_symbols=(SAFE_HAVEN, HYBRID_SAFE_CASH),
576+
safe_haven_symbols=(SAFE_HAVEN, TQQQ_GROWTH_SAFE_CASH),
577577
full_returns_reference=net_returns,
578578
)
579579
rows.append(
@@ -620,7 +620,7 @@ def compute_rolling_36m_capm_alpha(strategy_returns: pd.Series, benchmark_return
620620
)
621621

622622

623-
def run_hybrid_growth_income_backtest(
623+
def run_tqqq_growth_income_backtest(
624624
qqq_ohlc: pd.DataFrame,
625625
asset_returns: pd.DataFrame,
626626
*,
@@ -643,12 +643,12 @@ def run_hybrid_growth_income_backtest(
643643
entry_line_floor: float,
644644
entry_line_cap: float,
645645
) -> tuple[pd.Series, pd.DataFrame, pd.Series]:
646-
strategy_symbols = ["TQQQ", SAFE_HAVEN, "SPYI", "QQQI", HYBRID_SAFE_CASH]
646+
strategy_symbols = ["TQQQ", SAFE_HAVEN, "SPYI", "QQQI", TQQQ_GROWTH_SAFE_CASH]
647647
index = asset_returns.index.intersection(qqq_ohlc.index)
648648
qqq_history = qqq_ohlc.loc[index].copy()
649649
returns = asset_returns.reindex(index).fillna(0.0)
650650
weights_history = pd.DataFrame(0.0, index=index, columns=strategy_symbols)
651-
portfolio_returns = pd.Series(0.0, index=index, name=HYBRID_FULL_NAME)
651+
portfolio_returns = pd.Series(0.0, index=index, name=TQQQ_GROWTH_FULL_NAME)
652652
turnover_history = pd.Series(0.0, index=index, name="turnover")
653653

654654
current_weights: dict[str, float] = {SAFE_HAVEN: 1.0}
@@ -712,15 +712,15 @@ def run_hybrid_growth_income_backtest(
712712
SAFE_HAVEN: target_boxx_value,
713713
"SPYI": target_spyi_value,
714714
"QQQI": target_qqqi_value,
715-
HYBRID_SAFE_CASH: reserved,
715+
TQQQ_GROWTH_SAFE_CASH: reserved,
716716
}
717717
target_weights = {
718718
symbol: value / current_equity
719719
for symbol, value in target_values.items()
720720
if value > 1e-12 and current_equity > 0
721721
}
722722
if not target_weights:
723-
target_weights = {HYBRID_SAFE_CASH: 1.0}
723+
target_weights = {TQQQ_GROWTH_SAFE_CASH: 1.0}
724724

725725
threshold_weight = rebalance_threshold_ratio
726726
rebalance_needed = any(
@@ -738,7 +738,7 @@ def run_hybrid_growth_income_backtest(
738738
portfolio_returns.at[next_date] = sum(
739739
weight * float(next_returns.get(symbol, 0.0))
740740
for symbol, weight in current_weights.items()
741-
if symbol != HYBRID_SAFE_CASH
741+
if symbol != TQQQ_GROWTH_SAFE_CASH
742742
)
743743
current_equity *= 1.0 + float(portfolio_returns.at[next_date])
744744

@@ -748,7 +748,7 @@ def run_hybrid_growth_income_backtest(
748748
return portfolio_returns, weights_history, turnover_history
749749

750750

751-
def run_semiconductor_rotation_income_backtest(
751+
def run_soxl_soxx_trend_income_backtest(
752752
soxl_prices: pd.Series,
753753
asset_returns: pd.DataFrame,
754754
*,
@@ -772,7 +772,7 @@ def run_semiconductor_rotation_income_backtest(
772772
soxl_series = soxl_prices.loc[index].copy()
773773
returns = asset_returns.reindex(index).fillna(0.0)
774774
weights_history = pd.DataFrame(0.0, index=index, columns=strategy_symbols)
775-
portfolio_returns = pd.Series(0.0, index=index, name=SEMICONDUCTOR_FULL_NAME)
775+
portfolio_returns = pd.Series(0.0, index=index, name=SOXL_SOXX_TREND_FULL_NAME)
776776
turnover_history = pd.Series(0.0, index=index, name="turnover")
777777

778778
current_weights: dict[str, float] = {SAFE_HAVEN: 1.0}
@@ -1367,7 +1367,7 @@ def build_strategy_runs(
13671367
).reindex(master_index)
13681368
soxl_close = etf_frames["close"]["SOXL"].reindex(master_index)
13691369

1370-
hybrid_full_returns, hybrid_full_weights, hybrid_full_turnover = run_hybrid_growth_income_backtest(
1370+
hybrid_full_returns, hybrid_full_weights, hybrid_full_turnover = run_tqqq_growth_income_backtest(
13711371
qqq_ohlc,
13721372
etf_returns_matrix,
13731373
starting_equity=full_account_equity,
@@ -1389,7 +1389,7 @@ def build_strategy_runs(
13891389
entry_line_floor=1.02,
13901390
entry_line_cap=1.08,
13911391
)
1392-
hybrid_normalized_returns, hybrid_normalized_weights, hybrid_normalized_turnover = run_hybrid_growth_income_backtest(
1392+
hybrid_normalized_returns, hybrid_normalized_weights, hybrid_normalized_turnover = run_tqqq_growth_income_backtest(
13931393
qqq_ohlc,
13941394
etf_returns_matrix,
13951395
starting_equity=normalized_account_equity,
@@ -1412,7 +1412,7 @@ def build_strategy_runs(
14121412
entry_line_cap=1.08,
14131413
)
14141414

1415-
semiconductor_full_returns, semiconductor_full_weights, semiconductor_full_turnover = run_semiconductor_rotation_income_backtest(
1415+
semiconductor_full_returns, semiconductor_full_weights, semiconductor_full_turnover = run_soxl_soxx_trend_income_backtest(
14161416
soxl_close,
14171417
etf_returns_matrix,
14181418
starting_equity=full_account_equity,
@@ -1430,7 +1430,7 @@ def build_strategy_runs(
14301430
income_layer_qqqi_weight=0.70,
14311431
income_layer_spyi_weight=0.30,
14321432
)
1433-
semiconductor_normalized_returns, semiconductor_normalized_weights, semiconductor_normalized_turnover = run_semiconductor_rotation_income_backtest(
1433+
semiconductor_normalized_returns, semiconductor_normalized_weights, semiconductor_normalized_turnover = run_soxl_soxx_trend_income_backtest(
14341434
soxl_close,
14351435
etf_returns_matrix,
14361436
starting_equity=normalized_account_equity,
@@ -1524,17 +1524,17 @@ def build_strategy_runs(
15241524
strategy_runs = [
15251525
defensive_run,
15261526
StrategyRun(
1527-
strategy_name=HYBRID_FULL_NAME,
1528-
display_name="hybrid_growth_income::full",
1527+
strategy_name=TQQQ_GROWTH_FULL_NAME,
1528+
display_name="tqqq_growth_income::full",
15291529
comparison_layer=FULL_COMPARISON_LAYER,
15301530
gross_returns=hybrid_full_returns,
15311531
weights_history=hybrid_full_weights,
15321532
turnover_history=hybrid_full_turnover,
15331533
metadata={"research_group": "etf_full", "starting_equity": full_account_equity, "income_layer": "on"},
15341534
),
15351535
StrategyRun(
1536-
strategy_name=SEMICONDUCTOR_FULL_NAME,
1537-
display_name="semiconductor_rotation_income::full",
1536+
strategy_name=SOXL_SOXX_TREND_FULL_NAME,
1537+
display_name="soxl_soxx_trend_income::full",
15381538
comparison_layer=FULL_COMPARISON_LAYER,
15391539
gross_returns=semiconductor_full_returns,
15401540
weights_history=semiconductor_full_weights,
@@ -1552,17 +1552,17 @@ def build_strategy_runs(
15521552
metadata={"research_group": "baseline", "group_normalization": "sector", "data_assumption": "official_monthly_v2_alias"},
15531553
),
15541554
StrategyRun(
1555-
strategy_name=HYBRID_NORMALIZED_NAME,
1556-
display_name="hybrid_growth_income::no_income",
1555+
strategy_name=TQQQ_GROWTH_NORMALIZED_NAME,
1556+
display_name="tqqq_growth_income::no_income",
15571557
comparison_layer=NORMALIZED_COMPARISON_LAYER,
15581558
gross_returns=hybrid_normalized_returns,
15591559
weights_history=hybrid_normalized_weights,
15601560
turnover_history=hybrid_normalized_turnover,
15611561
metadata={"research_group": "etf_normalized", "starting_equity": normalized_account_equity, "income_layer": "off"},
15621562
),
15631563
StrategyRun(
1564-
strategy_name=SEMICONDUCTOR_NORMALIZED_NAME,
1565-
display_name="semiconductor_rotation_income::no_income",
1564+
strategy_name=SOXL_SOXX_TREND_NORMALIZED_NAME,
1565+
display_name="soxl_soxx_trend_income::no_income",
15661566
comparison_layer=NORMALIZED_COMPARISON_LAYER,
15671567
gross_returns=semiconductor_normalized_returns,
15681568
weights_history=semiconductor_normalized_weights,

research/backtest_stock_alpha_v1_robustness.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ def evaluate_period_metrics(
583583
benchmark_returns,
584584
start=start,
585585
end=end,
586-
safe_haven_symbols=(suite.SAFE_HAVEN, suite.HYBRID_SAFE_CASH),
586+
safe_haven_symbols=(suite.SAFE_HAVEN, suite.TQQQ_GROWTH_SAFE_CASH),
587587
full_returns_reference=net_returns,
588588
)
589589
period_returns = slice_series_or_frame(net_returns, start, end).dropna()

research/backtest_hybrid_growth_continuous_scaling_followup.py renamed to research/backtest_tqqq_growth_continuous_scaling_followup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
if str(CURRENT_DIR) not in sys.path:
1818
sys.path.insert(0, str(CURRENT_DIR))
1919

20-
import backtest_hybrid_growth_indicator_variants as base # noqa: E402
20+
import backtest_tqqq_growth_indicator_variants as base # noqa: E402
2121
import backtest_stock_alpha_suite as suite # noqa: E402
2222
from us_equity_strategies.strategies.tqqq_growth_income import get_hybrid_allocation # noqa: E402
2323

research/backtest_hybrid_growth_idle_throttle_followup.py renamed to research/backtest_tqqq_growth_idle_throttle_followup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
if str(CURRENT_DIR) not in sys.path:
1717
sys.path.insert(0, str(CURRENT_DIR))
1818

19-
import backtest_hybrid_growth_indicator_variants as base # noqa: E402
19+
import backtest_tqqq_growth_indicator_variants as base # noqa: E402
2020
import backtest_stock_alpha_suite as suite # noqa: E402
2121
from us_equity_strategies.strategies.tqqq_growth_income import get_hybrid_allocation # noqa: E402
2222

research/backtest_hybrid_growth_indicator_variants.py renamed to research/backtest_tqqq_growth_indicator_variants.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
"""Research-only TQQQ / hybrid_growth_income indicator overlay study.
2+
"""Research-only TQQQ / tqqq_growth_income indicator overlay study.
33
44
Goal:
55
- keep the current hybrid growth attack logic as the baseline;
@@ -521,7 +521,7 @@ def build_summary_rows(strategy_runs: list[StrategyRun], benchmark_returns: pd.S
521521

522522

523523
def build_runtime_full_reference(qqq_ohlc: pd.DataFrame, asset_returns: pd.DataFrame) -> StrategyRun:
524-
full_returns, full_weights, full_turnover = suite.run_hybrid_growth_income_backtest(
524+
full_returns, full_weights, full_turnover = suite.run_tqqq_growth_income_backtest(
525525
qqq_ohlc,
526526
asset_returns,
527527
starting_equity=RUNTIME_FULL_STARTING_EQUITY,
@@ -545,8 +545,8 @@ def build_runtime_full_reference(qqq_ohlc: pd.DataFrame, asset_returns: pd.DataF
545545
)
546546
index = full_returns.index
547547
return StrategyRun(
548-
strategy_name="hybrid_growth_income_runtime_full_reference",
549-
display_name="hybrid_growth_income_runtime_full_reference",
548+
strategy_name="tqqq_growth_income_runtime_full_reference",
549+
display_name="tqqq_growth_income_runtime_full_reference",
550550
gross_returns=full_returns,
551551
weights_history=full_weights.reindex(index).fillna(0.0),
552552
turnover_history=full_turnover.reindex(index).fillna(0.0),
@@ -652,12 +652,12 @@ def build_markdown(summary: pd.DataFrame, recommendation: dict[str, object]) ->
652652
runtime_reference = focus.loc[focus["income_mode"] == "runtime_full_reference"].copy()
653653

654654
lines = [
655-
"# TQQQ / hybrid_growth_income indicator overlay review",
655+
"# TQQQ / tqqq_growth_income indicator overlay review",
656656
"",
657657
"## Setup",
658658
"- Baseline signal: current QQQ MA200 + ATR staged TQQQ logic.",
659659
"- Main research set: attack-only normalization (`income_threshold_usd = 1e9`) so the idle-asset choice is visible.",
660-
"- Runtime reference: current full `hybrid_growth_income` with income layer on.",
660+
"- Runtime reference: current full `tqqq_growth_income` with income layer on.",
661661
"- Idle asset candidates: `CASH`, `BOXX`, `QQQ`.",
662662
"- Extra indicator gates are nested from simple to complex to avoid blind indicator stuffing.",
663663
"",

research/backtest_hybrid_growth_position_scaling_followup.py renamed to research/backtest_tqqq_growth_position_scaling_followup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
"""Focused TQQQ position-scaling study on top of the existing hybrid attack sleeve."""
2+
"""Focused TQQQ position-scaling study on top of the existing TQQQ growth sleeve."""
33

44
from __future__ import annotations
55

@@ -17,7 +17,7 @@
1717
if str(CURRENT_DIR) not in sys.path:
1818
sys.path.insert(0, str(CURRENT_DIR))
1919

20-
import backtest_hybrid_growth_indicator_variants as base # noqa: E402
20+
import backtest_tqqq_growth_indicator_variants as base # noqa: E402
2121
import backtest_stock_alpha_suite as suite # noqa: E402
2222
from us_equity_strategies.strategies.tqqq_growth_income import get_hybrid_allocation # noqa: E402
2323

research/configs/growth_pullback_tech_pullback_cash_buffer.json

Lines changed: 0 additions & 41 deletions
This file was deleted.

tests/test_tech_pullback_cash_buffer_daily_overlay.py renamed to tests/test_qqq_tech_enhancement_daily_overlay.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
def load_module():
9-
path = Path(__file__).resolve().parents[1] / 'research' / 'backtest_tech_pullback_cash_buffer_daily_overlay.py'
9+
path = Path(__file__).resolve().parents[1] / 'research' / 'backtest_qqq_tech_enhancement_daily_overlay.py'
1010
spec = importlib.util.spec_from_file_location('backtest_cash_buffer_branch_daily_overlay_test', path)
1111
module = importlib.util.module_from_spec(spec)
1212
sys.modules[spec.name] = module
@@ -71,7 +71,7 @@ def test_result_export_not_empty(tmp_path):
7171
module = load_module()
7272
summary_path = tmp_path / 'summary.md'
7373
baseline_payload = {
74-
'name': 'tech_pullback_cash_buffer',
74+
'name': 'qqq_tech_enhancement',
7575
'family': 'tech_heavy_pullback',
7676
'exposures': {'risk_on': 0.8, 'soft_defense': 0.6, 'hard_defense': 0.0},
7777
'holdings_count': 8,
@@ -81,7 +81,7 @@ def test_result_export_not_empty(tmp_path):
8181
'benchmark_symbol': 'QQQ',
8282
}
8383
baseline_row = pd.Series({
84-
'strategy': 'tech_pullback_cash_buffer_monthly_baseline',
84+
'strategy': 'qqq_tech_enhancement_monthly_baseline',
8585
'CAGR': 0.25,
8686
'Max Drawdown': -0.2,
8787
'Information Ratio vs QQQ': 0.8,
@@ -129,4 +129,4 @@ def test_result_export_not_empty(tmp_path):
129129
)
130130

131131
assert summary_path.exists() and summary_path.stat().st_size > 0
132-
assert 'cash_buffer_branch daily overlay research' in summary_path.read_text(encoding='utf-8')
132+
assert 'qqq_tech_enhancement daily overlay research' in summary_path.read_text(encoding='utf-8')

0 commit comments

Comments
 (0)