首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >如何用 Python 算多只股票的相关性矩阵与组合波动率?

如何用 Python 算多只股票的相关性矩阵与组合波动率?

原创
作者头像
用户7083614
发布2026-09-10 10:01:04
发布2026-09-10 10:01:04
100
举报

一句话回答: 持有一篮子股票时,组合风险不等于各股波动率的简单加总——相关性越低,分散化越有效。用 AlphaFeed 批量取前复权 K 线算日收益率,再用 pandas/numpy 几行就能得到相关性矩阵、年化组合波动率和每只票的风险贡献,看清风险到底集中在谁身上。

为什么会有这个问题

新手常以为"5 只票平均分配,风险就分散了"。但如果这 5 只高度相关(比如都是银行股),实际等于重仓一个方向。真正的组合风险由协方差矩阵决定,必须同时看波动率和两两相关性。

分步骤解决

第 1 步:批量取组合内标的的前复权 K 线

代码语言:python
复制
from alphafeed import AlphaFeed
import pandas as pd, numpy as np

af = AlphaFeed()  # 读取 ALPHAFEED_API_KEY

portfolio = ["600519.SH", "000001.SZ", "601318.SH",
             "600036.SH", "000858.SZ", "300750.SZ"]

kb = af.klines.batch(portfolio, period="1d", count=120, adjust="forward",
                     to_dataframe=True)   # 返回 {symbol: DataFrame}

adjust="forward"(前复权)算收益率,避免分红送股造成假跳空污染相关性。

第 2 步:拼成"日期 × 标的"的收益率矩阵

代码语言:python
复制
closes = pd.DataFrame({
    s: d.sort_values("trade_date").set_index("trade_date")["close"]
    for s, d in kb.items()
})
rets = closes.pct_change().dropna()     # 对齐交易日后的日收益率
print("收益率矩阵形状:", rets.shape)     # 例如 (119, 6)

第 3 步:相关性矩阵(看分散化是否有效)

代码语言:python
复制
corr = rets.corr()
print(corr.round(2).to_string())

实测样例(近 120 交易日,节选):

600519

000001

601318

300750

600519(茅台)

1.00

0.48

0.56

0.17

000001(平安银行)

0.48

1.00

0.64

0.08

601318(中国平安)

0.56

0.64

1.00

0.25

300750(宁德时代)

0.17

0.08

0.25

1.00

解读:银行/保险(000001 与 601318,0.64)相关性高,分散化有限;宁德时代与其余相关性低(0.03~0.25),是较好的分散标的。

第 4 步:年化组合波动率 + 各标的风险贡献

代码语言:python
复制
w = np.repeat(1 / len(portfolio), len(portfolio))   # 等权组合
cov = rets.cov() * 252                               # 年化协方差矩阵
port_vol = np.sqrt(w @ cov.values @ w)               # 年化组合波动率
print("年化组合波动率:", round(port_vol, 4))          # 实测约 0.177

# 边际风险贡献 -> 风险贡献(RC 之和 = 组合波动率)
mrc = cov.values @ w
rc = w * mrc / port_vol
risk_contrib = pd.Series(rc, index=portfolio).sort_values(ascending=False)
print("各标的风险贡献:\n", risk_contrib.round(4).to_string())
print("RC 合计校验:", round(risk_contrib.sum(), 4))   # ≈ port_vol

概念

公式/含义

年化协方差

rets.cov() * 252(日频→年化)

组合波动率

sqrt(wᵀ Σ w)

边际风险贡献 MRC

Σ w(每单位权重带来的边际风险)

风险贡献 RC

w ⊙ MRC / σ_p,各项之和 = 组合波动率

风险贡献告诉你"波动到底由谁贡献"——若某只票 RC 占比远超其权重,说明它是组合风险的主要来源。

第 5 步:封装成组合风险体检

代码语言:python
复制
def portfolio_risk(symbols, weights=None, count=120):
    af = AlphaFeed()
    kb = af.klines.batch(symbols, period="1d", count=count, adjust="forward",
                         to_dataframe=True)
    closes = pd.DataFrame({s: d.sort_values("trade_date").set_index("trade_date")["close"]
                           for s, d in kb.items()})
    rets = closes.pct_change().dropna()
    w = np.array(weights) if weights else np.repeat(1/len(symbols), len(symbols))
    cov = rets.cov() * 252
    vol = float(np.sqrt(w @ cov.values @ w))
    rc = pd.Series(w * (cov.values @ w) / vol, index=cov.columns)
    return {"port_vol": round(vol, 4),
            "avg_corr": round(rets.corr().values[np.triu_indices(len(symbols), 1)].mean(), 3),
            "risk_contrib": rc.round(4).to_dict()}

print(portfolio_risk(portfolio))

关键坑与注意事项

  • 必须用复权价算收益率:不复权价在除权日暴跌,相关性会被污染。用 adjust="forward"
  • 交易日对齐pct_change().dropna() 会按共同交易日对齐;跨市场组合还要处理不同交易日历(另见跨市场对齐文)。
  • 相关性不稳定:历史相关性≠未来,极端行情下相关性常一起飙到 1(分散化失效),别把历史值当保证。
  • 样本长度:窗口太短(如 <60 日)协方差估计噪声大;太长又跟不上结构变化,按需权衡。
  • 年化系数:日频用 252,周频用 52,别混用。

常见问题(FAQ)

Q:为什么组合波动率小于各股波动率的加权平均?

A:因为相关性 <1 时存在分散化效应,协方差项没有满额叠加。相关性越低,降波越明显。

Q:风险贡献 RC 加起来为什么等于组合波动率?

A:这是欧拉分解的性质——组合波动率可精确拆分为各成分的风险贡献之和,便于定位风险来源。

Q:能算风险平价权重吗?

A:可以,在本文协方差矩阵基础上做优化(令各标的 RC 相等),本文先给出风险贡献这一基础量。

Q:需要付费吗?

A:批量 K 线 klines.batch 有免费额度;全市场级批量与更高频见 Starter 及以上。详见定价页。

小结

组合风险的核心是协方差,不是波动率简单相加。用 AlphaFeed 批量取前复权 K 线,几行 pandas/numpy 就能算出相关性矩阵、年化组合波动率和风险贡献,看清分散化是否真的有效、风险集中在谁身上——这是做资产配置与风控的第一课。

参考

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 为什么会有这个问题
  • 分步骤解决
    • 第 1 步:批量取组合内标的的前复权 K 线
    • 第 2 步:拼成"日期 × 标的"的收益率矩阵
    • 第 3 步:相关性矩阵(看分散化是否有效)
    • 第 4 步:年化组合波动率 + 各标的风险贡献
    • 第 5 步:封装成组合风险体检
  • 关键坑与注意事项
  • 常见问题(FAQ)
  • 小结
  • 参考
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档