第 3 章 · 03 指数协方差


文档摘要

第 3 章 · 03 指数协方差 本节摘要:本节讲 的指数加权协方差—— 。它的动机与第 2 章的 EMA 收益完全一致:近期数据比远期更能代表当前的资产联动关系。样本协方差把 5 年前和昨天的数据等权对待,但两家公司十年前的业务关系可能早已瓦解。 给近期收益对更高的权重、远期权重指数衰减,从而对「当前真实的联动结构」更敏感。本节拆解它的源码实现:逐对算 (用 )、为何要双重循环(协方差没有现成的 pandas 向量化接口)、span 参数(默认 180)与第 2 章 EMA 的 span(默认 500)的差异,以及作者论文背景。 内容来源:原项目源码 (函数 、 )、文档 ,汉化并套用体系化模板。 学习目标 阅读完本节,你应当能够: 解释指数协方差对近期联动更敏感的动机。

第 3 章 · 03 指数协方差

本节摘要:本节讲 risk_models.py 的指数加权协方差——exp_cov。它的动机与第 2 章的 EMA 收益完全一致:近期数据比远期更能代表当前的资产联动关系。样本协方差把 5 年前和昨天的数据等权对待,但两家公司十年前的业务关系可能早已瓦解。exp_cov 给近期收益对更高的权重、远期权重指数衰减,从而对「当前真实的联动结构」更敏感。本节拆解它的源码实现:逐对算 _pair_exp_cov(用 ewm(span).mean())、为何要双重循环(协方差没有现成的 pandas 向量化接口)、span 参数(默认 180)与第 2 章 EMA 的 span(默认 500)的差异,以及作者论文背景。

内容来源:原项目源码 pypfopt/risk_models.py(函数 exp_cov_pair_exp_cov)、文档 docs/RiskModels.rst,汉化并套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 解释指数协方差对近期联动更敏感的动机。
  2. 描述 _pair_exp_cov 的算法:协变积的 EWMA。
  3. exp_cov 算指数加权协方差矩阵。
  4. 区分 exp_covspan(默认 180)与 ema_historical_returnspan(默认 500)。
  5. 知道这是作者博客文章里的实验性方法,实务定位。

一、动机:联动会变

资产之间的相关性不是恒定的。最典型的例子:

  • 危机时相关性趋 1:2008 金融危机、2020 疫情期间,几乎所有股票一起跌,「分散化失效」。
  • 行业变迁:两家公司十年前是上下游,现在可能已无关;或十年前无关,现在变成直接竞争对手。

样本协方差把所有历史数据等权,无法捕捉这种「联动结构随时间变化」。指数加权给近期更高权重,更聚焦「现在的联动」。

二、源码逐行解析

打开 pypfopt/risk_models.py,定位 exp_cov_pair_exp_cov(源码 256-327 行)。

2.1 单对协方差 _pair_exp_cov

def _pair_exp_cov(X, Y, span=180): covariation = (X - X.mean()) * (Y - Y.mean()) if span < 10: warnings.warn("it is recommended to use a higher span, e.g 30 days") return covariation.ewm(span=span).mean().iloc[-1]

算法分三步:

  1. 协变积:(X - μ_X) × (Y - μ_Y)——两个序列的中心化乘积,逐期算。正值表示同期同向偏离(正相关),负值表示反向(负相关)。
  2. 指数加权平均:对协变积序列做 ewm(span).mean(),近期协变积权重更大。
  3. 取最后一期:.iloc[-1]——EWMA 是递推式,最后一期已包含全部历史的指数加权信息,就是当前的「指数协方差」估计。

⚠️ span < 10 警告:太小的 span 会让 EWMA 只看最近几期,协方差估计噪声爆炸。源码主动发警告推荐至少 30 天。

2.2 矩阵组装 exp_cov

def exp_cov(prices, returns_data=False, span=180, frequency=252, log_returns=False, **kwargs): ... N = len(assets) S = np.zeros((N, N)) for i in range(N): for j in range(i, N): S[i, j] = S[j, i] = _pair_exp_cov( returns.iloc[:, i], returns.iloc[:, j], span ) cov = pd.DataFrame(S * frequency, columns=assets, index=assets) return fix_nonpositive_semidefinite(cov, kwargs.get("fix_method", "spectral"))

要点:

  1. 双重循环:协方差矩阵没有现成的 pandas 向量化「指数加权」接口(不像 returns.cov()),必须逐对算。利用对称性 S[i,j] = S[j,i],只算上三角,N(N+1)/2 次。
  2. 年化:* frequency,与 sample_cov 一致。
  3. PSD 修正:指数加权后矩阵可能不再 PSD(因为各对独立估),仍要 fix_nonpositive_semidefinite 修复。

💡 为什么不能用 returns.ewm().cov():pandas 确实有 returns.ewm(span=...).cov() 的接口,但它返回的是逐期的协方差序列(每个时点一个矩阵),不是「整体」的指数加权协方差矩阵。PyPortfolioOpt 想要的是「用最后一期 EWMA 代表当前协方差」,所以手动逐对算。这也是 N 大时(比如 100 资产,需 5050 次循环)该方法较慢的原因。

三、span 参数与第 2 章的差异

exp_covspan 默认 180,而 ema_historical_returnspan 默认 500。两者都是「指数加权」,为何默认值不同?

方法 span 默认 直觉
ema_historical_return 500 收益的长期均值相对稳定,用更长窗口
exp_cov 180 联动结构变化更快,用更短窗口捕捉近期

这反映了作者的实务判断:资产间的相关性比单只资产的均值更容易且更快地变化,所以协方差用更小的 span。当然,这只是默认值,实务中可根据你的调仓频率调整。

四、参数速查表

参数 默认 含义 备注
prices 价格 DataFrame 必填
returns_data False True 表示输入已是收益
span 180 指数加权窗口 小于 10 会警告
frequency 252 年化系数 月度改 12
log_returns False 是否用对数收益
fix_method "spectral" PSD 修正方法

五、用法示例

默认调用:

from pypfopt import risk_models S_exp = risk_models.exp_cov(df)

调短 span 捕捉近期联动:

S_exp = risk_models.exp_cov(df, span=60)

通过统一入口:

risk_models.risk_matrix(df, method="exp_cov", span=90)

直接喂优化器:

from pypfopt import EfficientFrontier, expected_returns mu = expected_returns.capm_return(df) S = risk_models.exp_cov(df, span=90) ef = EfficientFrontier(mu, S) ef.max_sharpe()

六、与样本协方差的对比

同一份数据,两种协方差矩阵的热力图差异明显:

  • 样本协方差:整体更「亮」(等权平均让远期高联动也计入),极端值多。
  • 指数协方差:更聚焦近期结构——若近期某些资产联动减弱,矩阵对应位置变「暗」;若危机期一起波动加剧,矩阵更「亮」。

作者在博客 Exponential Covariance 里写到,这个估计的灵感来自 EMA 价格相对 SMA 价格的优势——给近期更高权重,通常更贴近「当前市场状态」。

💡 何时用 exp_cov:如果你相信近期联动结构比长期平均更能预示未来(例如交易频率较高、市场处于状态切换期),exp_covsample_cov 更合理。但要注意 span 调节——太小噪声大,太大退化为样本协方差。

七、实务定位与局限

docs/RiskModels.rstexp_cov 描述为 PyPortfolioOpt 的「实验性替代方案」之一(与 semicovariance 并列):

指数协方差矩阵是一种新颖的方法,在计算协方差时给近期数据更高权重,就像指数移动平均价格常优于简单平均价格一样。完整解释请参考作者的博客文章。

它的局限:

  1. :N 大时双重循环开销大(无向量化接口)。
  2. 仍非收缩:像样本协方差一样,它没有结构先验,在 N 接近 T 时仍可能噪声大。
  3. PSD 修复:逐对估计的矩阵可能非半正定,需修复,修复会引入微小偏差。
  4. 作者更推荐收缩:文档明确建议用 Ledoit-Wolf 收缩作为生产默认(见下一节),而非 exp_cov。

⚠️ 生产首选仍是收缩:虽然 exp_cov 直觉合理,但作者在多个场合强调,Ledoit-Wolf 收缩是更稳健的生产默认(偏差-方差权衡更优)。exp_cov 适合作为「快速捕捉近期」的备选,或与收缩估计做对比验证。

本节要点回顾

  1. 动机:资产联动会随时间变化(危机期相关性趋 1、行业变迁),指数加权对近期更敏感。
  2. 算法:_pair_exp_cov 对协变积 (r_i-μ_i)(r_j-μ_j)ewm(span).mean(),取最后一期。
  3. 双重循环:无现成向量化接口,利用对称性算 N(N+1)/2 对;N 大时较慢。
  4. span 默认 180:比 EMA 收益(500)短,因相关性变化更快;小于 10 会警告。
  5. 与收缩的关系:实验性方法,适合捕捉近期;生产默认仍推荐 Ledoit-Wolf 收缩(下一节)。
  6. PSD 修正:逐对估计可能非半正定,需 fix_nonpositive_semidefinite 修复。

下一节是本章重头戏——收缩估计(Ledoit-Wolf 与 OAS),这是 PyPortfolioOpt 文档反复强调的生产默认风险模型。


发布者: 作者: 灏天文库 转发
评论区 (0)
U