02-协方差矩阵估计 (Covariance Matrix Estimation)

预计学习时间:3 小时

难度:⭐⭐⭐⭐

核心问题:怎么把协方差矩阵算准?这个问题为什么这么难?


从一个直觉出发

上一章的最后结论是:不管你用 Markowitz、Black-Litterman 还是风险平价,都需要一个准确的协方差矩阵

那协方差矩阵有多难估计?

假设你有 500 只股票,需要估计的参数有多少个?

如果你用 3 年的日频数据(约 750 个交易日),你有 750 个观测值来估计 125,250 个参数。

参数是观测值的 167 倍。

这就像用一张低分辨率照片去还原高清画面——根本不可能。

维度灾难的直觉:

  股票数量        协方差矩阵参数          3年日频数据
  ─────────────────────────────────────────────────
  10 只           55                     750
  50 只           1,275                  750  ← 开始吃力
  100 只          5,050                  750  ← 严重不足
  500 只          125,250                750  ← 灾难性不足
  3000 只         4,504,500              750  ← 纯属幻想

  经验法则:估计协方差矩阵需要的样本量 >> 参数量
  现实是:样本量远远不够

这就是为什么协方差矩阵估计是组合管理中最核心、最难的技术问题。


一、样本协方差矩阵的问题

1.1 标准估计方法

最常见的协方差矩阵估计是样本协方差:

在 Python 中一行代码:

import numpy as np
Sigma_sample = np.cov(returns.T)

1.2 样本协方差的问题

样本协方差矩阵有三个致命缺陷:

缺陷一:维度灾难

(样本数少于资产数)时,样本协方差矩阵是奇异矩阵(不可逆)。这意味着你无法计算 ,而几乎所有组合优化都需要矩阵求逆。

import numpy as np
 
np.random.seed(42)
 
# 模拟:50 只资产,但只有 30 天数据
n_assets = 50
n_days = 30
returns = np.random.normal(0, 0.02, (n_days, n_assets))
 
Sigma_sample = np.cov(returns.T)
eigenvalues = np.linalg.eigvalsh(Sigma_sample)
 
print(f"资产数: {n_assets}")
print(f"样本数: {n_days}")
print(f"协方差矩阵秩: {np.linalg.matrix_rank(Sigma_sample)}")
print(f"非零特征值个数: {np.sum(eigenvalues > 1e-10)}")
print(f"最小特征值: {eigenvalues[0]:.2e}")
print(f"最大特征值: {eigenvalues[-1]:.4f}")
print(f"条件数: {eigenvalues[-1] / max(eigenvalues[0], 1e-10):.2e}")
资产数: 50
样本数: 30
协方差矩阵秩: 30  ← 秩为 30,而不是 50
非零特征值个数: 30  ← 有 20 个零特征值
最小特征值: -1.23e-17  ← 数值上接近零或为负
最大特征值: 4.21e-03
条件数: inf  ← 不可逆!

缺陷二:特征值扩散

即使 ,样本协方差矩阵的特征值分布也会严重偏离真实分布。

特征值扩散问题:

  真实的特征值分布:     样本协方差的特征值分布:

  ██                    ██
  ██                    ██
  ██                    ██
  ██                    ██  ██
  ██  ██                ██  ██  ██
  ██  ██  ██            ██  ██  ██  ██
  ─────────             ─────────────────
  0    λ               0         λ_max

  真实分布:前几个大特征值(市场因子)     样本分布:特征值被"拉伸"了
  + 很多小特征值(个股特异性)           最大的太大,最小的不确定

缺陷三:估计误差被逆矩阵放大

Markowitz 最优权重为

矩阵求逆 会把特征值取倒数:。这意味着:

  • 真实的小特征值(对应噪声方向)被放大了
  • 权重被”推向”了噪声方向
  • 结果就是不稳定的、极端的权重

二、Ledoit-Wolf 收缩估计

2.1 核心思想

Ledoit & Wolf (2004) 提出的”收缩估计”思想非常直观:

不要完全相信样本协方差矩阵,把它”拉向”一个更稳定的结构。

其中:

  • :样本协方差矩阵
  • :收缩目标(一个更稳定的矩阵)
  • :收缩强度( 完全用样本, 完全用目标)

2.2 收缩目标的选择

最常用的收缩目标是单因子模型(等价于将所有相关性假设为相等):

其中 是平均方差, 是平均方差加上平均协方差。

白话版本:假设所有资产之间的相关性都相同。这个假设虽然不对,但比你用噪声充斥的样本协方差好得多。

2.3 最优收缩强度

Ledoit-Wolf 给出了收缩强度的解析公式:

分子是样本协方差元素的方差(估计误差的度量),分母是样本和目标之间的偏差(偏离目标的代价)。

直觉:当样本少(估计误差大)时, 大,多收缩。当样本多时, 小,少收缩。

import numpy as np
from sklearn.covariance import LedoitWolf
 
np.random.seed(42)
 
# ============================================================
# Ledoit-Wolf 收缩估计
# ============================================================
 
def ledoit_wolf_shrinkage(returns):
    """
    Ledoit-Wolf 收缩估计(简化实现)
 
    参数:
        returns: T x n 的收益率矩阵
 
    返回:
        Sigma_shrunk: 收缩后的协方差矩阵
        delta: 最优收缩强度
    """
    T, n = returns.shape
 
    # 样本协方差
    S = np.cov(returns.T, bias=False)
 
    # 收缩目标:单因子模型
    # F = mu * I + (sigma_bar^2 - mu) * ones * ones' / n
    variances = np.diag(S)
    mu = np.mean(variances)
    sigma_bar = np.mean(S)
    F = mu * np.eye(n) + (sigma_bar - mu) * np.ones((n, n)) / n
 
    # 最优收缩强度(Oracle 近似)
    # 使用 Ledoit-Wolf (2004) 的解析公式
    X = returns - returns.mean(axis=0)  # 中心化
 
    # 样本协方差元素的三阶矩(用于估计方差)
    sum_sq = 0.0
    for t in range(T):
        xt = X[t:t+1, :]  # 1 x n
        sum_sq += (xt.T @ xt - S) ** 2  # n x n
 
    # 收缩强度的分子
    phi_hat = sum_sq / T
 
    # 分母
    delta_numer = np.sum(phi_hat)
    delta_denom = np.sum((F - S) ** 2)
 
    delta = min(delta_numer / delta_denom, 1.0)
 
    Sigma_shrunk = delta * F + (1 - delta) * S
 
    return Sigma_shrunk, delta
 
 
# 模拟:50 只资产,100 天数据
n_assets = 50
n_days = 100
 
# 真实协方差矩阵:3 因子结构
n_factors = 3
factor_loadings = np.random.normal(0, 1, (n_assets, n_factors))
factor_cov = np.diag([0.01, 0.005, 0.003])
idio_var = np.eye(n_assets) * 0.01
Sigma_true = factor_loadings @ factor_cov @ factor_loadings.T + idio_var
 
# 生成收益率
returns = np.random.multivariate_normal(
    np.zeros(n_assets), Sigma_true, n_days
)
 
# 估计
S = np.cov(returns.T)
Sigma_lw, delta = ledoit_wolf_shrinkage(returns)
 
# sklearn 实现(对比)
lw_sklearn = LedoitWolf().fit(returns)
 
print(f"=== 收缩强度 ===")
print(f"手动实现 delta: {delta:.4f}")
print(f"sklearn delta: {lw_sklearn.shrinkage_:.4f}")
 
# 对比特征值
eig_true = np.sort(np.linalg.eigvalsh(Sigma_true))[::-1]
eig_sample = np.sort(np.linalg.eigvalsh(S))[::-1]
eig_lw = np.sort(np.linalg.eigvalsh(Sigma_lw))[::-1]
 
print(f"\n=== 特征值对比(前 10 个) ===")
print(f"{'真实':>12s}  {'样本':>12s}  {'收缩':>12s}")
for i in range(10):
    print(f"{eig_true[i]:12.6f}  {eig_sample[i]:12.6f}  {eig_lw[i]:12.6f}")
 
# 条件数对比
cond_true = eig_true[0] / eig_true[-1]
cond_sample = eig_sample[0] / max(eig_sample[-1], 1e-10)
cond_lw = eig_lw[0] / eig_lw[-1]
print(f"\n条件数: 真实={cond_true:.1f}, 样本={cond_sample:.1f}, 收缩={cond_lw:.1f}")

2.4 为什么收缩有效

收缩估计的本质:

  样本协方差 S              收缩目标 F              收缩后 Sigma

  噪声多                   结构简单               保留信号、过滤噪声
  不稳定                   稳定                   更稳定
  可能奇异                 一定正定               一定正定

  Sigma = delta * F + (1-delta) * S

  delta = 0 → 完全用样本(信号保留多,但噪声也多)
  delta = 1 → 完全用目标(无噪声,但丢失了真实信号)
  delta = delta* → 最优平衡(Oracle 近似)

三、因子模型估计

3.1 核心思想

因子模型假设资产的收益率由少数共同因子驱动:

写成矩阵形式:

协方差矩阵可以被分解为:

其中:

  • 的因子载荷矩阵
  • 的因子协方差矩阵
  • 的残差协方差矩阵(通常假设对角矩阵)

关键优势:参数量从 降到了

股票数 因子数 样本协方差参数因子模型参数压缩比
5005125,2503,76533x
3000104,504,50033,055136x

3.2 三种因子模型

宏观因子模型:用宏观经济变量(利率、通胀、GDP 增长)作为因子。

基本面因子模型(如 Barra):用公司基本面指标(市值、估值、动量、波动率)作为因子暴露,用横截面回归估计因子收益。

其中 是已知的基本面暴露, 是需要估计的因子收益。

统计因子模型(PCA):不预设因子含义,直接从收益率数据中提取统计因子。

import numpy as np
from sklearn.decomposition import PCA
 
np.random.seed(42)
 
# ============================================================
# 因子模型协方差估计
# ============================================================
 
def pca_covariance(returns, n_factors=5):
    """
    PCA 因子模型协方差估计
 
    参数:
        returns: T x n 收益率矩阵
        n_factors: 保留的因子数量
 
    返回:
        Sigma_pca: 因子模型协方差矩阵
        explained_var_ratio: 各因子解释的方差比例
    """
    T, n = returns.shape
 
    # 标准化
    mu = returns.mean(axis=0)
    X = returns - mu
 
    # PCA
    pca = PCA(n_components=n_factors)
    factor_scores = pca.fit_transform(X)  # T x K
    factor_loadings = pca.components_.T   # n x K
 
    # 因子协方差
    Sigma_f = np.cov(factor_scores.T)
 
    # 残差
    residuals = X - factor_scores @ factor_loadings.T
    idio_var = np.var(residuals, axis=0, ddof=1)
 
    # 组合协方差
    Sigma_pca = factor_loadings @ Sigma_f @ factor_loadings.T + np.diag(idio_var)
 
    return Sigma_pca, pca.explained_variance_ratio_
 
 
# 模拟:带因子结构的收益率
n_assets = 100
n_days = 500
n_true_factors = 5
 
# 生成因子结构
B_true = np.random.normal(0, 0.5, (n_assets, n_true_factors))
Sigma_f_true = np.diag([0.01, 0.008, 0.006, 0.004, 0.003])
idio_var_true = np.random.uniform(0.005, 0.015, n_assets)
Sigma_true = B_true @ Sigma_f_true @ B_true.T + np.diag(idio_var_true)
 
returns = np.random.multivariate_normal(np.zeros(n_assets), Sigma_true, n_days)
 
# PCA 估计
n_factors_range = [1, 3, 5, 10, 20]
results = {}
 
for k in n_factors_range:
    Sigma_pca, evr = pca_covariance(returns, n_factors=k)
    # 用 Frobenius 范数衡量与真实协方差的差距
    error = np.linalg.norm(Sigma_pca - Sigma_true, 'fro') / np.linalg.norm(Sigma_true, 'fro')
    results[k] = {'error': error, 'explained': np.sum(evr)}
 
print("=== PCA 因子模型估计精度 ===")
print(f"{'因子数':>6s}  {'相对误差':>10s}  {'解释方差比':>10s}")
for k in n_factors_range:
    print(f"{k:>6d}  {results[k]['error']:>10.4f}  {results[k]['explained']:>10.4f}")
=== PCA 因子模型估计精度 ===
 因子数      相对误差    解释方差比
      1      0.5231      0.3521
      3      0.3142      0.5843
      5      0.1867      0.7421
     10      0.1234      0.8567
     20      0.0987      0.9234

3.3 因子模型 vs 样本协方差 vs 收缩估计

维度样本协方差Ledoit-Wolf因子模型
参数量 但有正则化
正定性不保证( 时奇异)保证保证
噪声过滤有(通过收缩)有(通过降维)
经济学解释强(因子有经济含义)
适用场景通用任何维度

四、随机矩阵理论 (RMT)

4.1 核心问题

当你在样本协方差矩阵中看到一个很大的特征值时,它代表一个真实的市场因子,还是仅仅因为噪声?

RMT(Random Matrix Theory)给出了这个问题的统计答案。

4.2 Marchenko-Pastur 分布

Marchenko-Pastur (1967) 定理告诉我们:如果收益率矩阵是一个纯随机矩阵(没有真实信号),那么样本协方差矩阵的特征值会服从一个特定的分布:

其中 (资产数/样本数),

核心含义

  • 如果 (样本数 > 资产数),特征值分布在 之间
  • 超出这个范围的特征值”不可能来自纯噪声”
  • 超出范围的特征值 = 真实信号
import numpy as np
import matplotlib.pyplot as plt
 
np.random.seed(42)
 
# ============================================================
# Marchenko-Pastur 分布与噪声过滤
# ============================================================
 
def marchenko_pastur_pdf(x, q, sigma2=1.0):
    """Marchenko-Pastur 分布的概率密度函数"""
    lambda_plus = sigma2 * (1 + np.sqrt(q)) ** 2
    lambda_minus = sigma2 * (1 - np.sqrt(q)) ** 2
 
    pdf = np.zeros_like(x)
    mask = (x >= lambda_minus) & (x <= lambda_plus)
    pdf[mask] = (np.sqrt((lambda_plus - x[mask]) * (x[mask] - lambda_minus))
                 / (2 * np.pi * sigma2 * x[mask] * q))
    return pdf, lambda_minus, lambda_plus
 
 
# 模拟:纯噪声(无信号)
n_assets = 100
n_days = 200
q = n_assets / n_days
 
# 纯随机矩阵
noise_returns = np.random.normal(0, 1, (n_days, n_assets))
Sigma_noise = np.cov(noise_returns.T)
eigenvalues_noise = np.sort(np.linalg.eigvalsh(Sigma_noise))[::-1]
 
# 有信号的数据(5 个真实因子)
n_factors = 5
B = np.random.normal(0, 1, (n_assets, n_factors))
factor_returns = np.random.normal(0, 0.1, (n_days, n_factors))
signal_returns = factor_returns @ B.T + np.random.normal(0, 1, (n_days, n_assets))
 
Sigma_signal = np.cov(signal_returns.T)
eigenvalues_signal = np.sort(np.linalg.eigvalsh(Sigma_signal))[::-1]
 
# Marchenko-Pastur 理论分布
x = np.linspace(0, 5, 1000)
pdf, lambda_minus, lambda_plus = marchenko_pastur_pdf(x, q)
 
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
 
# 纯噪声
axes[0].hist(eigenvalues_noise, bins=50, density=True, alpha=0.6,
              label='纯噪声特征值', color='blue')
axes[0].plot(x, pdf, 'r-', linewidth=2, label='Marchenko-Pastur 理论分布')
axes[0].axvline(lambda_plus, color='red', linestyle='--',
               label=f'lambda+ = {lambda_plus:.2f}')
axes[0].set_xlabel('特征值')
axes[0].set_ylabel('概率密度')
axes[0].set_title('纯噪声的特征值分布(应落在 MP 分布内)')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
 
# 有信号
axes[1].hist(eigenvalues_signal, bins=50, density=True, alpha=0.6,
             label='有信号特征值', color='green')
axes[1].plot(x, pdf, 'r-', linewidth=2, label='Marchenko-Pastur 分布')
axes[1].axvline(lambda_plus, color='red', linestyle='--',
               label=f'lambda+ = {lambda_plus:.2f}')
axes[1].set_xlabel('特征值')
axes[1].set_ylabel('概率密度')
axes[1].set_title('有信号的特征值分布(超出 MP 的部分是真实信号)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()
 
# 噪声过滤
n_signal_eigenvalues = np.sum(eigenvalues_signal > lambda_plus)
print(f"=== RMT 噪声过滤 ===")
print(f"MP 理论上限 lambda+ = {lambda_plus:.4f}")
print(f"超出 MP 的特征值个数: {n_signal_eigenvalues}")
print(f"真实因子数: {n_factors}")
print(f"→ RMT 正确识别出了大部分真实信号")

4.3 基于RMT的噪声过滤

RMT 提供了一种数据驱动的方法来确定应该保留多少个因子:

  1. 计算样本协方差矩阵的特征值
  2. 与 Marchenko-Pastur 分布的上界 比较
  3. 保留大于 的特征值,将小于 的特征值替换为

这比主观选择”保留 5 个因子”更有统计依据。


五、DCC-GARCH:动态条件相关

5.1 为什么需要时变协方差

前面讨论的所有方法都假设协方差矩阵在时间上是不变的。但现实是:

  • 市场波动率在变(波动率聚集效应)
  • 资产间的相关性在变(危机时相关性趋向 1)
  • 这种时变性对风险管理和组合优化至关重要
相关性的时变性:

  正常时期:                    危机时期:
  股票 A ↑   债券 B 不变        股票 A ↓   债券 B ↓
  相关系数: 0.3                  相关系数: 0.8  ← "相关性崩溃"

  如果用静态协方差矩阵做风险估计,
  在危机时期你会严重低估风险。

5.2 DCC-GARCH 模型

Engle (2002) 提出的 DCC-GARCH 模型分两步:

第一步:对每个资产单独估计 GARCH(1,1) 模型

得到标准化残差:

第二步:对标准化残差估计动态相关矩阵

最终的动态协方差矩阵:

其中

import numpy as np
from arch import arch_model
 
np.random.seed(42)
 
# ============================================================
# DCC-GARCH 模型(简化实现)
# ============================================================
 
def univariate_garch(returns, p=1, q=1):
    """
    单变量 GARCH(p,q) 估计
 
    返回:
        conditional_vol: 条件波动率序列
        standardized_resid: 标准化残差
    """
    am = arch_model(returns * 100, vol='Garch', p=p, q=q, dist='normal')
    result = am.fit(disp='off')
    conditional_vol = result.conditional_volatility / 100
    standardized_resid = result.resid / result.conditional_volatility
    return conditional_vol, standardized_resid
 
 
def dcc_correlations(standardized_resids, a=0.05, b=0.90):
    """
    DCC 动态相关估计(简化版)
 
    参数:
        standardized_resids: T x n 标准化残差矩阵
        a: DCC 参数(滞后冲击的影响)
        b: DCC 参数(滞后相关的影响)
 
    返回:
        R_t: T x n x n 动态相关矩阵序列
    """
    T, n = standardized_resids.shape
 
    # Q_bar:无条件相关矩阵
    Q_bar = np.corrcoef(standardized_resids.T)
 
    # 初始化
    Q_t = Q_bar.copy()
    R_t = np.zeros((T, n, n))
 
    for t in range(T):
        # 更新 Q
        if t == 0:
            z_prev = standardized_resids[0]
        else:
            z_prev = standardized_resids[t-1]
            Q_t = (1 - a - b) * Q_bar + a * np.outer(z_prev, z_prev) + b * Q_t
 
        # 标准化得到相关矩阵
        diag_sqrt = np.sqrt(np.diag(Q_t))
        D_inv = np.diag(1.0 / (diag_sqrt + 1e-10))
        R_t[t] = D_inv @ Q_t @ D_inv
 
    return R_t
 
 
# 模拟:2 只资产,带 GARCH 效应
n_days = 1000
 
# GARCH(1,1) 过程
omega, alpha, beta = 0.00001, 0.05, 0.90
sigma = np.zeros(n_days)
eps1 = np.zeros(n_days)
eps2 = np.zeros(n_days)
 
sigma[0] = 0.02
for t in range(1, n_days):
    sigma[t] = np.sqrt(omega + alpha * eps1[t-1]**2 + beta * sigma[t-1]**2)
    eps1[t] = sigma[t] * np.random.normal()
    # 第二只资产:有动态相关性
    corr_t = 0.3 + 0.5 * np.exp(-abs(eps1[t-1]) / 0.05)
    eps2[t] = sigma[t] * (corr_t * np.random.normal()
                           + np.sqrt(1 - corr_t**2) * np.random.normal())
 
returns1 = eps1
returns2 = eps2
returns = np.column_stack([returns1, returns2])
 
# 估计条件波动率
vol1, z1 = univariate_garch(returns1)
vol2, z2 = univariate_garch(returns2)
z = np.column_stack([z1, z2])
 
# DCC 相关矩阵
R_t = dcc_correlations(z, a=0.05, b=0.90)
 
# 提取动态相关系数
dynamic_corr = np.array([R_t[t, 0, 1] for t in range(n_days)])
 
# 可视化
fig, axes = plt.subplots(3, 1, figsize=(14, 10))
 
axes[0].plot(returns1, alpha=0.5, label='资产 1 收益率')
axes[0].plot(returns2, alpha=0.5, label='资产 2 收益率')
axes[0].set_title('两只资产的收益率')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
 
axes[1].plot(vol1 * np.sqrt(252), label='资产 1 条件波动率')
axes[1].plot(vol2 * np.sqrt(252), label='资产 2 条件波动率')
axes[1].set_title('GARCH 条件波动率(年化)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
 
axes[2].plot(dynamic_corr, color='purple', linewidth=0.5)
rolling_corr = pd.Series(returns1).rolling(60).corr(pd.Series(returns2))
axes[2].plot(rolling_corr.values, color='red', linewidth=1, label='60日滚动相关')
axes[2].axhline(0.5, color='gray', linestyle='--')
axes[2].set_title('DCC 动态相关系数')
axes[2].set_ylabel('相关系数')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
 
plt.tight_layout()
plt.show()

5.3 DCC-GARCH 的实务意义

应用说明
风险预警动态相关性升高预示系统性风险增加
动态对冲根据时变相关性调整对冲比率
组合优化用时变协方差矩阵做滚动组合优化
压力测试在相关性极端情景下评估组合风险

六、协方差矩阵在组合优化中的应用

6.1 协方差矩阵估计方法的选择

# 完整对比:不同协方差估计对组合优化的影响
import numpy as np
from scipy.optimize import minimize
 
np.random.seed(42)
 
n_assets = 50
n_days = 200
 
# 真实协方差(因子结构)
n_factors = 5
B = np.random.normal(0, 0.5, (n_assets, n_factors))
Sigma_f = np.diag(np.random.uniform(0.005, 0.015, n_factors))
idio = np.diag(np.random.uniform(0.005, 0.015, n_assets))
Sigma_true = B @ Sigma_f @ B.T + idio
 
# 生成数据
returns = np.random.multivariate_normal(np.zeros(n_assets), Sigma_true, n_days)
mu = returns.mean(axis=0) * 252
 
# 不同估计方法
S_sample = np.cov(returns.T)
Sigma_lw, _ = ledoit_wolf_shrinkage(returns)
Sigma_pca, _ = pca_covariance(returns, n_factors=5)
 
# 组合优化函数
def min_var_portfolio(Sigma):
    n = len(Sigma)
    constraints = [{'type': 'eq', 'fun': lambda w: np.sum(w) - 1}]
    bounds = [(0, 1) for _ in range(n)]
    w0 = np.ones(n) / n
    result = minimize(lambda w: w @ Sigma @ w, w0,
                      method='SLSQP', bounds=bounds, constraints=constraints)
    return result.x
 
methods = {
    '样本协方差': S_sample,
    'Ledoit-Wolf': Sigma_lw,
    'PCA 因子模型': Sigma_pca,
    '真实协方差': Sigma_true
}
 
print("=== 不同协方差估计下的 GMV 组合 ===")
print(f"{'方法':>15s}  {'波动率':>8s}  {'最大权重':>8s}  {'非零权重':>8s}")
 
for name, Sigma in methods.items():
    try:
        w = min_var_portfolio(Sigma)
        vol = np.sqrt(w @ Sigma_true @ w)  # 用真实协方差评估
        max_w = np.max(w)
        n_nonzero = np.sum(w > 0.001)
        print(f"{name:>15s}  {vol:>8.4f}  {max_w:>8.2%}  {n_nonzero:>8d}")
    except Exception as e:
        print(f"{name:>15s}  失败: {e}")
=== 不同协方差估计下的 GMV 组合 ===
             方法      波动率    最大权重   非零权重
      样本协方差    0.0823     15.23%        50
      Ledoit-Wolf    0.0798      5.67%        50
   PCA 因子模型    0.0789      8.34%        50
       真实协方差    0.0772      4.89%        50

6.2 选择建议

场景推荐方法原因
(数据充足)样本协方差 + Ledoit-Wolf样本可靠,收缩进一步降噪
(数据勉强够)Ledoit-Wolf收缩提供必要的正则化
(数据不足)因子模型(PCA)降维到可行范围
需要经济学解释基本面因子模型因子有经济含义
时变风险DCC-GARCH捕捉波动率和相关性的动态变化
高频数据已实现协方差 + EMA用高频数据提供更精确的估计

七、小结

概念要点
维度灾难当资产数接近或超过样本数时,样本协方差矩阵不可用
Ledoit-Wolf把样本协方差”拉向”稳定目标。简单有效,推荐作为默认选择
因子模型假设协方差由少数因子驱动,大幅降低参数量
RMTMarchenko-Pastur 分布帮助区分真实信号和噪声
DCC-GARCH捕捉协方差的时变性,对风险预警和动态对冲至关重要

一句话总结:协方差矩阵估计是组合管理的”基础设施”。样本协方差矩阵在维度灾难面前几乎不可用,收缩估计和因子模型是两个最实用的解决方案。RMT 提供了选择因子数量的统计依据,DCC-GARCH 处理了协方差的时变性。实务中建议先尝试 Ledoit-Wolf,再根据具体场景选择更高级的方法。


参考阅读:Ledoit & Wolf (2004), “A Well-Conditioned Estimator for Large-Dimensional Covariance Matrices”; Engle (2002), “Dynamic Conditional Correlation”; Marchenko & Pastur (1967); Laloux et al. (2000), “Random Matrix Theory”

下一章:03-绩效归因 — 赚的钱从哪来