第 3 章 · 01 样本协方差 本节摘要:本节是「风险模型」五节里的第一节,讲最直接也最常被滥用的协方差估计—— 。协方差矩阵 Σ 是均值-方差优化的基石:对角线是每只资产的方差(波动率的平方),非对角线是两两资产的联动程度。 直接用历史收益的样本协方差 年化,无偏但噪声极大。本节拆解它的源码实现、PSD(半正定)修正机制、frequency 参数,以及它最大的隐患——在「资产数接近样本数」时严重失真,优化器会把噪声当成信号放大成错误权重。文档明确建议:不要把它当默认,应优先用收缩估计。 内容来源:原项目源码 (函数 、 )、文档 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 写出协方差矩阵的数学含义与对角/非对角元素。 用 算年化样本协方差。
本节摘要:本节是「风险模型」五节里的第一节,讲最直接也最常被滥用的协方差估计——
sample_cov。协方差矩阵 Σ 是均值-方差优化的基石:对角线是每只资产的方差(波动率的平方),非对角线是两两资产的联动程度。sample_cov直接用历史收益的样本协方差returns.cov() × 252年化,无偏但噪声极大。本节拆解它的源码实现、PSD(半正定)修正机制、frequency 参数,以及它最大的隐患——在「资产数接近样本数」时严重失真,优化器会把噪声当成信号放大成错误权重。文档明确建议:不要把它当默认,应优先用收缩估计。
内容来源:原项目源码
pypfopt/risk_models.py(函数sample_cov、fix_nonpositive_semidefinite)、文档docs/RiskModels.rst,汉化并套用体系化模板。
阅读完本节,你应当能够:
sample_cov 算年化样本协方差。协方差矩阵 Σ 是一个 N×N 对称矩阵(N 是资产数),编码了资产两两之间的联动:
Σ_ii:资产 i 的方差 = 波动率 σ_i 的平方。Σ_ij:资产 i 与 j 的协方差,= 相关性 × σ_i × σ_j。它在均值-方差优化里扮演核心角色:
组合方差 = wᵀ Σ w
💡 核心心法:分散化的收益来自「低相关」资产——把不相关甚至负相关的资产组合在一起,组合方差比单只资产方差加权平均还小,这就是「免费午餐」。协方差矩阵就是量化这种联动的工具。估得越准,优化的「免费午餐」越扎实。
打开 pypfopt/risk_models.py,定位 sample_cov(源码 172-203 行):
def sample_cov(prices, returns_data=False, frequency=252, log_returns=False, **kwargs): if not isinstance(prices, pd.DataFrame): warnings.warn("data is not in a dataframe", RuntimeWarning) prices = pd.DataFrame(prices) if returns_data: returns = prices else: returns = returns_from_prices(prices, log_returns) return fix_nonpositive_semidefinite( returns.cov() * frequency, kwargs.get("fix_method", "spectral") )
简洁明了:
returns_data=False(默认)时调用 returns_from_prices 算日度收益。returns.cov()——pandas 内置方法,直接算 N×N 样本协方差矩阵(用样本均值,无偏估计的 (T-1) 分母)。* frequency,日度方差乘 252 = 年化方差(协方差同理,因为日度收益近似独立)。fix_nonpositive_semidefinite 检查并修复——见下节。协方差矩阵必须是半正定(positive semidefinite, PSD)的——否则 wᵀ Σ w 可能为负,组合方差失去意义,且优化器求逆/Cholesky 分解会失败。
但数值上,样本协方差有时不满足 PSD(尤其资产数 N 接近样本数 T 时)。PyPortfolioOpt 用 fix_nonpositive_semidefinite 兜底,提供两种修正方式:
def fix_nonpositive_semidefinite(matrix, fix_method="spectral"): if _is_positive_semidefinite(matrix): return matrix warnings.warn("The covariance matrix is non positive semidefinite. Amending eigenvalues.") q, V = np.linalg.eigh(matrix) if fix_method == "spectral": q = np.where(q > 0, q, 0) # 负特征值置零 fixed_matrix = V @ np.diag(q) @ V.T # 重构 elif fix_method == "diag": min_eig = np.min(q) fixed_matrix = matrix - 1.1 * min_eig * np.eye(len(matrix)) # 加常数到对角 ...
| 方法 | 做法 | 适用 |
|---|---|---|
spectral(默认) |
特征值分解,负特征值置零后重构 | 通用,保秩最小 |
diag |
对角加一个小正数(负特征值绝对值的 1.1 倍) | 简单,但改变对角方差 |
💡 PSD 检测原理:代码里
_is_positive_semidefinite用 Cholesky 分解尝试,成功即 PSD——这比检查特征值快得多。
| 参数 | 默认 | 含义 | 备注 |
|---|---|---|---|
prices |
— | 价格 DataFrame | 必填 |
returns_data |
False |
True 表示输入已是收益 | 不应是对数收益 |
frequency |
252 |
年化系数 | 月度改 12,周度改 52 |
log_returns |
False |
是否用对数收益算 | 简单/对数收益数值接近但不等 |
fix_method |
"spectral" |
PSD 修正方法 | spectral 或 diag |
⚠️ frequency 必须匹配数据频率:喂日度价格用 252,月度用 12——与
mean_historical_return一致。年化错误的协方差会让后续优化的「波动率」全错。
默认调用:
from pypfopt import risk_models S = risk_models.sample_cov(df)
返回 N×N 的 pd.DataFrame,索引与列均为资产代码。UserGuide 给出的示例片段:
GOOG AAPL FB BABA AMZN GE AMD GOOG 0.045529 0.022143 0.006389 0.003720 0.026085 0.015815 0.021761 AAPL 0.022143 0.207037 0.004334 0.002954 0.058200 0.038102 0.084053 FB 0.006389 0.004334 0.029233 0.003770 0.007619 0.003008 0.005804 ...
对角线 GOOG-GOOG = 0.0455 是 GOOG 的年化方差,波动率 sqrt(0.0455) ≈ 21.3%。
通过统一入口:
S = risk_models.risk_matrix(df, method="sample_cov")
样本协方差是无偏估计(E(S) = Σ),但在「资产数 N 接近样本数 T」时会严重失真。这是均值-方差优化的著名陷阱。
直觉解释:
更糟的是,均值-方差优化器会最大化噪声:
⚠️ 官方警告:
docs/RiskModels.rst在sample_cov下面明确写:这不应是你的默认选择!请使用收缩估计器代替。
原文用大写强调。原因就是上面的噪声放大问题。
尽管不推荐做默认,sample_cov 仍有用武之地:
plotting.plot_covariance 对比两者的热力图。CovarianceShrinkage 内部也先算样本协方差,再向目标收缩。💡 研究支撑:Kritzman et al. (2010) 等研究表明,最小方差组合(只用 Σ、不喂 μ)样本外常常跑赢最大夏普组合——这进一步说明:若你要简化,优先把 Σ 估好,而不是把 μ 估好。所以第 04 节的收缩估计才是生产默认。
wᵀ Σ w 是组合方差。returns.cov() × 252,无偏但噪声大。fix_nonpositive_semidefinite 用 spectral(负特征值置零)或 diag(对角加常数)修复非半正定矩阵。下一节,我们看半协方差——一个只惩罚下行偏差、更贴合「风险是亏损」直觉的估计。