3. 跟踪误差的分解:系统风险 vs 特质风险
上一章我们聊了怎么算跟踪误差,但光知道一个数字其实没啥用。你想想看,如果跟踪误差是2%,这2%到底是怎么来的?是市场整体波动导致的,还是咱们选股选歪了?
嗯,这就引出了今天要讲的核心——跟踪误差的分解。说白了,就是把总误差拆成两块:一块是系统风险,一块是特质风险。
3.1 为什么要拆开看?
我在做量化策略的时候,经常遇到这种情况:组合的跟踪误差看着不大,但就是跑不赢基准。后来我发现,问题出在风险结构上。
举个例子:
- 如果跟踪误差主要来自系统风险,说明你的组合在行业配置、风格因子上和基准偏离太大
- 如果主要来自特质风险,说明你选的个股和基准成分股差异明显
这两种情况,处理方式完全不同。所以,拆开看是第一步。
3.2 理论框架:Barra模型视角
我个人习惯用Barra多因子模型来做分解。核心思路是这样的:
组合的超额收益可以写成:
R_p - R_b = (β_p - β_b) * f + (ε_p - ε_b)
其中:
- β_p 和 β_b 分别是组合和基准在因子上的暴露
- f 是因子收益率
- ε_p 和 ε_b 是特质收益部分
那么跟踪误差的平方就可以分解为:
TE² = (β_p - β_b)² * Var(f) + Var(ε_p - ε_b)
前面一项是系统风险贡献,后面一项是特质风险贡献。
关键点:系统风险是可以通过因子对冲来管理的,特质风险则需要通过分散化来降低。
3.3 实战:用Python做分解
我记得有一次帮一个私募做归因分析,他们组合的跟踪误差有1.8%,但怎么调都降不下来。我跑了一下分解,发现系统风险贡献占了1.5%。问题出在他们重仓了某个行业,和基准严重偏离。
下面是我常用的分解代码:
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
def tracking_error_decomposition(returns_p, returns_b, factor_returns):
"""
分解跟踪误差为系统风险和特质风险
参数:
returns_p: 组合收益率序列
returns_b: 基准收益率序列
factor_returns: 因子收益率矩阵 (T x K)
返回:
dict: 包含系统风险、特质风险和总跟踪误差
"""
# 计算超额收益
excess_returns = returns_p - returns_b
# 计算组合和基准的因子暴露
model_p = LinearRegression().fit(factor_returns, returns_p)
model_b = LinearRegression().fit(factor_returns, returns_b)
beta_p = model_p.coef_
beta_b = model_b.coef_
# 因子暴露差异
beta_diff = beta_p - beta_b
# 因子收益率协方差矩阵
factor_cov = np.cov(factor_returns.T)
# 系统风险贡献
systematic_risk = beta_diff @ factor_cov @ beta_diff.T
# 特质风险贡献
residual_p = returns_p - model_p.predict(factor_returns)
residual_b = returns_b - model_b.predict(factor_returns)
residual_diff = residual_p - residual_b
idiosyncratic_risk = np.var(residual_diff)
# 总跟踪误差
total_te = np.var(excess_returns)
return {
'systematic_risk': np.sqrt(systematic_risk) * np.sqrt(252),
'idiosyncratic_risk': np.sqrt(idiosyncratic_risk) * np.sqrt(252),
'total_te': np.sqrt(total_te) * np.sqrt(252),
'systematic_pct': systematic_risk / total_te * 100,
'idiosyncratic_pct': idiosyncratic_risk / total_te * 100
}
# 使用示例
result = tracking_error_decomposition(
portfolio_returns,
benchmark_returns,
factor_returns
)
print(f"系统风险贡献: {result['systematic_risk']:.4f}")
print(f"特质风险贡献: {result['idiosyncratic_risk']:.4f}")
print(f"系统风险占比: {result['systematic_pct']:.1f}%")
小技巧:实际应用中,我一般会用5-10个常见因子(市值、估值、动量、波动率等)。因子太少,分解不干净;因子太多,容易过拟合。
3.4 知识体系结构图
下面这张图展示了跟踪误差分解的完整逻辑:
3.5 避坑指南
我曾经踩过一个坑,分享给大家:
我曾经在分解时只用了3个因子,结果系统风险占比只有20%,我以为组合很安全。后来加了行业因子,系统风险占比直接飙到70%。
教训是:因子选择要全面。少选因子会低估系统风险,让你误以为组合很分散。
另外,还有几个常见问题:
- 频率问题:用日频数据分解,和用周频数据分解,结果可能差很多。我建议至少用日频,样本量够大。
- 因子相关性:因子之间如果有强相关性,分解结果会不稳定。可以考虑用正交化处理。
- 时间窗口:滚动窗口太短(比如20天),分解结果噪声大;太长(比如252天),又反应太慢。我个人习惯用60-120天。
3.6 实际案例
去年我帮一个指数增强产品做诊断,他们的跟踪误差是2.3%。分解结果如下:
| 风险来源 | 贡献值(年化) | 占比 |
|---|---|---|
| 系统风险(行业+风格) | 1.85% | 80.4% |
| 其中:行业因子 | 1.20% | 52.2% |
| 其中:风格因子 | 0.65% | 28.2% |
| 特质风险 | 0.45% | 19.6% |
| 总跟踪误差 | 2.30% | 100% |
看到这个结果,问题就很清楚了——行业配置是主要矛盾。后来我们做了行业中性化处理,跟踪误差降到了1.1%。
我的建议:做分解时,不仅要看系统风险和特质风险的占比,还要进一步拆开系统风险,看看是哪个因子在作祟。这样调优才有方向。
好了,这一章的核心就这些。记住一句话:跟踪误差不可怕,可怕的是不知道它从哪来。下一章我们聊聊怎么用这些分解结果来做真正的调优。