docs: add strategy summary snapshot (2026-06-06, ca933e4)

First stage summary documenting core strategy logic, key design
decisions, and select_num/weight backtest comparison results.
Stored in dedicated docs/strategy_summaries/ directory with
date + commit hash naming for reproducibility.
This commit is contained in:
2026-06-06 23:59:41 +08:00
parent ca933e43e4
commit 7b229ced14

View File

@@ -0,0 +1,258 @@
# ETF 轮动策略阶段总结
> **生成日期**2026-06-06
> **Git Commit**`ca933e4`
> **复现方式**`git checkout ca933e4` 后运行 `rotation/simple_rotation.py`
---
## 1. 策略概述
**定位**:基于动量因子的跨市场 ETF 轮动策略,通过每日截面排名在 11 个全球资产中选择强势标的,利用动量效应获取超额收益。
**资产池**11 个信号标的,覆盖 A 股、港股、美股、日股、欧洲、商品、债券七个大类。
| 大类 | 信号标的 | 交易 ETF | 说明 |
|:---|:---|:---|:---|
| A 股 | 399006.SZ 创业板指 | 159915.SZ 创业板 ETF | |
| A 股 | H30269.CSI 红利低波 | 512890.SH 红利低波 ETF | |
| 港股 | HSI 恒生指数 | 159920.SZ 恒生 ETF | |
| 港股 | HSTECH.HK 恒生科技 | 513130.SH 恒生科技 ETF | 2020-07 上市 |
| 美股 | NDX 纳指 100 | 513100.SH 纳指 ETF | |
| 日股 | N225 日经 225 | 513520.SH 日经 ETF | |
| 欧洲 | GDAXI 德国 DAX | 513030.SH 德国 ETF | |
| 商品 | GC=F 黄金期货 | 518880.SH 黄金 ETF | |
| 商品 | HG=F 铜期货 | 159980.SZ 有色 ETF | |
| 商品 | CL=F 原油期货 | 160723.SZ 嘉实原油 | |
| 债券 | 931862.CSI 短债指数 | 931862.CSI 短债指数 | 防御配置 |
**回测区间**2020-01-10 ~ 2026-06-05共 1549 个交易日。
---
## 2. 核心逻辑流程
### 2.1 每日时间线
```
T 日 09:00 信号生成(使用 T-1 日及之前的收盘价)
T 日 09:30 执行调仓(如触发)
T 日 15:00 计算当日收益,更新 NAV
```
### 2.2 信号生成
**动量因子**:当前使用 `slope_r2`slope ×25 天回看窗口。
```
slope_r2_score = 10000 × slope ×
其中:
slope: 对归一化价格 p/p[0] 做线性回归的斜率
R²: 回归决定系数(拟合优度)
```
该因子的设计意图:
- **slope** 捕捉趋势方向和强度
- **R²** 衡量趋势质量(高 R² = 趋势稳定,低 R² = 噪声大)
- 二者相乘实现"趋势强度 × 拟合质量"的双重过滤
**其他可用因子**(通过配置切换):
| 因子 | 公式 | 特点 |
|:---|:---|:---|
| `momentum` | (p[-1] / p[0]) - 1 | 简单收益率 |
| `weighted_momentum` | annualized_return × R² | 加权回归 |
| `slope_r2` | 10000 × slope × R² | 当前默认 |
| `standardized_slope` | slope / SE(slope) | t 统计量 |
| `vol_adjusted_momentum` | (return / vol) × R² | 类夏普构造 |
### 2.3 持仓选择
信号生成采用**大类竞争 + 动态阈值 + 债券填充**三层机制:
1. **大类竞争**:每个 group 内选动量 score 最高的标的作为 winner
2. **动态阈值**:非 BOND 组 winner 的 raw_momentum 必须 >= bond_momentum × ratio当前 ratio=1.0),否则被过滤
3. **截面排名**:所有通过阈值的 winner 按 score 降序排列,选 top-Nselect_num
4. **债券填充**:若 winner 数量不足 select_num用债券填充剩余 slot
**债券的双重角色**
- **阈值过滤器**:债券动量作为其他标的入选的动态门槛
- **仓位填充**:市场弱势时(多数标的动量低于债券),作为防御性持仓
### 2.4 调仓判定
```
is_rebalance = (sorted(new_holdings) != sorted(current_holdings)) and len(current_holdings) > 0
```
当新选出的持仓与当前持仓不同时触发调仓。调仓时扣除交易成本(默认 0.1%)。
### 2.5 仓位加权
支持两种模式,仅在调仓日更新权重(权重锁定机制):
**equal等权**
```
weight_i = 1/N
```
**rank排名加权**
```
weight_i = (N - i) / triangular(N)
其中 triangular(N) = N × (N + 1) / 2
```
以 select_num=3 为例:第 1 名 50%,第 2 名 33%,第 3 名 17%。
**权重锁定机制**
```
_generate_signals() → 计算 _pending_weights每天仅用于信号选择
run() 主循环 → is_rebalance?
├─ 是: active_weights = _pending_weights (锁定新权重)
└─ 否: 保持 active_weights 不变 (权重不变)
```
这确保了持仓不变时,仓位权重不会因排名顺序变化而波动。
### 2.6 收益计算
| 场景 | 计算方式 |
|:---|:---|
| 调仓日 - 卖出 | (open - prev_close) / prev_close × weight |
| 调仓日 - 买入 | (close - open) / open × weight |
| 调仓日 - 调仓成本 | -0.1% |
| 非调仓日 - 持有 | (close - prev_close) / prev_close × weight |
---
## 3. 关键设计决策
| 决策 | 选择 | 原因 |
|:---|:---|:---|
| 动量因子 | slope × R² | slope 捕捉趋势方向R² 过滤噪声趋势,双重过滤提升信噪比 |
| 回看窗口 | 25 天 | 中期动量(约 1 个月),平衡响应速度与噪声过滤 |
| 债券阈值 | 使用配置的因子函数 | 保持阈值两边量纲一致,仅在 VOL_ADJUSTED_MOMENTUM 时 fallback |
| 权重锁定 | 仅调仓日更新 active_weights | 避免持仓不变时权重随排名波动,减少无效换手 |
| 大类竞争 | 每组选 top 1 | 天然实现大类分散,避免同一大类集中持仓 |
| 交易成本 | 0.1% / 次 | 模拟真实 ETF 交易摩擦成本 |
| 信号时间 | T-1 日收盘价 | 模拟 T 日 9:00 前可获取的信息,避免未来数据 |
---
## 4. 对比实验结果
### 4.1 实验矩阵
| 维度 | 取值 |
|:---|:---|
| select_num | 1, 2, 3 |
| weight | equal, rank |
| 其他参数 | 与默认配置一致 |
共 6 组配置select_num=1 时 equal/rank 等价,实际 5 种独立结果)。
### 4.2 结果汇总
| select_num | weight | 年化收益 | 夏普比率 | 最大回撤 | Calmar |
|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | equal/rank | **43.20%** | 1.246 | -26.33% | **1.641** |
| 2 | equal | 22.57% | 1.082 | -18.34% | 1.230 |
| 2 | rank | 26.38% | 1.117 | -19.09% | 1.382 |
| 3 | equal | 20.39% | **1.160** | **-14.65%** | 1.392 |
| 3 | rank | 23.52% | 1.150 | -16.27% | 1.446 |
### 4.3 关键发现
**1. rank 加权系统性提升年化收益**
在 select_num >= 2 时rank 加权相比 equal 加权:
- select_num=226.38% vs 22.57%+3.81%
- select_num=323.52% vs 20.39%+3.13%
原因rank 加权将更多仓位集中在排名最高的标的上,放大了动量最强的资产的收益贡献。
**2. select_num=1 是收益天花板**
单标的集中持仓实现了 43.20% 的年化收益,但代价是 -26.33% 的最大回撤。select_num=1 时 equal/rank 等价,因为只选 1 个标的。
**3. equal 加权夏普比率略高**
| select_num | equal 夏普 | rank 夏普 | 差值 |
|:---:|:---:|:---:|:---:|
| 2 | 1.082 | 1.117 | rank +0.035 |
| 3 | **1.160** | 1.150 | equal +0.010 |
select_num=3 + equal 的夏普比率最高1.160),同时回撤最小(-14.65%),体现了分散化的风险调整优势。
**4. Calmar 比率视角**
- select_num=1Calmar 1.641(收益/回撤最优)
- select_num=3 + rankCalmar 1.446(多标的中最优)
**5. 回撤控制**
select_num 越大,回撤越小:
- 1 标的:-26.33%
- 2 标的:-18% ~ -19%
- 3 标的:-14% ~ -16%
分散化有效降低了极端回撤。
---
## 5. 当前默认配置
```yaml
rotation:
diversified: true # 大类竞争(每组选 top 1
select_num: 3 # 持仓数量
weight: rank # 排名加权50%/33%/17%
threshold:
mode: dynamic
dynamic:
reference: 931862.CSI # 债券作为动态阈值参考
ratio: 1.0 # 阈值 = bond_momentum × 1.0
fallback_enabled: true
fallback_value: 0.0
factor:
type: slope_r2 # 动量因子类型
n_days: 25 # 回看窗口
rebalance:
min_hold_days: 1
trade_cost: 0.001 # 0.1% 交易成本
backtest:
start_date: '2020-01-10'
benchmark:
code: 000300.SH # 沪深300作为基准
```
---
## 6. 代码文件索引
| 文件 | 职责 |
|:---|:---|
| `rotation/simple_rotation.py` | 核心策略引擎:动量因子、信号生成、回测主循环、收益计算 |
| `rotation/config_loader.py` | 配置加载Pydantic Schema、枚举类型、YAML 解析 |
| `rotation/config_simple.yaml` | 策略配置文件:资产池、因子、轮动、回测参数 |
| `rotation/backtest_viewer.html` | 回测可视化NAV 曲线、持仓明细、交易记录 |
### 核心函数速查
| 函数 | 位置 | 说明 |
|:---|:---|:---|
| `slope_r2_score()` | simple_rotation.py | 默认动量因子10000 × slope × R² |
| `compute_position_weights()` | simple_rotation.py | 仓位加权equal / rank 两种模式 |
| `_generate_signals()` | simple_rotation.py | 信号生成:大类竞争 + 阈值过滤 + 债券填充 |
| `_calculate_daily_return()` | simple_rotation.py | 收益计算:调仓/持有两种场景 |
| `run()` | simple_rotation.py | 回测主循环:每日迭代 + 权重锁定 |