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 核心逻辑流程图

下面这张图,是我自己画的一个跟踪误差计算流程。你跟着走一遍,思路就清晰了。

跟踪误差计算核心流程 输入:收益率数据 步骤1:计算每日超额收益 R_excess = R_portfolio - R_benchmark 步骤2:计算超额收益标准差 TE_daily = std(R_excess, ddof=1) 是否年化? TE_annual = TE_daily × √252 输出:日度跟踪误差 输出:跟踪误差值

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