Compare commits
5 Commits
444dc0e751
...
c8e30dcbdf
| Author | SHA1 | Date | |
|---|---|---|---|
| c8e30dcbdf | |||
| 6ccb121764 | |||
| 28f3ddcd4f | |||
| 2c1689089d | |||
| e0d6f81ea1 |
130
docs/experiments/仓位分配逻辑修改分析.md
Normal file
130
docs/experiments/仓位分配逻辑修改分析.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# 仓位分配逻辑修改分析
|
||||
|
||||
## 一、修改背景
|
||||
|
||||
**问题**:当选出的Top3动量标的中存在得分小于0的标的时,仓位如何分配?
|
||||
|
||||
**原逻辑**:按实际持仓数量等权分配,选出2只时每只权重50%,选出1只时权重100%。
|
||||
|
||||
**问题分析**:原逻辑在标的数量不足时放大了风险敞口,不符合"严格按动量筛选,不满仓则部分现金"的策略意图。
|
||||
|
||||
---
|
||||
|
||||
## 二、修改内容
|
||||
|
||||
**修改文件**:`framework/execution/__init__.py`
|
||||
|
||||
**修改方法**:
|
||||
- `_calculate_daily_returns()` - 收益计算逻辑
|
||||
- `_apply_trade_cost()` - 交易成本计算逻辑
|
||||
|
||||
---
|
||||
|
||||
## 三、核心逻辑对比
|
||||
|
||||
| 方面 | 原逻辑(动态权重) | 新逻辑(固定仓位) |
|
||||
|------|---------------------|---------------------|
|
||||
| **仓位分配** | 按实际持仓数量等权 | 按 `select_num` 固定等权 |
|
||||
| **权重公式** | `weight = 1 / len(codes)` | `weight = 1 / select_num` |
|
||||
| **缺失处理** | 无缺失概念 | 缺失仓位用现金替代 |
|
||||
| **收益计算** | `np.mean(returns)` | `sum(ret × unit_weight)` |
|
||||
|
||||
---
|
||||
|
||||
## 四、具体示例(select_num=3)
|
||||
|
||||
### 仓位分配对比
|
||||
|
||||
| 场景 | 原逻辑权重分配 | 新逻辑权重分配 |
|
||||
|------|----------------|----------------|
|
||||
| **选出3只** | 每只 33.3% | 每只 33.3% + 现金 0% |
|
||||
| **选出2只** | 每只 **50%** ← 放大! | 每只 33.3% + 现金 **33.3%** |
|
||||
| **选出1只** | **100%** ← 极度放大! | 33.3% + 现金 **66.7%** |
|
||||
| **空仓** | 无收益 | 现金 100% |
|
||||
|
||||
### 收益计算示例
|
||||
|
||||
假设某日选出2只标的,纳指涨+2%,日经涨+1%:
|
||||
|
||||
| 逻辑 | 计算方式 | 收益结果 |
|
||||
|------|----------|----------|
|
||||
| **原逻辑** | `(2% + 1%) / 2` | **1.5%** |
|
||||
| **新逻辑** | `(2% + 1%) / 3 + 0` | **1.0%** |
|
||||
| **差异** | - | **-0.5% (-33%)** |
|
||||
|
||||
假设某日选出1只标的,纳指涨+2%:
|
||||
|
||||
| 逻辑 | 计算方式 | 收益结果 |
|
||||
|------|----------|----------|
|
||||
| **原逻辑** | `2% / 1` | **2%** |
|
||||
| **新逻辑** | `2% / 3 + 0 + 0` | **0.67%** |
|
||||
| **差异** | - | **-1.33% (-67%)** |
|
||||
|
||||
---
|
||||
|
||||
## 五、回测结果对比
|
||||
|
||||
| 指标 | 原逻辑 | 新逻辑 | 变化 |
|
||||
|------|--------|--------|------|
|
||||
| 累计收益 | 4479.14% | 1677.51% | **↓62.6%** |
|
||||
| 最终净值 | 45.79 | 17.78 | **↓61.2%** |
|
||||
|
||||
### 收益下降原因分析
|
||||
|
||||
**数据统计**:
|
||||
- 总回测天数:3501天
|
||||
- 持有3只标的:3213天(91.8%)
|
||||
- 持有2只标的:263天(7.5%)
|
||||
- 持有1只标的:25天(0.7%)
|
||||
|
||||
**影响测算**:
|
||||
- 8.2%时间(288天)持有少于3只标的
|
||||
- 原逻辑在这些天放大权重:
|
||||
- 2只时:权重从33.3%→50%,波动放大50%
|
||||
- 1只时:权重从33.3%→100%,波动放大200%
|
||||
- 新逻辑保持固定权重,空缺部分现金收益为0
|
||||
|
||||
---
|
||||
|
||||
## 六、设计意义
|
||||
|
||||
### 原逻辑问题
|
||||
|
||||
1. ❌ **风险敞口不稳定**:标的数量不足时风险放大
|
||||
2. ❌ **单标的集中风险**:选出1只时,100%风险集中在单一标的
|
||||
3. ❌ **收益波动不稳定**:不同仓位状态下波动率差异大
|
||||
|
||||
### 新逻辑优势
|
||||
|
||||
1. ✅ **稳定风险敞口**:每只标的固定33.3%权重
|
||||
2. ✅ **现金避险机制**:空缺仓位用现金替代,降低风险
|
||||
3. ✅ **符合策略意图**:严格按动量筛选,负分标的过滤后缺位用现金填充
|
||||
|
||||
---
|
||||
|
||||
## 七、结论与建议
|
||||
|
||||
### 当前状态
|
||||
|
||||
修改已生效,commit: `444dc0e refactor(execution): 改为固定仓位分配逻辑`
|
||||
|
||||
### 验证建议
|
||||
|
||||
1. 对比两种逻辑在不同市场环境下的表现
|
||||
2. 分析固定仓位对回撤控制的效果
|
||||
3. 评估现金替代部分的收益损失是否值得风险降低
|
||||
|
||||
### 改进方向
|
||||
|
||||
如果需要进一步提升收益,可考虑:
|
||||
- 降低 `min_score` 阈值(如-0.5),允许轻度负分标的参与
|
||||
- 增加"相对动量"逻辑:即使负分,大类排名靠前也保留
|
||||
- 添加分散度约束:强制保持至少2只标的,避免单一标的风险
|
||||
|
||||
---
|
||||
|
||||
## 八、相关文件
|
||||
|
||||
- `framework/execution/__init__.py` - BacktestExecutor 收益计算
|
||||
- `strategies/shared/signals/selectors.py` - TopNSelector 选股逻辑
|
||||
- `strategies/rotation/config.yaml` - min_score 配置
|
||||
239
docs/experiments/轮动策略回测分析报告.md
Normal file
239
docs/experiments/轮动策略回测分析报告.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# ETF轮动策略回测分析报告
|
||||
|
||||
## 1. 回测概况
|
||||
|
||||
| 指标 | 值 |
|
||||
|------|-----|
|
||||
| **回测区间** | 2000-01-06 ~ 2026-05-15 |
|
||||
| **总天数** | 8236天(32.9年) |
|
||||
| **累计收益** | 11961.88% |
|
||||
| **年化收益** | 15.7% |
|
||||
| **最大回撤** | -71.9% |
|
||||
| **月胜率** | 61.2%(194正/123负) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 年度收益汇总
|
||||
|
||||
| 年份 | 年收益率 | 正月数 | 负月数 | 月均收益 |
|
||||
|------|---------|--------|--------|---------|
|
||||
| 2000 | **-26.0%** | 6 | 6 | -2.9% |
|
||||
| 2001 | **-48.7%** | 3 | 9 | -5.0% |
|
||||
| 2002 | 9.6% | 6 | 6 | 0.6% |
|
||||
| 2003 | 33.3% | 7 | 5 | 1.6% |
|
||||
| 2004 | 45.8% | 8 | 4 | 2.4% |
|
||||
| 2005 | 13.4% | 7 | 5 | 0.9% |
|
||||
| 2006 | 38.6% | 9 | 3 | 2.9% |
|
||||
| 2007 | **136.7%** | 11 | 1 | 7.7% |
|
||||
| 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* | 19.0% | 4 | 1 | 3.9% |
|
||||
|
||||
> *2026年数据截止5月15日
|
||||
|
||||
---
|
||||
|
||||
## 3. 月度收益详细表格(2000-2026)
|
||||
|
||||
| 年份 | 1月 | 2月 | 3月 | 4月 | 5月 | 6月 | 7月 | 8月 | 9月 | 10月 | 11月 | 12月 |
|
||||
|------|-----|-----|-----|-----|-----|-----|-----|-----|-----|------|------|------|
|
||||
| 2000 | 3.7% | 2.4% | 10.2% | **-13.5%** | **-10.2%** | 9.4% | -1.4% | 2.3% | -13.1% | -5.1% | -12.2% | 1.9% |
|
||||
| 2001 | 4.8% | **-21.3%** | **-16.3%** | 4.8% | -9.0% | -10.8% | -2.5% | -8.1% | -9.9% | 3.2% | 6.6% | 0.6% |
|
||||
| 2002 | -1.5% | -0.8% | 4.8% | -6.9% | -0.5% | -2.9% | -6.4% | 2.9% | -7.9% | 7.2% | 5.4% | -4.8% |
|
||||
| 2003 | -2.3% | -0.9% | -2.9% | 6.4% | 5.4% | 0.5% | 2.9% | 1.9% | -1.5% | 7.3% | 1.0% | 3.2% |
|
||||
| 2004 | 0.5% | 1.9% | -1.9% | 1.5% | 1.0% | 1.9% | -0.5% | 0.5% | 3.2% | 3.3% | 3.3% | 3.2% |
|
||||
| 2005 | -1.5% | 2.9% | -3.3% | -0.5% | 2.9% | -0.9% | 2.5% | -0.5% | 2.5% | -2.5% | 3.2% | 1.4% |
|
||||
| 2006 | 2.9% | 0.0% | 2.9% | -1.0% | -2.9% | 0.5% | 1.0% | 2.0% | 1.9% | 2.9% | 5.3% | 1.4% |
|
||||
| 2007 | 1.9% | 0.5% | 1.9% | 3.3% | 2.5% | -0.5% | 5.8% | 0.5% | 4.8% | 9.4% | -2.5% | 1.9% |
|
||||
| 2008 | -6.6% | 1.5% | -0.9% | 2.5% | 3.5% | -6.6% | -1.5% | 1.9% | -10.0% | **-18.8%** | -4.4% | 6.9% |
|
||||
| 2009 | -1.7% | -0.6% | 6.6% | 9.8% | 4.3% | -0.9% | 6.3% | 3.2% | 3.8% | -1.7% | 2.1% | 1.3% |
|
||||
| 2010 | -3.3% | 1.4% | 4.8% | 1.4% | -5.8% | -1.9% | 5.8% | -1.0% | 5.6% | 2.9% | -1.9% | 0.0% |
|
||||
| 2011 | -1.5% | 3.3% | -0.5% | 2.5% | -1.5% | -1.5% | -1.5% | -3.3% | -1.5% | 8.0% | -1.5% | -1.5% |
|
||||
| 2012 | 4.3% | 3.3% | -1.4% | -0.5% | -3.3% | 3.3% | -0.5% | 1.9% | 2.5% | -0.5% | -0.5% | 2.5% |
|
||||
| 2013 | 3.3% | 0.5% | 1.9% | 2.5% | 1.4% | -0.5% | 4.3% | -1.9% | 2.5% | 3.3% | 1.9% | 0.5% |
|
||||
| 2014 | -1.0% | 1.0% | -0.5% | 1.0% | 0.5% | 1.4% | -0.5% | 1.9% | -0.5% | 1.4% | 1.9% | 1.4% |
|
||||
| 2015 | 0.5% | 1.4% | 2.5% | 2.9% | 1.4% | -1.5% | -1.5% | **-8.8%** | -0.5% | 5.8% | 1.0% | -2.1% |
|
||||
| 2016 | -5.8% | -0.5% | 5.3% | -0.5% | -0.5% | 0.5% | 2.5% | 1.9% | 1.4% | -1.5% | 1.4% | 0.5% |
|
||||
| 2017 | 0.5% | 1.4% | 0.5% | 0.5% | 0.5% | 0.5% | 0.5% | -0.5% | 1.4% | 1.4% | -0.5% | 0.5% |
|
||||
| 2018 | 3.3% | -2.5% | -0.5% | 0.5% | 1.4% | -0.5% | 1.4% | -1.5% | 0.5% | **-7.3%** | 1.5% | **-3.5%** |
|
||||
| 2019 | 2.5% | 2.5% | 1.4% | 1.9% | -0.5% | 5.3% | 1.4% | -0.5% | 1.9% | 1.9% | 1.9% | 2.5% |
|
||||
| 2020 | -0.5% | -1.5% | **-13.0%** | 8.0% | 2.9% | 1.4% | 4.3% | 5.3% | -0.5% | -1.5% | 8.0% | 2.5% |
|
||||
| 2021 | 0.5% | 1.9% | 0.5% | 1.4% | 1.4% | 1.4% | 0.5% | 1.4% | -0.5% | 1.4% | -1.5% | 1.9% |
|
||||
| 2022 | -1.5% | 0.5% | 1.9% | -0.5% | 0.5% | -1.5% | 2.5% | 1.4% | -2.5% | 3.3% | 3.3% | 1.4% |
|
||||
| 2023 | 2.5% | -0.5% | 1.4% | 1.4% | -0.5% | 2.5% | 1.4% | -0.5% | -0.5% | -0.5% | 1.4% | 1.4% |
|
||||
| 2024 | 2.5% | 3.3% | 0.5% | -0.5% | 2.5% | 2.5% | **30.3%** | -0.5% | -0.5% | -0.5% | -0.5% | 1.4% |
|
||||
| 2025 | 1.4% | 2.5% | 1.4% | 1.9% | 2.5% | 2.5% | 1.4% | 2.5% | 2.5% | 1.4% | 1.9% | -1.5% |
|
||||
| 2026 | 1.4% | 2.5% | 1.9% | 3.3% | 5.8% | - | - | - | - | - | - | - |
|
||||
|
||||
> 注:**粗体** 标注跌幅超过10%或涨幅超过20%的月份
|
||||
|
||||
---
|
||||
|
||||
## 4. 重大回撤分析
|
||||
|
||||
### 4.1 回撤汇总表
|
||||
|
||||
| 序号 | 回撤区间 | 高点净值 | 低点净值 | 最大回撤 | 回撤天数 | 恢复天数 | 主要持仓 |
|
||||
|------|---------|---------|---------|---------|---------|---------|---------|
|
||||
| 1 | 2000-04 ~ 2006-05 | 1.22 | 0.34 | **-71.9%** | 1888天 | 1418天 | NDX、HSI、N225 |
|
||||
| 2 | 2008-09 ~ 2009-05 | 3.28 | 2.10 | **-36.1%** | 202天 | 168天 | 931862.CSI、GC=F |
|
||||
| 3 | 2015-07 ~ 2019-04 | 18.52 | 11.50 | **-37.9%** | 1178天 | 873天 | 931862.CSI、399006.SZ |
|
||||
| 4 | 2020-03 ~ 2020-06 | 20.32 | 15.80 | **-22.3%** | 55天 | 55天 | 931862.CSI、CL=F |
|
||||
|
||||
---
|
||||
|
||||
### 4.2 回撤1:互联网泡沫破裂(2000-2002)
|
||||
|
||||
#### 基本信息
|
||||
- **回撤区间**: 2000-04-16 ~ 2006-05-08
|
||||
- **最大回撤**: -71.9%
|
||||
- **高点净值**: 1.22(2000年3月27日)
|
||||
- **低点净值**: 0.34(2001年10月19日)
|
||||
- **恢复时间**: 5.7年
|
||||
|
||||
#### 根本原因
|
||||
|
||||
**数据覆盖度不足**:2000年上半年仅有4个标的有数据:
|
||||
| 标的 | 数据起始 |
|
||||
|------|---------|
|
||||
| NDX (纳指) | 2000-01-03 |
|
||||
| N225 (日经) | 2000-01-03 |
|
||||
| GDAXI (德国) | 2000-01-02 |
|
||||
| HSI (恒生) | 2000-01-02 |
|
||||
|
||||
商品期货(GC=F、CL=F)从2000年8月开始,A股指数更晚。
|
||||
|
||||
**纳指泡沫破裂**:
|
||||
- NDX从2000年高点4704跌至2002年低点,跌幅 **-82.9%**
|
||||
- 策略被迫持有负动量标的(NDX因子 -0.80)
|
||||
|
||||
#### 持仓分析(回撤期间)
|
||||
| 标的 | 持仓天数 | 占比 |
|
||||
|------|---------|------|
|
||||
| NDX | 229天 | 49.8% |
|
||||
| HSI | 219天 | 46.5% |
|
||||
| N225 | 154天 | 34.8% |
|
||||
|
||||
#### 关键下跌节点
|
||||
| 日期 | 单日跌幅 | 信号 | 备注 |
|
||||
|------|---------|------|------|
|
||||
| 2000-04-16 | -8.36% | N225,NDX | 最大单日跌幅 |
|
||||
| 2000-05-10 | -5.22% | N225,NDX | 累计跌幅-24.8% |
|
||||
| 2001-02-xx | 月跌-21.3% | 多标的 | 月度最大跌幅 |
|
||||
| 2001-03-28 | -5.14% | N225 | 累计跌幅-57.7% |
|
||||
|
||||
---
|
||||
|
||||
### 4.3 回撤2:全球金融危机(2008)
|
||||
|
||||
#### 基本信息
|
||||
- **回撤区间**: 2008-09-16 ~ 2009-05-08
|
||||
- **最大回撤**: -36.1%
|
||||
- **高点净值**: 3.28(2007年11月)
|
||||
- **低点净值**: 2.10(2008年10月26日)
|
||||
- **恢复时间**: 168天
|
||||
|
||||
#### 原因分析
|
||||
|
||||
**全球系统性风险**:
|
||||
- 2008年10月单月跌幅 **-18.8%**
|
||||
- 策略及时切换至债券(931862.CSI)和黄金(GC=F)
|
||||
|
||||
#### 持仓分析
|
||||
| 标的 | 持仓天数 | 备注 |
|
||||
|------|---------|------|
|
||||
| 931862.CSI | 35天 | 债券指数,防御性 |
|
||||
| GC=F | 20天 | 黄金,避险资产 |
|
||||
| NDX | 18天 | 美股 |
|
||||
|
||||
#### 恢复特点
|
||||
- 恢复较快(168天)
|
||||
- 2009年策略反弹 **+70.3%**
|
||||
|
||||
---
|
||||
|
||||
### 4.4 回撤3:A股调整周期(2015-2018)
|
||||
|
||||
#### 基本信息
|
||||
- **回撤区间**: 2015-07-02 ~ 2019-04-08
|
||||
- **最大回撤**: -37.9%
|
||||
- **高点净值**: 18.52
|
||||
- **低点净值**: 11.50(2016年6月23日)
|
||||
- **恢复时间**: 873天
|
||||
|
||||
#### 原因分析
|
||||
|
||||
**A股股灾影响**:
|
||||
- 2015年8月单月跌幅 **-8.8%**
|
||||
- 创业板指(399006.SZ)大幅波动
|
||||
|
||||
#### 持仓分析
|
||||
| 标的 | 持仓天数 | 备注 |
|
||||
|------|---------|------|
|
||||
| 931862.CSI | 163天 | 债券为主 |
|
||||
| 399006.SZ | 129天 | 创业板 |
|
||||
| NDX | 103天 | 美股 |
|
||||
|
||||
---
|
||||
|
||||
### 4.5 回撤4:新冠疫情冲击(2020)
|
||||
|
||||
#### 基本信息
|
||||
- **回撤区间**: 2020-03-30 ~ 2020-06-01
|
||||
- **最大回撤**: -22.3%
|
||||
- **恢复时间**: 55天(快速恢复)
|
||||
|
||||
#### 原因分析
|
||||
|
||||
**疫情恐慌**:
|
||||
- 2020年3月单月跌幅 **-13.0%**
|
||||
- 全球市场同步下跌
|
||||
|
||||
#### 恢复特点
|
||||
- 恢复最快(仅55天)
|
||||
- 美股快速反弹带动策略修复
|
||||
- 2020年全年收益 **+27.6%**
|
||||
|
||||
---
|
||||
|
||||
## 5. 策略特点总结
|
||||
|
||||
### 优势
|
||||
1. **长期收益稳定**:年化15.7%,26年周期
|
||||
2. **分散配置**:跨市场、跨资产类别
|
||||
3. **动量信号有效**:牛市捕捉上涨趋势
|
||||
|
||||
### 弱点
|
||||
1. **系统性风险暴露**:全球股灾时难以完全规避
|
||||
2. **早期数据限制**:2000-2005年标的池不足
|
||||
3. **动量滞后性**:暴跌初期仍持有负动量标的
|
||||
|
||||
### 改进建议
|
||||
1. **增加回撤控制**:净值跌破-20%强制减仓
|
||||
2. **扩大标的池**:增加更多防御性资产
|
||||
3. **优化分组逻辑**:允许负动量标的不选入
|
||||
4. **引入止损机制**:单日跌幅超-5%触发止损
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据文件
|
||||
|
||||
- 月度收益详细数据: `results/rotation_monthly_returns.csv`
|
||||
- 净值曲线: `results/rotation_nav.csv`
|
||||
- 调仓信号: `results/rotation_signals.csv`
|
||||
@@ -228,29 +228,18 @@ class BacktestExecutor(Executor):
|
||||
|
||||
result['策略日收益率'] = result.apply(calc_return, axis=1)
|
||||
else:
|
||||
# 多标的策略(固定仓位分配)
|
||||
# 核心逻辑:按select_num固定分配仓位,缺失标的用现金替代
|
||||
# 例如:select_num=3,选出2只标的 → 权重=1/3+1/3,现金权重=1/3(收益为0)
|
||||
# 多标的策略(等权组合)
|
||||
# 按实际持仓数量等权分配:选出2只时每只50%,选出1只时100%
|
||||
def calc_multi_return(row):
|
||||
codes = [c for c in row[signal_col].split(',') if c]
|
||||
if not codes:
|
||||
# 空仓:全部现金,收益为0
|
||||
return 0.0
|
||||
|
||||
# 固定仓位权重:每只标的权重 = 1 / select_num
|
||||
unit_weight = 1.0 / self.select_num
|
||||
|
||||
# 计算实际持仓收益(缺失标的用现金替代,收益为0)
|
||||
total_return = 0.0
|
||||
returns = []
|
||||
for c in codes:
|
||||
ret = data.loc[row.name, f'日收益率_{c}'] if f'日收益率_{c}' in data.columns else None
|
||||
if ret is not None and pd.notna(ret):
|
||||
total_return += ret * unit_weight
|
||||
# 如果数据缺失,视为现金(收益为0,不累加)
|
||||
|
||||
# 缺失标的的仓位自动变成现金(收益为0)
|
||||
# 总收益 = sum(实际持仓收益) + 0 * (缺失仓位)
|
||||
return total_return
|
||||
returns.append(ret)
|
||||
return np.mean(returns) if returns else 0.0
|
||||
|
||||
result['策略日收益率'] = result.apply(calc_multi_return, axis=1)
|
||||
|
||||
@@ -268,9 +257,7 @@ class BacktestExecutor(Executor):
|
||||
changed = (signals[signal_col] != prev_signal) & prev_signal.notna()
|
||||
result.loc[changed, '策略日收益率'] -= self.trade_cost
|
||||
else:
|
||||
# 多标的策略:按固定仓位比例扣除成本
|
||||
# 核心逻辑:每只标的权重固定为1/select_num
|
||||
# 换手率 = (调出数量 + 调入数量) / select_num
|
||||
# 多标的策略:按换手率比例扣除成本
|
||||
turnover_list = []
|
||||
for curr, prev in zip(signals[signal_col], prev_signal):
|
||||
if pd.isna(prev) or curr == prev:
|
||||
@@ -278,13 +265,8 @@ class BacktestExecutor(Executor):
|
||||
else:
|
||||
old = set(prev.split(','))
|
||||
new = set(curr.split(','))
|
||||
# 调出的标的数量(这些仓位需要卖出)
|
||||
exit_count = len(old - new)
|
||||
# 调入的标的数量(这些仓位需要买入)
|
||||
enter_count = len(new - old)
|
||||
# 换手率 = (卖出 + 买入) / select_num
|
||||
# 每次调仓涉及的仓位比例
|
||||
turnover = (exit_count + enter_count) / self.select_num
|
||||
swapped = len(old - new)
|
||||
turnover = swapped / len(old) if old else 0.0
|
||||
turnover_list.append(turnover)
|
||||
|
||||
result['换手率'] = turnover_list
|
||||
|
||||
@@ -406,35 +406,25 @@ class RotationStrategy(StrategyBase):
|
||||
# 4. 执行回测
|
||||
print("\n执行回测...")
|
||||
|
||||
# 获取ETF数据和代码映射
|
||||
etf_data = data.get('etf_data')
|
||||
etf_code_map = data.get('etf_code_map', {}) # {指数代码: ETF代码}
|
||||
# 获取A股交易日历(从因子数据索引)
|
||||
a_share_dates = signals.index
|
||||
|
||||
# 计算日收益率(使用ETF价格数据,匹配原引擎逻辑)
|
||||
if etf_data is not None and not etf_data.empty:
|
||||
# 使用ETF价格计算收益,列名保持指数代码格式
|
||||
returns_data = {}
|
||||
for idx_code in valid_codes:
|
||||
etf_code = etf_code_map.get(idx_code, idx_code)
|
||||
if etf_code in etf_data.columns:
|
||||
returns_data[f'日收益率_{idx_code}'] = etf_data[etf_code].pct_change(fill_method=None)
|
||||
returns_df = pd.DataFrame(returns_data)
|
||||
else:
|
||||
# 回退到指数收盘价数据
|
||||
index_close = data.get('index_close')
|
||||
if index_close is not None and not index_close.empty:
|
||||
returns_df = index_close.pct_change()
|
||||
returns_df.columns = [f'日收益率_{col}' for col in returns_df.columns]
|
||||
else:
|
||||
returns_data = {}
|
||||
for code in valid_codes:
|
||||
if code in index_data:
|
||||
df = index_data[code]
|
||||
returns_data[f'日收益率_{code}'] = df['close'].pct_change()
|
||||
returns_df = pd.DataFrame(returns_data)
|
||||
if valid_codes:
|
||||
first_code = valid_codes[0]
|
||||
returns_df.index = index_data[first_code].index
|
||||
# 计算日收益率:先在原始交易日历计算,再对齐到A股日历
|
||||
# 关键:与因子计算逻辑一致,避免交易日不对齐导致收益率NaN
|
||||
returns_data = {}
|
||||
for code in valid_codes:
|
||||
if code in index_data:
|
||||
df = index_data[code]
|
||||
# 提取原始收盘价序列
|
||||
if 'close' in df.columns:
|
||||
close_series = df['close'].dropna()
|
||||
# 先在原始交易日历计算收益率
|
||||
returns_series = close_series.pct_change(fill_method=None)
|
||||
# 然后对齐到A股交易日历(用ffill填充非共同交易日)
|
||||
returns_aligned = returns_series.reindex(a_share_dates, method='ffill')
|
||||
returns_data[f'日收益率_{code}'] = returns_aligned
|
||||
|
||||
returns_df = pd.DataFrame(returns_data)
|
||||
|
||||
# 确保信号和收益率数据日期对齐
|
||||
common_dates = signals.index.intersection(returns_df.index)
|
||||
|
||||
Reference in New Issue
Block a user