3、风险因子归因:Barra模型框架、因子暴露计算、因子收益率估计

好,咱们进入第三章。风险因子归因,说白了就是回答一个问题:我的收益到底是从哪来的?

是运气好押对了大盘?还是真的选股能力强?又或者是偷偷承担了某种你都没意识到的风险?

我个人习惯把这一步叫做「拆解收益的黑箱」。Barra模型就是那个最趁手的螺丝刀。今天咱们就把它拆开,看看里面到底怎么转的。

3.1 Barra模型框架:从CAPM到多因子

传统的CAPM模型,只用一个市场因子解释一切。你想想看,这合理吗?

当然不合理。两只股票,同一天涨了2%,原因可能完全不同。一只可能是因为大盘涨了,另一只可能是因为它属于「小盘股」风格,而当天小盘股集体爆发。

Barra模型的核心思想就是:把收益拆解到多个共同因子上

Barra模型的基本形式:

r_i = ∑(X_ik * f_k) + u_i

其中:

  • r_i:股票i的超额收益
  • X_ik:股票i在因子k上的暴露(因子载荷)
  • f_k:因子k的因子收益率
  • u_i:股票i的特质收益(残差)

嗯,这里要注意。Barra模型不是用来预测收益的,它是用来解释收益的。这是本质区别。

我在项目中遇到过一位同事,拿着Barra模型去预测明天涨跌,结果亏得一塌糊涂。后来我告诉他:这玩意儿是后视镜,不是望远镜。

常见的Barra风险因子包括:

  • 市场因子:大盘涨跌的影响
  • 规模因子:小盘股 vs 大盘股
  • 价值因子:低估值 vs 高估值
  • 动量因子:过去涨得好的 vs 跌得多的
  • 波动率因子:低波动 vs 高波动
  • 行业因子:不同行业的差异

下面这张图,是我自己画的一个Barra模型归因流程,你看一眼就明白了。

Barra风险因子归因流程 股票收益率 + 因子暴露 横截面回归:r = X * f + u (WLS加权最小二乘法) 因子收益率 f_k + 特质收益 u_i 风险归因 | 绩效归因 | 风险预算 | 组合优化

3.2 因子暴露计算:你的组合到底像谁?

因子暴露,也叫因子载荷。它衡量的是:你的组合对某个因子有多敏感

举个例子。如果你的组合里全是银行股,那它对「利率因子」的暴露就很高。如果利率一涨,你的组合大概率要跌。

计算因子暴露,通常有两种方式:

  1. 基于持仓的暴露:直接看持仓股票在因子上的暴露,然后加权平均。
  2. 基于时间序列的暴露:用组合收益率对因子收益率做回归,回归系数就是暴露。

我个人更推荐第一种。为什么?因为它更透明,你能清楚地知道风险来自哪只股票。

避坑指南:

我曾经犯过一个错误——直接用市值加权计算因子暴露。结果发现组合的「价值因子」暴露很高,但实际上是因为重仓了一只超低估值的股票。后来我改用「等权平均 + 中位数」来校验,才避免了被单只股票带偏。

下面是一个计算因子暴露的Python示例:

import pandas as pd
import numpy as np

# 假设我们有股票的因子数据
# 因子:市值、估值、动量
stock_factors = pd.DataFrame({
    'stock': ['A', 'B', 'C', 'D'],
    'size': [1.2, 0.8, 1.5, 0.6],   # 标准化后的市值暴露
    'value': [0.5, 1.3, 0.7, 1.1],  # 标准化后的估值暴露
    'momentum': [0.9, 0.4, 1.1, 0.8] # 标准化后的动量暴露
})

# 组合权重
weights = np.array([0.3, 0.2, 0.4, 0.1])

# 计算组合的因子暴露(加权平均)
portfolio_exposure = {}
for factor in ['size', 'value', 'momentum']:
    exposure = np.dot(stock_factors[factor], weights)
    portfolio_exposure[factor] = round(exposure, 3)

print("组合因子暴露:", portfolio_exposure)
# 输出:组合因子暴露:{'size': 1.13, 'value': 0.76, 'momentum': 0.91}

你看,代码其实很简单。但真正难的是:因子数据本身的质量

我见过有人直接用原始市值做暴露,结果大股票和小股票的暴露差了100倍。标准化处理是必须的——通常用Z-score或者分位数排名。

3.3 因子收益率估计:横截面回归的艺术

因子暴露算出来了,那因子收益率怎么估计?

答案是:横截面回归

具体来说,在每个时间点(比如每天),我们用所有股票的收益率对它们的因子暴露做回归。回归系数就是当天的因子收益率。

横截面回归的数学形式:

r_i = α + β_1 * X_i1 + β_2 * X_i2 + ... + β_K * X_iK + ε_i

其中β_k就是因子k的因子收益率。

这里有个关键细节:要不要加截距项?

我个人习惯不加截距项。为什么?因为截距项会吸收掉市场因子的影响,导致市场因子收益率估计不准。如果你已经包含了市场因子,那截距项就是多余的。

下面是一个完整的因子收益率估计流程:

import statsmodels.api as sm

# 假设我们有100只股票,3个因子
# returns: 100只股票当天的收益率
# exposures: 100只股票在3个因子上的暴露(已标准化)

# 横截面回归(不加截距)
X = exposures.values  # shape: (100, 3)
y = returns.values    # shape: (100,)

# 使用WLS加权最小二乘法
# 权重通常用市值平方根
weights = np.sqrt(market_cap.values)
model = sm.WLS(y, X, weights=weights)
results = model.fit()

# 因子收益率
factor_returns = results.params
print("当日因子收益率:", factor_returns)
# 输出示例:当日因子收益率:[0.0023, -0.0015, 0.0031]

警告:

横截面回归对异常值非常敏感。我曾经有一次忘记剔除ST股票,结果因子收益率直接翻了一倍。建议在回归前做以下处理:

  • 剔除涨跌停的股票
  • 剔除市值最小的5%股票
  • 对因子暴露做MAD(中位数绝对偏差)截断

嗯,说到异常值,我再补充一点。因子收益率估计出来后,一定要做时间序列上的稳定性检验。如果一个因子的收益率今天+5%,明天-5%,那它大概率是个噪音因子,不是真正的风险源。

我习惯用滚动窗口计算因子收益率的t统计量。如果t值绝对值长期小于2,我会考虑把这个因子从模型中剔除。

3.4 实战中的几个坑

最后,分享几个我在实战中踩过的坑:

表现 解决方法
因子共线性 两个因子高度相关,回归系数不稳定 计算VIF(方差膨胀因子),剔除VIF>10的因子
行业因子陷阱 行业因子和风格因子混淆 对行业因子做正交化处理,或者用行业中性化后的风格因子
幸存者偏差 只用了当前还在交易的股票 必须包含已退市的股票,否则因子收益率会被高估
频率不匹配 日频因子暴露配月频收益率 保持频率一致,或者用高频数据滚动估计

好了,关于Barra模型的风险因子归因,核心就是这三步:框架理解 → 暴露计算 → 收益率估计

每一步都不难,但每一步都有细节。我建议你拿自己的组合跑一遍,看看收益到底来自哪里。结果可能会让你大吃一惊——我当初第一次跑的时候,发现我的「选股能力」其实全是「小盘因子」的功劳。

一个小建议:

刚开始做因子归因时,不要贪多。先用3-5个核心因子(市场、规模、价值、动量、波动率),跑通了再加行业因子和其他风格因子。因子越多,解释力不一定越强,但复杂度一定越高。


专注资料整理