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

一句话回答: 持有一篮子股票时,组合风险不等于 各股波动率的简单加总------相关性越低,分散化越有效。用 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 就能算出相关性矩阵、年化组合波动率和风险贡献,看清分散化是否真的有效、风险集中在谁身上------这是做资产配置与风控的第一课。

参考

相关推荐
摇滚侠1 天前
《On Java 中文版 基础卷》阅读笔记 对象无处不在 03
java·笔记·python
小小小小钰儿1 天前
1.5-Python网络编程
开发语言·网络·python·web安全·计算机·网络安全
keke.shengfengpolang1 天前
2026年金融风控岗技术能力拆解:SQL取数、Python建模、评分卡项目,校招面试到底考什
python
kyrie_sakura1 天前
大模型学习7 -- 深度学习2 -- 张量
python·深度学习
智鸟科技GemeOpen开发者智能设备1 天前
智能插座二次开发如何接入Home Assistant,从零开始完整工程实现(Python+React)
开发语言·python·物联网·react.js·智能家居
hqyjzsb1 天前
法律 Prompt 干货:搭建合同审查提示词要包含哪些要素
开发语言·人工智能·python·职场和发展·数据分析·prompt·业界资讯
Andreapiki1 天前
风险审计校招技术栈拆解:SQL、Python、Power BI在2026届JD中的真实权重
数据库·python·sql
2601_966949651 天前
股票池发生变化后,如何高效更新行情数据?从全量刷新到增量同步
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
玖石书1 天前
python-uv-windows环境安装方案
windows·python·uv