
1. 归因模型到底在回答什么问题1.1 从一场投研复盘说起每次季末或者年终基金团队内部复盘的时候总会被领导或者渠道问一个看起来很基础的问题这期产品业绩不错到底是靠什么赚的钱你要是一句我们配置能力强那就等着被追问到哑火。到底是仓位押对了行业还是个股选得好是赶上了风格切换还是纯粹因为基准里权重大的板块涨了你碰巧也在里面没有量化拆分讲不清楚这个问题后续的考核、激励、市场宣传全是糊涂账。绩效归因Performance Attribution干的就是这件事把组合相对基准的超额收益拆解到可解释、可验证、可追溯的来源上。而在这一堆归因工具里最经典、最通用、几乎被全行业当成默认起点的就是Brinson归因模型。这篇文章主要聊Brinson模型的多期版本。单期大家多少都会用但一旦把期限拉长到季度、年度复利效应会直接让逐期简单加总的结果失真。怎么处理多期累积的问题这里面的门道恰恰是很多拿现成系统的人没有深究过的。不管你是在私募、公募、保险资管做基金研究还是在FOF、投顾团队做产品绩效分析这套方法都属于必须会、最好还能讲清楚原理的基本功。看完这篇文章你至少能搞明白Brinson到底怎么拆收益、多期归因为什么难、实际落地时数据怎么备、代码怎么写、踩坑怎么排。1.2 Brinson模型的思想骨架先退一步想一个问题一个组合跑赢基准超额收益从哪来无非三条路。第一条你比基准多配了涨得好的资产或者少配了跌得多的资产这叫资产配置效应Allocation Effect。第二条同样在某个资产类别里你选的品种比基准里的品种涨得多这叫选股效应Selection Effect。第三条两者叠加产生的交叉影响比如你超配了一个板块同时在板块里还选出了超额品种两个动作的收益会互相放大这叫交互效应Interaction Effect。Brinson先生以及Hood、Beebower、Fachler等人在1986年前后提出的模型就是把这三种效应从组合收益里分离出来。它的好处在于逻辑简单、数据需求相对可控、结果可解释性强。具体拆解时我们需要两组信息。第一组是组合的权重和收益第二组是基准的权重和收益。放在某个资产维度上比如行业、板块、风格最粗粒度可以是股债我们能算出整个组合相比基准到底赢在哪一步。这里有个细节需要先说明模型里的收益可以是资产类别的收益可以是行业的收益也可以是底层股票/债券个券的收益。维度取决于你想回答什么问题。要想讲清楚行业配置贡献了多少就用行业维度归因要想讲清楚个券选择贡献了多少就用底层持仓维度归因。后面实战部分我会展开讲数据粒度的问题。单期比如一个月的Brinson归因是整套体系的地基。地基打不牢多期延展全是歪的。1.3 单期公式先把手里的牌数清楚我们先约定符号后面所有讨论都基于这套记号( R_p )组合在某个单期内的收益率( R_b )基准在该期内的收益率( w_{i,p} )组合中资产 ( i ) 的权重( w_{i,b} )基准中资产 ( i ) 的权重( r_{i,p} )组合中资产 ( i ) 在该期内的收益率( r_{i,b} )基准中资产 ( i ) 在该期内的收益率那么[ R_p \sum_i w_{i,p} \cdot r_{i,p}, \quad R_b \sum_i w_{i,b} \cdot r_{i,b} ]超额收益就是两者之差。Brinson-BHBBrinson, Hood, Beebower, 1986有两个基础分解版本最常用的BHB形式是配置效应( A_i (w_{i,p} - w_{i,b}) \cdot r_{i,b} )选股效应( S_i w_{i,b} \cdot (r_{i,p} - r_{i,b}) )交互效应( I_i (w_{i,p} - w_{i,b}) \cdot (r_{i,p} - r_{i,b}) )三者加起来恰好等于 ( w_{i,p} \cdot r_{i,p} - w_{i,b} \cdot r_{i,b} )也就是该资产对超额收益的贡献。把全部分类加起来正好是 ( R_p - R_b )。数学上严丝合缝一分钱都不差。实际使用中更多人用的是Brinson-FachlerBF变体配置效应( A_i (w_{i,p} - w_{i,b}) \cdot (r_{i,b} - R_b) )选股效应( S_i w_{i,p} \cdot (r_{i,p} - r_{i,b}) )BF版本的逻辑更贴近投资语义如果你超配了一个资产但这个资产本身跑输整个基准相对 ( R_b )那你其实不该因为超配它而计功。所以配置效应要看相对基准收益的偏离。选股效应则直接用组合实际权重去放大因为你在该资产上的仓位确实参与了选股超额。至于交互效应BF有时候合并进选股效应有时候单独列一行。后面我会专门说这个拆不拆的坑。总的来说单期模型只要数据干净计算半小时内就能搞定。真正的麻烦在多期。2. 从单期到多期跨过复利这道坎2.1 为什么直接加总会失真假设我们要做半年度的归因手头有6个月的单期Brinson结果。一个最朴素的念头是把每个月算出来的配置效应、选股效应分别加总不就是半年度的配置贡献、选股贡献吗听起来没毛病但实际算出来会发现一个尴尬的事实加总后的三项效应之和不等于半年度的真实超额收益。原因在于复利。组合不是每个月重新发牌的独立赌局上个月的收益会变成下个月的本金。把各期分成相加忽略的是跨期的滚动效应和路径依赖。换句话说你6个月分别赚5%、5%、5%、5%、5%、5%和前面几个月大赚后面几个月大亏即便月度算术平均一样最终组合财富完全不同但逐期效应直接相加的手法会对这两种路径给出一样的归因答案。这当然不合理。再举个更通俗的例子。你连续两个月的超额收益都是2%简单相加是4%。但实际的几何超额收益是 ( (1.02 \times 1.02 - 1) ) 对基准收益做同样处理后相减这个数值往往不是4%。基准本身也在复利它滚动的路径和组合滚动的路径不一样。归因里每一分钱的效应都得放到复利链条里去对齐。所以多期归因的核心问题变成怎么在保持各效应相加等于超额收益这一约束的前提下把跨期复利的影响合理地分摊回每一期、每一个效应上。这里要注意没有唯一的正确答案。多期归因本质上是一个分配问题——把已经实现的总超额收益按某种规则追溯给不同来源。规则不同结果就不同。行业里流传着好几种算法比如Carino调整、Menchero优化系数、几何归因、修正后内部收益率法Modified BAI、逐日归因汇总法等等。我不会把每种都铺开讲重点说两条最典型、也最容易在Excel或者Python里自己实现的路线Carino算术调整法以及几何归因法。2.2 Carino调整给每期效应称重Carino1999提出的思路是既然各期简单相加不等于总超额收益那我干脆给每一期算一个权重系数把各期效应修正到同一个复利口径下让调整后的各期效应加起来正好等于区间的总超额收益。单期 ( t ) 的Carino系数 ( k_t ) 定义为[ k_t \frac{\ln(1R_t) - \ln(1B_t)}{R_t - B_t} ]如果 ( R_t B_t )分母为零这时定义 ( k_t \frac{1}{1R_t} )。整个考察区间汇总后的总系数 ( K ) 为[ K \frac{\ln(1R) - \ln(1B)}{R - B} ]其中 ( R )、( B ) 分别是整个区间的组合累计收益和基准累计收益。然后每一项单期效应配置、选股、交互都乘以 ( k_t / K )再把所有期加总得到的就是多期累计视角下的效应值。数学上可以证明这样加总出来的三项效应之和恰好等于区间总超额收益 ( R - B )。Carino调整的优点非常实在它不改变单期归因的内在逻辑运算量小而且能完美适配任何已有的单期Brinson结果。也就是说如果你现在系统里已经有月度归因了想快速升级成年度累计口径加一层Carino调整是最省事的路径。不过它也有一个让人不太舒服的瑕疵( k_t ) 对所有效应一视同仁。同一个系数会缩放配置效应、选股效应、交互效应但跨期复利对不同性质的效应影响其实不完全一样。不过在实际分析层面这个瑕疵通常不致命行业里用得很广。2.3 几何归因的另一条路算术归因的基准公式是相减组合收益减去基准收益再把差值拆开。几何归因则换成相除把每一期的超额收益表达成 ( (1R_t)/(1B_t) - 1 )然后在多期里做连乘整个过程相当于把超额收益放在复合增长率的意义下去分解。用公式表达就是[ 1 R \prod_t (1 R_t), \quad 1 B \prod_t (1 B_t) ]整个区间的几何超额收益[ G \frac{1R}{1B} - 1 ]它在每一期 ( t ) 里也可以分解成配置和选股两部分只要把单期的Brinson效应放到相对收益比的框架里处理。最后把所有期的相对收益比连乘起来就能得到总的几何效应而且各个几何效应相乘等于 ( 1G )。几何归因的好处在于它天然具备可乘性能处理多期而不产生剩余项同时以比值来度量表现在跨期比较时更稳定。缺点是它不像算术归因那样直观——大家习惯了说超额收益是百分之几而不是超额收益是你的1.02倍。我在实际工作中对外汇报通常用Carino调整后的算术结果因为访谈对象销售、渠道、投资者更习惯3%配置贡献1%选股贡献4%超额收益这种说法。内部做投研复盘时我偶尔会同时看几何口径避免单一算法带来的路径误导。3. 实操搭建一套可落地的Brinson多期归因流程3.1 数据准备没有干净的持仓数据一切都是白搭这部分是我想着力强调的。很多人在模型原理上钻研得很透一上手真实数据就翻车因为Brinson对数据质量的要求其实非常高。你需要准备的最核心数据有四张表组合每日或每期各资产/行业的权重组合每日或每期各资产/行业的收益率基准对应口径的权重基准对应口径的收益率注意一个关键点组合权重和收益率要来自同一套持仓口径。什么叫同一套口径就是这个月月初的持仓权重必须匹配这一个月的实际收益结果。很多人用月末持仓去对当月收益结果归因出来的选股效应完全失真因为月末持仓已经是市场波动之后的结果了。行业上最常用的做法是期初比如月初权重对应整个期间的收益。也就是说归因区间是上月最后一个交易日收盘的持仓对应本月整月的收益。如果你的组合在月内频繁调仓或者用T1确认、涨跌停冻结之类那就需要把颗粒度降到日做逐日归因再汇总。另外还有个容易被忽略的细节收益率的计算口径要统一。组合里某只股票如果遇到分红除息是用包含分红再投资的全收益还是简单的价格收益基准指数通常给的是全收益或者价格收益率务必保证两边口径一致否则所有选股效应都失真。我自己做过的项目里数据清洗通常占去六成的时间模型计算只占四成甚至更少。你如果只打算做一次性的历史复盘可以手动小样本验证如果要做常态化报告强烈建议把数据校验逻辑写成自动化脚本尤其要核对权重之和是否为100%、收益是否落在合理范围内、持仓中的现金项是不是被剔除了。3.2 基准构建与区间切分基准不是随便拿个指数就完事。Brinson模型里 ( w_{i,b} ) 和 ( r_{i,b} ) 必须和组合的资产分类维度保持一致。也就是说如果你把组合按申万一级行业分类那么基准也必须是同一套申万一级行业的权重和收益。实际做FOF或者多资产组合归因时基准往往是自建的混合基准。比如40%股票指数、60%债券指数那么整个区间的基准权重就要按固定比例或者再平衡日调整去拆分。这里有个非常常见的坑混合基准在区间内如果做再平衡权重是期初锁定还是期末锁定会显著影响基准收益从而影响超额收益为正是负。另一个问题是区间切分。做多期归因你要先确定子期间的颗粒度。按月拆逐月归因再Carino汇总还是按周理论上颗粒度越小路径依赖处理得越精确但也越费数据。实际操作中对于月频调仓的组合月度粒度就够用了对于高换手产品至少要做到周频。区间切分的原则只有一个在数据可得的前提下尽量模拟真实调仓周期。不要为了好看硬把高换手组合按季度归因那个结果拿去讲给投资经理听肯定会被驳回来。3.3 核心计算逻辑与代码实现我直接给出一个最基础但能直接改着用的Python实现。这里假设输入数据是长表每一行代表某个日期、某个资产分类、组合权重、组合收益、基准权重、基准收益。import pandas as pd import numpy as np def brinson_single_period(df): 单期Brinson-BF归因 df必须包含列: asset, wr_p, r_p, wr_b, r_b df[alloc] (df[wr_p] - df[wr_b]) * (df[r_b] - df[r_b].sum()) df[sele] df[wr_p] * (df[r_p] - df[r_b]) df[inte] (df[wr_p] - df[wr_b]) * (df[r_p] - df[r_b]) return df.groupby(asset)[[alloc, sele, inte]].sum()这段代码对应BF形态配置效应使用相对基准总收益的偏离选股效应使用组合权重放大个股超额。注意df[r_b].sum()在长表中就是基准该期总收益前提是你每期只跑一次该函数。单期算完后接下来做Carino多期调整。你需要先把组合和基准的累计净值算出来然后算总区间的 ( R, B )再逐期算 ( k_t )、汇总 ( K )。def carino_adjustment(period_results, R, B): period_results: 每一期包含组合收益/基准收益的单期归因结果 R/B: 整个区间组合累计收益/基准累计收益 K (np.log(1 R) - np.log(1 B)) / (R - B) weights [] for idx, row in period_results.iterrows(): r_t row[r_p] b_t row[r_b] if abs(r_t - b_t) 1e-12: k_t 1 / (1 r_t) else: k_t (np.log(1 r_t) - np.log(1 b_t)) / (r_t - b_t) weights.append(k_t / K) period_results[carino_w] weights return period_results[[alloc, sele, inte]].multiply( period_results[carino_w], axis0 ).sum()这段逻辑不复杂核心就是把每个月的三效应乘以 ( k_t / K )求和。稍微解释下为什么要用对数差除以算术差。当组合和基准收益差距很小或者接近0时( k_t ) 会很接近 ( 1/(1R_t) )而当超额收益明显时这个比值会偏离单纯复利缩放可以理解为用连续复利视角来调整算术差异。它是整个Carino方法最关键的一步。如果数据是逐日粒度你可以用同样的逻辑做日度归因再逐日Carino汇总到月度这也是很多商业化系统内部用的标准方案。3.4 结果报表怎么把归因讲给别人听光算出来不够还得讲清楚。我的习惯是输出三张表。第一张是总览表。列包括区间超额收益、配置效应合计、选股效应合计、交互效应合计、其他/残差。就这么一行给管理层看。第二张是资产分类贡献表。按行业/资产类别展开每一行展示该类别对超额收益的贡献是来自配置还是选股。这张表要和投资经理一起看因为它能直接引发讨论医药明明配置超配了怎么配置贡献是负的第三张是逐期效应明细表。把每个月或周的三项效应列出来附带Carino系数。这张表主要供研究员交叉验证也是我发现数据问题最常用的入口。关于可视化一个小建议不要一上来就画最炫的堆积图。先把流量表讲清楚再配一个简单的累计效应曲线横轴时间纵轴累计配置/选股贡献比花里胡哨的桑基图有效得多。我自己见过太多团队在精美图表上花大力气结果基础计算错了被问两句就露馅。4. 实战中踩过的坑与排查技巧4.1 交互效应拆不拆口径先定死交互效应是个陈年旧账。有些报告里把它和选股效应合并有些单独列一行有些干脆不出现在报表里丢进其他。问题在于交互项的符号和大小受分类方式影响很大。行业越细交互项通常会越小行业越粗交互项往往会变大。如果你用申万一级行业超配医药并且选出了医药里的牛股这个超配×选股的叠加就很容易被单独拆出来记一大笔。投研团队如果不知道你这一项是拆出来还是藏进选股里对结果的理解会产生很大偏差。我在落地项目时硬性要求是输出报表里必须有明确的一行注释或者列注释写上用的是BHB形式单独交互项还是BF形式交互并入选股。并且在向投资经理汇报时第一时间说明这个口径。别小看这个举动它能省掉后面无数扯皮。4.2 现金与衍生品剩余项不一定等于模型错了现金在经典Brinson框架里是个麻烦。你如果直接把现金当做一个零收益资产纳入资产分类配置效应会合理但如果组合里有大比例保证金、期货、期权或者场外衍生品传统Brinson的线性拆分逻辑就不够用了。特别是期货它的收益来自保证金杠杆和基差变化权重的处理方式和现货完全不同。用期货做对冲的量化组合一味套Brinson模型会把择时效应和基差损益全都搅在一起归因结果基本没法看。我踩过的坑是某产品里有一段股指期货对冲仓位我当时简单地把期货列成衍生品资产权重设成保证金金额结果出来的选股效应和投资经理的真实操作完全对不上。后来我把对冲仓单独摘出来先对组合未对冲部分做Brinson再用一个独立科目记录对冲成本整个归因结果才回归合理。所以记住剩余项不等于模型错了很可能只是数据形态超出了Brinson的适用范围。这时候先分离非标资产而不是硬套公式。4.3 数据口径估值日、行业分类、权重漂移这三兄弟是数据坑里的高频之王。估值日不一致是最隐蔽的。托管行给的净值是15:00后预估或17:00确认券商结算数据可能是T1的收盘价和行情商给的价也偶有偏差。归因模型里同一只股票的收益如果两边差了0.1%在30个行业、几百只个券汇总后误差会被不均衡地灌进选股效应或者残差项方向甚至可能反转。行业分类见风使舵也是大问题。同一个公司在中信分类里是计算机在申万分类里可能是传媒。如果你用组合方提供的分类做组合端、用基准公司给的分类做基准端两边分类不一致那配置效应的解释就完全是空中楼阁。我这里只讲一个经验任何跨口径的数据拼接都要先做分类字典对齐宁可让样本量小一点也要保证两边分类一致。权重漂移指什么呢它指的是Brinson模型默认权重保持期初不变但实际操作中即便没有任何调仓市场涨跌也会让实际权重漂移。如果你拿期末权重去复算组合收益和真实收益之间必然差一段漂移收益。正规做法是严格用期初权重加期末真实收益或者逐日归因后汇总别用期末权重硬凑。4.4 手工验算的土办法就算所有逻辑都对程序算出来也难免出错。我推荐一个土办法手工构造一个两期、两个资产的最小实例期望结果你笔算都能推出来然后跑程序对比。比如期一股票组合权重60%、收益10%基准股票权重50%、收益8%债券组合权重40%、收益2%基准债券权重50%、收益4%。期二把股票/债券的权重和收益都改一下。你手工用BHB公式把两期配置、选股、交互算出来再用Excel搭一个Carino系数表最后把你的Python结果和Excel结果对一遍。这个验证流程很笨但几乎能兜住90%的编码错误。我每次接新数据源、改分类口径、升级代码版本之前都会保留这么一组金标准用例跑完自动比对不一致就立刻报错。这套用例花不了多少时间但长期省下来的查错时间非常可观。5. 模型的边界与后续可以怎么延伸Brinson归因模型最大的优点和最大的缺点其实是同一个它假设收益可以线性拆成配置、选股、交互三块。这个假设在大部分传统股债组合里都能很好工作但一旦涉及风格切换、行业轮动、宏观择时这些动态策略单靠Brinson就很难完全表达清楚。这些年我在实际项目里发现一个趋势监督机构不是专业的资产管理买方和FOF管理人已经越来越多地把归因拆成两层来做。上层用Brinson看资产配置和行业配置贡献下层再用多因子归因比如Barra模型看风格因子、行业因子的暴露贡献比如价值因子、成长因子、动量因子、质量因子等等。Brinson负责回答我在哪类资产上比基准多赚了多因子模型负责回答我在这个资产内部是靠市值还是靠估值赚钱。另一个方向是持仓穿透归因。很多FOF组合直接投资的是子基金子基金底层持仓并不透明这时你只能用子基金净值做回归型归因或者依赖子基金管理人披露的全持仓数据做穿透。这类场景下Brinson计算的前置数据要求会变得更加苛刻但也正是真正体现归因功力的时候。我个人这两年的体会是Brinson模型更像是一个优秀的沟通框架而不是一个完美的解释工具。它最大的价值是让投资经理、研究员、销售、客户坐到同一张桌子前用同一套语言讨论钱到底是怎么赚来的。只要这个目的达成了它略微粗糙的理论假设其实没那么重要。最后再分享一个自己在落地时坚持的做法任何归因报告在发布之前一定先让负责该产品的投资经理本人手动确认一遍配置贡献最大的三个行业和选股贡献最大的三个行业如果和他们的体感偏差超过一个量级那就不是模型问题而是数据问题。先查数据别急着改算法。顺序反了你会在一堆正确的公式里浪费整整一下午。