In [1]:
Copied!
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.covariance import LedoitWolf, EmpiricalCovariance
np.random.seed(42)
print('Libraries loaded')
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.covariance import LedoitWolf, EmpiricalCovariance
np.random.seed(42)
print('Libraries loaded')
Libraries loaded
In [2]:
Copied!
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. 样本协方差矩阵的缺陷¶
当股票数量 N 接近或超过历史数据行数 T 时,样本协方差矩阵会出现严重问题:
- 矩阵估计噪音大:历史有限,样本协方差包含大量估计误差
- 条件数极大:矩阵几乎奇异,求逆不稳定
- Markowitz 优化放大误差:优化器会过度利用噪音,产生极端权重
这就是马科维茨优化被称为 "误差放大器" (Error Maximizer) 的原因。
In [3]:
Copied!
# 演示:T/N 比例对协方差估计质量的影响
N = 50 # 股票数量
T = 100 # 历史数据行数 (T/N = 2)
# 模拟真实协方差结构
true_vols = np.random.uniform(0.1, 0.3, N) / np.sqrt(252) # 日波动率
true_corr = np.eye(N)
true_corr[0:10, 0:10] = 0.6 # 前10只股票高度相关(同一板块)
np.fill_diagonal(true_corr, 1.0)
true_cov = np.outer(true_vols, true_vols) * true_corr
# 从真实协方差生成模拟收益率
returns = np.random.multivariate_normal(np.zeros(N), true_cov, T)
# 样本协方差矩阵
sample_cov = np.cov(returns.T)
# 误差:Frobenius 范数
error = np.linalg.norm(sample_cov - true_cov, 'fro')
print(f'样本协方差矩阵 Frobenius 误差: {error:.4f}')
print(f'矩阵条件数(越大越不稳定): {np.linalg.cond(sample_cov):.1f}')
# 演示:T/N 比例对协方差估计质量的影响
N = 50 # 股票数量
T = 100 # 历史数据行数 (T/N = 2)
# 模拟真实协方差结构
true_vols = np.random.uniform(0.1, 0.3, N) / np.sqrt(252) # 日波动率
true_corr = np.eye(N)
true_corr[0:10, 0:10] = 0.6 # 前10只股票高度相关(同一板块)
np.fill_diagonal(true_corr, 1.0)
true_cov = np.outer(true_vols, true_vols) * true_corr
# 从真实协方差生成模拟收益率
returns = np.random.multivariate_normal(np.zeros(N), true_cov, T)
# 样本协方差矩阵
sample_cov = np.cov(returns.T)
# 误差:Frobenius 范数
error = np.linalg.norm(sample_cov - true_cov, 'fro')
print(f'样本协方差矩阵 Frobenius 误差: {error:.4f}')
print(f'矩阵条件数(越大越不稳定): {np.linalg.cond(sample_cov):.1f}')
样本协方差矩阵 Frobenius 误差: 0.0008 矩阵条件数(越大越不稳定): 158.5
2. Ledoit-Wolf 收缩估计¶
Ledoit-Wolf(2004) 提出了最优线性收缩估计器:
$$\hat{\Sigma}_{LW} = (1-\alpha) \hat{\Sigma}_{sample} + \alpha \cdot \mu I$$
- $\alpha$ 是最优收缩系数(自动计算)
- $\mu I$ 是目标矩阵(单位矩阵乘以平均特征值)
收缩有效地将极端的特征值拉向均值,使矩阵更接近真实分布,更适合优化器使用。
In [4]:
Copied!
# Ledoit-Wolf 收缩估计
lw = LedoitWolf()
lw.fit(returns)
lw_cov = lw.covariance_
error_lw = np.linalg.norm(lw_cov - true_cov, 'fro')
print(f'Ledoit-Wolf 协方差矩阵 Frobenius 误差: {error_lw:.4f}')
print(f' vs 样本协方差误差: {error:.4f}')
print(f'LW 减少误差: {(error - error_lw)/error:.2%}')
print(f'\n收缩系数 alpha: {lw.shrinkage_:.4f}')
print(f'LW 矩阵条件数: {np.linalg.cond(lw_cov):.1f} (远比样本协方差小)')
# 可视化:特征值分布对比
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for ax, cov_mat, title in zip(axes,
[true_cov, sample_cov, lw_cov],
['真实协方差', '样本协方差(噪音大)', 'Ledoit-Wolf 收缩']):
eigvals = np.linalg.eigvalsh(cov_mat)
ax.hist(eigvals, bins=30, color='steelblue', alpha=0.7, edgecolor='white')
ax.set_title(f'{title}')
ax.set_xlabel('特征值大小')
ax.set_ylabel('频率')
ax.grid(alpha=0.3)
plt.suptitle('特征值分布对比:收缩后更集中(减少极端值)', fontsize=12)
plt.tight_layout()
plt.show()
# Ledoit-Wolf 收缩估计
lw = LedoitWolf()
lw.fit(returns)
lw_cov = lw.covariance_
error_lw = np.linalg.norm(lw_cov - true_cov, 'fro')
print(f'Ledoit-Wolf 协方差矩阵 Frobenius 误差: {error_lw:.4f}')
print(f' vs 样本协方差误差: {error:.4f}')
print(f'LW 减少误差: {(error - error_lw)/error:.2%}')
print(f'\n收缩系数 alpha: {lw.shrinkage_:.4f}')
print(f'LW 矩阵条件数: {np.linalg.cond(lw_cov):.1f} (远比样本协方差小)')
# 可视化:特征值分布对比
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for ax, cov_mat, title in zip(axes,
[true_cov, sample_cov, lw_cov],
['真实协方差', '样本协方差(噪音大)', 'Ledoit-Wolf 收缩']):
eigvals = np.linalg.eigvalsh(cov_mat)
ax.hist(eigvals, bins=30, color='steelblue', alpha=0.7, edgecolor='white')
ax.set_title(f'{title}')
ax.set_xlabel('特征值大小')
ax.set_ylabel('频率')
ax.grid(alpha=0.3)
plt.suptitle('特征值分布对比:收缩后更集中(减少极端值)', fontsize=12)
plt.tight_layout()
plt.show()
Ledoit-Wolf 协方差矩阵 Frobenius 误差: 0.0008 vs 样本协方差误差: 0.0008 LW 减少误差: 4.97% 收缩系数 alpha: 0.3723 LW 矩阵条件数: 11.5 (远比样本协方差小)
🎯 练习¶
- 将股票数量 N 改为 100,保持 T=100(T/N=1),观察样本协方差矩阵的条件数如何爆炸。
- 使用两种协方差矩阵(样本 vs Ledoit-Wolf)分别运行最小方差优化(
scipy.optimize.minimize),对比权重向量的差异。 - 查阅
sklearn的OAS(Oracle Approximating Shrinkage)估计器,对比其与 Ledoit-Wolf 的误差。
下一节 → ../06_ml_trading/04_dangers_of_overfitting.ipynb
In [ ]:
Copied!