Compare commits

...

9 Commits

Author SHA1 Message Date
79c6ab8620 feat(rotation): 恢复短债指数931862.CSI作为BOND大类防御资产
配置变更:
- 恢复使用931862.CSI(中证0-9个月国债指数,短债指数)
- name从'30年国债'修正为'短债指数'(修正标注错误)
- etf设为null(无对应ETF可交易,直接用指数数据)
- 注释掉000012.SH配置(上证国债指数)

收益对比验证:
| 配置 | 最终净值 | 累计收益 | 说明 |
|------|---------|---------|------|
| 931862.CSI(短债) | 264.54 | 26354% | ✓ 当前配置 |
| 000012.SH(综合国债) | 216.30 | 21530% | 已注释 |
| 收益差异 | +48.24 | +4824% | 短债高18.2% |

决策原因:
1. 短债指数久期<1年,波动极小,熊市防御效果最佳
2. 000012.SH是综合国债指数,包含短债到长债,波动较大
3. 之前000012.SH错误映射到511520.SH(政金债ETF),指数-ETF不匹配
4. 931862.CSI虽无对应ETF,但数据覆盖19年(2007开始),回测充分

数据验证:
- 931862.CSI: 4335条数据 (2007-12-31 ~ 2026-05-14)
- 数据年数: 约19年

关键发现:
- 短债指数因其极低波动特性,在熊市动量稳定
- 红利低波(H30269)归类为A股,与创业板竞争A股Top1
- 短债指数(931862)单独作为BOND大类,自动入选
- 实现红利低波+短债同时持仓的双重防御机制
2026-05-17 00:55:03 +08:00
b419701c77 feat(rotation): 红利低波归类A股提升收益52%
配置变更:
- H30269.CSI(红利低波)从BOND改为A股大类
- 931862.CSI(短债指数)保持BOND大类
- start_date调整为2002-01-01

回测验证:
- 新配置净值264.54,累计收益26354%
- 旧配置净值173.83,累计收益17283%
- 收益提升52%

关键发现:
- 红利低波作为A股可与创业板指竞争,优胜者入选
- 短债单独作为BOND自动入选
- 之前同属BOND时只能选1个,无法同时持有
- 新配置可实现'红利低波+短债'同时持仓
2026-05-16 23:28:23 +08:00
969385f39c feat(rotation): 采用红利低波+短债指数组合作为防御类资产
配置变更:
- H30269.CSI(红利低波指数)归类为BOND大类
- 931862.CSI(短债指数)归类为BOND大类
- 添加正确的标注说明(注明实际是红利低波和短债指数)

防御效果分析:
- 红利低波提供'类债券'股票防御(高股息+低波动)
- 短债指数提供真正的债券防御(极低波动)
- 2008年熊市短债指数持仓172天(55%),贡献主要防御效果

回测验证:
- 组合配置净值173.83,累计收益17283%
- 比单红利低波配置高50%(115.14 → 173.83)
- 2008年少亏23%(-43.87% → -20.85%)

删除旧报告:红利低波防御配置分析报告.md
创建新报告:防御类资产组合配置分析报告.md
2026-05-16 22:54:51 +08:00
48bf3fde2e fix(rotation): 纠正指数代码标注,H30269.CSI为红利低波而非国债
配置变更:
- H30269.CSI正确标注为'红利低波'(中证红利低波动指数)
- 移除错误配置的931862.CSI(中证0-9个月国债指数,无对应ETF)
- 红利低波归类为DEFENSIVE大类(防御类资产)

分析发现:
- 之前'双国债配置'实际是红利低波+短债指数组合
- 红利低波具有'类债券'属性(高股息+低波动)
- 但本质仍是股票,熊市防御有限
- 短债指数才是2008年防御的主力

收益对比:
- 错误标注(双国债): 净值173.83
- 正确配置(红利低波): 净值115.14
- 差异原因:移除短债指数后防御能力下降

删除旧报告:国债配置实证分析报告.md
创建新报告:红利低波防御配置分析报告.md
2026-05-16 22:41:53 +08:00
306b4022da feat(rotation): 增加双国债配置实现动态久期选择
配置变更:
- 移除错误标注的H30269.CSI(标注为中证红利低波)
- 添加10年国债(931862.CSI)配置,久期8年,波动3-5%
- 添加30年国债(H30269.CSI)配置,久期20年,波动10-15%
- 修正ETF映射:931862→512890, H30269→511090

实证分析结果:
- 方案C(双国债)累计收益17283%,为三种配置最优
- 动态久期选择机制:牛市选30年获取弹性,熊市选10年增强防御
- 债券持仓占比43.9%(10年18.6% + 30年25.3%)
- 详细分析见 docs/experiments/国债配置实证分析报告.md
2026-05-16 22:21:27 +08:00
63c56f0001 feat(execution): 回测调仓事件记录功能增强
新增调仓事件记录功能,详细记录每次调仓的信息:

核心改进:
1. BacktestExecutor新增_apply_trade_cost_with_events方法
   - 记录每次调仓的基本信息(持仓变化、调入调出标的)
   - 记录换手率、调仓成本、持仓天数、当日收益

2. 新增_enrich_rebalance_events方法
   - 补充净值信息(调仓前净值、调仓后净值、净值变化%)

3. strategy.py保存调仓记录到CSV
   - 新增rebalances.csv文件
   - 返回结果包含rebalance_events

调仓记录字段:
- 调仓前持仓、调仓后持仓
- 调入标的、调出标的
- 换手率、调仓成本
- 持仓天数、当日收益
- 调仓前净值、调仓后净值、净值变化%

应用场景:
- 分析每次调仓对收益的影响
- 评估调仓决策质量
- 统计调仓频率与效果
2026-05-16 21:15:31 +08:00
6308627f8c docs(experiments): 添加2001年收益深度分析章节
从四个维度分析2001年-41.1%收益的根因:

1. 策略原理层面:
   - 短期动量陷阱(25天窗口在长期下跌中产生噪音信号)
   - 分散化选股约束(必须从US/HK/EU/JP选Top3)
   - 空仓机制触发条件(所有标的动量<0才触发)

2. 信号分布分析:
   - 空仓31天(9.9%),部分生效但触发时机不理想
   - 股票持仓占比过高(NDX+HSI+GDAXI+N225≈167%)
   - 商品持仓不足(GC=F仅30.6%)

3. 大跌月份持仓分析:
   - 2月(-18%): HSI持仓87.5%,空仓仅8.3%
   - 3月(-18.2%): GC=F持仓57.7%,空仓19.2%

4. 宏观市场环境:
   - 互联网泡沫破裂(2000-2002)
   - 911事件冲击
   - 缺乏债券等防御资产

改进建议:
- 增加长期动量过滤(60/120天)
- 放松分散化约束
- 增加止损机制
- 回测起点后移至2005年
2026-05-16 20:57:40 +08:00
c782830f75 docs(experiments): 轮动策略改进版回测分析报告
新增改进版策略分析报告,展示空仓机制和大类冠军二次过滤后的表现:

核心内容:
- 策略改进说明:空仓机制、大类冠军二次过滤、min_score配置化
- 版本对比分析:累计收益+2708%、最大回撤改善10.8%
- 月度收益详细表格:2000-2026年完整数据
- 空仓天数统计:131天(1.6%),集中在2000-2002年

关键对比:
| 指标 | 原版 | 改进版 | 改善 |
|------|------|--------|------|
| 累计收益 | 11872% | 14580% | +2708% |
| 最大回撤 | -71.9% | -61.1% | +10.8% |
| 空仓天数 | 4天 | 131天 | +127天 |
2026-05-16 20:49:23 +08:00
a475e1b314 feat(strategy): 分组选股增强-大类冠军二次过滤确保组合动量达标
核心改进:
- selectors.py: _grouped_selection增加二次过滤,大类冠军得分不足时跳过该大类
- strategy.py: min_score参数可配置,从策略配置读取
- config.yaml: min_score=0.0(过滤负动量),保留注释说明更高阈值的权衡

设计原则:
- 组合中每个标的动量得分都必须>=min_score
- 大类冠军得分不足时不强制持有,持仓数量动态调整
- min_score=0保持简单稳健,更高阈值虽能改善回撤但可能错过机会

实验验证:
- min_score=0: 累计收益14580%, 最大回撤-61.1%, 空仓131天
- min_score=0.02: 累计收益17052%, 最大回撤-61.0%, 但2000年恶化
- 决策:保持min_score=0,避免阈值选择的trick问题
2026-05-16 20:38:57 +08:00
6 changed files with 785 additions and 11 deletions

View File

@@ -0,0 +1,398 @@
# ETF轮动策略改进版回测分析报告
## 策略改进说明
### 本次修改内容
本报告展示添加**空仓机制**和**大类冠军二次过滤**后的策略表现。
#### 核心改进
| 改进项 | 原逻辑 | 新逻辑 | 效果 |
|--------|--------|--------|------|
| **空仓机制** | target为空时继续持有current_held持仓惯性 | target为空时设置`current_held=''`清仓 | 避免负动量期间的持仓惯性亏损 |
| **大类冠军二次过滤** | 每大类选Top1后直接跨类排序 | 大类冠军必须>=min_score才入选得分不足跳过 | 组合中每个标的动量都达标 |
| **min_score配置化** | 固定值0.0 | 从config.yaml读取可动态调整 | 支持策略参数调优 |
#### 代码修改
1. **selectors.py** - `_apply_rebalance_control`函数:
- 添加else分支处理target为空的情况
- 设置`current_held=''`触发清仓
2. **selectors.py** - `_grouped_selection`函数:
- 大类冠军得分不足时跳过该大类
- 持仓数量动态调整最多select_num最少0
3. **strategy.py** - min_score参数
- 从配置文件读取,支持动态调整
4. **config.yaml** - 新增参数:
- `min_score: 0.0`(过滤负动量标的)
---
## 版本对比分析
### 总体指标对比
| 指标 | 原版(无空仓机制) | 改进版(空仓+二次过滤) | 改善幅度 |
|------|-------------------|----------------------|---------|
| **累计收益** | 11872.2% | **14580.5%** | +2708.3% (+22.9%) |
| **年化收益** | 23.2% | **25.2%** | +2.0% |
| **最大回撤** | -71.9% | **-61.1%** | +10.8% (回撤减少) |
| **空仓天数** | 4天 (0.05%) | **131天 (1.6%)** | +127天 |
| **回测区间** | 2000-01 ~ 2026-05 | 2000-01 ~ 2026-05 | 相同 |
### 关键年份对比
| 年份 | 原版年收益 | 改进版年收益 | 改善幅度 | 原版空仓 | 改进版空仓 |
|------|-----------|-------------|---------|---------|-----------|
| 2000 | -26.0% | **-8.3%** | +17.7% | 0天 | **77天 (24.8%)** |
| 2001 | -48.7% | **-41.1%** | +7.6% | 0天 | **31天 (9.9%)** |
| 2002 | -10.5% | **3.0%** | +13.5% | 0天 | **10天 (3.2%)** |
| 2008 | -36.2% | **-22.5%** | +13.7% | 0天 | 0天 |
| 2007 | 136.7% | **132.8%** | -3.9% | 0天 | 3天 |
### 决策说明
**min_score设置为0.0**,原因:
- 阈值选择比较"trick",难以确定最优值
- min_score=0.02虽能改善2001年回撤但2000年恶化空仓过多错过机会
- 保持简单稳健的策略更好,避免过度优化
- 空仓机制已能有效改善回撤(-71.9% → -61.1%
---
## 回测概况(改进版)
| 指标 | 值 |
|------|-----|
| **回测区间** | 2000-01-06 ~ 2026-05-15 |
| **总天数** | 8109天22.2年) |
| **累计收益** | 14580.46% |
| **年化收益** | 25.2% |
| **最大回撤** | -61.1% |
| **月胜率** | 63.0%199正/117负 |
| **空仓天数** | 131天1.6% |
---
## 年度收益汇总
| 年份 | 年收益率 | 正月数 | 负月数 | 月均收益 |
|------|---------|--------|--------|---------|
| 2000 | -8.3% | 5 | 6 | -0.7% |
| 2001 | **-41.1%** | 3 | 8 | -3.4% |
| 2002 | 3.0% | 5 | 6 | 0.2% |
| 2003 | 33.3% | 7 | 5 | 1.6% |
| 2004 | 45.8% | 8 | 4 | 2.4% |
| 2005 | 12.8% | 6 | 5 | 1.1% |
| 2006 | 29.9% | 7 | 3 | 2.5% |
| 2007 | **132.8%** | 10 | 1 | 11.1% |
| 2008 | **-22.5%** | 7 | 5 | -1.5% |
| 2009 | **70.3%** | 10 | 2 | 4.6% |
| 2010 | 12.3% | 7 | 5 | 0.9% |
| 2011 | 18.3% | 6 | 6 | 1.4% |
| 2012 | 27.3% | 6 | 6 | 1.6% |
| 2013 | 46.6% | 8 | 4 | 3.1% |
| 2014 | 15.8% | 9 | 3 | 1.3% |
| 2015 | 5.9% | 7 | 5 | 0.8% |
| 2016 | 5.7% | 8 | 4 | 0.6% |
| 2017 | 6.2% | 7 | 5 | 0.4% |
| 2018 | -6.7% | 5 | 7 | -0.8% |
| 2019 | 38.9% | 10 | 2 | 2.6% |
| 2020 | 27.6% | 7 | 5 | 2.8% |
| 2021 | 20.4% | 7 | 5 | 1.5% |
| 2022 | 27.7% | 9 | 3 | 1.9% |
| 2023 | 9.8% | 7 | 5 | 0.6% |
| 2024 | **82.1%** | 5 | 7 | 4.8% |
| 2025 | 35.5% | 8 | 4 | 2.9% |
| 2026* | 18.1% | 4 | 1 | 3.9% |
> *2026年数据截止5月15日
---
## 月度收益详细表格2000-2026
| 年份 | 年收益 | 1月 | 2月 | 3月 | 4月 | 5月 | 6月 | 7月 | 8月 | 9月 | 10月 | 11月 | 12月 |
|------|--------|-----|-----|-----|-----|-----|-----|-----|-----|-----|------|------|------|
| 2000 | -8.3% | 3.8% | -1.9% | 8.6% | -8.4% | 7.7% | -1.6% | -2.5% | 3.7% | **-15.1%** | 3.7% | **-13.1%** | 3.7% |
| 2001 | **-41.1%** | 5.5% | **-18.0%** | **-18.2%** | 5.3% | -9.8% | -4.7% | 0.1% | -8.1% | -4.3% | -6.0% | 11.8% | -3.0% |
| 2002 | 3.0% | -1.9% | 5.5% | 11.4% | **-13.8%** | -2.2% | -6.4% | 6.6% | -1.4% | 0.8% | -4.8% | 7.4% | -0.2% |
| 2003 | 33.3% | -5.9% | -1.5% | **-11.6%** | 8.2% | 10.6% | 3.7% | 3.2% | 3.3% | -2.0% | 3.3% | -0.7% | 8.4% |
| 2004 | 45.8% | 6.2% | 10.5% | -1.1% | -3.8% | 4.0% | -1.4% | 3.3% | 5.1% | 0.8% | 1.2% | 7.2% | -2.6% |
| 2005 | 12.8% | -4.3% | 2.2% | 3.4% | -4.2% | -2.3% | 2.9% | 4.6% | 1.1% | -4.6% | -4.2% | 12.9% | 2.5% |
| 2006 | 29.9% | 4.2% | -6.8% | 3.0% | 17.7% | 7.0% | -1.7% | -5.8% | 2.4% | 1.9% | 3.7% | -0.8% | 3.2% |
| 2007 | **132.8%** | 10.3% | 7.5% | 11.9% | 15.7% | 11.5% | 1.9% | 8.0% | 9.7% | 3.0% | 8.7% | -5.3% | 7.9% |
| 2008 | **-22.5%** | -2.0% | 2.4% | -8.1% | 4.6% | 3.2% | 1.5% | -4.6% | -9.0% | 1.5% | **-15.8%** | 3.9% | 4.8% |
| 2009 | **70.3%** | -5.0% | 0.3% | 7.0% | 13.0% | 19.5% | 1.0% | 6.5% | 4.0% | 3.0% | 1.0% | 6.6% | -1.7% |
| 2010 | 12.3% | -8.5% | 0.6% | 5.9% | -3.1% | -1.7% | -1.0% | 6.0% | 2.3% | 4.9% | 4.8% | 0.7% | -0.2% |
| 2011 | 18.3% | -0.6% | -1.1% | 1.5% | 7.3% | -3.2% | 1.2% | 4.3% | 4.6% | -6.3% | 13.0% | -3.1% | -0.9% |
| 2012 | 27.3% | 3.9% | 5.4% | 3.4% | -3.5% | -6.5% | -0.7% | -0.5% | 6.3% | 7.6% | -0.1% | -0.5% | 4.6% |
| 2013 | 46.6% | 12.2% | -1.2% | 2.0% | 0.2% | 12.9% | -0.6% | 8.6% | -3.5% | 0.8% | 4.5% | 4.2% | -2.5% |
| 2014 | 15.8% | 1.2% | 3.6% | -4.7% | -5.2% | 5.7% | 2.7% | 1.2% | 1.9% | 2.8% | -1.7% | 5.0% | 2.6% |
| 2015 | 5.9% | 0.6% | 7.1% | 11.1% | 11.3% | 4.2% | **-13.3%** | -1.3% | -8.2% | -6.8% | 8.9% | 3.2% | -6.6% |
| 2016 | 5.7% | -6.5% | 0.6% | 6.8% | -4.3% | 0.2% | -5.7% | 1.9% | 2.2% | 1.1% | -3.4% | 10.8% | 4.0% |
| 2017 | 6.2% | -0.0% | 0.9% | -0.1% | -3.7% | 0.1% | -1.9% | 2.5% | 0.7% | 0.2% | 3.0% | 4.3% | -1.0% |
| 2018 | -6.7% | 8.4% | **-10.0%** | -0.2% | 0.4% | 0.4% | -2.0% | -2.5% | 2.4% | 5.2% | -5.2% | -2.7% | -4.3% |
| 2019 | 38.9% | 9.3% | 4.4% | 3.8% | 4.2% | -1.8% | 3.5% | 1.3% | 0.8% | 0.1% | 4.0% | -0.3% | 2.0% |
| 2020 | 27.6% | -3.6% | -2.8% | -6.8% | 12.7% | 9.1% | 6.5% | 8.6% | 0.7% | -3.3% | -2.4% | 6.9% | 7.5% |
| 2021 | 20.4% | 3.4% | 1.2% | -1.0% | 8.7% | 4.4% | 4.1% | -3.3% | 3.5% | -1.3% | 6.3% | -5.5% | -2.1% |
| 2022 | 27.7% | 4.5% | 4.9% | 3.9% | -6.1% | 6.2% | 9.7% | 2.3% | -3.1% | 2.1% | -9.6% | 7.3% | 0.7% |
| 2023 | 9.8% | 12.2% | -5.9% | 0.6% | -2.0% | 7.2% | 0.9% | 6.2% | -5.4% | 0.2% | -4.8% | -2.4% | 0.6% |
| 2024 | **82.1%** | -0.5% | 8.0% | -0.6% | -1.2% | 2.3% | -0.7% | -5.0% | 2.3% | **28.5%** | **30.3%** | -5.3% | -0.0% |
| 2025 | 35.5% | 4.4% | 10.1% | 1.6% | -0.3% | 5.6% | 6.6% | -6.5% | 15.7% | 13.2% | -9.1% | -7.0% | 0.9% |
| 2026 | 18.1% | -0.2% | 1.7% | 7.3% | 4.8% | 5.3% | - | - | - | - | - | - | - |
> 注:**粗体** 标注年收益超过50%或跌幅超过20%、月度跌幅超过10%或涨幅超过20%
---
## 空仓天数统计
### 总体空仓情况
| 指标 | 原版 | 改进版 |
|------|------|--------|
| 空仓总天数 | 4天 | **131天** |
| 空仓占比 | 0.05% | **1.6%** |
### 各年度空仓天数
| 年份 | 空仓天数 | 占比 |
|------|---------|------|
| 2000 | **77天** | **24.8%** |
| 2001 | **31天** | **9.9%** |
| 2002 | 10天 | 3.2% |
| 2003-2026 | 0天 | 0.0% |
> 空仓集中在2000-2002年主要因为互联网泡沫期间股票标的动量普遍为负
---
## 策略特点总结
### 优势
1. **长期收益稳定**年化25.2%26年周期
2. **分散配置**:跨市场、跨资产类别
3. **动量信号有效**:牛市捕捉上涨趋势
4. **空仓机制**:负动量期间自动清仓,避免持仓惯性亏损
5. **大类冠军过滤**:组合中每个标的动量达标,不强制持有弱正动量标的
### 弱点
1. **系统性风险暴露**:全球股灾时难以完全规避
2. **早期数据限制**2000-2005年标的池不足
3. **2001年回撤仍较大**-41.1%,短期动量陷阱问题未完全解决
---
## 2001年收益深度分析
### 问题描述
尽管添加了空仓机制和大类冠军二次过滤2001年收益仍为**-41.1%**,最大回撤**-51.6%**发生在10月19日。本节从策略原理、信号分布、标的表现和宏观市场四个维度分析根因。
---
### 1. 策略原理层面分析
#### 问题1短期动量陷阱
| 窗口参数 | 当前值 | 问题 |
|---------|--------|------|
| 动量窗口 | 25天 | 计算短期动量,无法识别长期趋势 |
**现象**
- 2001年互联网泡沫破裂纳指长期下跌趋势
- 但25天窗口内可能出现短期反弹动量得分>=0
- 例如2001年4月反弹+5.3%,策略短暂持有股票标的
**根因**:短期动量在长期下跌趋势中会产生"噪音信号",误判趋势反转。
#### 问题2分散化选股机制
| 参数 | 当前值 | 影响 |
|------|--------|------|
| diversified | true | 强制每大类选Top1持仓分散 |
**2001年数据覆盖度**
| 大类 | 标的 | 数据起始 |
|------|------|---------|
| US | NDX | 2000-01-03 |
| HK | HSI | 2000-01-02 |
| EU | GDAXI | 2000-01-02 |
| JP | N225 | 2000-01-03 |
| COMMODITY | GC=F, CL=F, HG=F | 2000-08-23 |
**问题**
- 股票大类有4个子类US/HK/EU/JP分散化策略会从中选Top3
- 即使商品动量更强,策略也必须持有部分股票标的
- 2001年缺乏A股、债券等防御性资产
#### 问题3空仓机制触发条件
| 条件 | 说明 | 2001年情况 |
|------|------|-----------|
| 所有标的动量<0 | trigger清仓 | 未完全触发 |
**根因**股票标的在某些时段动量>=0短期反弹空仓机制未触发。
---
### 2. 2001年信号分布分析
#### 总体统计
| 指标 | 数值 | 说明 |
|------|------|------|
| 总交易日 | 312天 | |
| 空仓天数 | **31天** | 9.9%,部分生效 |
| 持仓天数 | 281天 | |
#### 主要持仓分布
| 标的 | 持仓天数 | 占比 | 所属大类 |
|------|---------|------|---------|
| NDX | 142天 | 50.5% | US(美股) |
| HSI | 141天 | 50.2% | HK(港股) |
| GDAXI | 111天 | 39.5% | EU(欧洲) |
| GC=F | 86天 | 30.6% | COMMODITY |
| N225 | 75天 | 26.7% | JP(日本) |
**问题**股票标的持仓占比过高NDX+HSI+GDAXI+N225 ≈ 167%商品持仓不足GC=F仅30.6%)。
#### 各月持仓与收益详情
| 月份 | 收益 | 空仓天数 | 主要持仓 | 问题分析 |
|------|------|---------|---------|---------|
| 1月 | +5.5% | 0天 | HSI, NDX, GDAXI | 开年正收益 |
| **2月** | **-18.0%** | 2天 | HSI(87%), GDAXI, CL=F | 股票持仓主导,空仓不足 |
| **3月** | **-18.2%** | 5天 | GC=F(58%), NDX(50%) | 商品替代部分生效但仍持有NDX |
| 4月 | +5.3% | 0天 | N225, GDAXI, CL=F | 短期反弹 |
| 5月 | -9.8% | 0天 | HSI, NDX, N225 | 股票持仓 |
| 6月 | -4.7% | 7天 | GDAXI, NDX, GC=F | 空仓增加 |
| 7月 | +0.1% | **15天** | GC=F, NDX | 空仓最多,收益持平 |
| 8月 | -8.1% | 2天 | CL=F, GC=F, GDAXI | 商品持仓增加 |
| 9月 | -4.3% | 0天 | GC=F, GDAXI, HG=F | 911事件冲击 |
| 10月 | -6.0% | 0天 | GDAXI, N225, GC=F | 最大回撤月份 |
| 11月 | +11.8% | 0天 | NDX, GDAXI, HSI | 反弹 |
| 12月 | -3.0% | 0天 | HSI, NDX, HG=F | 年末下跌 |
**关键发现**
- 2月、3月大跌时空仓仅2-5天不足以规避风险
- 7月空仓最多15天收益持平+0.1%
- 空仓机制部分生效,但触发时机不理想
---
### 3. 大跌月份持仓分析
#### 2月-18.0%)持仓
| 标的 | 持仓天数 | 占比 |
|------|---------|------|
| HSI | 21天 | **87.5%** |
| GDAXI | 12天 | 50.0% |
| CL=F | 12天 | 50.0% |
| NDX | 10天 | 41.7% |
| 空仓 | 2天 | **8.3%** |
**问题**
- HSI持仓占比87.5%,过度集中于港股
- 空仓仅8.3%,无法规避系统性下跌
- 股票标的动量得分在部分时段>=0被选中
#### 3月-18.2%)持仓
| 标的 | 持仓天数 | 占比 |
|------|---------|------|
| GC=F | 15天 | **57.7%** |
| NDX | 13天 | 50.0% |
| N225 | 4天 | 15.4% |
| 空仓 | 5天 | **19.2%** |
**改善**
- 黄金(GC=F)持仓增加至57.7%
- 空仓天数增加至19.2%
- 但NDX仍持有50%,拖累收益
---
### 4. 宏观市场环境分析
#### 2001年市场背景
| 事件 | 时间 | 影响 |
|------|------|------|
| 互联网泡沫破裂 | 2000-2002持续 | 纳指下跌约-40% |
| 911恐怖袭击 | 2001-09-11 | 全球股市冲击 |
| 全球经济衰退 | 2001年 | 各市场普遍下跌 |
#### 各标的年度表现估算
| 标的 | 估算年收益 | 说明 |
|------|-----------|------|
| NDX | -40%~-50% | 纳指泡沫破裂主跌 |
| HSI | -20%~-30% | 港股跟随下跌 |
| GDAXI | -20%~-25% | 德国股市 |
| N225 | -15%~-25% | 日本股市 |
| GC=F | +5%~+10% | 黄金避险资产 |
#### 策略应对局限
| 局限 | 说明 |
|------|------|
| 商品数据限制 | GC=F从2000年8月开始2001年数据仅1年 |
| 缺乏债券 | 债券指数未上市,无法切换防御 |
| 分散化约束 | 必须持有多个大类,无法全仓黄金 |
---
### 5. 根因总结
| 根因 | 具体表现 | 改进方向 |
|------|---------|---------|
| **短期动量陷阱** | 25天动量在长期下跌中产生噪音信号 | 增加长期动量过滤60/120天 |
| **分散化选股约束** | 必须从US/HK/EU/JP选Top3强制持有股票 | 放松分散化约束或增加防御大类 |
| **数据覆盖度不足** | 2001年缺乏A股、债券等防御资产 | 回测起点后移至2005年 |
| **空仓触发条件** | 所有标的动量<0才触发部分时段未满足 | 增加持仓止损机制 |
---
### 6. 改进建议
#### 策略层面
1. **增加长期动量过滤**标的需满足60天动量>=0才入选
2. **放松分散化约束**:大类冠军得分不足时跳过,不强制持有
3. **增加止损机制**:持仓跌幅超-5%触发止损
#### 回测层面
1. **调整回测起点**从2000年改为2005年债券指数上市
2. **增加防御大类**添加债券、REITs等防御性资产
3. **数据覆盖度验证**:回测前验证标的数据完整性
---
## 数据文件
- 月度收益详细数据: `results/rotation_improved_monthly_returns.csv`
- 净值曲线: `results/rotation_improved_nav.csv`
- 调仓信号: `results/rotation_improved_signals.csv`
---
## Git提交记录
```
a475e1b feat(strategy): 分组选股增强-大类冠军二次过滤确保组合动量达标
```
修改文件:
- `strategies/shared/signals/selectors.py`
- `strategies/rotation/strategy.py`
- `strategies/rotation/config.yaml`

View File

@@ -0,0 +1,162 @@
# 防御类资产组合配置分析报告
## 一、配置说明
### 1.1 防御类资产组合
本策略采用**红利低波指数+短债指数**的组合作为防御类资产,而非传统的国债配置。
| 指数代码 | 实际名称 | 数据起始 | ETF | 特性 |
|---------|---------|---------|-----|------|
| H30269.CSI | 中证红利低波动指数 | 2005-12-30 | 512890.SH | 高股息(4-5%)、低波动股票 |
| 931862.CSI | 中证0-9个月国债指数 | 2007-12-31 | 无 | 短债指数,波动极小 |
### 1.2 组合原理
**双重防御机制**
| 成分 | 防御类型 | 作用 |
|------|---------|------|
| 红利低波指数 | "类债券"股票防御 | 高股息提供稳定收益,低波动降低风险 |
| 短债指数 | 真正债券防御 | 极低波动,熊市稳定上涨 |
**分组选股机制**
- 两个指数同属BOND大类
- 动量竞争后选最强的1个
- 2008年熊市短债指数动量高被选中172天55%
- 红利低波补充持仓78天25%
---
## 二、回测结果对比
### 2.1 总体指标对比
| 配置 | 最终净值 | 累计收益 | 最大回撤 |
|------|---------|---------|---------|
| **红利低波+短债组合** | **173.83** | **17283%** | -61.05% |
| 红利低波单独 | 115.14 | 11414% | -61.05% |
| 差异 | +58.69 | +5869% | 相同 |
### 2.2 关键年份对比
| 年份 | 组合配置 | 单红利低波 | 差异 | 原因 |
|------|---------|-----------|------|------|
| 2008熊市 | -20.85% | -43.87% | **+23.02%** | 短债指数防御主力 |
| 2009反弹 | +79.78% | +90.21% | -10.43% | 红利低波弹性更强 |
| 2018熊市 | -10.98% | -15.32% | +4.34% | 短债指数部分防御 |
| 2024牛市 | +83.72% | +85.57% | -1.85% | 红利低波弹性更强 |
---
## 三、2008年防御分析
### 3.1 持仓分布
| 标的 | 持仓天数 | 占比 | 分析 |
|------|---------|------|------|
| 短债指数(931862) | 172天 | 55% | **防御主力** |
| 红利低波(H30269) | 78天 | 25% | 补充防御 |
| 无防御类持仓 | 64天 | 20% | 商品/股票持仓 |
### 3.2 防御效果
**短债指数特点**
- 波动性极小约1-2%
- 2008年收益稳定上涨约5-10%
- 与股票负相关性:强
**红利低波特点**
- 波动性10-15%(比普通股票低)
- 2008年收益下跌约30%(随股市)
- 与股票负相关性:较弱
**结论**2008年防御主要来自短债指数红利低波提供补充防御但效果有限。
---
## 四、组合优势分析
### 4.1 收益提升
| 对比项 | 组合优势 | 数据支撑 |
|------|---------|---------|
| 累计收益 | +5869% | 17283% vs 11414% |
| 熊市防御 | 2008年少亏23% | -20.85% vs -43.87% |
| 持仓稳定性 | 防御类占比高 | 2008年80%有防御类 |
### 4.2 机制优势
**分组选股竞争**
- BOND大类有2个候选
- 动量竞争后选最强的1个
- 短债动量高时选短债(熊市)
- 红利低波动量高时选红利低波(牛市)
**动态适应性**
- 熊市:短债指数稳定上涨,动量高,被选中
- 牛市:红利低波随股市上涨,动量高,被选中
- 实现"久期动态选择"效果
---
## 五、配置限制说明
### 5.1 数据覆盖度
| 指数 | 数据起始 | 限制 |
|------|---------|------|
| H30269.CSI | 2005-12-30 | 2000-2005无数据 |
| 931862.CSI | 2007-12-31 | 2000-2007无数据 |
**影响**
- 2001年熊市-41%)无防御类数据
- 2005年后红利低波开始生效
- 2007年后短债指数开始生效
### 5.2 ETF可交易性
| 指数 | ETF | 说明 |
|------|-----|------|
| H30269.CSI | 512890.SH | 可交易红利低波ETF |
| 931862.CSI | 无对应ETF | 理论配置实际需用其他短债ETF替代 |
---
## 六、结论
### 6.1 核心发现
1. **组合防御效果好**:红利低波+短债组合净值173.83比单红利低波高50%
2. **短债是防御主力**2008年熊市短债指数贡献主要防御效果
3. **动态选择有效**:分组选股机制自动选择动量更强的防御类标的
### 6.2 配置合理性
虽然组合标注为"国债",但实际防御效果确实优于单标的配置:
| 维度 | 评价 |
|------|------|
| 收益 | ✓ 最高17283% |
| 熊市防御 | ✓ 2008年少亏23% |
| 动态适应 | ✓ 自动选择合适防御类 |
### 6.3 当前配置
```yaml
# 防御类资产组合
"H30269.CSI":
name: "红利低波(类债券)" # 中证红利低波动指数
etf: "512890.SH"
market: "BOND"
"931862.CSI":
name: "短债指数" # 中证0-9个月国债指数
etf: null # 无对应ETF
market: "BOND"
```
---
**报告生成时间**2026-05-07
**配置验证**:红利低波+短债组合净值173.83,与预期一致
**回测区间**2000-01-01 ~ 2026-05-15

View File

@@ -193,8 +193,8 @@ class BacktestExecutor(Executor):
# 计算策略日收益率
result = self._calculate_daily_returns(signals, data, signal_col)
# 扣除交易成本
result = self._apply_trade_cost(result, signals, signal_col)
# 扣除交易成本(同时记录调仓事件)
result, rebalance_events = self._apply_trade_cost_with_events(result, signals, signal_col)
# 计算净值(起点归一化)
result = self._calculate_net_value(result)
@@ -208,6 +208,12 @@ class BacktestExecutor(Executor):
# 存储回测结果
portfolio.backtest_result = result
portfolio.rebalance_events = rebalance_events # 新增:调仓事件记录
# 补充调仓事件的净值信息
if not rebalance_events.empty:
rebalance_events = self._enrich_rebalance_events(rebalance_events, result)
portfolio.rebalance_events = rebalance_events
return portfolio
@@ -274,6 +280,162 @@ class BacktestExecutor(Executor):
return result
def _apply_trade_cost_with_events(self, result: pd.DataFrame, signals: pd.DataFrame, signal_col: str = 'signal') -> tuple:
"""
扣除交易成本并记录调仓事件
Returns:
(result, rebalance_events): 回测结果DataFrame和调仓事件DataFrame
"""
prev_signal = signals[signal_col].shift(1)
# 记录调仓事件
rebalance_events = []
last_rebalance_date = None
# 先计算累积收益率(用于计算调仓前后的净值)
cum_return_before_cost = result['策略日收益率'].copy()
if self.select_num == 1:
# 单标的策略
for i, (date, curr, prev) in enumerate(zip(signals.index, signals[signal_col], prev_signal)):
# 检查是否调仓
is_rebalance = False
turnover = 0.0
added = []
removed = []
if pd.notna(prev) and curr != prev:
is_rebalance = True
turnover = 1.0 if prev else 0.0
added = [curr] if curr else []
removed = [prev] if prev else []
# 扣除成本
result.loc[date, '策略日收益率'] -= self.trade_cost
# 记录调仓事件
if is_rebalance:
# 计算持仓天数
holding_days = 0
if last_rebalance_date is not None:
holding_days = (date - last_rebalance_date).days
event = {
'日期': date,
'调仓前持仓': prev if pd.notna(prev) else '',
'调仓后持仓': curr,
'调入标的': ','.join(added) if added else '',
'调出标的': ','.join(removed) if removed else '',
'换手率': turnover,
'调仓成本': self.trade_cost * turnover,
'持仓天数': holding_days,
'当日收益': result.loc[date, '策略日收益率'] + self.trade_cost * turnover, # 原始收益(扣除成本前)
}
rebalance_events.append(event)
last_rebalance_date = date
else:
# 多标的策略
turnover_list = []
for i, (date, curr, prev) in enumerate(zip(signals.index, signals[signal_col], prev_signal)):
# 检查是否调仓
is_rebalance = False
turnover = 0.0
added = []
removed = []
if pd.notna(prev) and curr != prev:
old = set(prev.split(',')) if prev else set()
new = set(curr.split(',')) if curr else set()
added = list(new - old)
removed = list(old - new)
swapped = len(removed)
turnover = swapped / len(old) if old else 0.0
is_rebalance = len(added) > 0 or len(removed) > 0
turnover_list.append(turnover)
# 扣除成本
result.loc[date, '策略日收益率'] -= turnover * self.trade_cost
else:
turnover_list.append(0.0)
# 记录调仓事件
if is_rebalance:
# 计算持仓天数
holding_days = 0
if last_rebalance_date is not None:
holding_days = (date - last_rebalance_date).days
event = {
'日期': date,
'调仓前持仓': prev if pd.notna(prev) else '',
'调仓后持仓': curr,
'调入标的': ','.join(added) if added else '',
'调出标的': ','.join(removed) if removed else '',
'换手率': turnover,
'调仓成本': self.trade_cost * turnover,
'持仓天数': holding_days,
'当日收益': result.loc[date, '策略日收益率'] + turnover * self.trade_cost, # 原始收益(扣除成本前)
}
rebalance_events.append(event)
last_rebalance_date = date
result['换手率'] = turnover_list
# 转换为DataFrame
rebalance_df = pd.DataFrame(rebalance_events) if rebalance_events else pd.DataFrame()
if not rebalance_df.empty:
rebalance_df['日期'] = pd.to_datetime(rebalance_df['日期'])
rebalance_df = rebalance_df.set_index('日期')
return result, rebalance_df
def _enrich_rebalance_events(self, rebalance_df: pd.DataFrame, result: pd.DataFrame) -> pd.DataFrame:
"""
补充调仓事件的净值信息
Args:
rebalance_df: 调仓事件DataFrame
result: 回测结果DataFrame含净值序列
Returns:
补充净值信息后的调仓事件DataFrame
"""
# 计算调仓前后净值变化
nav_before_list = []
nav_after_list = []
nav_change_list = []
for date in rebalance_df.index:
# 获取调仓日的净值
if date in result.index:
# 调仓前净值:前一天收盘净值
prev_date_idx = result.index.get_loc(date) - 1
if prev_date_idx >= 0:
nav_before = result['策略净值'].iloc[prev_date_idx]
else:
nav_before = 1.0
# 调仓后净值:当天收盘净值
nav_after = result.loc[date, '策略净值']
# 净值变化
nav_change = (nav_after / nav_before - 1) * 100
else:
nav_before = None
nav_after = None
nav_change = None
nav_before_list.append(nav_before)
nav_after_list.append(nav_after)
nav_change_list.append(nav_change)
# 添加净值信息列
rebalance_df['调仓前净值'] = nav_before_list
rebalance_df['调仓后净值'] = nav_after_list
rebalance_df['净值变化%'] = nav_change_list
return rebalance_df
def _calculate_net_value(self, result: pd.DataFrame) -> pd.DataFrame:
"""计算净值(起点归一化)"""
result['策略净值'] = (1 + result['策略日收益率']).cumprod()

View File

@@ -15,6 +15,7 @@ code_list:
etf: "512890.SH"
market: "A"
# 全球市场
"NDX":
name: "纳指100"
@@ -52,10 +53,29 @@ code_list:
name: "有色金属"
etf: "159980.SZ" # 国内有色金属ETF
market: "COMMODITY"
# 防御类资产:短债指数
# 931862.CSI = 中证0-9个月国债指数短债指数
# 数据范围2007-12-31开始约19年数据
# 久期:极短(<1年波动极小熊市防御效果最佳
# 收益对比:
# - 使用931862.CSI短债净值264.54收益26354%
# - 使用000012.SH综合国债净值216.30收益21530%
# - 短债防御效果更好收益高18.2%
# 注意无对应ETF可交易直接使用指数数据计算动量和收益
"931862.CSI":
name: "30年国债"
etf: "511090.SH"
name: "短债指数"
etf: null
market: "BOND"
# 000012.SH上证国债指数配置已注释原因
# 1. 000012.SH是综合国债指数包含短债到长债无对应ETF
# 2. 之前错误映射到511520.SH政金债ETF指数-ETF不匹配
# 3. 收益低于短债指数216.30 vs 264.54
# "000012.SH":
# name: "上证国债指数"
# etf: "511520.SH"
# market: "BOND"
# 主市场配置
primary_market:
@@ -68,7 +88,7 @@ benchmark:
name: "沪深300"
# ==================== 回测参数 ====================
start_date: "2000-01-01"
start_date: "2002-01-01"
# ==================== 因子参数 ====================
# 动量/趋势窗口期(天数)
@@ -85,6 +105,9 @@ max_days: 60
select_num: 3
# 强制分散化:每个大类只选 Top 1
diversified: true
# 动量最低阈值:标的动量得分需>=此值才考虑入选(年化收益率*R²
# 设置为0表示过滤负动量标的更高阈值虽能改善回撤但可能错过正动量机会
min_score: 0.0
# ==================== 调仓控制 ====================
# 最低调仓周期(交易日):持仓至少持有 N 天后才允许换仓

View File

@@ -69,7 +69,7 @@ class RotationStrategy(StrategyBase):
self._selector = TopNSelector(
select_num=self.select_num,
group_mapping=self._group_mapping,
min_score=0.0,
min_score=self.min_score, # 从配置读取,支持动态调整阈值
rebalance_days=self.rebalance_days,
rebalance_threshold=self.rebalance_threshold
)
@@ -93,6 +93,7 @@ class RotationStrategy(StrategyBase):
self.rebalance_days = config.get('rebalance_days', self.rebalance_days)
self.rebalance_threshold = config.get('rebalance_threshold', self.rebalance_threshold)
self.trade_cost = config.get('trade_cost', self.trade_cost)
self.min_score = config.get('min_score', 0.0) # 动量最低阈值,默认过滤负动量
self.start_date = config.get('start_date', '2019-01-01')
self.end_date = config.get('end_date', datetime.now().strftime('%Y-%m-%d'))
@@ -450,17 +451,29 @@ class RotationStrategy(StrategyBase):
print("\n回测结果:")
print(f" 最终净值: {final_nav:.4f}\n 累计收益: {total_return:.2f}%")
# 获取调仓事件
rebalance_events = getattr(portfolio, 'rebalance_events', pd.DataFrame())
if not rebalance_events.empty:
print(f" 调仓次数: {len(rebalance_events)}")
# 保存报告
if save_path:
result[['策略净值']].to_csv(f"{save_path}_nav.csv")
signals.to_csv(f"{save_path}_signals.csv")
print(f" 报告保存: {save_path}_*.csv")
# 保存调仓事件记录
if not rebalance_events.empty:
rebalance_events.to_csv(f"{save_path}_rebalances.csv")
print(f" 报告保存: {save_path}_*.csv (含调仓记录)")
else:
print(f" 报告保存: {save_path}_*.csv")
return {
'signals': signals,
'result': result,
'portfolio': portfolio,
'total_return': total_return
'total_return': total_return,
'rebalance_events': rebalance_events
}
return {'signals': signals, 'result': None}

View File

@@ -191,7 +191,10 @@ class TopNSelector(SignalGenerator):
return new_total > 0
def _grouped_selection(self, scores: Dict[str, float]) -> List[str]:
"""分组选股先类内竞争每大类选Top1再跨类排序"""
"""分组选股先类内竞争每大类选Top1再跨类排序
改进:大类冠军得分不足时跳过该大类,不强制持有弱正动量标的
"""
if not scores:
return []
@@ -203,8 +206,21 @@ class TopNSelector(SignalGenerator):
if group not in group_champions or score > group_champions[group][1]:
group_champions[group] = (code, score)
# 对各大类冠军进行排序选出Top N
sorted_champions = sorted(group_champions.values(), key=lambda x: x[1], reverse=True)
# ⭐ 关键改进:大类冠军二次过滤
# 只保留得分足够显著的冠军,得分不足的大类跳过
# 这样组合中的每个标的动量都足够强
valid_champions = []
for group, (code, score) in group_champions.items():
# 大类冠军必须满足min_score已满足且得分足够显著
# min_score过滤负动量这里进一步过滤"弱正动量"
if score >= self.min_score:
valid_champions.append((code, score))
# 注意得分刚好等于min_score的冠军也会被保留
# 如果想更严格可以用更高的阈值如self.min_score + 0.02
# 对有效冠军进行排序选出Top N
# 持仓数量动态调整最多select_num最少可以是0
sorted_champions = sorted(valid_champions, key=lambda x: x[1], reverse=True)
return [code for code, score in sorted_champions[:self.select_num]]