docs: 添加ETF跟踪误差计算方法文档
- 完整计算流程(ETF单位净值 vs 基准指数) - 数据源选择(Tushare指数/期货/Flask API) - 关键注意事项(unit_nav、标的指数基准、年化因子) - 与天天基金数据校验结果(平均差异0.009%) - Python代码示例
This commit is contained in:
233
docs/etf_tracking_error_calculation.md
Normal file
233
docs/etf_tracking_error_calculation.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# ETF跟踪误差计算方法
|
||||
|
||||
**文档版本**: v1.0
|
||||
**创建日期**: 2026-06-19
|
||||
**适用范围**: 轮动策略标的池ETF跟踪准确率评估
|
||||
|
||||
---
|
||||
|
||||
## 一、定义
|
||||
|
||||
**跟踪误差(Tracking Error, TE)**:衡量ETF净值收益率与标的指数收益率之间偏离程度的指标,反映基金经理的追踪能力。
|
||||
|
||||
**核心公式**:
|
||||
```
|
||||
跟踪误差(TE) = STDEV(每日跟踪偏离度) × √252
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、计算步骤
|
||||
|
||||
### 2.1 数据准备
|
||||
|
||||
| 数据类型 | 字段 | 来源 | 说明 |
|
||||
|---------|------|------|------|
|
||||
| ETF单位净值 | `unit_nav` | Tushare `fund_nav` | 必须用单位净值,不能用累计净值 |
|
||||
| 基准收盘价 | `close` | Tushare `index_daily` / `fut_daily` / Flask API | 根据标的类型选择数据源 |
|
||||
|
||||
### 2.2 计算流程
|
||||
|
||||
```
|
||||
步骤1: 获取ETF单位净值序列
|
||||
NAV[t], NAV[t-1], NAV[t-2], ...
|
||||
|
||||
步骤2: 获取基准收盘价序列
|
||||
Index[t], Index[t-1], Index[t-2], ...
|
||||
|
||||
步骤3: 计算ETF日收益率
|
||||
ETF_ret[t] = (NAV[t] - NAV[t-1]) / NAV[t-1]
|
||||
|
||||
步骤4: 计算基准日收益率
|
||||
Index_ret[t] = (Index[t] - Index[t-1]) / Index[t-1]
|
||||
|
||||
步骤5: 计算每日跟踪偏离度
|
||||
Deviation[t] = ETF_ret[t] - Index_ret[t]
|
||||
|
||||
步骤6: 计算偏离度标准差
|
||||
Std = STDEV(Deviation序列)
|
||||
|
||||
步骤7: 年化处理
|
||||
TE = Std × √252
|
||||
```
|
||||
|
||||
### 2.3 Python代码示例
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
def calculate_tracking_error(etf_nav: pd.Series, benchmark_close: pd.Series) -> dict:
|
||||
"""
|
||||
计算ETF跟踪误差
|
||||
|
||||
Args:
|
||||
etf_nav: ETF单位净值序列(index=date, value=unit_nav)
|
||||
benchmark_close: 基准收盘价序列(index=date, value=close)
|
||||
|
||||
Returns:
|
||||
dict: 包含跟踪误差、R²、相关系数等指标
|
||||
"""
|
||||
# 计算收益率
|
||||
etf_ret = etf_nav.pct_change().dropna()
|
||||
bench_ret = benchmark_close.pct_change().dropna()
|
||||
|
||||
# 对齐日期
|
||||
common = etf_ret.index.intersection(bench_ret.index)
|
||||
if len(common) < 20:
|
||||
return None
|
||||
|
||||
e = etf_ret.loc[common]
|
||||
b = bench_ret.loc[common]
|
||||
|
||||
# 每日偏离度
|
||||
daily_deviation = e - b
|
||||
|
||||
# 跟踪误差 = 标准差 × √252
|
||||
tracking_error = daily_deviation.std() * np.sqrt(252)
|
||||
|
||||
# 其他指标
|
||||
correlation = e.corr(b)
|
||||
r_squared = correlation ** 2
|
||||
|
||||
# 累计收益
|
||||
etf_cum = (1 + e).prod() - 1
|
||||
bench_cum = (1 + b).prod() - 1
|
||||
excess = etf_cum - bench_cum
|
||||
|
||||
return {
|
||||
'annual_tracking_error': round(tracking_error * 100, 4), # %
|
||||
'correlation': round(correlation, 6),
|
||||
'r_squared': round(r_squared, 6),
|
||||
'etf_cum_return': round(etf_cum * 100, 2), # %
|
||||
'benchmark_cum_return': round(bench_cum * 100, 2), # %
|
||||
'excess_return': round(excess * 100, 2), # %
|
||||
'common_days': len(common),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、基准数据来源
|
||||
|
||||
### 3.1 数据源选择
|
||||
|
||||
| 标的类型 | 示例 | 基准来源 | Tushare接口 | 数据可用性 |
|
||||
|---------|------|---------|------------|-----------|
|
||||
| A股指数 | 创业板指、红利低波 | 指数收盘价 | `index_daily` | ✅ 完整 |
|
||||
| 商品期货 | 黄金、有色金属 | 期货主力合约 | `fut_daily` | ✅ 完整 |
|
||||
| 海外指数 | 纳指、恒生、日经、DAX | 指数收盘价 | Flask API (yfinance) | ✅ 完整 |
|
||||
|
||||
### 3.2 接口调用示例
|
||||
|
||||
```python
|
||||
# A股指数
|
||||
index_data = pro.index_daily(
|
||||
ts_code='399006.SZ',
|
||||
start_date='20250601',
|
||||
end_date='20260619'
|
||||
)
|
||||
|
||||
# 商品期货
|
||||
futures_data = pro.fut_daily(
|
||||
ts_code='AU.SHF',
|
||||
start_date='20250601',
|
||||
end_date='20260619'
|
||||
)
|
||||
|
||||
# 海外指数(通过Flask API)
|
||||
from datasource.flask_api_source import FlaskAPIDataSource
|
||||
flask_source = FlaskAPIDataSource()
|
||||
index_data = flask_source.fetch('^NDX', '2025-06-01', '2026-06-19')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、关键注意事项
|
||||
|
||||
### 4.1 必须使用单位净值(unit_nav)
|
||||
|
||||
| 净值类型 | 含义 | 是否可用 |
|
||||
|---------|------|---------|
|
||||
| **单位净值** (unit_nav) | 当前每份基金的实际价值 | ✅ **必须用这个** |
|
||||
| 累计净值 (accum_nav) | 单位净值 + 历史分红 | ❌ 会虚高规模 |
|
||||
|
||||
**原因**:累计净值包含了历史分红再投资,会导致规模计算偏大。
|
||||
|
||||
### 4.2 必须使用标的指数做基准
|
||||
|
||||
| 基准类型 | 计算结果 | 说明 |
|
||||
|---------|---------|------|
|
||||
| **标的指数** | 真实跟踪误差 | ✅ 反映基金经理追踪能力 |
|
||||
| 另一只ETF价格 | 价格一致性 | ❌ 包含溢价率波动噪声 |
|
||||
|
||||
### 4.3 年化因子
|
||||
|
||||
- 使用 **√252**(假设一年252个交易日)
|
||||
- 如果使用月度数据,则用 **√12**
|
||||
- 如果使用周度数据,则用 **√52**
|
||||
|
||||
---
|
||||
|
||||
## 五、校验结果
|
||||
|
||||
### 5.1 与天天基金数据对比
|
||||
|
||||
我们用 Tushare 计算的创业板指 ETF 跟踪误差 vs 天天基金官方数据:
|
||||
|
||||
| ETF代码 | Tushare TE | 天天基金 TE | 差异 |
|
||||
|--------|-----------|------------|------|
|
||||
| 159948.SZ | 0.3302% | 0.32% | +0.0102% |
|
||||
| 159952.SZ | 0.3559% | 0.35% | +0.0059% |
|
||||
| 159205.SZ | 0.3698% | 0.36% | +0.0098% |
|
||||
| 159977.SZ | 0.3727% | 0.36% | +0.0127% |
|
||||
|
||||
**平均差异:+0.0091%** → 高度一致
|
||||
|
||||
### 5.2 结论
|
||||
|
||||
- Tushare 数据计算的跟踪误差与天天基金官方数据**高度一致**
|
||||
- 验证了计算方法的正确性
|
||||
- 可用于日常跟踪误差监控
|
||||
|
||||
---
|
||||
|
||||
## 六、完整计算脚本
|
||||
|
||||
参考文件:`rotation/tracking_error_full.py`
|
||||
|
||||
### 6.1 主要功能
|
||||
|
||||
- 覆盖轮动策略标的池全部10个标的
|
||||
- 自动选择合适的数据源(Tushare指数/期货/Flask API)
|
||||
- 批量获取ETF净值数据
|
||||
- 计算跟踪误差并排序
|
||||
- 与天天基金数据对比校验
|
||||
|
||||
### 6.2 运行方式
|
||||
|
||||
```bash
|
||||
cd /Users/aszer/code/etf
|
||||
python3 rotation/tracking_error_full.py
|
||||
```
|
||||
|
||||
### 6.3 输出结果
|
||||
|
||||
- JSON文件:`rotation/results/tracking_error_full.json`
|
||||
- 包含每个标的下所有ETF的跟踪误差、R²、超额收益等指标
|
||||
|
||||
---
|
||||
|
||||
## 七、相关文档
|
||||
|
||||
- [ETF竞品分析报告](./etf_competitor_analysis_report.md)
|
||||
- [跟踪误差校验报告](./tracking_error_validation_report.md)
|
||||
- [ETF数据源说明](../datasource/README.md)
|
||||
|
||||
---
|
||||
|
||||
## 八、更新日志
|
||||
|
||||
| 版本 | 日期 | 变更内容 |
|
||||
|------|------|---------|
|
||||
| v1.0 | 2026-06-19 | 初始版本,包含完整计算方法和校验结果 |
|
||||
Reference in New Issue
Block a user