某沪深300指增私募的中级量化:把 4.4.1 L4 的 Ledoit-Wolf 收缩协方差直接套到 500 只 A 股、5 年月度数据上, 条件数压到 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 模块的入口。下面四节课的所有 都用 L1 这里构造的 。
术语对齐表
本课新增的核心术语,与 data/glossaries/quant-glossary.yaml 标准译名一致:因子模型(factor model)、因子暴露(factor loading)、Barra 模型(Barra model)、协方差矩阵(covariance matrix)、特征值(eigenvalue)、特征向量(eigenvector)、条件数(condition number,,刻画矩阵反演稳定性)、半正定(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 资产宇宙,样本协方差 有 个独立参数。 时是 1,275; 时是 125,250。可用的月度收益独立观测数通常是 (5–10 年历史),数据点总数 ;当 进入「高维」(high-dimensional)区, 直接秩亏(4.4.1 L4 已铺过这件事)。
Barra 的解法是强加一个低秩加对角结构先验:假设跨资产共动由少数 个公共因子驱动,剩下的是资产特有噪声。因子模型只有 个参数:、 时是 ——约 4.7 倍参数压缩。代价是偏差(假设跨资产相关性能被 个共同因子分解);收益是方差极小(每个参数都被充分数据约束)。Frobenius 距离意义下,在常见的 、、 组合下因子模型完胜样本 。
结构化分解: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 风格的因子模型把 时刻的资产收益向量写成
其中 是 的因子暴露矩阵(从横截面基本面数据当期可观察)、 是 维因子收益向量(隐变量,每期估计)、 是 维特异收益向量,满足 、、。
两边求协方差,收益协方差矩阵就是
其中 是 因子收益协方差, 是 对角特异方差。第一项秩 (因子风险,变量来自共同因子暴露);第二项秩 (特异风险,逐资产对角)。这就是规范的「低秩加对角」分解——4.4.1 L4 的 LW 单因子目标 正是这套结构在 时的特例。Barra 把它推广到 ,得到机构级生产协方差。
三类因子:风格 / 行业 / 国家
商业 Barra 模型(USE4 / CNE6 / GEM4)有三类因子。
风格因子(style factor), 个,是横截面回报驱动因子。规范 Barra 风格因子包括:规模因子(对数总市值 z-score)、价值因子(账面市值比 z-score)、动量(过去 12 个月减去最近 1 个月累计收益的 z-score)、质量因子(ROE z-score)、低波动因子(过去 60 日残差标准差取负的 z-score)、流动性(Amihud 非流动性或换手率)、收益率(EPS / Price)、成长(5 年 EPS 增速)、杠杆(资产负债率)、股息率。每个风格因子取横截面 z-score 并在 处缩尾(winsorise)以控制极端值——这一步必须在每个截面独立做,不能跨期标准化。
行业因子(industry factor), 个,是 0/1 行业归属指示。每只股票恰好属于一个行业,行业暴露每行加总等于 1。CN A 股的规范分类是中信一级行业(30 个)或申万一级(31 个);因子投资(石川 等 2020)给的开源 CN Barra 复刻就采用中信一级。本课的捷径版用 10 个超级行业:金融(银行 + 非银金融)/ 房地产 / 食品饮料 / 医药 / 新能源 / 电子 / 通信 / 消费 / 周期 / 公用事业。
国家因子(country factor), 个,只用于跨国全球模型(Barra GEM3 / GEM4)。单国模型(USE4 单美国、CNE6 单 A 股)不需要。
总因子数 ,单国模型典型落在 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。
每个 时刻,给定可观察收益 ( 维)与可观察暴露 (),通过横截面加权最小二乘回归回收因子收益 :
规范权重选择是 :这把超大市值降权,防止因子收益被几只巨头单独拖走(贵州茅台 + 宁德时代两只就占沪深300约 6%;若不降权,「食品饮料」行业因子收益会被茅台一只股票的特异波动主导)。另一种选择是 (GLS,迭代用上一轮特异方差),理论上更高效但要迭代两轮。
行业暴露每行加和等于 1 会跟截距产生完美共线性,需要 (i) 砍掉一个行业作为基准(得到 个行业因子);或 (ii) 拉格朗日乘子约束「行业因子收益当期加和为零」。两种处理在线性回归下等价。涨跌停股票如果占截面 5% 以下,可以直接剔除;占比更大时用前一日 VWAP 反推未实现收益。
收集时间序列 与残差 ,进入下一步 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。
因子收益 和特异残差 各自只来自单个横截面的回归,二阶矩估计噪声很大;需要时间方向平滑才能用。机构标准做法是指数加权移动平均(EWMA,J.P. Morgan RiskMetrics 1996 默认):
用于日频(RiskMetrics 默认), 用于月频。EWMA 半衰期(half-life): 天, 天(日频)或 23 个月(月频)。 的选择是经典偏差-方差权衡:小 对波动率换档反应快但估计方差大;大 平滑但反应慢。Barra USE4 在日频上用 (约 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 单一机构席位许可费 /年,中小型私募和学术研究通常走开源路线。
开源复刻栈: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.97 与 lambda_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 月度上, 量级,,;Frobenius 到样本外的距离按同样排序——Barra 最近、LW 居中、样本最远。
可视化:特征值谱
Formula Explorer
x^2把 的特征值从大到小排;前 个特征值反映共同因子贡献,后 个反映特异噪声底盘。健康签名:特征值谱在第 位有明显「断崖」(spectral gap),后续平稳。如果断崖不明显,说明因子数 选少了或风格因子构造有问题——这是 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 行业暴露矩阵 (规模=对数市值 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_BARRA、Sigma_F、D、因子收益时序。(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 才能让回归矩阵良态。提示
f_t f_t^T 与 eps_t^2;别用零矩阵,否则 Sigma_F 头几个月会被 0 拉低。桥接到 L2
、、、 是 L1 的全部产出。L2 把这 4 个对象当作给定输入,做事前组合风险与跟踪误差归因: 拆成因子项和特异项;边际风险贡献 MCTR、成分风险贡献 CCTR、百分比风险贡献 PCTR 逐资产计算;对基准的事前跟踪误差就是主动权重在 下的二次型范数。机构风险官每日凌晨在 L2 报告上签字之前先核对的就是 L1 这里的 和因子特征值谱——L1 的协方差出错,L2 / L3 / L4 全错。