← 返回模块
4.4.2.1beta 可读 · 未来免费校验通过内容版本 2026-05-28

因子风险模型基础:Barra 分解

4.4.2 · 风险模型与风险管理 · 量化全流程

某沪深300指增私募的中级量化:把 4.4.1 L4 的 Ledoit-Wolf 收缩协方差直接套到 500 只 A 股、5 年月度数据上,ΣLW\Sigma_{\text{LW}} 条件数压到 800——可以接受。把宇宙扩到 1500 只(中证1000 + 沪深300),条件数又跳回 5000;再做一轮调参也压不下去。她去问做风控的资深同事:「机构生产栈到底用什么?」对面甩出三个字:「Barra 模型」。这堂课讲的就是机构生产栈用什么、为什么 LW 在 500 只以上的宇宙上力不从心、Barra USE4 / CNE6 这套​​多因子结构化协方差​​(multi-factor structural covariance)如何用「低秩加对角」(low-rank-plus-diagonal)把参数从十几万压到两万出头,以及怎么用 NumPy 自己复刻一个 50 资产的开源版本。

L1 是 4.4.2 模块的入口。下面四节课的所有 Σ\Sigma 都用 L1 这里构造的 ΣBARRA\Sigma_{\text{BARRA}}

术语对齐表

本课新增的核心术语,与 data/glossaries/quant-glossary.yaml 标准译名一致:​​因子模型​​(factor model)、​​因子暴露​​(factor loading)、​​Barra 模型​​(Barra model)、​​协方差矩阵​​(covariance matrix)、​​特征值​​(eigenvalue)、​​特征向量​​(eigenvector)、​​条件数​​(condition number,σmax/σmin\sigma_{\max}/\sigma_{\min},刻画矩阵反演稳定性)、​​半正定​​(positive semidefinite)、​​矩阵逆​​(matrix inverse)、​​规模因子​​(size factor)、​​价值因子​​(value factor)、​​质量因子​​(quality factor)、​​低波动因子​​(low-volatility factor)。后续所有讨论用这些标准译名。

参数计数:为什么样本 S 在 N=500 上撑不住

sample_cov_S:        N(N+1)/2 free parameters; N=500 -> 125,250
factor_cov_BARRA:    N*K + K(K+1)/2 + N parameters; N=500, K=50 -> 26,775
ratio:               ~4.7x parameter reduction
trade_off:           sample S is unbiased but high-variance; factor Sigma is biased but low-variance

规则:factor model is biased but low-variance; sample S is unbiased but high-variance; for typical N, K, T the factor model dominates on Frobenius distance to the population covariance

对 N 资产宇宙,样本协方差 SSN(N+1)/2N(N+1)/2 个独立参数。N=50N = 50 时是 1,275;N=500N = 500 时是 125,250。可用的月度收益独立观测数通常是 T=60120T = 60\text{–}120(5–10 年历史),数据点总数 TN[6000,60000]T \cdot N \in [6000, 60000];当 NTN \geq T 进入「高维」(high-dimensional)区,SS 直接秩亏(4.4.1 L4 已铺过这件事)。

Barra 的解法是​​强加一个低秩加对角结构先验​​:假设跨资产共动由少数 KK 个公共因子驱动,剩下的是资产特有噪声。因子模型只有 NK+K(K+1)/2+NN \cdot K + K(K+1)/2 + N 个参数:N=500N = 500K=50K = 50 时是 25,000+1,275+500=26,77525{,}000 + 1{,}275 + 500 = 26{,}775——​​约 4.7 倍参数压缩​​。代价是偏差(假设跨资产相关性能被 KK 个共同因子分解);收益是方差极小(每个参数都被充分数据约束)。Frobenius 距离意义下,在常见的 NNKKTT 组合下因子模型​​完胜​​样本 SS

结构化分解:r = B f + epsilon

下面是 ​Inline-code listing​ 中按固定顺序的四条:

model:           r = B * f + epsilon
covariance:      Sigma = B * Sigma_F * B^T + D
parameter_count: sample S has N(N+1)/2 free parameters; factor Sigma has N*K + K(K+1)/2 + N parameters; for N=500, K=50: 125,250 vs 26,775 — ~5x reduction
factor_classes:  K = K_style (10-15) + K_industry (25-50) + K_country (0 single-country / 30-50 global)

Barra 风格的因子模型把 tt 时刻的资产收益向量写成

rt=Btft+ϵt,r_t = B_t f_t + \epsilon_t,

其中 BtB_tN×KN \times K 的因子暴露矩阵(从横截面基本面数据当期可观察)、ftf_tKK 维因子收益向量(隐变量,每期估计)、ϵt\epsilon_tNN 维特异收益向量,满足 E[ϵt]=0\mathbb{E}[\epsilon_t] = 0Cov(ϵt,ft)=0\mathrm{Cov}(\epsilon_t, f_t) = 0Cov(ϵit,ϵjt)=0 (ij)\mathrm{Cov}(\epsilon_{it}, \epsilon_{jt}) = 0\ (i \neq j)

两边求协方差,​​收益协方差矩阵​​就是

Σ=BΣFB+D,\Sigma = B \Sigma_F B^{\top} + D,

其中 ΣF=Cov(f,f)\Sigma_F = \mathrm{Cov}(f, f)K×KK \times K 因子收益协方差,D=diag(Var(ϵi))D = \mathrm{diag}(\mathrm{Var}(\epsilon_i))N×NN \times N 对角特异方差。第一项秩 KK(​​因子风险​​,变量来自共同因子暴露);第二项秩 NN(​​特异风险​​,逐资产对角)。这就是规范的「低秩加对角」分解——4.4.1 L4 的 LW 单因子目标 F=BσM2B+diagF = B \sigma_M^2 B^{\top} + \mathrm{diag} 正是这套结构在 K=1K = 1 时的特例。Barra 把它推广到 K=5080K = 50\text{–}80,得到机构级生产协方差。

三类因子:风格 / 行业 / 国家

商业 Barra 模型(USE4 / CNE6 / GEM4)有三类因子。

​风格因子​​(style factor),Kstyle1015K_{\text{style}} \approx 10\text{–}15 个,是横截面回报驱动因子。规范 Barra 风格因子包括:​​规模因子​​(对数总市值 z-score)、​​价值因子​​(账面市值比 z-score)、动量(过去 12 个月减去最近 1 个月累计收益的 z-score)、​​质量因子​​(ROE z-score)、​​低波动因子​​(过去 60 日残差标准差取负的 z-score)、流动性(Amihud 非流动性或换手率)、收益率(EPS / Price)、成长(5 年 EPS 增速)、杠杆(资产负债率)、股息率。每个风格因子取​​横截面 z-score​ 并在 ±3σ\pm 3 \sigma 处缩尾(winsorise)以控制极端值——这一步必须在每个截面独立做,不能跨期标准化。

​行业因子​​(industry factor),Kindustry2550K_{\text{industry}} \approx 25\text{–}50 个,是 0/1 行业归属指示。每只股票恰好属于一个行业,行业暴露每行加总等于 1。CN A 股的规范分类是​​中信一级行业​​(30 个)或申万一级(31 个);因子投资(石川 等 2020)给的开源 CN Barra 复刻就采用中信一级。本课的捷径版用 10 个超级行业:​​金融​​(银行 + 非银金融)/ 房地产 / 食品饮料 / 医药 / 新能源 / 电子 / 通信 / 消费 / 周期 / 公用事业。

​国家因子​​(country factor),Kcountry3050K_{\text{country}} \approx 30\text{–}50 个,只用于跨国全球模型(Barra GEM3 / GEM4)。单国模型(USE4 单美国、CNE6 单 A 股)不需要。

总因子数 K=Kstyle+Kindustry+KcountryK = K_{\text{style}} + K_{\text{industry}} + K_{\text{country}},单国模型典型落在 50–80。

因子收益估计:横截面 WLS

下面是 ​Inline-code listing​ 中按固定顺序的五条:

each_period_t:       r_t = B_t * f_t + epsilon_t
WLS_weight:          W_t_ii = 1 / sqrt(market_cap_i_t)
closed_form:         f_t = (B_t^T * W_t * B_t)^{-1} * B_t^T * W_t * r_t
residuals:           epsilon_t = r_t - B_t * f_t
industry_constraint: industry rows of B sum to 1 per asset; drop one industry as reference OR enforce industry returns sum to zero per period via Lagrange multiplier

规则:WLS with sqrt-inv-market-cap weights prevents mega-cap dominance of factor returns

每个 tt 时刻,给定可观察收益 rtr_t(NN 维)与可观察暴露 BtB_t(N×KN \times K),通过横截面​​加权最小二乘​​回归回收因子收益 ftf_t:

ft=(BtWtBt)1BtWtrt.f_t = (B_t^{\top} W_t B_t)^{-1} B_t^{\top} W_t r_t.

规范权重选择是 Wt,ii=1/market_capitW_{t,ii} = 1 / \sqrt{\text{market\_cap}_{it}}:这把超大市值降权,防止因子收益被几只巨头单独拖走(贵州茅台 + 宁德时代两只就占沪深300约 6%;若不降权,「食品饮料」行业因子收益会被茅台一只股票的特异波动主导)。另一种选择是 Wt,ii=1/Var(ϵit)W_{t,ii} = 1 / \mathrm{Var}(\epsilon_{it})(GLS,迭代用上一轮特异方差),理论上更高效但要迭代两轮。

行业暴露每行加和等于 1 会跟截距产生完美共线性,需要 (i) 砍掉一个行业作为基准(得到 K1K - 1 个行业因子);或 (ii) 拉格朗日乘子约束「行业因子收益当期加和为零」。两种处理在线性回归下等价。涨跌停股票如果占截面 5% 以下,可以直接剔除;占比更大时用前一日 VWAP 反推未实现收益。

收集时间序列 {ft:t=1,,T}\{f_t : t = 1, \ldots, T\} 与残差 {ϵt=rtBtft:t=1,,T}\{\epsilon_t = r_t - B_t f_t : t = 1, \ldots, T\},进入下一步 EWMA 平滑。

EWMA 估计 Sigma_F 与 D

下面是 ​Inline-code listing​ 中按固定顺序的五条:

Sigma_F_t = (1 - lambda_F) * f_t * f_t^T + lambda_F * Sigma_F_{t-1}
D_ii_t = (1 - lambda_D) * epsilon_it^2 + lambda_D * D_ii_{t-1}
lambda_daily: 0.94 (RiskMetrics default; ~11-day half-life)
lambda_monthly: 0.97 (~23-month half-life)
half_life: log(0.5) / log(lambda)

规则:small lambda reacts fast to vol regimes but is high-variance; large lambda is smoother but slow to react — Barra USE4 uses lambda=0.95 on daily data, ~14-day half-life

因子收益 ftf_t 和特异残差 ϵt\epsilon_t 各自只来自单个横截面的回归,二阶矩估计噪声很大;需要​​时间方向​​平滑才能用。机构标准做法是​​指数加权移动平均​​(EWMA,J.P. Morgan RiskMetrics 1996 默认):

ΣF,t=(1λF)ftft+λFΣF,t1,Dii,t=(1λD)ϵit2+λDDii,t1.\Sigma_{F,t} = (1 - \lambda_F)\, f_t f_t^{\top} + \lambda_F \Sigma_{F, t-1}, \qquad D_{ii, t} = (1 - \lambda_D)\, \epsilon_{it}^2 + \lambda_D D_{ii, t-1}.

λF=0.94\lambda_F = 0.94 用于日频(RiskMetrics 默认),0.970.97 用于月频。EWMA 半衰期(half-life)=log(0.5)/log(λ)= \log(0.5) / \log(\lambda):λ=0.9411\lambda = 0.94 \to 11 天,λ=0.9723\lambda = 0.97 \to 23 天(日频)或 23 个月(月频)。λ\lambda 的选择是经典偏差-方差权衡:小 λ\lambda 对波动率换档反应快但估计方差大;大 λ\lambda 平滑但反应慢。Barra USE4 在日频上用 λ=0.95\lambda = 0.95(约 14 天半衰期),是这个权衡的工业经验落点。完整的 GARCH / 随机波动率层延伸到 2.3.2,这里只用最简单的 EWMA。

机构参考模型与开源复刻

商业 Barra 家族:USE4(2011 美国大盘当代版)、USE5(2022 美国生产版)、CNE6(2018 A 股)、CNE7(2023 更新)、GEM3 / GEM4(全球)、EUE4(欧洲)。竞品:Axioma AX-US4 / AX-WW4(FTSE Russell)、Northfield US Fundamental、MSCI Barra GEM。商业 Barra 单一机构席位许可费 100k500k100k\text{–}500k/年,中小型私募和学术研究通常走开源路线。

开源复刻栈:riskfolio-lib(完整的 Python 因子模型 + 风险归因 + 优化栈)、pyfolio(分析与风险报告)、Microsoft qlib(自带 A 股因子模型管线和横截面回归);自定义实现用 numpy + pandas + scipy.linalg.solve。CN 数据源:Tushare(免费)、Wind 万得(机构付费)。因子构造规范参考因子投资(石川 等 2020)第 9 章,GitHub 上有完整代码。

实现:barra_covariance

import numpy as np
import pandas as pd

def barra_covariance(
    returns_df: pd.DataFrame,
    exposures: pd.DataFrame,
    market_caps: pd.DataFrame,
    lambda_F: float = 0.97,
    lambda_D: float = 0.97,
) -> dict:
    # returns_df: T x N 月度收益;exposures: 多 index (t, asset) x K 暴露;
    # market_caps: T x N 月末市值。每月做一次横截面 WLS。
    dates = returns_df.index
    N = returns_df.shape[1]
    K = exposures.columns.size
    factor_returns = []
    specific_returns = []
    Sigma_F = np.zeros((K, K))
    D = np.zeros(N)
    init = False
    for t in dates:
        r_t = returns_df.loc[t].values
        B_t = exposures.loc[t].values  # N x K
        mcap_t = market_caps.loc[t].values
        # WLS 权重:超大市值降权
        w = 1.0 / np.sqrt(mcap_t)
        W = np.diag(w)
        # 闭式 f_t = (B^T W B)^{-1} B^T W r
        A = B_t.T @ W @ B_t
        b = B_t.T @ W @ r_t
        f_t = np.linalg.solve(A, b)
        eps_t = r_t - B_t @ f_t
        factor_returns.append(f_t)
        specific_returns.append(eps_t)
        # EWMA 更新
        if not init:
            Sigma_F = np.outer(f_t, f_t)
            D = eps_t ** 2
            init = True
        else:
            Sigma_F = (1 - lambda_F) * np.outer(f_t, f_t) + lambda_F * Sigma_F
            D = (1 - lambda_D) * (eps_t ** 2) + lambda_D * D
    # 最近时点构造 Sigma_BARRA
    B_T = exposures.loc[dates[-1]].values
    Sigma_BARRA = B_T @ Sigma_F @ B_T.T + np.diag(D)
    return {
        "Sigma_BARRA": Sigma_BARRA,
        "Sigma_F": Sigma_F,
        "D": D,
        "factor_returns": np.array(factor_returns),
        "specific_returns": np.array(specific_returns),
        "condition_number": float(np.linalg.cond(Sigma_BARRA)),
    }

签名(参数名 returns_df / exposures / market_caps / lambda_F / lambda_D、默认 lambda_F=0.97lambda_D=0.97、返回字典键)与 US 版逐字节一致;只有注释翻译。

对照实验:barra vs ledoit-wolf vs 样本

下面这段 Fenced Python 代码块给出三协方差估计的对比工具,把 L1 的 barra_covariance 输出与 4.4.1 L4 的 Ledoit-Wolf 收缩、原始样本 S 一起送入条件数 / Frobenius 距离 / 对数行列式三个指标的横评。把这套工具放在每月底任务里跑一次,就能持续监控生产协方差相对样本外的健康度——这是任何 Barra-style 风险模型上线前的强制冒烟测试。

import numpy as np
import pandas as pd
from sklearn.covariance import LedoitWolf

def compare_covariances(
    returns_df: pd.DataFrame,
    exposures: pd.DataFrame,
    market_caps: pd.DataFrame,
    oos_returns_df: pd.DataFrame,
) -> pd.DataFrame:
    # 三个估计 + 样本外参照
    S = returns_df.cov().values
    Sigma_LW = LedoitWolf().fit(returns_df.values).covariance_
    out = barra_covariance(returns_df, exposures, market_caps)
    Sigma_BARRA = out["Sigma_BARRA"]
    Sigma_OOS = oos_returns_df.cov().values
    rows = []
    for name, S_in in [("sample_S", S), ("ledoit_wolf", Sigma_LW), ("barra", Sigma_BARRA)]:
        rows.append({
            "estimator": name,
            "condition_number": float(np.linalg.cond(S_in)),
            "frobenius_to_oos": float(np.linalg.norm(S_in - Sigma_OOS, "fro")),
            "log_det": float(np.linalg.slogdet(S_in)[1]),
        })
    return pd.DataFrame(rows).set_index("estimator")[["condition_number", "frobenius_to_oos", "log_det"]]

预期签名:CN 50 只沪深300板块龙头 2019–2023 月度上,cond(ΣBARRA)10100\mathrm{cond}(\Sigma_{\text{BARRA}}) \approx 10\text{–}100 量级,cond(ΣLW)1001000\mathrm{cond}(\Sigma_{\text{LW}}) \approx 100\text{–}1000,cond(S)100010000\mathrm{cond}(S) \approx 1000\text{–}10000;Frobenius 到样本外的距离按同样排序——Barra 最近、LW 居中、样本最远。

可视化:特征值谱

Formula Explorer

x^2

ΣBARRA\Sigma_{\text{BARRA}} 的特征值从大到小排;前 KK 个特征值反映共同因子贡献,后 NKN - K 个反映特异噪声底盘。健康签名:特征值谱在第 KK 位有明显「断崖」(spectral gap),后续平稳。如果断崖不明显,说明因子数 KK 选少了或风格因子构造有问题——这是 Bai-Ng(2002 Econometrica)信息准则的诊断窗口。

练习

Exercise

你拿到 (i) returns_df(本地区 50 资产大盘宇宙 2019-01 到 2023-12 月度收益:CN 是沪深300板块龙头按 10 个并表行业各取市值前五;US 是 2019-01 标普500前 50 大市值股),(ii) exposures 在每月末的 5 风格 + 10 行业暴露矩阵 BtB_t(规模=对数市值 z-score;价值=账面市值比 z-score;动量=12-1 累计收益 z-score;质量=过去 12 月 ROE z-score;低波=过去 60 日收益标准差取负 z-score;10 个行业 0/1 哑变量,每行加和为 1),(iii) market_caps 每月市值。(a) 调 barra_covariance(returns_df, exposures, market_caps, lambda_F=0.97, lambda_D=0.97)Sigma_BARRASigma_FD、因子收益时序。(b) 报告 cond(Sigma_BARRA);验证落在 [10, 100] 区间(结构化构造的健康签名)。(c) 调 compare_covariances(in_sample_returns, exposures, market_caps, oos_returns_2023);验证规范排序 cond(Sigma_BARRA) < cond(Sigma_LW) < cond(S)frobenius_to_oos(Sigma_BARRA) < frobenius_to_oos(Sigma_LW) < frobenius_to_oos(S)。(d) 画 2019–2023 期间规模因子收益与动量因子收益的时序;指认动量回撤最大的窗口(双地区都可能是 2020-Q2 COVID 反弹的动量崩盘;CN 还有 2017 年大盘股鹿持的「茅指数」启动造成的规模因子翻号)。(e) 报告样本期内行业因子实际方差的前 3 名;一句话说明哪一个行业 2019–2023 波动最大(CN 候选:新能源;US 候选:科技)。

提示
先把 B_t 中风格因子在每个截面 z-score 化并 ±3σ 缩尾;行业暴露每行加和必须等于 1 才能让回归矩阵良态。
提示
EWMA 初始化用第一个观测的 f_t f_t^Teps_t^2;别用零矩阵,否则 Sigma_F 头几个月会被 0 拉低。

桥接到 L2

ΣBARRA\Sigma_{\text{BARRA}}BBΣF\Sigma_FDD 是 L1 的全部产出。L2 把这 4 个对象当作给定输入,做事前组合风险与跟踪误差归因:σp2=wΣBARRAw\sigma_p^2 = w^{\top} \Sigma_{\text{BARRA}} w 拆成因子项和特异项;边际风险贡献 MCTR、成分风险贡献 CCTR、百分比风险贡献 PCTR 逐资产计算;对基准的事前​​跟踪误差​​就是主动权重在 ΣBARRA\Sigma_{\text{BARRA}} 下的二次型范数。机构风险官每日凌晨在 L2 报告上签字之前先核对的就是 L1 这里的 cond(ΣBARRA)\mathrm{cond}(\Sigma_{\text{BARRA}}) 和因子特征值谱——L1 的协方差出错,L2 / L3 / L4 全错。