Compare commits
9 Commits
788120387a
...
79c6ab8620
| Author | SHA1 | Date | |
|---|---|---|---|
| 79c6ab8620 | |||
| b419701c77 | |||
| 969385f39c | |||
| 48bf3fde2e | |||
| 306b4022da | |||
| 63c56f0001 | |||
| 6308627f8c | |||
| c782830f75 | |||
| a475e1b314 |
398
docs/experiments/轮动策略改进版回测分析报告.md
Normal file
398
docs/experiments/轮动策略改进版回测分析报告.md
Normal 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`
|
||||
162
docs/experiments/防御类资产组合配置分析报告.md
Normal file
162
docs/experiments/防御类资产组合配置分析报告.md
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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 天后才允许换仓
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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]]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user