
gs-quant Brinson 归因拆解3 个分项定位你的超额收益从哪来【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant月底复盘老板问这 4.7 个点超额里哪部分是行业配置、哪部分是选股——你没做过 Brinson 归因第一反应是导出一堆收益数据、在表格里手工对齐权重。这篇文章用一个最小例子串起来演示如何用 gs-quant 的 Brinson 归因把组合超额收益拆成配置、选择、交互三个分项。读完你手里会有一段能直接跑的脚本并且知道输出里该先看哪一项。Brinson 归因的核心思想一句话就能说完把组合和基准放进同一套行业桶里对先看你把钱放在了哪些行业配置再看行业里选的具体标的赢没赢选择剩下两者的乘积交互。三项加起来恰好等于超额收益没有一个无法解释的残差项这也是它比直接算 beta更能说服人的地方。先跑通最小可用示例下面这段代码不需要接任何数据服务generate_series生成 4 条模拟价格序列科技/医疗 × 组合/基准returns转成算术收益率sum_负责跨行业加总。实际项目里你只需要把这 4 条序列换成真实的持仓与指数数据后面的三项计算原样不变。之所以能脱机跑是因为 timeseries 里的这些函数本质是对 pandas Series 的操作不依赖会话和数据源。import pandas as pd from gs_quant.timeseries import generate_series, returns, sum_ # 模拟 2 个行业科技/医疗各生成组合与基准的价格序列 pf_tech, pf_health generate_series(252), generate_series(252) bm_tech, bm_health generate_series(252), generate_series(252) # 价格 → 算术收益率 r {pf_tech: returns(pf_tech), pf_health: returns(pf_health), bm_tech: returns(bm_tech), bm_health: returns(bm_health)} # 固定权重组合 70/30基准 50/50 pf_w, bm_w [0.7, 0.3], [0.5, 0.5] # 三项各自是“权重差 × 收益”的逐日贡献再跨行业累加 alloc sum_([(pf_w[0]-bm_w[0]) * r[bm_tech], (pf_w[1]-bm_w[1]) * r[bm_health]]) select sum_([bm_w[0] * (r[pf_tech]-r[bm_tech]), bm_w[1] * (r[pf_health]-r[bm_health])]) inter sum_([(pf_w[0]-bm_w[0]) * (r[pf_tech]-r[bm_tech]), (pf_w[1]-bm_w[1]) * (r[pf_health]-r[bm_health])]) result pd.DataFrame({allocation: alloc, selection: select, interaction: inter}).dropna() print(result.tail(3))运行后你会看到一个三列的 DataFrame列名是 allocation、selection、interaction。注意每行数值不是当天的贡献而是截至当天的累计归因——sum_不指定窗口时等价于从起点累加。取最后一行三项之和就是全周期的超额收益它应当接近组合总收益与基准总收益的算术差首行数值接近 0 是正常的因为收益率序列的第一个点是空值或 0。公式里的行业就是图中的深色节点。在 gs-quant 里基准通常是一个Index它的树形结构中间层是行业Underlier、叶子是成分股Constituent。归因做的事就是把浅色节点上的收益差逐层聚合到深色节点上再和组合比对。拆解每一步在做什么数据对齐先把日期和行业对上这一步解决的是组合某天有价、基准没价时公式直接炸掉的问题。先取日期交集再把权重、收益放进行业顺序一致的容器里后面的广播运算才不会错位。common pf_tech.index.intersection(bm_tech.index) for k in r: r[k] r[k].loc[common] pf_w_s pd.Series([0.7, 0.3], index[tech, health]) bm_w_s pd.Series([0.5, 0.5], index[tech, health])把intersection改成union且不做填充缺失日期会有一侧是 NaN三项全被污染反过来把样本砍到只有一周再跑你会看到交互项的波动明显变大——窗口越短归因越噪月报级分析至少取整月。三项收益分别怎么算配置、选择、交互各一行这一步背后的数学不复杂直接映射到 pandas 广播上。配置项是权重差 × 基准收益衡量你钱放对了行业没有选择项是基准权重 × 收益差衡量行业里选对了个股没有交互项是两者之积衡量两个效应有没有互相放大。pf_ret pd.DataFrame({tech: r[pf_tech], health: r[pf_health]}) bm_ret pd.DataFrame({tech: r[bm_tech], health: r[bm_health]}) alloc ((pf_w_s - bm_w_s) * bm_ret).sum(axis1) # 配置 select (bm_w_s * (pf_ret - bm_ret)).sum(axis1) # 选择 inter ((pf_w_s - bm_w_s) * (pf_ret - bm_ret)).sum(axis1) # 交互把固定值pf_w_s换成逐日变化的权重 DataFrame这几行就变成能捕捉每次调仓效应的滚动归因改用 22 天窗口则可以换成sum_([各分项的序列列表], 22)窗口长度直接决定归因观察期改大它结果会更平滑、对单日的敏感度下降。结果怎么读哪一项是主要贡献最后把三项堆成一行看占比。out pd.DataFrame({allocation: alloc, selection: select, interaction: inter}).iloc[-1] print(out, \n总超额收益:, out.sum().round(4))读法上交互项正常应该是三者里量级最小的。如果它比配置项还大说明那段时期你的权重差和收益差方向一致、互相放大了这不是模型算错而是说明那个月下了双重注选择项为负则意味着你在行业里选的个股比基准还差行业看对了也会被选股拖后腿。常见坑与边界情况权重没归一化某一侧权重之和不是 1 时三项加总对不上真实超额收益而且偏差随时间拉长而累积求和前先按行做w / w.sum()归一。组合和基准的行业口径不同同一只股票在一边叫科技、另一边叫信息配置项量出来的其实是分类差异而不是真实决策先统一到同一套分类如 GICS 一级再算。基准期间内调样基准做再平衡、成分股上新下市后期初权重无法代表后半段按调样日切分两段分别算或每段用段初的权重。收益率类型混用Brinson 拆解在算术收益下才严格成立对数收益和简单收益混在一起会让三项多出个第三残差调用returns()时统一用简单收益。滚动窗口数据不足sum_(x, 22)在数据不足 22 天时只返回已有值的和前 21 天看起来正常实则偏小在报表里标注窗口长度或直接砍掉前 21 天。什么时候别用它这套方法的前提是组合长期多头、满仓股票。一旦有杠杆、空头或衍生品权重可能大于 1 甚至为负配置项会被杠杆效应污染数字不再代表配置能力。行业数量很少两三个时拆解基本退化成纯选股问题配置项的参考价值有限。另外如果你想解释的是风险而不是收益——比如 VaR 里有多少来自哪个行业——直接看 gs-quant 的因子风险模型那才是风险归因该用的工具。到这里你可以把任何多头股票组合的超额收益拆成三个分项直接放进月报换成真实数据时只需替换那 4 条模拟序列为接口拉到的持仓权重和行业收益三项逻辑不变。窗口参数或日期对齐卡住时先看 gs_quant/timeseries/statistics.py 里的函数签名全部计量函数的清单在 docs/timeseries.rst 也能查到。【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考