第 3 章 · 04 收缩估计 Ledoit-Wolf 与 OAS 本节摘要:本节是「风险模型」的重头戏,讲 PyPortfolioOpt 文档反复强调的生产默认——收缩估计(shrinkage)。样本协方差虽无偏但噪声大,在「资产数 N 接近样本数 T」时严重失真。收缩估计把样本协方差 S 向一个结构化但偏差大的目标 F 拉近: ,通过偏差-方差权衡显著降噪。 的 类提供三种 Ledoit-Wolf 收缩目标(constantvariance、singlefactor、constantcorrelation)以及 Oracle Approximating Shrinkage(OAS)。
本节摘要:本节是「风险模型」的重头戏,讲 PyPortfolioOpt 文档反复强调的生产默认——收缩估计(shrinkage)。样本协方差虽无偏但噪声大,在「资产数 N 接近样本数 T」时严重失真。收缩估计把样本协方差 S 向一个结构化但偏差大的目标 F 拉近:
Σ̂ = δF + (1-δ)S,通过偏差-方差权衡显著降噪。risk_models.py的CovarianceShrinkage类提供三种 Ledoit-Wolf 收缩目标(constant_variance、single_factor、constant_correlation)以及 Oracle Approximating Shrinkage(OAS)。本节拆解收缩原理、三种目标的差异、delta收缩常数的自动选择,以及作者的实务建议——默认用 Ledoit-Wolf。
内容来源:原项目源码
pypfopt/risk_models.py(类CovarianceShrinkage)、文档docs/RiskModels.rst,汉化并套用体系化模板。
阅读完本节,你应当能够:
Σ̂ = δF + (1-δ)S,解释偏差-方差权衡。delta(收缩常数)的含义与自动估计原理。CovarianceShrinkage 类调用 Ledoit-Wolf 与 OAS。样本协方差 S 是无偏估计(E(S) = Σ),但估计误差(方差)大——它有 N(N+1)/2 个独立元素要估,样本不够时矩阵充满噪声。
收缩的思路:把 S 与一个结构化、低方差但有偏的估计 F 混合:
Σ̂ = δ · F + (1 - δ) · S
δ = 0:完全用样本,无偏但高方差(原始 S)。δ = 1:完全用结构 F,低方差但有偏。δ ∈ (0, 1):权衡——稍增偏差换大幅降方差,总均方误差(MSE)更小。直觉:F 是「猜一个简单结构」(如「所有方差相等、相关性为 0」),它肯定不准,但很稳;S 是「让数据说话」,它准但很跳。收缩就是「在数据与先验之间找平衡」。
💡 核心心法:这与机器学习里的 L2 正则、贝叶斯先验是同一思想——「数据不够时,引入结构先验」。在金融里,样本永远不够(N 大、T 相对小),所以收缩几乎总是更优。
docs/RiskModels.rst 列出 PyPortfolioOpt 支持的三种 F:
F = μ · I (μ = trace(S)/N,即平均样本方差)
目标结构:所有对角元素相等(=平均方差)、所有非对角元素为 0(完全不相关)。这是最简单的结构——假设资产之间独立,只用一个标量描述风险。Ledoit-Wolf 2004 年论文证明了这种收缩的最优 δ 有解析解,sklearn 的 LedoitWolf 实现就是它。CovarianceShrinkage.ledoit_wolf() 默认用这个。
基于 Sharpe 单指数模型:假设资产收益只受市场单一因子驱动,资产间的协方差来自它们对市场的 beta:
F_ij = β_i · β_j / σ²_mkt (非对角) F_ii = S_ii (对角保留样本方差)
源码里 _ledoit_wolf_single_factor 实现了 Ledoit-Wolf 2001 年的版本——用资产等权均值代理市场,估 beta,构造 F。适合「市场因子主导」的资产池(如大盘成分股)。
假设所有资产对的相关性相等(等于平均相关性 r̄),但方差保留:
F_ij = r̄ · σ_i · σ_j (非对角) F_ii = S_ii (对角保留)
_ledoit_wolf_constant_correlation 实现了 Ledoit-Wolf 2003 年的版本——估 r̄,构造 F。适合「相关性结构相对均匀」的资产池(如同一板块的股票)。
| 目标 | 结构假设 | 论文 | 适用 |
|---|---|---|---|
constant_variance |
资产独立、方差相等 | Ledoit-Wolf 2004 | 通用默认 |
single_factor |
单一市场因子驱动 | Ledoit-Wolf 2001 | 大盘成分股 |
constant_correlation |
相关性均匀 | Ledoit-Wolf 2003 | 板块内股票 |
每种目标 F 都对应一个最优 δ——它决定「拉多近」。Ledoit-Wolf 的核心贡献是给出了 δ 的渐近最优解析估计:
源码里 _ledoit_wolf_constant_correlation 的最后几行就是这套公式的实现:
kappa_hat = (pi_hat - rho_hat) / gamma_hat delta = max(0.0, min(1.0, kappa_hat / t)) # 截断到 [0, 1] shrunk_cov = delta * F + (1 - delta) * S
三个量 pi_hat(样本协方差的方差)、rho_hat(协方差估计的渐进协方差)、gamma_hat(S 与 F 的距离)共同决定 kappa_hat,再除以样本数 t 得 δ。这套公式让收缩强度自适应数据——样本越少、噪声越大,δ 自动越大,拉得越近。
💡 实例属性
delta:CovarianceShrinkage实例的.delta属性会保存最后求出的收缩常数,可以打印出来看「这次收缩拉了多近」。δ 接近 0 说明样本协方差已足够好;接近 1 说明样本噪声太大,几乎完全用结构 F。
oracle_approximating 方法用 Chen et al. (2010) 的 OAS 估计器,sklearn 的 covariance.OAS 实现:
def oracle_approximating(self): X = np.nan_to_num(self.X.values) shrunk_cov, self.delta = self.covariance.oas(X) return self._format_and_annualize(shrunk_cov)
OAS 的目标与 Ledoit-Wolf 一致(收缩到 constant_variance 目标),但它对样本是高斯或近高斯的情形有更低的均方误差。文档原话:
OAS 由 Chen et al. (2010) 提出,在样本是高斯或近高斯时,比 Ledoit-Wolf 收缩的均方误差更低。
实务上,资产收益往往有厚尾(非高斯),所以 Ledoit-Wolf 仍是更稳健的默认。OAS 适合「你确信收益分布接近高斯」或想试不同收缩做敏感性分析的场景。
CovarianceShrinkage 还提供 shrunk_covariance(delta=0.2)——手动指定 δ,不自动估。结构 F 固定为 constant_variance(对角 = 平均方差,非对角 = 0):
def shrunk_covariance(self, delta=0.2): self.delta = delta N = self.S.shape[1] mu = np.trace(self.S) / N F = np.identity(N) * mu shrunk_cov = delta * F + (1 - delta) * self.S return self._format_and_annualize(shrunk_cov)
用途:做敏感性分析(扫不同 δ 看权重如何变化)、或你有先验知识想固定收缩强度。
from pypfopt import risk_models S = risk_models.CovarianceShrinkage(df).ledoit_wolf() print(cs.delta) # 查看收缩强度
这是 UserGuide 与 cookbook 反复推荐的生产默认:
from pypfopt.expected_returns import mean_historical_return from pypfopt.risk_models import CovarianceShrinkage mu = mean_historical_return(df) S = CovarianceShrinkage(df).ledoit_wolf()
cs = risk_models.CovarianceShrinkage(df) S_sf = cs.ledoit_wolf(shrinkage_target="single_factor") S_cc = cs.ledoit_wolf(shrinkage_target="constant_correlation")
S_oas = risk_models.CovarianceShrinkage(df).oracle_approximating()
risk_matrix 函数支持所有收缩方法:
risk_models.risk_matrix(df, method="ledoit_wolf") risk_models.risk_matrix(df, method="ledoit_wolf_constant_variance") # 同上 risk_models.risk_matrix(df, method="ledoit_wolf_single_factor") risk_models.risk_matrix(df, method="ledoit_wolf_constant_correlation") risk_models.risk_matrix(df, method="oracle_approximating")
docs/RiskModels.rst 在结尾给出明确建议:
对多数用例,我会直接选 Ledoit-Wolf 收缩——正如 Quantopian 在其量化金融系列讲座里所推荐的。
理由汇总:
CovarianceShrinkage(df).ledoit_wolf() 一行搞定。⚠️ 依赖 scikit-learn:
CovarianceShrinkage内部用sklearn.covariance的ledoit_wolf与OAS,源码里检查依赖:
if not _check_soft_dependencies(["scikit-learn"], severity="none"): raise ImportError("scikit-learn is required ...")
请确保环境装了 scikit-learn。
Σ̂ = δ·F + (1-δ)·S,偏差-方差权衡——稍增偏差换大幅降方差。cs.delta 属性可查看。shrunk_covariance(delta=0.2),固定 δ 做敏感性分析。CovarianceShrinkage(df).ledoit_wolf(),文档与 Quantopian 都主推。下一节是本章最后一节——最小行列式协方差(MCD),用极大似然的鲁棒估计应对异常值。