1. 这节究竟解决什么实际问题?¶
4.10 策略业绩归因分析(Performance Attribution) 的具体任务是:把策略收益拆为市场、行业、选股或因子来源,避免只看总收益。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
理解收益率、仓位、交易成本与基本技术指标。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
默认固定历史样本。历史数据只能说明过去在既定假设下发生了什么,不能承诺未来收益。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 4.10 策略业绩归因分析(Performance Attribution) 的输入、计算步骤、输出和局限;尤其能说明:把策略收益拆为市场、行业、选股或因子来源,避免只看总收益。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
回测按照明确的成交和成本假设重放历史数据。它不能完整复现未来流动性、订单竞争和系统故障。
策略收益 = 滞后仓位 × 资产收益 − 换手 × 成本;每一项都要有明确单位。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
回测框架替你管理订单和记录,不会自动判断数据、成本或策略假设是否合理。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“把策略收益拆为市场、行业、选股或因子来源,避免只看总收益。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
信号未滞后、把手续费只扣一次、忽略空仓收益、年化频率错配、将净值曲线当显著性证明。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
检查未来函数、幸存者偏差、重复测试集调参、成交可得性和失败区间;必须报告基线。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/04_backtesting/10_performance_attribution_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/04_backtesting/10_performance_attribution_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Advances in Financial Machine Learning(Lopez de Prado)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
完成本模块后,进入下一模块的概览页,并把本节的检查清单带到后续研究中。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
print('Libraries loaded')
Libraries loaded
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. 为什么需要业绩归因?¶
一个基金今年跑赢基准 5%,原因可能是:
- 选对了行业(配置了更多科技股,科技股整体暴涨)
- 选对了个股(在科技行业里选了龙头,跑赢了科技行业平均)
- 以上两者的叠加(交互效应)
Brinson-Hood-Beebower 模型(1986) 将超额收益精确分解为这三个来源。
# 模拟 5 个行业的基准权重、组合权重、基准收益率、组合收益率
np.random.seed(42)
sectors = ['科技','金融','消费','医药','工业']
# 基准权重(如沪深300的行业分布)
w_b = np.array([0.25, 0.30, 0.20, 0.15, 0.10])
# 组合权重(基金经理的超配/低配)
w_p = np.array([0.35, 0.20, 0.25, 0.15, 0.05])
# 基准各行业收益率
r_b = np.array([0.15, 0.05, 0.08, 0.12, 0.03])
# 组合各行业收益率(选股效果)
r_p = np.array([0.18, 0.06, 0.07, 0.14, 0.04])
df = pd.DataFrame({'行业': sectors, 'w_b':w_b, 'w_p':w_p, 'r_b':r_b, 'r_p':r_p})
df.set_index('行业', inplace=True)
print(df.round(3).to_string())
w_b w_p r_b r_p 行业 科技 0.25 0.35 0.15 0.18 金融 0.30 0.20 0.05 0.06 消费 0.20 0.25 0.08 0.07 医药 0.15 0.15 0.12 0.14 工业 0.10 0.05 0.03 0.04
2. BHB 归因公式¶
$$\text{行业配置效应} = (w^p_i - w^b_i) \times (r^b_i - R^b)$$ $$\text{个股选择效应} = w^b_i \times (r^p_i - r^b_i)$$ $$\text{交互效应} = (w^p_i - w^b_i) \times (r^p_i - r^b_i)$$
其中 $R^b = \sum_i w^b_i r^b_i$ 是基准总收益率。
R_b = (df['w_b'] * df['r_b']).sum() # 基准总收益
R_p = (df['w_p'] * df['r_p']).sum() # 组合总收益
df['行业配置效应'] = (df['w_p'] - df['w_b']) * (df['r_b'] - R_b)
df['个股选择效应'] = df['w_b'] * (df['r_p'] - df['r_b'])
df['交互效应'] = (df['w_p'] - df['w_b']) * (df['r_p'] - df['r_b'])
df['总效应'] = df['行业配置效应'] + df['个股选择效应'] + df['交互效应']
total = df[['行业配置效应','个股选择效应','交互效应','总效应']].sum()
print(f'组合总收益: {R_p:.4f} ({R_p:.2%})')
print(f'基准总收益: {R_b:.4f} ({R_b:.2%})')
print(f'超额收益: {R_p-R_b:.4f} ({R_p-R_b:.2%})')
print(f'\n归因分解:')
print(f' 行业配置效应: {total["行业配置效应"]:.4f}')
print(f' 个股选择效应: {total["个股选择效应"]:.4f}')
print(f' 交互效应: {total["交互效应"]:.4f}')
print(f' 合计: {total["总效应"]:.4f} (应≈ {R_p-R_b:.4f})')
组合总收益: 0.1155 (11.55%) 基准总收益: 0.0895 (8.95%) 超额收益: 0.0260 (2.60%) 归因分解: 行业配置效应: 0.0125 个股选择效应: 0.0125 交互效应: 0.0010 合计: 0.0260 (应≈ 0.0260)
# 可视化行业级别归因
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 各行业配置超配/低配
bar_colors = ['green' if x > 0 else 'red' for x in (df['w_p']-df['w_b'])]
axes[0].bar(df.index, (df['w_p']-df['w_b'])*100, color=bar_colors, alpha=0.8)
axes[0].axhline(0, color='black', lw=0.8)
axes[0].set_title('各行业超配(+)/低配(-)百分点')
axes[0].set_ylabel('权重差 (%)')
# 各行业归因贡献
width = 0.25
x = np.arange(len(df))
for i, (col, color) in enumerate(zip(['行业配置效应','个股选择效应','交互效应'], ['steelblue','green','orange'])):
axes[1].bar(x + i*width, df[col]*100, width, label=col, color=color, alpha=0.8)
axes[1].axhline(0, color='black', lw=0.8)
axes[1].set_title('各行业三效应归因分解')
axes[1].set_xticks(x + width)
axes[1].set_xticklabels(df.index, rotation=15)
axes[1].set_ylabel('贡献收益 (%)')
axes[1].legend()
plt.suptitle('BHB 业绩归因报告', fontsize=13)
plt.tight_layout()
plt.show()
练习¶
- 设计一个季度再平衡的策略(每季度更换行业权重),用 BHB 框架对 4 个季度分别做归因,哪季度选股最强?
- 引入「纯选股效应」(Pure Stock Selection)= 个股选择效应 + 交互效应,哪个行业的选股贡献最大?
- 查阅 Carhart 四因子归因框架,理解如何将 BHB 行业归因扩展到因子层面(市场、规模、价值、动量)。
下一节 → ../05_portfolio/08_dynamic_asset_allocation.ipynb