34.金融模型分析——相关性分析

⭐ 本章目标

  • 理解皮尔逊相关系数的定义与性质
  • 掌握使用 Python 计算对数收益率相关系数矩阵
  • 学会绘制相关性热力图滚动相关性
  • 理解相关性在投资组合风险管理中的核心作用

⭐ 相关性在金融中的核心地位

相关性分析是现代金融学的基石:

  • 资本资产定价模型 (CAPM):衡量资产与市场的联动
  • 风险价值 (VaR):组合风险依赖于资产间相关性
  • 投资组合优化:通过低相关性资产实现分散化
  • 对冲策略设计:利用负相关资产降低风险

⭐ 皮尔逊相关系数的数学定义

皮尔逊相关系数衡量两个变量线性相关的强度和方向:

\[ \rho_{X,Y} = \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y} = \frac{E[(X-\mu_X)(Y-\mu_Y)]}{\sigma_X \sigma_Y} \]

  • \(\text{Cov}(X,Y)\):协方差,衡量两个变量的联合变动
  • \(\sigma_X, \sigma_Y\):各自的标准差
  • 取值范围:\([-1, 1]\)

⭐ 相关系数的直观解读

相关系数范围 含义 金融示例
\(\rho \approx 1\) 强正相关 同行业股票同涨同跌
\(0.5 < \rho < 1\) 中度正相关 不同行业大盘股
\(\rho \approx 0\) 无相关性 股票与商品期货
\(-1 < \rho < -0.5\) 中度负相关 股票与债券(部分时段)
\(\rho \approx -1\) 强负相关 多头头寸与空头对冲

⭐ 相关性的四大金融应用

  1. 投资组合分散化:相关性越低,组合风险越小(马科维茨理论核心)
  2. 系统性风险识别:高相关性意味着暴露于共同因子
  3. 风险对冲策略:负相关资产可用于构建市场中性策略
  4. 因子模型构建:相关性聚类识别行业因子与风格因子

⭐ A股市场主要指数简介

指数名称 代码 特点
上证综指 000001.SH 沪市全部股票,总市值加权
深证成指 399001.SZ 深市500只股票,综合考虑规模与流动性
创业板指 399006.SZ 创业板代表,高成长性、科技股集中

我们将分析这三个指数间的收益率相关性

⭐ 平台任务:获取指数数据并绘制热力图

Listing 1
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 注:Tushare接口权限不足,如要运行,可能需要更换api key
import numpy as np  # 导入NumPy数值计算库
import pandas as pd  # 导入Pandas数据分析库
import seaborn as sns  # 导入Seaborn可视化库
import matplotlib.pyplot as plt  # 导入Matplotlib绑图库
import tushare as ts  # 导入tushare模块

# 初始化Tushare api
pro = ts.pro_api("ba1646815a79a63470552889a69f957f5544bef01d3f082159bf8474")

# 获取上证指数、深证指数和创业板三个指数的收盘价
sh = pro.index_daily(ts_code="000001.SH", start_date="20220101", end_date="20230418")
sz = pro.index_daily(ts_code="399001.SZ", start_date="20220101", end_date="20230418")  # 获取深证成指日线行情数据
cyb = pro.index_daily(ts_code="399006.SZ", start_date="20220101", end_date="20230418")  # 获取创业板指日线行情数据

# 将数据框中的交易日期格式化为日期,并将其设置为索引
sh["trade_date"] = pd.to_datetime(sh["trade_date"], format="%Y%m%d")
sh.set_index("trade_date", inplace=True)  # 将trade_date列设为sh数据框的索引

sz["trade_date"] = pd.to_datetime(sz["trade_date"], format="%Y%m%d")  # 转换为日期时间格式
sz.set_index("trade_date", inplace=True)  # 将trade_date列设为sz数据框的索引

cyb["trade_date"] = pd.to_datetime(cyb["trade_date"], format="%Y%m%d")  # 转换为日期时间格式
cyb.set_index("trade_date", inplace=True)  # 将trade_date列设为cyb数据框的索引

# 合并收盘价
data = pd.concat([sh["close"], sz["close"], cyb["close"]], axis=1)
data.columns = ["SH", "SZ", "CYB"]  # 定义列表data.columns

# 计算收益率
returns = np.log(data / data.shift(1))

# 绘制相关矩阵热力图
sns.set(style="white")
corr = returns.corr().round(2)  # 计算相关系数矩阵
mask = np.zeros_like(corr, dtype=bool)  # 创建布尔掩码矩阵(用于隐藏热力图上三角)
# mask[np.triu_indices_from(mask)] = True
f, ax = plt.subplots(figsize=(9, 6))
cmap = sns.diverging_palette(220, 10, as_cmap=True)  # 创建蓝-红发散色板用于相关性热力图
# 绑制热力图
sns.heatmap(corr, annot=True, cmap=cmap, vmax=1, vmin=-1, center=0, square=True, linewidths=.5, cbar_kws={"shrink": .5})
ax.set_title("Correlation Matrix")  # 设置图表标题
plt.savefig("1.png")  # 保存图形至文件

⭐ 平台代码解析:API调用与数据预处理

代码核心步骤拆解:

  • ts.pro_api():初始化 Tushare pro 接口,需提供 API token
  • pro.index_daily():获取指数日线行情数据(含 OHLCV)
  • pd.to_datetime():将 Tushare 的 %Y%m%d 格式转换为标准日期
  • set_index('trade_date'):将日期设为索引,便于时间序列对齐

⭐ 平台代码解析:数据合并与相关性计算

  • pd.concat([...], axis=1):按列横向合并三个指数的收盘价
  • np.log(data / data.shift(1)):计算对数收益率
  • returns.corr():计算皮尔逊相关系数矩阵
  • sns.heatmap():用颜色深浅直观展示相关性强弱

⭐ 简单收益率 vs 对数收益率

特性 简单收益率 对数收益率
公式 \(R_t = (P_t - P_{t-1}) / P_{t-1}\) \(r_t = \ln(P_t / P_{t-1})\)
可加性 多期不可直接相加 可相加:\(r_{1 \to 3} = r_1 + r_2 + r_3\)
对称性 不对称:+10% 和 -10% 不对等 对称
分布 偏态 更接近正态分布

相关性分析中推荐使用对数收益率,因为其时间可加性和接近正态分布的特性。

⭐ 计算对数收益率与相关系数矩阵

Listing 2
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# ==================== 计算对数收益率 ====================
# 对数收益率: r_t = ln(P_t / P_{t-1})
returns = np.log(data / data.shift(1))
returns = returns.dropna()  # 删除第一行NaN

# ==================== 收益率数据探索 ====================
print('收益率数据:')
print(returns.head())

# ==================== 收益率统计 ====================
print(f'\n收益率描述统计:')
print(returns.describe().T)

# ==================== 计算相关系数矩阵 ====================
corr_matrix = returns.corr()
print('\n相关系数矩阵:')
print(corr_matrix.round(4))

# ==================== 验证矩阵性质 ====================
print(f'\n矩阵对称性: {np.allclose(corr_matrix, corr_matrix.T)}')
print(f'对角线元素: {np.diag(corr_matrix)}')

⭐ 相关系数矩阵的三大性质

相关系数矩阵具有严格的数学性质:

  • 对称性\(\rho_{ij} = \rho_{ji}\),矩阵关于对角线对称
  • 对角线为 1\(\rho_{ii} = 1\),资产与自身完全正相关
  • 半正定性:协方差矩阵是半正定的,保证组合方差非负

.corr() 方法支持三种相关系数

  • 'pearson'(默认):线性相关
  • 'spearman':秩相关,适合非线性关系
  • 'kendall':序数相关,对异常值更稳健

⭐ 绘制相关性热力图

Listing 3
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# ==================== 设置绘图风格 ====================
sns.set(style='white')
f, ax = plt.subplots(figsize=(9, 6))

# ==================== 创建发散调色板 ====================
cmap = sns.diverging_palette(220, 10, as_cmap=True)
# 220:蓝色(负相关), 10:红色(正相关)

# ==================== 绘制热力图 ====================
sns.heatmap(
    corr_matrix,
    annot=True,       # 显示数值标注
    cmap=cmap,         # 蓝-红发散配色
    vmax=1, vmin=-1,   # 色阶范围
    center=0,          # 中心值为0
    square=True,       # 正方形格子
    linewidths=.5,
    cbar_kws={'shrink': .5},
    ax=ax,
    fmt='.3f',         # 保留3位小数
    annot_kws={'size': 12}
)
ax.set_title('A股主要指数相关性矩阵\n(2022-2023年对数收益率)', fontsize=14)
plt.tight_layout()
plt.show()

⭐ 热力图解读要点

如何正确解读相关性热力图:

  • 颜色越红:正相关越强(接近 +1)
  • 颜色越蓝:负相关越强(接近 -1)
  • 白色:相关性接近 0

A股指数间的典型特征

  • 上证综指与深证成指通常高度正相关(0.85–0.95)
  • 创业板指与大盘指数相关性略低,体现风格差异

⭐ 指数收益率时间序列与累计收益率

Listing 4
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# ==================== 计算累计收益率 ====================
cum_returns = (1 + returns).cumprod() - 1

# ==================== 创建子图 ====================
fig, axes = plt.subplots(2, 1, figsize=(14, 10))

# 子图1:每日收益率
returns.plot(ax=axes[0], linewidth=1)
axes[0].set_title('每日收益率', fontsize=14)
axes[0].set_ylabel('收益率', fontsize=12)
axes[0].legend(fontsize=10)
axes[0].grid(True, alpha=0.3)
axes[0].axhline(y=0, color='k', linestyle='--', linewidth=0.8)

# 子图2:累计收益率
cum_returns.plot(ax=axes[1], linewidth=2)
axes[1].set_title('累计收益率', fontsize=14)
axes[1].set_xlabel('日期', fontsize=12)
axes[1].set_ylabel('累计收益率', fontsize=12)
axes[1].legend(fontsize=10)
axes[1].grid(True, alpha=0.3)
axes[1].axhline(y=0, color='k', linestyle='--', linewidth=0.8)

plt.tight_layout()
plt.show()

⭐ 年化收益率与夏普比率

Listing 5
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

print('\n收益率统计:')
for col in returns.columns:
    print(f'\n{col}:')
    # 年化收益率 = 日均收益率 × 252
    print(f'  年化收益率: {returns[col].mean() * 252:.4f}')
    # 年化波动率 = 日标准差 × √252
    print(f'  年化波动率: {returns[col].std() * np.sqrt(252):.4f}')
    # 夏普比率 = (年化收益率 - 无风险利率) / 年化波动率
    print(f'  夏普比率(无风险利率3%): {(returns[col].mean() * 252 - 0.03) / (returns[col].std() * np.sqrt(252)):.4f}')

关键指标解读

  • 年化收益率:日均收益率 × 252(年交易日数)
  • 年化波动率:日标准差 × \(\sqrt{252}\)
  • 夏普比率:单位风险的超额收益,越高越好

⭐ 相关性的不稳定性

相关系数不是恒定的,它会随市场状态变化:

  • 牛市:股票普遍同涨,相关性高
  • 熊市:恐慌抛售,相关性极高(“相关性崩塌”)
  • 震荡市:个股分化,相关性降低

2008年金融危机的教训

  • 所有资产相关性趋近 1
  • 分散化策略失效
  • 对冲变得极为困难

⭐ 滚动相关性分析

Listing 6
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

index1 = 'SH'  # 上证综指
index2 = 'SZ'  # 深证成指
window = 60     # 滚动窗口: 60个交易日(约3个月)

# 计算滚动相关性
rolling_corr = returns[index1].rolling(window).corr(returns[index2])

# 创建子图
fig, axes = plt.subplots(2, 1, figsize=(14, 10))

# 子图1: 滚动相关性
axes[0].plot(rolling_corr.index, rolling_corr, linewidth=2, label='滚动相关性')
axes[0].axhline(y=0.8, color='r', linestyle='--', linewidth=1, label='高度相关阈值0.8')
axes[0].axhline(y=0.5, color='orange', linestyle='--', linewidth=1, label='中度相关阈值0.5')
axes[0].set_title(f'{index1}{index2}的滚动相关性(窗口={window}日)', fontsize=14)
axes[0].set_ylabel('相关系数', fontsize=12)
axes[0].legend(fontsize=10)
axes[0].grid(True, alpha=0.3)

# 子图2: 归一化指数走势
norm_prices = (data[[index1, index2]] / data[[index1, index2]].iloc[0]) * 100
axes[1].plot(norm_prices.index, norm_prices[index1], label=index1, linewidth=2)
axes[1].plot(norm_prices.index, norm_prices[index2], label=index2, linewidth=2)
axes[1].set_title('归一化指数走势', fontsize=14)
axes[1].set_ylabel('初始价格=100', fontsize=12)
axes[1].legend(fontsize=10)
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

⭐ 滚动相关性统计与解读

Listing 7
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

print(f'\n滚动相关性分析:')
print(f'平均相关性: {rolling_corr.mean():.4f}')
print(f'相关性标准差: {rolling_corr.std():.4f}')
print(f'相关性范围: [{rolling_corr.min():.4f}, {rolling_corr.max():.4f}]')

high_corr_days = (rolling_corr > 0.8).sum()
total_days = rolling_corr.dropna().shape[0]
print(f'\n高度相关天数(>0.8): {high_corr_days}天 ({high_corr_days/total_days*100:.1f}%)')

策略启示

  • 不能依赖静态的历史相关性做资产配置
  • 动态监控相关性变化,及时调整组合
  • 极端事件时相关性可能急剧变化

⭐ 马科维茨投资组合理论

投资组合方差的核心公式:

\[ \sigma_p^2 = \mathbf{w}^T \mathbf{\Sigma} \mathbf{w} = \sum_{i=1}^{n}\sum_{j=1}^{n} w_i w_j \sigma_i \sigma_j \rho_{ij} \]

  • \(\mathbf{w}\):权重向量(各资产配置比例)
  • \(\mathbf{\Sigma}\):协方差矩阵
  • \(\rho_{ij}\):资产 \(i\)\(j\) 的相关系数

⭐ 分散化效应的关键洞见

  • \(\rho < 1\) 时,组合风险 < 加权平均风险
  • 相关系数 \(\rho\) 越低,分散化效果越好
  • 完全负相关(\(\rho = -1\))时,理论上可完全消除风险

这就是”不要把所有鸡蛋放在一个篮子里”的数学本质!

⭐ 不同相关性场景下的组合风险对比

import numpy as np
import matplotlib.pyplot as plt

# ==================== 三种相关性场景 ====================
scenarios = [
    {'name': '低相关(ρ=0.3)', 'corr': 0.3},
    {'name': '中度相关(ρ=0.7)', 'corr': 0.7},
    {'name': '高相关(ρ=0.95)', 'corr': 0.95}
]

# ==================== 资产参数 ====================
sigma1 = 0.20  # 资产1年化波动率20%
sigma2 = 0.25  # 资产2年化波动率25%
w1 = 0.5       # 等权配置
w2 = 0.5

print('投资组合风险分析:')
print(f'资产1波动率: {sigma1:.2%}')
print(f'资产2波动率: {sigma2:.2%}')
print(f'等权重配置(w1={w1}, w2={w2})\n')

# ==================== 计算组合风险 ====================
results = []
for scenario in scenarios:
    rho = scenario['corr']
    # 组合方差: σ² = w₁²σ₁² + w₂²σ₂² + 2w₁w₂σ₁σ₂ρ
    portfolio_var = (w1**2) * (sigma1**2) + (w2**2) * (sigma2**2) + 2 * w1 * w2 * sigma1 * sigma2 * rho
    portfolio_std = np.sqrt(portfolio_var)
    weighted_avg_std = w1 * sigma1 + w2 * sigma2
    risk_reduction = (weighted_avg_std - portfolio_std) / weighted_avg_std

    results.append({
        '场景': scenario['name'],
        '相关系数': rho,
        '组合波动率': portfolio_std,
        '加权平均波动率': weighted_avg_std,
        '风险降低': risk_reduction
    })

    print(f"{scenario['name']}:")
    print(f"  组合波动率: {portfolio_std:.4f} ({portfolio_std:.2%})")
    print(f"  加权平均波动率: {weighted_avg_std:.4f} ({weighted_avg_std:.2%})")
    print(f"  风险降低: {risk_reduction:.2%}\n")

# ==================== 可视化 ====================
fig, ax = plt.subplots(figsize=(10, 6))
scenarios_names = [r['场景'] for r in results]
portfolio_stds = [r['组合波动率'] for r in results]
weighted_stds = [r['加权平均波动率'] for r in results]

x = np.arange(len(scenarios_names))
width = 0.35
bars1 = ax.bar(x - width/2, weighted_stds, width, label='加权平均波动率', color='coral', alpha=0.7)
bars2 = ax.bar(x + width/2, portfolio_stds, width, label='组合波动率', color='steelblue', alpha=0.7)

ax.set_title('相关性对投资组合风险的影响', fontsize=14)
ax.set_ylabel('年化波动率', fontsize=12)
ax.set_xticks(x)
ax.set_xticklabels(scenarios_names)
ax.legend(fontsize=10)
ax.grid(axis='y', alpha=0.3)

for bars in [bars1, bars2]:
    for bar in bars:
        height = bar.get_height()
        ax.text(bar.get_x() + bar.get_width()/2., height,
                f'{height:.2%}', ha='center', va='bottom', fontsize=10)

plt.tight_layout()
plt.show()
投资组合风险分析:
资产1波动率: 20.00%
资产2波动率: 25.00%
等权重配置(w1=0.5, w2=0.5)

低相关(ρ=0.3):
  组合波动率: 0.1820 (18.20%)
  加权平均波动率: 0.2250 (22.50%)
  风险降低: 19.11%

中度相关(ρ=0.7):
  组合波动率: 0.2077 (20.77%)
  加权平均波动率: 0.2250 (22.50%)
  风险降低: 7.70%

高相关(ρ=0.95):
  组合波动率: 0.2222 (22.22%)
  加权平均波动率: 0.2250 (22.50%)
  风险降低: 1.24%
Listing 8

⭐ 组合风险对比图解读

  • 珊瑚色柱:不考虑相关性的加权平均波动率(恒为 22.5%)
  • 钢蓝色柱:考虑相关性后的实际组合波动率
  • 两者差距越大,分散化效果越好
场景 风险降低幅度
低相关 (\(\rho=0.3\)) 约 8–10%
中度相关 (\(\rho=0.7\)) 约 3–5%
高相关 (\(\rho=0.95\)) 不到 1%

⭐ 全局最小方差组合 (GMVP)

Listing 9
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# ==================== 年化协方差矩阵 ====================
cov_matrix = returns.cov() * 252

# ==================== 全局最小方差组合权重 ====================
# w = Σ^(-1) * 1 / (1' * Σ^(-1) * 1)
cov_inv = np.linalg.inv(cov_matrix)
ones = np.ones(len(cov_matrix))
weights_gmvp = np.dot(cov_inv, ones) / np.dot(np.dot(ones.T, cov_inv), ones)

print('全局最小方差组合权重:')
for i, col in enumerate(cov_matrix.columns):
    print(f'{col}: {weights_gmvp[i]:.4f}')

# ==================== 组合表现 ====================
expected_returns = returns.mean() * 252
portfolio_return = np.dot(weights_gmvp, expected_returns)
portfolio_var = np.dot(weights_gmvp.T, np.dot(cov_matrix, weights_gmvp))
portfolio_std = np.sqrt(portfolio_var)

print(f'\n全局最小方差组合:')
print(f'年化期望收益率: {portfolio_return:.2%}')
print(f'年化波动率: {portfolio_std:.2%}')
print(f'夏普比率(无风险利率3%): {(portfolio_return - 0.03) / portfolio_std:.4f}')

⭐ GMVP vs 等权重组合对比

Listing 10
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行

# ==================== 等权重组合 ====================
equal_weights = np.ones(len(cov_matrix)) / len(cov_matrix)
equal_return = np.dot(equal_weights, expected_returns)
equal_var = np.dot(equal_weights.T, np.dot(cov_matrix, equal_weights))
equal_std = np.sqrt(equal_var)

print('等权重组合表现:')
print(f'年化期望收益率: {equal_return:.2%}')
print(f'年化波动率: {equal_std:.2%}')
print(f'夏普比率(无风险利率3%): {(equal_return - 0.03) / equal_std:.4f}')

print(f'\n最小方差组合 vs 等权重组合:')
print(f'波动率降低: {(equal_std - portfolio_std) / equal_std:.2%}')

GMVP 的优势

  • 通过优化权重配置实现最低波动率
  • 充分利用资产间协方差信息
  • 是马科维茨有效前沿的最左端点

⭐ 本章核心总结

概念 关键要点
皮尔逊相关系数 衡量线性关系,取值 \([-1, 1]\)
对数收益率 时间可加、近似正态,推荐用于相关性分析
热力图可视化 发散配色直观展示正/负相关
滚动相关性 相关性随时间变化,不可依赖静态估计
投资组合理论 低相关性资产组合可有效降低风险
GMVP 利用协方差矩阵求解最小风险组合