2. 偏离度计算:跟踪误差(Tracking Error)的数学公式与Python实现
各位同学,咱们今天来啃一块硬骨头——跟踪误差。说实话,这玩意儿在量化圈里,就像厨师手里的盐,看着不起眼,但少了它,菜就完全不对味了。
我刚开始做指数增强策略那会儿,总觉得只要选股够牛,收益跑赢指数就行。结果呢?有一次回测数据漂亮得不行,实盘一跑,客户直接打电话来问:「你们这基金怎么跟指数走势差这么多?」嗯,从那以后,我再也不敢小看跟踪误差了。
2.1 跟踪误差到底在算什么?
说白了,跟踪误差就是衡量你的投资组合跟基准指数之间「步调不一致」的程度。它不是看收益差了多少,而是看收益差的波动有多大。
举个例子你就明白了:
- 如果你的组合每天都比指数多赚0.1%,这叫稳定的超额收益,跟踪误差很小
- 如果今天比指数多赚2%,明天又少赚1.5%,这种上蹿下跳,跟踪误差就大了
你想想看,作为基金经理,客户把钱交给你,是希望你能稳稳地复制指数走势,再稍微多赚一点。要是你的组合走势跟指数完全不像,那客户干嘛不直接买ETF呢?
2.2 数学公式:别怕,就两个核心
跟踪误差的数学定义其实不复杂。我个人习惯把它分成两种场景:
2.2.1 样本跟踪误差(历史数据)
这是最常用的,公式长这样:
TE = σ(R_p - R_b)
其中:
- R_p 是组合收益率
- R_b 是基准收益率
- σ 是标准差
展开来写就是:
TE = sqrt( Σ(R_p,i - R_b,i - μ_diff)² / (n-1) )
这里 μ_diff 是超额收益的均值。说白了,就是先算每天的收益差,再算这些差值的标准差。
关键点:跟踪误差是标准差,不是平均差。它衡量的是「波动」,不是「大小」。
2.2.2 预测跟踪误差(前瞻性)
这个在实盘中更有用。公式是:
TE_pred = sqrt( w' * Σ * w )
其中:
- w 是组合相对基准的权重偏离向量
- Σ 是资产收益率的协方差矩阵
我在项目中遇到过这种情况:用历史数据算出来的跟踪误差只有0.5%,但用预测公式一算,发现某些行业权重偏离太大,实际风险远高于历史表现。这就是为什么我建议两个公式都要掌握。
2.3 Python实现:从零开始写
光说不练假把式。咱们直接上代码,我习惯用最直观的方式写,方便你理解每一步在干什么。
2.3.1 准备数据
import numpy as np
import pandas as pd
# 模拟数据:假设我们有252个交易日(一年)
np.random.seed(42)
n_days = 252
# 基准日收益率(年化12%,日波动0.8%)
benchmark_returns = np.random.normal(0.12/252, 0.08/np.sqrt(252), n_days)
# 组合日收益率(比基准多赚一点,但波动也大一点)
portfolio_returns = benchmark_returns + np.random.normal(0.05/252, 0.01/np.sqrt(252), n_days)
# 放进DataFrame方便处理
df = pd.DataFrame({
'benchmark': benchmark_returns,
'portfolio': portfolio_returns
})
2.3.2 计算跟踪误差
def calculate_tracking_error(portfolio_returns, benchmark_returns, annualize=True):
"""
计算跟踪误差
参数:
portfolio_returns: 组合收益率序列
benchmark_returns: 基准收益率序列
annualize: 是否年化
返回:
tracking_error: 跟踪误差
"""
# 计算每日超额收益
excess_returns = portfolio_returns - benchmark_returns
# 计算超额收益的标准差(样本标准差,ddof=1)
te_daily = np.std(excess_returns, ddof=1)
if annualize:
# 年化:乘以sqrt(252)
te_annual = te_daily * np.sqrt(252)
return te_annual
else:
return te_daily
# 计算
te_annual = calculate_tracking_error(df['portfolio'], df['benchmark'])
print(f"年化跟踪误差: {te_annual:.4f} ({te_annual*100:.2f}%)")
小提示:为什么年化要乘以sqrt(252)?因为方差是累加的,标准差是方差的平方根。252个交易日,方差乘以252,标准差就乘以sqrt(252)。这个逻辑跟波动率年化一模一样。
2.3.3 进阶版:滚动跟踪误差
只看一个数字太笼统了。我建议你算滚动跟踪误差,看看它随时间怎么变化。
def rolling_tracking_error(portfolio_returns, benchmark_returns, window=60):
"""
计算滚动跟踪误差
参数:
window: 滚动窗口大小(默认60个交易日)
"""
excess = portfolio_returns - benchmark_returns
rolling_te = excess.rolling(window=window).std(ddof=1) * np.sqrt(252)
return rolling_te
# 计算60日滚动跟踪误差
df['rolling_te'] = rolling_tracking_error(df['portfolio'], df['benchmark'])
# 看看结果
print("滚动跟踪误差统计:")
print(f" 均值: {df['rolling_te'].mean():.4f}")
print(f" 最大值: {df['rolling_te'].max():.4f}")
print(f" 最小值: {df['rolling_te'].min():.4f}")
2.4 核心逻辑流程图
下面这张图,是我自己画的一个跟踪误差计算流程。你跟着走一遍,思路就清晰了。
2.5 实战中的坑与避坑指南
代码写完了,但实战中还有几个地方容易翻车。我一个个说。
2.5.1 频率问题
日度数据算出来的跟踪误差,跟周度、月度数据算出来的,完全不是一个量级。我曾经见过一个团队,用月度数据算跟踪误差,然后拿去跟同行日度数据的结果比,闹了笑话。
注意:比较跟踪误差时,必须确保计算频率和年化方式一致。日度数据年化乘√252,周度乘√52,月度乘√12。
2.5.2 数据对齐
组合和基准的收益率数据,日期必须严格对齐。少一天、多一天,结果都会偏。我习惯用pandas的merge或join,确保索引一致。
# 正确的对齐方式
aligned = pd.DataFrame({
'portfolio': portfolio_returns,
'benchmark': benchmark_returns
}).dropna() # 删除任何一方缺失的数据
2.5.3 极端值处理
市场剧烈波动时,某一天的收益差可能特别大。这会导致跟踪误差被拉高。我个人习惯先做winsorize(缩尾处理),或者用滚动窗口来平滑。
我的经验:如果发现跟踪误差突然飙升,先别急着调仓。检查一下是不是某只股票分红、停牌或者发生了极端事件。有时候,这不是策略的问题,是数据的问题。
2.6 小结一下
跟踪误差的计算,说白了就是三步:算差值、算标准差、年化。但真正难的不是公式,而是理解这个数字背后的含义。
0.5%的跟踪误差意味着什么?意味着你的组合跟指数走势高度一致,超额收益很稳定。3%的跟踪误差呢?说明你的组合跟指数已经「各走各的路」了,这时候客户可能会问:「我买的是指数基金还是主动管理基金?」
嗯,今天就到这儿。代码你拿回去跑一跑,把数据换成你自己的组合和基准,看看跟踪误差是多少。心里有个数,后面讲偏离度修复的时候,你才知道该往哪个方向调。
公众号:蓝海资料掘金营,微信deep3321