34 金融模型分析 相关性分析
34.1 引言相关性在金融中的核心地位
相关性的金融意义:
相关性分析是现代金融学的基石。从资本资产定价模型(CAPM)到风险价值(VaR)计算,从投资组合优化到对冲策略设计,相关性无处不在。
理论背景:相关性的数学定义
皮尔逊相关系数(Pearson Correlation Coefficient)衡量两个变量线性相关的强度和方向:
\[ \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]\)
金融应用中的相关性:
- 投资组合分散化:
- 投资组合方差: \(\sigma_p^2 = \sum_i \sum_j w_i w_j \sigma_i \sigma_j \rho_{ij}\)
- 相关性越低,组合风险越小
- 马科维茨理论的核心
- 系统性风险识别:
- 高相关性意味着暴露于共同因子
- 市场崩盘时可能同跌
- 分散化失效的风险
- 风险对冲策略:
- 负相关资产可用于对冲
- 寻找反向变动资产
- 构建市场中性策略
- 因子模型构建:
- 相关性聚类识别行业因子
- 风格因子暴露分析
- 多因子模型验证
34.2 获取指数数据
34.2.1 数据源与API
补充说明:A股指数体系
中国A股市场主要指数:
- 上证综指(000001.SH):
- 包含上海证券交易所所有股票
- 以总市值为权重
- 反映沪市整体表现
- 深证成指(399001.SZ):
- 包含深圳证券交易所500只股票
- 综合考虑规模、流动性
- 反映深市整体表现
- 创业板指(399006.SZ):
- 创业板市场的代表性指数
- 高成长性、高风险
- 科技股集中
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 注: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调用:
tushare: A股免费数据源pro_api(): 初始化pro接口index_daily(): 获取日线数据
- 日期处理:
format="%Y%m%d": Tushare日期格式set_index(): 将日期设为索引- 便于后续时间序列操作
- 数据合并:
pd.concat(): 横向合并DataFrameaxis=1: 按列合并- 自动对齐日期索引
34.3 计算收益率与相关性
34.3.1 收益率计算
补充说明:简单收益率 vs 对数收益率
| 特性 | 简单收益率 | 对数收益率 |
|---|---|---|
| 公式 | \(R_t = (P_t - P_{t-1}) / P_{t-1}\) | \(r_t = \ln(P_t / P_{t-1})\) |
| 可加性 | 多期收益率不可直接相加 | 可相加: \(r_{1→3} = r_1 + r_2 + r_3\) |
| 对称性 | 不对称: +10%和-10%不对等 | 对称 |
| 分布 | 偏态 | 更接近正态分布 |
| 金融应用 | 报告收益 | 计算波动率、建模 |
相关性分析建议: 使用对数收益率,因为: 1. 更接近正态分布 2. 时间可加性 3. 适合连续复利假设
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行
# ==================== 计算对数收益率 ====================
# 对数收益率的计算公式: r_t = ln(P_t / P_{t-1})
# 相当于: r_t = ln(P_t) - ln(P_{t-1})
returns = np.log(data / data.shift(1))
# data.shift(1): 将数据向下移动一行,使P_t与P_{t-1}对齐
# np.log(): 计算自然对数
# ==================== 删除第一行 ====================
# 第一行没有上期数据,计算结果为NaN,需要删除
returns = returns.dropna() # 删除包含NaN的行
# ==================== 收益率数据探索 ====================
print('收益率数据:')
print(returns.head()) # 显示前5行收益率数据
# ==================== 收益率统计 ====================
print(f'\n收益率描述统计:')
print(returns.describe().T) # .T转置,使指数为行,统计量为列
# 输出解读:观察各指数的收益率均值(平均收益)和标准差(波动率)
# ==================== 计算相关系数矩阵 ====================
# 使用皮尔逊相关系数计算指数间的相关性
corr_matrix = returns.corr() # 计算相关系数矩阵
# corr()默认使用pearson相关系数
print('\n相关系数矩阵:')
print(corr_matrix.round(4)) # 保留4位小数
# 输出解读:
# 对角线为1(自己与自己的相关系数)
# 非对角线元素表示不同指数间的相关性
# 值越接近1,正相关越强;越接近-1,负相关越强
# ==================== 验证对称性 ====================
print(f'\n矩阵对称性: {np.allclose(corr_matrix, corr_matrix.T)}')
# 相关系数矩阵应该是对称的(ρ_ij = ρ_ji)
print(f'对角线元素: {np.diag(corr_matrix)}')
# 对角线元素应该全为1(ρ_ii = 1)代码深度解析:
- 对数收益率计算:
np.log(): 自然对数data.shift(1): 向下移动一行P_t / P_{t-1}: 价格比
- 相关系数矩阵性质:
- 对称性: \(\rho_{ij} = \rho_{ji}\)
- 对角线为1: \(\rho_{ii} = 1\)
- 正定性: 协方差矩阵半正定
.corr()方法参数:method='pearson': 皮尔逊相关(默认)method='spearman': 斯皮尔曼秩相关method='kendall': 肯德尔τ相关
34.4 可视化相关性
34.4.1 基础热力图
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行
# ==================== 设置绘图风格 ====================
sns.set(style="white") # 使用白色背景风格
f, ax = plt.subplots(figsize=(9, 6)) # 创建9x6英寸的画布
# ==================== 创建发散调色板 ====================
# 发散调色板适合有正负值的数据,0为中性色
cmap = sns.diverging_palette(220, 10, as_cmap=True)
# 220:蓝色(负相关), 10:红色(正相关)
# as_cmap=True:返回colormap对象
# ==================== 绘制热力图 ====================
sns.heatmap(
corr_matrix, # 相关系数矩阵
annot=True, # 显示数值标注
cmap=cmap, # 配色方案
vmax=1, # 最大值为1
vmin=-1, # 最小值为-1
center=0, # 中心值为0
square=True, # 格子为正方形
linewidths=.5, # 格子线宽0.5
cbar_kws={"shrink": .5}, # 颜色条缩小为50%
ax=ax, # 在指定子图上绘制
fmt='.3f', # 数值格式:保留3位小数
annot_kws={'size': 12} # 标注字体大小12
)
ax.set_title("A股主要指数相关性矩阵\n(2022-2023年对数收益率)", fontsize=14)
# 设置标题,包含时间范围说明
plt.tight_layout() # 自动调整布局
plt.show() # 显示图形
# 图形解读:
# 颜色越红,相关性越强(接近1)
# 颜色越蓝,负相关性越强(接近-1)
# 白色表示相关性接近0
# 通常A股指数间高度正相关(0.7-0.95)代码深度解析:
- 配色方案选择:
diverging_palette: 发散配色(适合有正负的数据)center=0: 强调正负相关对比annot=True: 显示数值便于精确解读
- 参数说明:
square=True: 保持格子为正方形fmt='.3f': 保留三位小数cbar_kws: 颜色条设置
34.4.2 时间序列对比
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行
# ==================== 计算累计收益率 ====================
# 累计收益率表示从起点投资的收益
cum_returns = (1 + returns).cumprod() - 1
# (1 + returns): 1 + 收益率 = 价格比
# .cumprod(): 累计乘积(连续复利)
# - 1: 转换为收益率形式
# ==================== 创建子图 ====================
fig, axes = plt.subplots(2, 1, figsize=(14, 10))
# 2行1列的子图,总大小14x10英寸
# ==================== 子图1:每日收益率 ====================
returns.plot(ax=axes[0], linewidth=1)
# 在第一个子图上绘制收益率曲线
axes[0].set_title('每日收益率', fontsize=14) # 设置标题
axes[0].set_ylabel('收益率', fontsize=12) # y轴标签
axes[0].legend(fontsize=10) # 显示图例
axes[0].grid(True, alpha=0.3) # 添加网格线
axes[0].axhline(y=0, color='k', linestyle='--', linewidth=0.8)
# 添加y=0的参考线,黑色虚线
# ==================== 子图2:累计收益率 ====================
cum_returns.plot(ax=axes[1], linewidth=2)
# 在第二个子图上绘制累计收益率曲线
axes[1].set_title('累计收益率', fontsize=14) # 设置标题
axes[1].set_xlabel('日期', fontsize=12) # x轴标签
axes[1].set_ylabel('累计收益率', fontsize=12) # y轴标签
axes[1].legend(fontsize=10) # 显示图例
axes[1].grid(True, alpha=0.3) # 添加网格线
axes[1].axhline(y=0, color='k', linestyle='--', linewidth=0.8)
# 添加y=0的参考线
plt.tight_layout() # 自动调整布局
plt.show() # 显示图形
# ==================== 统计分析 ====================
print('\n收益率统计:')
for col in returns.columns: # 遍历每个指数
print(f'\n{col}:')
# 年化收益率 = 日均值收益率 * 252个交易日
print(f' 年化收益率: {returns[col].mean() * 252:.4f}')
# 年化波动率 = 日标准差 * sqrt(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}')
# 夏普比率衡量单位风险的超额收益,越高越好34.4.3 滚动相关性分析
金融应用:相关性的不稳定性
相关系数不是恒定的,它会随时间变化:
- 市场状态依赖:
- 牛市:股票普遍同涨,相关性高
- 熊市:恐慌抛售,相关性极高(相关性崩塌)
- 震荡市:个股分化,相关性降低
- 危机期间:
- 2008金融危机:所有资产相关性趋近1
- 分散化失效
- 对冲困难
- 策略启示:
- 不能依赖历史相关性
- 需要动态监控相关性
- 极端事件时相关性可能逆转
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行
# ==================== 选择分析对象 ====================
# 选择上证综指和深证成指进行滚动相关性分析
index1 = 'SH' # 上证综指
index2 = 'SZ' # 深证成指
# ==================== 计算滚动相关性 ====================
window = 60 # 滚动窗口大小:60个交易日(约3个月)
rolling_corr = returns[index1].rolling(window).corr(returns[index2])
# .rolling(window):创建滚动窗口
# .corr():计算窗口内的相关系数
# ==================== 创建子图 ====================
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:指数走势(归一化) ====================
# 将指数归一化到100,便于比较走势
norm_prices = (data[[index1, index2]] / data[[index1, index2]].iloc[0]) * 100
# 除以初始值再乘100,使起点为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()
# ==================== 统计分析 ====================
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}%)')
# 输出解读:了解两个指数在多大程度上保持高度相关34.5 投资组合应用
34.5.1 投资组合风险计算
理论背景:马科维茨投资组合理论
哈里·马科维茨(Harry Markowitz, 1952)提出现代投资组合理论(MPT),其核心公式:
\[ \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}\): 协方差矩阵 - \(\sigma_i, \sigma_j\): 资产标准差 - \(\rho_{ij}\): 相关系数
关键洞见: 1. 分散化效应: - 当相关系数<1时,组合风险<加权平均风险 - 相关系数越低,分散化效果越好 - 完全负相关(ρ=-1)可完全消除风险
- 有效前沿:
- 给定风险下收益最大的组合
- 给定收益下风险最小的组合
- 通过优化权重得到
import numpy as np # 导入NumPy数值计算库
import matplotlib.pyplot as plt # 导入Matplotlib绑图库
# ==================== 定义三种相关性场景 ====================
# 比较不同相关系数对组合风险的影响
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 # 资产1权重50%
w2 = 0.5 # 资产2权重50%
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) # 组合波动率 = sqrt(组合方差)
# 加权平均波动率(不考虑相关性的简单平均)
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)) # x轴位置
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) # 设置x轴刻度位置
ax.set_xticklabels(scenarios_names) # 设置x轴刻度标签
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()
# 图形解读:
# 蓝色柱代表不考虑相关性的简单加权平均
# 橙色柱代表考虑相关性后的实际组合风险
# 两者差距越大,分散化效果越好34.5.2 最优配置权重
# 注:该代码块依赖的数据来自上方平台任务代码块,因其未执行,本块也无法执行
# ==================== 计算协方差矩阵 ====================
cov_matrix = returns.cov() * 252 # 年化协方差矩阵
# returns.cov(): 日协方差矩阵
# * 252: 年化(假设一年252个交易日)
print('年化协方差矩阵:')
print(cov_matrix)
# ==================== 计算全局最小方差组合 ====================
# Global Minimum Variance Portfolio (GMVP)
# 公式: w = Σ^(-1) * 1 / (1' * Σ^(-1) * 1)
# 其中: Σ是协方差矩阵, 1是全1向量
# 逆协方差矩阵
cov_inv = np.linalg.inv(cov_matrix)
# np.linalg.inv(): 矩阵求逆
# 全1向量
ones = np.ones(len(cov_matrix))
# 创建长度为资产数量的全1向量
# 计算权重
weights_gmvp = np.dot(cov_inv, ones) / np.dot(np.dot(ones.T, cov_inv), ones)
# 分子: Σ^(-1) * 1
# 分母: 1' * Σ^(-1) * 1 (标量)
# 结果归一化,使权重和为1
# ==================== 输出权重 ====================
print('\n全局最小方差组合权重:')
for i, col in enumerate(cov_matrix.columns):
print(f'{col}: {weights_gmvp[i]:.4f}')
# 输出解读:展示各指数的最优配置权重
# 权重之和为1,权重可正可负(负值表示做空)
# ==================== 计算组合表现 ====================
expected_returns = returns.mean() * 252 # 年化期望收益
portfolio_return = np.dot(weights_gmvp, expected_returns)
# 组合收益 = w' * μ (权重向量点乘收益向量)
portfolio_var = np.dot(weights_gmvp.T, np.dot(cov_matrix, weights_gmvp))
# 组合方差 = w' * Σ * w (二次型)
portfolio_std = np.sqrt(portfolio_var) # 组合波动率
print(f'\n全局最小方差组合表现:')
print(f'年化期望收益率: {portfolio_return:.4f} ({portfolio_return:.2%})')
print(f'年化波动率: {portfolio_std:.4f} ({portfolio_std:.2%})')
print(f'夏普比率(假设无风险利率3%): {(portfolio_return - 0.03) / portfolio_std:.4f}')
# ==================== 对比等权重组合 ====================
equal_weights = np.ones(len(cov_matrix)) / len(cov_matrix)
# 等权重:每个资产配置1/n
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(f'\n等权重组合表现:')
print(f'年化期望收益率: {equal_return:.4f} ({equal_return:.2%})')
print(f'年化波动率: {equal_std:.4f} ({equal_std:.2%})')
print(f'夏普比率(假设无风险利率3%): {(equal_return - 0.03) / equal_std:.4f}')
# ==================== 风险对比 ====================
print(f'\n风险对比:')
print(f'波动率降低: {(equal_std - portfolio_std) / equal_std:.2%}')
# 输出解读:最小方差组合相比等权重组合的风险降低程度