34 金融模型分析 相关性分析
34.1 引言相关性在金融中的核心地位
相关性的金融意义:
相关性分析是现代金融学的基石。从资本资产定价模型(CAPM)到风险价值(VaR)计算,从投资组合优化到对冲策略设计,相关性无处不在。
34.2 本章学习目标
先修内容:阅读本章前,建议先完成第 章节 7 章(NumPy 矩阵处理,涉及协方差与矩阵运算)、第 章节 10 章(Pandas 数据框基础)、第 章节 21 章(股票相关性热力图)与第 章节 33 章(均值、标准差等分布统计量)。
- 皮尔逊相关系数的定义、取值范围与对称性等性质;
- 简单收益率与对数收益率的计算及口径差异,以及为何相关性分析多用对数收益率;
- 用 Tushare 获取指数行情、合并收盘价并计算相关系数矩阵的完整流程;
- 相关系数矩阵热力图、滚动相关性等可视化与解读方法;
- 相关性在投资组合风险公式中的作用及其随时间的不稳定性。
34.3 获取指数数据
34.3.1 数据源与API
补充说明:A股指数体系
中国A股市场主要指数:
- 上证综指(000001.SH):
- 包含上海证券交易所所有股票
- 以样本股总股本(发行量)为权数的加权综合指数(派氏公式)
- 反映沪市整体表现
- 深证成指(399001.SZ):
- 包含深圳证券交易所500只股票
- 综合考虑规模、流动性
- 反映深市整体表现
- 创业板指(399006.SZ):
- 创业板市场的代表性指数
- 高成长性、高风险
- 科技股集中
任务要求:将下方代码原样输入教学平台并运行(注释可省略)。该代码通过 Tushare 接口获取上证综指、深证成指与创业板指 2022-01-01 至 2023-04-18 的日线收盘价,按交易日期对齐合并后计算对数收益率的相关系数矩阵,并绘制相关性热力图。
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
# 注: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") # 注:正式使用时请替换为你自己的 tushare token(tushare.pro 注册获取)
# 获取上证指数、深证指数和创业板三个指数的收盘价
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") # 保存图形至文件预期输出:平台输出一张 3×3 的 “Correlation Matrix” 热力图。判读要点:对角线恒为 1(自身与自身完全相关);非对角线格子给出两两指数对数收益率的相关系数,颜色越偏红正相关越强;A股主要指数通常呈现 0.7 以上的高度正相关,创业板指与主板指数的相关性一般略低于沪深两市指数之间的相关性。相关系数的具体数值随数据窗口与接口权限而变,以平台运行结果为准。
本地复现提示:本任务依赖 tushare 接口与 token。本地练习时可用第 章节 21 章平台任务取得的收盘价序列,或自行构造 2–3 年合成月度收益率矩阵替代接口数据,方法与流程完全一致。
代码深度解析:
- API调用:
tushare: A股免费数据源pro_api(): 初始化pro接口index_daily(): 获取日线数据
- 日期处理:
format="%Y%m%d": Tushare日期格式set_index(): 将日期设为索引- 便于后续时间序列操作
- 数据合并:
pd.concat(): 横向合并DataFrameaxis=1: 按列合并- 自动对齐日期索引
相关不等于因果。热力图只能告诉我们上证综指与深证成指”同涨同跌”,不能证明是其中一个指数的涨跌”导致”了另一个。事实上,两者的高相关很可能来自共同的混杂因素——宏观流动性、政策与市场风险偏好同时驱动所有A股资产。同理,若观察到创业板指与某类消费股相关系数很高,更可能是两者同时受”春节消费旺季与风险偏好回升”这类共同原因的驱动,而非互为因果。把相关系数直接读成因果影响,是金融数据分析中最常见的误读之一。
34.4 计算收益率与相关性
理论背景:相关性的数学定义
本章的核心工具是皮尔逊相关系数(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]\)
34.4.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%不对等 | 对称 |
| 分布 | 偏态 | 更接近正态分布 |
| 金融应用 | 报告收益 | 计算波动率、建模 |
相关性分析建议: 使用对数收益率,因为:
- 更接近正态分布
- 时间可加性
- 适合连续复利假设
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# ==================== 计算对数收益率 ====================
# 对数收益率的计算公式: 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.5 可视化相关性
34.5.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.5.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.5.3 滚动相关性分析
金融应用:相关性的不稳定性
相关系数不是恒定的,它会随时间变化:
- 市场状态依赖:
- 牛市:股票普遍同涨,相关性高
- 熊市:恐慌抛售,相关性急剧抬升(correlation spike)
- 震荡市:个股分化,相关性降低
- 危机期间:
- 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.6 投资组合应用
金融应用中的相关性:
在动手计算组合风险之前,先明确相关性在投资实践中的四个主要用途:
- 投资组合分散化:
- 投资组合方差: \(\sigma_p^2 = \sum_i \sum_j w_i w_j \sigma_i \sigma_j \rho_{ij}\)
- 相关性越低,组合风险越小
- 马科维茨理论的核心
- 系统性风险识别:
- 高相关性意味着暴露于共同因子
- 市场崩盘时可能同跌
- 分散化失效的风险
- 风险对冲策略:
- 负相关资产可用于对冲
- 寻找反向变动资产
- 构建市场中性策略
- 因子模型构建:
- 相关性聚类识别行业因子
- 风格因子暴露分析
- 多因子模型验证
34.6.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)时,需按波动率反比配置权重(如 w₁=σ₂/(σ₁+σ₂))才能将组合风险降为零;等权配置只能降低而不能完全消除风险
- 有效前沿:
- 给定风险下收益最大的组合
- 给定收益下风险最小的组合
- 通过优化权重得到
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.6.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%}')
# 输出解读:最小方差组合相比等权重组合的风险降低程度34.7 本章小结
要点:
- 皮尔逊相关系数以无量纲的方式刻画线性联动,取值 [-1, 1],矩阵对称且对角线为 1;
- 相关性分析应基于对数收益率而非价格水平,以缓解非平稳趋势带来的虚假相关;
- 相关系数矩阵热力图与滚动相关线图分别回答”整体多强”与”随时间多稳”两个问题;
- 组合方差公式中的 \(\rho_{ij}\) 决定分散化效果:相关越低,等权组合风险越低于加权平均风险;
- 相关性在危机期间会系统性抬升,历史相关性不能外推当作常态。
易错点:
- 直接对价格水平(而非收益率)计算相关系数,得到受共同趋势支配的虚高数值;
- 把高相关解读为因果关系,忽视宏观流动性等共同驱动因素(见上文”相关不等于因果”);
- 用全样本单一相关系数描述关系强度,忽略滚动窗口下相关性的时变甚至符号翻转;
- 混淆对数收益率与简单收益率的口径,或忘记删除收益率序列首行的 NaN。
34.8 动手与思考
以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。
输出预测:两资产等权重配置(\(w_1=w_2=0.5\)),年化波动率分别为 \(\sigma_1=20\%\) 与 \(\sigma_2=25\%\)。先笔算 \(\rho=0.3\) 时的组合年化波动率,再与 \(\rho=0.95\) 的情形对比,最后运行
lst-portfolio-risk代码块验证。参考答案(先写下你的预测再点开)
解题思路:先代入两资产组合方差公式 \(\sigma_p^2 = w_1^2\sigma_1^2 + w_2^2\sigma_2^2 + 2w_1w_2\sigma_1\sigma_2\rho\) 逐项笔算。公共部分:\(w_1^2\sigma_1^2 = 0.25\times0.04 = 0.01\),\(w_2^2\sigma_2^2 = 0.25\times0.0625 = 0.015625\),交叉项系数 \(2w_1w_2\sigma_1\sigma_2 = 0.5\times0.5\times0.2\times0.25\times2 = 0.025\),故 \(\sigma_p^2 = 0.025625 + 0.025\rho\)。当 \(\rho=0.3\):\(\sigma_p^2 = 0.025625 + 0.0075 = 0.033125\),\(\sigma_p \approx 0.1820\),即 18.20%;当 \(\rho=0.95\):\(\sigma_p^2 = 0.025625 + 0.02375 = 0.049375\),\(\sigma_p \approx 0.2222\),即 22.22%。对比:等权重下的加权平均波动率是 \((20\%+25\%)/2 = 22.5\%\)。\(\rho=0.3\) 时组合波动率比加权平均低约 19%(相对降幅 19.11%,绝对差额 4.3 个百分点),分散化收益明显;\(\rho=0.95\) 时组合波动率 22.22% 几乎贴住 22.5%(仅降低 1.24%)——相关越高,分散化带来的风险下降越小,高相关资产的组合近似于“风险加权平均”,这正是 列表 34.6 三个场景要演示的结论。
# 验证脚本:笔算结果的程序化核对 import numpy as np # 导入NumPy数值计算库 sigma1 = 0.20 # 资产1年化波动率20% sigma2 = 0.25 # 资产2年化波动率25% w1 = 0.5 # 资产1权重 w2 = 0.5 # 资产2权重 weighted_avg_std = w1 * sigma1 + w2 * sigma2 # 不考虑相关性的加权平均波动率 print('加权平均波动率: {:.2%}'.format(weighted_avg_std)) # 分散化收益的比较基准 for rho in (0.3, 0.95): # 题目要求的两种相关性情形 portfolio_var = (w1 ** 2) * (sigma1 ** 2) + (w2 ** 2) * (sigma2 ** 2) + 2 * w1 * w2 * sigma1 * sigma2 * rho # 组合方差公式 portfolio_std = np.sqrt(portfolio_var) # 组合年化波动率 print('rho={}: 组合波动率={:.2%}, 较加权平均降低{:.2%}'.format(rho, portfolio_std, (weighted_avg_std - portfolio_std) / weighted_avg_std)) # 输出对比预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):
加权平均波动率: 22.50% rho=0.3: 组合波动率=18.20%, 较加权平均降低19.11% rho=0.95: 组合波动率=22.22%, 较加权平均降低1.24%回扣本章:对应本章小结“要点”第 4 条——组合方差公式中的 \(\rho_{ij}\) 决定分散化效果,相关越低,等权组合风险越低于加权平均风险。
概念辨析:协方差与相关系数有何联系与区别?为什么比较”哪两组资产联动更强”时必须用相关系数而不是协方差?
参考答案(点开前请先独立完成)
解题思路:联系——相关系数就是标准化了的协方差:\(\rho_{X,Y} = \text{Cov}(X,Y)/(\sigma_X\sigma_Y)\),分子是协方差,分母用两个变量各自的标准差把量纲消掉。区别——协方差带量纲(价格×价格、收益×利率),取值无固定边界,数值大小同时受“联动强弱”与“计量单位、波动幅度”影响:同一组资产,价格以元计与以千元计,协方差数值会相差一百万倍,但联动关系没有任何变化;相关系数无量纲、限定在 [-1, 1],只刻画线性联动的方向与强度。因此比较“哪两组资产联动更强”必须用相关系数:不同资产对的量纲与波动幅度各不相同,协方差没有共同的比较标尺,跨组比较毫无意义;标准化之后所有资产对都落在同一把 [-1, 1] 的尺子上,才能排出强弱的次序。这也提醒我们:协方差并非无用——它保留着原始尺度信息,组合方差公式 \(\sigma_p^2 = \mathbf{w}^T\mathbf{\Sigma}\mathbf{w}\) 里用的正是协方差矩阵。
回扣本章:对应本章小结“要点”第 1 条——皮尔逊相关系数以无量纲的方式刻画线性联动,取值 [-1, 1]。
概念辨析:对数收益率与简单收益率在可加性与分布形态上有何差异?为何本章平台代码用
np.log(data / data.shift(1))而不是直接对收盘价求相关?参考答案(点开前请先独立完成)
解题思路:可加性差异——多期简单收益率不可直接相加,须按 \((1+R_1)(1+R_2)\cdots\) 连乘还原;对数收益率可直接相加:\(r_{1\to3} = r_1 + r_2 + r_3\),因为对数把乘法变成了加法,跨期汇总与年化计算都更方便。分布形态差异——简单收益率不对称(先涨 10% 再跌 10%,价格只回到原值的 99%),且右偏有下界 -100%;对数收益率近似对称、更接近正态分布,尾部更规整,适合作为相关性、波动率建模的输入。不对收盘价直接求相关的原因:价格水平是非平稳序列,随时间累积漂移(趋势、随机游走成分),两条都在漂移的价格序列很容易呈现“共同趋势”支配的高相关——即便逐期变动毫无联动,水平相关也可能很高甚至符号误导(本章“易错点”第 1 条称其为虚高数值)。转换为对数收益率(等价于对数价差分)后序列近似平稳,相关系数衡量的是“逐期涨跌是否同向”,才是联动性与分散化分析想要的口径。
回扣本章:对应本章小结“要点”第 2 条(相关性分析应基于对数收益率而非价格水平,以缓解非平稳趋势带来的虚假相关)与“易错点”第 1、4 条。
变式任务:把平台代码的分析窗口改为 2020-01-01 至 2021-12-31(覆盖疫情冲击期),在平台上重跑并对比相关系数矩阵;再按
lst-ch34-rolling-correlation的做法计算 60 日滚动相关,观察危机时段相关性是否抬升。参考答案(点开前请先独立完成)
解题思路:改造思路——只改取数窗口,不动分析流程:把三次
pro.index_daily(...)调用中的start_date="20220101"、end_date="20230418"分别改为"20200101"、"20211231",其余的日期索引设置、收盘价合并、np.log(data / data.shift(1))对数收益率与相关矩阵计算保持原样;滚动相关沿用 列表 34.5 的做法,对上证综指与深证成指的对数收益率做 60 日窗口滚动相关并绘图。为什么这样改:2020 年上半年全球资产遭遇疫情冲击下的急跌与流动性挤兑,是检验“危机期间相关性抬升”命题的天然样本窗口。结构性判读——预期全样本相关矩阵仍显示三个指数高度正相关(A 股主要指数常态下相关系数普遍在 0.7 以上);关键看滚动相关曲线的时变形态:在 2020 年 2—3 月急跌时段,滚动相关大概率明显抬升、贴近上限并伴随波动加大(即“恐慌抛售、同涨同跌”的 correlation spike),随后回落震荡——若窗口内滚动相关的均值与高位天数占比高于原窗口,即支持“危机期联动增强、分散化效果阶段性失效”的结论;热力图与滚动曲线应对照阅读,前者答“整体多强”,后者答“随时间多稳”。预期输出:需 tushare 在线接口(且需读者自己的 token 与相应权限),本机无法复现,具体数值以平台运行结果为准。
注意:以上为窗口改造的思路与判读要点;列表 34.1 对应的平台原始代码块仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应本章小结“要点”第 3、5 条——热力图答“整体多强”、滚动相关答“随时间多稳”;相关性在危机期间系统性抬升,历史相关性不能外推当作常态。
相关不等于因果思考题:某研究员发现 2022 年创业板指与深证成指日收益率的相关系数高达 0.85,于是断言”买入深证成指会推高创业板指”。已知期间两指数同时受宏观流动性宽松与北向资金净流入的共同驱动。请判断这一结论能否作因果解读,并说明混杂因素在其中扮演的角色;若要检验因果,你会提出什么更进一步的研究设计?
参考答案(点开前请先独立完成)
解题思路:结论——不能作因果解读。0.85 只说明同期日收益率高度同向联动(这是相关分析能回答的全部),而“买入深证成指会推高创业板指”是一个方向性的因果命题:它断言对一个变量的干预会传导到另一个变量。相关是因果的必要条件而非充分条件,本题恰恰给出了相关的另一种来源——混杂因素。混杂因素的角色:宏观流动性宽松与北向资金净流入作为共同驱动因素,同时进入两个指数的定价——流动性宽松抬升整体风险偏好、北向资金同时增配主板与创业板标的,两个指数于是“同涨同跌”;观测到的 0.85 是共同响应叠加的结果,并非彼此传导的证据。用共同因素解释相关,与用因果解释相关,在观测数据上往往同样自洽,这正是不能凭相关系数下因果结论的原因。更进一步的研究设计(由弱到强):其一,控制混杂后重看关系——把流动性指标、北向资金净流入作为控制变量,回归或计算偏相关,若控制后两指数收益的关联显著减弱,说明原始相关主要由共同因素驱动;其二,分时期与分样本检验——若正相关在流动性驱动的时期强、在基本面驱动的时期弱,支持混杂机制;其三,事件研究——寻找只冲击其中一个指数的外生事件(如单指数成分调整、行业政策冲击),观察另一指数是否有响应;其四,领先—滞后分析——检验一方的滞后收益能否预测另一方,排除“同刻被共同因素驱动”的混淆(但预测性仍不等于因果)。同时应诚实说明:即便做了上述设计,观测数据也只能逐步排除备择解释,严格的因果识别需要更强的外生变异(如自然实验或工具变量)。
回扣本章:对应本章正文“相关不等于因果”的讨论与“易错点”第 2 条——把高相关解读为因果关系、忽视宏观流动性等共同驱动因素,是金融数据分析中最常见的误读之一。