Files
etf/docs/动态ETF池筛选引擎-调研与方案.md
aszerW 2829f80427 feat(backtest): 消除前视偏差,实现动态ETF池重建
消除回测前视偏差(Look-Ahead Bias):
- 新增 ETFDataCache 本地缓存系统,预下载全量ETF(含已退市)基础信息和日线数据
- 改造 ETFUniverseBuilder 支持纯历史模式,每个时间点只使用当时可获得的数据
- 动量.py 新增 dynamic 模式,回测中每60交易日动态重建ETF候选池
- momentum_experiment.py 同步支持动态重建
- 新增 ETF筛选引擎文档和动态池方案文档

无前视偏差实验结果(6组对比,2015-2026):
  A: 全仓1只       CAGR=3.32%, MaxDD=-63.19%, Sharpe=0.26
  B: 等权3只       CAGR=3.40%, MaxDD=-49.72%, Sharpe=0.30 ← 最优
  C: 反波动率3只   CAGR=1.73%, MaxDD=-38.59%, Sharpe=0.21
  D: 等权5只       CAGR=2.77%, MaxDD=-42.39%, Sharpe=0.29
  E: 反波动率5只   CAGR=-0.37%, MaxDD=-19.56%, Sharpe=-0.03
  F: 动量>0全选等权 CAGR=2.02%, MaxDD=-43.27%, Sharpe=0.24

最优方案: B(等权3只)夏普、Calmar、CAGR三项均最高
2026-04-29 22:15:01 +08:00

248 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 动态ETF池自动化筛选引擎 — 调研与方案
## 1. 问题背景
当前ETF轮动策略的标的池是人工预选的存在严重的**幸存者偏差**。回测对比实验显示:
| 标的池 | 累计收益 | CAGR | 最大回撤 |
|--------|---------|------|---------|
| 9只精选ETF (全仓1只, 2015-2026) | 2733.60% | 34.38% | -32.79% |
| 20只行业ETF (全仓1只, 2015-2026) | 208.16% | 10.46% | -67.16% |
| 20只轮动ETF (等权5只, 2020-2026) | 171.36% | 17.48% | -30.85% |
**结论**: 标的池选择是策略最大的 alpha 来源,需要构建**系统化、无偏差**的动态筛选能力。
---
## 2. 学术论文与权威机构调研
### 2.1 TrendFolios (UCLA, 2024)
- **论文**: Lu et al. "TrendFolios: A Portfolio Construction Framework for Utilizing Momentum and Trend-Following In a Multi-Asset Portfolio"
- **来源**: arxiv:2506.09330
- **核心方法**:
- 按资产类别的风险因子对ETF进行分类
- Universe 随时间自然扩展 — 新ETF上市后才纳入杜绝前视偏差
- 结合趋势跟踪信号与动量因子构建多资产组合
- **对本方案的启发**: Layer 3 资产标签化设计 + 滚动重建机制中的无前视偏差原则
### 2.2 AEGIS (2024)
- **论文**: Chakraborty & Singh. "Taming the Black Swan: A Momentum-Gated Hierarchical Optimisation Framework"
- **来源**: arxiv:2604.09060
- **核心方法**:
- 流动性硬门槛: FALR (Fraction of Available Liquidity Ratio) >= 0.75
- 每年根据动量领先者重建 universe
- 分层优化框架: 先筛选 universe → 动量门控 → 层次化权重优化
- **对本方案的启发**: Layer 1 流动性过滤的硬门槛设计 + 定期重建机制
### 2.3 HRP — Hierarchical Risk Parity (Lopez de Prado, 2016)
- **论文**: Lopez de Prado. "Building Diversified Portfolios that Outperform Out-of-Sample"
- **来源**: SSRN:2708678
- **核心方法**:
- 基于收益率相关性矩阵进行层次聚类Hierarchical Clustering
- 将资产分为互不相关的簇,同簇内取代表性资产
- 相比传统均值-方差优化HRP 在样本外表现更稳健,不依赖协方差矩阵求逆
- **对本方案的启发**: Layer 5 相关性优化选择算法的理论基础
### 2.4 Faber GTAA — Global Tactical Asset Allocation (2006)
- **论文**: Faber. "A Quantitative Approach to Tactical Asset Allocation"
- **来源**: SSRN:962461
- **核心方法**:
- 选择 5-13 个大类资产ETF每个代表一个独立的经济驱动因子
- 用 10 个月移动平均线作为趋势信号(价格 > MA10 则持有,否则转现金)
- 关键洞察: **资产类别的覆盖度比选择数量更重要**
- **对本方案的启发**: Universe 设计原则 — 确保风险因子全覆盖股票、债券、商品、REITs、外汇
### 2.5 Antonacci Dual Momentum (2012)
- **论文**: Antonacci. "Risk Premia Harvesting Through Dual Momentum"
- **来源**: SSRN:2042750
- **核心方法**:
- **绝对动量** (时间序列动量): 资产自身是否处于上升趋势
- **相对动量** (横截面动量): 资产间的相对强弱排序
- 资产对pairs作为构建模块每对代表一个风险溢价
- 当绝对动量为负时,转入债券避险
- **对本方案的启发**: 跨资产分散化设计理念 — 每个资产类别用"对"来覆盖
### 2.6 Jegadeesh & Titman (1993) — 动量效应经典文献
- **论文**: "Returns to Buying Winners and Selling Losers: Implications for Stock Market Efficiency"
- **来源**: Journal of Finance, 48(1), 65-91
- **核心发现**:
- 买入过去 3-12 个月的赢家、卖出输家,可获得显著超额收益
- 动量效应在不同市场、不同时期持续存在
- 这是所有动量策略的学术基石
### 2.7 华宝基金动量优选基金 (业界实践)
- **来源**: 华宝基金动量优选混合型基金招募说明书
- **实践方法**:
- 年度 ETF 池调整(非固定池)
- 行业、风格、主题多维度覆盖
- 定量筛选 + 基金经理主观判断结合
- **对本方案的启发**: 实业级重建周期参考(年度/季度)
---
## 3. 方案设计:多层漏斗筛选
### 3.1 架构概览
```
全量ETF (~1000只)
|
v [Layer 1] 基础过滤 (流动性/类型/上市时间)
约300只
|
v [Layer 2] 同指数去重 (每个指数只留1只最优ETF)
约200只
|
v [Layer 3] 大类资产标签化 (自动分类)
约200只 (含标签)
|
v [Layer 4] 类内预筛选 (每类留Top-N)
约30-50只
|
v [Layer 5] 相关性优化选择 (贪心/HRP聚类)
10-15只 最终池
```
### 3.2 Layer 1 — 基础过滤(硬性门槛)
| 过滤条件 | 原因 |
|---------|------|
| 上市满1年 | 新基金数据不足,无法计算有效因子 |
| 日均成交额 > 5000万 | 流动性不足会导致冲击成本过大 (参考AEGIS的FALR门槛) |
| 非货币/非债券增强类 | 货币基金无轮动意义 |
| 非杠杆/非反向ETF | 杠杆ETF不适合持有 |
| 有明确跟踪指数 | 需要指数数据计算因子 |
### 3.3 Layer 2 — 同指数去重
一个指数可能有 20+ 只 ETF 跟踪如沪深300有30+只),按 `index_code` 分组每组选1只
1. 优先选**日均成交额最大**的(流动性最好)
2. 同等条件下选**管理费最低**的
3. 同等条件下选**上市时间最早**的(历史数据最长)
### 3.4 Layer 3 — 大类资产标签化
根据指数名称和类别,自动打上资产大类标签(参考 Faber GTAA 的风险因子覆盖思想):
```python
ASSET_CLASS_RULES = {
'A股宽基': ['沪深300', '中证500', '中证1000', '创业板', '上证50', '科创50'],
'A股行业': ['银行', '证券', '医疗', '白酒', '军工', '新能源', '芯片', '煤炭', ...],
'A股主题': ['红利', '消费', '科技', '央企', '国企', ...],
'港股': ['恒生', '港股', 'H股'],
'美股': ['纳斯达克', '纳指', '标普', '美股'],
'全球/其他': ['日经', '德国', '法国', '越南', '印度', 'MSCI'],
'商品': ['黄金', '白银', '原油', '有色', '豆粕'],
'债券': ['国债', '利率债', '信用债', '可转债'],
}
```
### 3.5 Layer 4 — 类内预筛选
每个大类保留最具代表性的 Top-N 只,避免单一类别占满池子:
| 大类 | 保留数量 | 选择依据 |
|------|---------|---------|
| A股宽基 | 3-5 | 按规模/流动性排序 |
| A股行业 | 8-12 | 按行业分散度每个细分行业最多1只 |
| A股主题 | 3-5 | 按流动性 |
| 港股 | 2-3 | 按流动性 |
| 美股 | 2-3 | 按流动性 |
| 全球/其他 | 2-3 | 按流动性 |
| 商品 | 2-3 | 按流动性 |
| 债券 | 2-3 | 按流动性 |
### 3.6 Layer 5 — 相关性优化选择(核心算法)
基于 HRP (Lopez de Prado) 的层次聚类思想用贪心最大分散化算法从30-50只候选中选出最终10-15只
```python
def greedy_max_diversification(candidates, n_select, lookback_days=120):
"""
1. 计算所有候选的 lookback_days 日收益率相关系数矩阵
2. 先选入每个大类中流动性最好的1只确保类别覆盖
3. 剩余名额贪心填充:
- 对每个未选候选,计算其与已选集合的最大相关系数
- 选入 max_corr 最小的(即与已有持仓最不相关的)
4. 重复直到选满 n_select 只
"""
```
**约束条件**:
- 每个大类至少1只确保资产类别覆盖
- 任意两只的相关系数不超过 0.85(强制分散)
- A股行业类别不超过总数的 50%避免A股过度集中
---
## 4. 定期重建机制
- **重建周期**: 每季度90个交易日重建一次
- **平滑切换**: 新旧池差异超过 30% 时才执行切换,避免频繁调整
- **反前视偏差** (TrendFolios/AEGIS 强调): 重建时只用截止到重建日的历史数据Universe 随时间自然扩展
- **退市ETF处理**: 回测中需包含已退市ETF的历史数据避免幸存者偏差
---
## 5. 实现规划
### 5.1 独立脚本 `scripts/build_etf_universe.py`
```python
class ETFUniverseBuilder:
def __init__(self, config):
self.min_trading_days = 250 # 上市满1年
self.min_daily_amount = 5000 # 日均成交额万元
self.n_select = 12 # 最终池大小
self.max_corr = 0.85 # 最大相关系数
self.lookback_days = 120 # 相关性计算窗口
def run(self):
raw = self.fetch_etf_universe() # Layer 0: 获取全量
filtered = self.basic_filter(raw) # Layer 1: 基础过滤
deduped = self.dedup_by_index(filtered) # Layer 2: 同指数去重
labeled = self.label_asset_class(deduped) # Layer 3: 标签化
shortlist = self.intra_class_select(labeled) # Layer 4: 类内筛选
final = self.correlation_optimize(shortlist) # Layer 5: 相关性优化
self.save_results(final)
return final
```
### 5.2 输出文件
- `data/etf_universe/universe_{date}.csv`: 最终筛选结果
- `data/etf_universe/pipeline_log_{date}.txt`: 每层过滤日志
- `data/etf_universe/corr_matrix_{date}.csv`: 相关性矩阵
### 5.3 集成到动量策略
修改 `动量.py`,支持从动态池加载:
```python
CONFIG = {
'etf_pool': 'auto', # 'auto' 表示使用动态池
'rebuild_interval': 90, # 每90个交易日重建
}
```
回测时每隔90天调用一次 `ETFUniverseBuilder`,用截止到当前回测日期的数据重建池子,确保不使用未来数据。
---
## 6. 参考文献
1. Lu et al. (2024). "TrendFolios: A Portfolio Construction Framework for Utilizing Momentum and Trend-Following In a Multi-Asset Portfolio". *arxiv:2506.09330*
2. Chakraborty & Singh (2024). "Taming the Black Swan: A Momentum-Gated Hierarchical Optimisation Framework". *arxiv:2604.09060*
3. Lopez de Prado (2016). "Building Diversified Portfolios that Outperform Out-of-Sample" (HRP). *SSRN:2708678*
4. Faber (2006). "A Quantitative Approach to Tactical Asset Allocation". *SSRN:962461*
5. Antonacci (2012). "Risk Premia Harvesting Through Dual Momentum". *SSRN:2042750*
6. Jegadeesh & Titman (1993). "Returns to Buying Winners and Selling Losers". *Journal of Finance, 48(1), 65-91*