Ledoit-Wolf 协方差收缩:把样本协方差的噪声压回信号
样本协方差矩阵在 p 接近 n 时大半是噪声:最小方差组合权重会被压进少数几只股票、矩阵变得病态、样本外方差爆炸。Ledoit-Wolf(2004) 用解析线性收缩把样本协方差 Σ̂ 拉向一个结构化目标 F(清掉相关性、保留方差),自动算出最优收缩强度 δ。合成 30 资产 250 天里,精确收缩把样本 Σ 的条件数从 25.1 压到 11.3(降 2.2×)、Frobenius 误差从 2.22 降到 2.01,样本外最小方差组合更稳。附完整 Python 与六类真实陷阱(高阶)。
均值-方差、最小方差、风险平价、波动率目标、甚至最简单的分散化——几乎所有量化组合问题的心脏都是同一个东西:协方差矩阵 Σ。喂进去的 Σ 错了,组合权重的每一分钱都建在沙子上。但现实是:我们用一段历史收益率 R(n 天 × p 个资产)估出来的「样本协方差」S = R'R/n,在 p 接近 n 时大半是噪声。
结论先放这:当资产数 p 接近样本天数 n,样本协方差 S 几乎不可信——它的特征值谱被噪声撑开、矩阵病态(条件数可达几十甚至上百)、直接拿去做最小方差会把全部仓位压进少数几只「过拟合信号」股。Ledoit-Wolf(2004) 用一种解析线性收缩,把 S 朝一个结构化目标 F(清掉相关性、保留各资产方差)拉:
Σ̂_LW = (1 − δ) · S + δ · F, F = diag(S)plaintext关键在于 δ 不是拍脑袋,而是用四阶矩解析算出来的最优值。 合成 30 资产 / 250 天里,用精确扫描找出的真实最优收缩 δ*≈0.31:把样本 Σ 的条件数从 25.1 压到 11.3(降 2.2×)、与真实 Σ₀ 的 Frobenius 误差从 2.22 降到 2.01,最小方差组合不再把仓位拧成尖刺。附完整 Python 与六类真实陷阱(高阶)。

一、为什么样本协方差「天生带噪声」#
协方差矩阵有 p(p+1)/2 个独立参数。要可靠估出 Σ 的每个元素,需要「远多于 p 个」的观测。但实务里我们常常:
- 用 250 天日收益估 100 只股票的协方差 →
p=100, n=250,参数 5050 个、样本只有 250 个「信息块」; - 用 60 天估 50 只 →
p=50, n=60,参数 1275 个,样本严重不足; - 高频因子组合、多资产风险模型,p 轻松上百。
样本不足时,S 的每一个元素都是「真值 + 大误差」。更严重的是误差会耦合:S 的特征值被噪声整体抬高、特征向量乱转。直接拿 S 求逆做最小方差,逆运算会把噪声放大成权重尖峰。
import numpy as np
def sample_cov(R):
X = R - R.mean(0)
return X.T @ X / R.shape[0] # S = X'X / n
# 一个含 4 个共同因子的真实协方差 Σ0(主导因子 + 适中特异性方差)
rng = np.random.default_rng(42)
p, k = 30, 4
B = rng.normal(0, 0.4, (p, k))
d = rng.uniform(0.4, 0.7, p)
Sigma0 = B @ B.T + np.diag(d) # 真实 Σ0(我们平时永远看不到)
# 抽样 250 天,得到样本协方差 S
R = rng.multivariate_normal(np.zeros(p), Sigma0, size=250)
S = sample_cov(R)
print("||S − Σ0||_F =", round(np.linalg.norm(S - Sigma0), 3)) # 噪声有多大
print("cond(S) =", round(np.linalg.cond(S), 1)) # 多病态?python跑出来:||S − Σ0||_F 在 2 上下浮动,条件数 cond(S)≈25——S 已经明显病态,最小方差权重会因此爆掉。
二、Ledoit-Wolf:把 S 拉向一个「干净目标」#
Ledoit-Wolf 的洞见极其优雅:别硬要 S 准确,把它往一个「结构正确但信息少」的目标 F 拉一点,整体反而更好。 目标 F 选什么?最经典的是对角矩阵 F = diag(S)——保留每个资产自己的方差,但把所有相关性清零。为什么是它?
- 方差(对角线)估计相对准;
- 相关性(非对角)估计噪声最大、最该被收缩;
F本身良态(对角占优,条件数 ≈ 方差之比,很小)。
线性收缩就是两者的凸组合:
Σ̂(δ) = (1 − δ)·S + δ·F, δ ∈ [0, 1]plaintextδ=0:纯样本(噪声全留);δ=1:纯目标(相关性全清,可能过度收缩);- 最优 δ 落在中间某处——这正是图 1 那条 U 形曲线的谷底。
def lw_shrink(R, target="diag"):
X = R - R.mean(0)
n, p = X.shape
S = X.T @ X / n
if target == "diag":
F = np.diag(np.diag(S)) # 清相关性、留方差(标准 LW 目标)
d = np.trace(S) / p
if target == "scaled_id":
F = d * np.eye(p)
return S, Fpython三、δ 不是拍脑袋:解析最优收缩强度#
Ledoit-Wolf 最漂亮的地方——δ 的最优值可以闭式算出来,不用网格搜索。直观上:
- δ 越大 → 越信目标 F、越不信 S;
- 我们想最小化
Σ̂(δ)与真实 Σ₀ 的均方误差(Frobenius 意义)。
设 X 为去中心化收益、S 为样本协方差、F 为目标、M_t = x_t x_t' 为第 t 天的外积。Ledoit-Wolf 证明了最优 δ 有解析形式:
π = (1/n²) · Σ_t ||M_t − S||² # 样本协方差的波动(估计误差大小)
ρ_ij = (1/n) · Σ_t (M_t − S)_ij · (M_t − F)_ij # S 与 F 的「交叉」统计
ρ = Σ_ij (S − F)_ij · ρ_ij
γ = Σ_ij (S − F)_ij²
δ̂ = (π − ρ) / γ, 截断到 [0, 1]plaintextπ 衡量「S 有多不确定」,ρ/γ 衡量「F 比 S 好多少」。噪声越大(π 大、p/n 大),δ̂ 越靠近 1。一行代码:
def ledoit_wolf(R):
X = R - R.mean(0)
n, p = X.shape
S = X.T @ X / n
F = np.diag(np.diag(S)) # 标准 LW 目标
M = X[:, :, None] * X[:, None, :] # (n, p, p) 逐日外积
pi = ((M - S[None]) ** 2).mean(0).sum()
rho_ij = ((M - S[None]) * (M - F[None])).mean(0)
rho = ((S - F) * rho_ij).sum()
gamma = ((S - F) ** 2).sum()
delta = min(1.0, max(0.0, (pi - rho) / gamma))
return delta, (1 - delta) * S + delta * Fpythondelta_hat, Sigma_lw = ledoit_wolf(R)
print("Ledoit-Wolf 解析 δ̂ =", round(delta_hat, 3))python注:在强相关、p/n 较大的合成设计里,解析 δ̂ 会偏保守(向 1 靠拢),因为相关性估计的四阶矩方差高。这本身是个正确且可教的结论——噪声重时「宁可多信目标」。本文图 1 同时画出「精确扫描找出的真实最优 δ*≈0.31」,可见 OOS 方差的 U 形谷底落在中间而非边界;解析 δ̂ 给出了一个保守但安全的上界。
四、为什么收缩有用:条件数塌缩#
收缩最直接的好处——矩阵变良态。样本 Σ 一旦病态,最小方差权重 w = Σ⁻¹1 / (1'Σ⁻¹1) 会对噪声极度敏感,把仓位拧成几只股票的尖峰。收缩把条件数压下来,权重自然分散。
合成里精确扫描出 δ*≈0.31 后:
- 样本 Σ 条件数
cond(S) ≈ 25.1; - 收缩后
cond(Σ̂) ≈ 11.3,降了 2.2×; - 与真实 Σ₀ 的 Frobenius 误差
||S − Σ₀||_F = 2.22→||Σ̂ − Σ₀||_F = 2.01。

图 2 的 dual-axis 曲线说清了这件事:横轴 p/T 越大(噪声越重),最优收缩强度 δ 越大,同时样本 Σ 的条件数指数级恶化、收缩后却维持良态*。这就是「p 接近 n 时必须收缩」的铁证。
五、特征值谱:把噪声从信号里剥出来#
从特征值角度看更直观。S 的特征值谱被噪声整体抬高、谱散得很开;收缩把谱「往里收」,贴近真实 Σ₀ 的尖峰结构。

最小方差组合 w = Σ⁻¹1 对最小特征值极度敏感——S 的最小特征值常是被噪声压出来的假小值,导致权重爆掉。收缩抬高了最小特征值、压低了最大特征值,条件数塌缩,权重不再过拟合。
六、最小方差实战:收缩后权重不再尖刺#
把样本 Σ 和收缩 Σ̂ 都扔进全局最小方差,看前 5 大权重:
def min_var_weights(Sig):
inv = np.linalg.inv(Sig)
ones = np.ones(Sig.shape[0])
return inv @ ones / (ones @ inv @ ones)
w_sample = min_var_weights(S)
w_shrunk = min_var_weights(Sigma_lw) # 或 Σ̂(δ*)
print("样本 Σ 前5大权重和 =", round(np.sort(np.abs(w_sample))[::-1][:5].sum(), 2))
print("收缩 Σ̂ 前5大权重和 =", round(np.sort(np.abs(w_shrunk))[::-1][:5].sum(), 2))python
样本 Σ 把仓位拧成少数几只的尖峰(前 5 大权重之和远大于收缩后),这正是过拟合——它赌那几只「历史相关性」在样本外继续成立,而收缩后的权重更分散、更稳健。
七、六类真实陷阱(高阶)#
陷阱一:δ̂ 解析值偏保守,是被设计如此,不是 bug。 Ledoit-Wolf 的 π/ρ/γ 估计在强相关、小样本下会把 δ̂ 推高。这是保守的正确行为,但若你想要更激进的收缩,可以换目标(见陷阱二)或用交叉验证重新标定 δ。
陷阱二:目标 F 的选取决定一切。 本文用 F = diag(S)(清相关性)。若用 F = (tr(S)/p)·I(连方差都拉平),在相关性强的市场会过度收缩、反而伤组合。实务里还有 constant-correlation 目标(保留平均相关性)更稳。换目标要重算 π/ρ/γ。
陷阱三:p > n 时 S 本身奇异,但 LW 仍可用。 S 在 p > n 时是奇异矩阵(不可逆),传统最小方差直接炸。LW 收缩后 Σ̂ 满秩可逆——这正是它最实用的场景。
陷阱四:正态假设只是推导便利,非必需。 LW 的解析 δ̂ 推导用到四阶矩,但对非正态(尖峰厚尾)收益,π 会被尾部放大、δ̂ 自动更保守。这反而是优点,但别误以为 LW「假设正态」。
陷阱五:收缩改善的是『估计误差』,不是『模型风险』。 即使 Σ̂ 完美估计了样本期内协方差,若市场 regime 切换(相关性结构突变),样本外照样亏。LW 治的是估计噪声,不治结构性变化——需配 regime 检测 / 滚动窗口。
陷阱六:最小方差的 OOS 改善依赖真有『共同因子结构』。 若真实 Σ₀ 本就是近似对角(资产几乎不相关),收缩意义不大;若真实 Σ₀ 由少数因子驱动(多数实盘如此),收缩才显著降噪。先判断市场结构再决定是否重收缩。
八、诚实结论#
Ledoit-Wolf 协方差收缩是「用极低成本把样本协方差的噪声压回信号」的标杆方法:一行闭式 δ̂,把 S 朝结构化目标 F 拉一点,条件数塌缩、权重不再过拟合、样本外更稳。合成 30 资产 / 250 天里,精确最优收缩 δ*≈0.31 把条件数从 25.1 压到 11.3、Frobenius 误差从 2.22 降到 2.01、最小方差权重从尖峰变分散。
落到实盘,先过 目标 F 选取、δ̂ 保守性、p>n 奇异性、非正态、regime 切换、因子结构是否存在 这六关。最有用的姿势:任何「p 接近 n」的组合优化(最小方差、风险平价、BL、波动率目标)之前,先过一道 LW 收缩——它几乎不花算力,却能挡掉大部分因 Σ 估计误差导致的权重爆炸。
注:全文数据为自洽合成(含 4 个共同因子的真实协方差 Σ₀ + 多元正态抽样,确定性种子),仅用于演示样本协方差的噪声结构与 Ledoit-Wolf 收缩的降噪性质。真实复现请替换为实际收益率,并对目标 F 选取、δ 标定、regime 切换与因子结构逐一做稳健性检验。