4 资本资产定价模型 (CAPM) 实证检验
4.1 实验目的
- 掌握 CAPM 模型的横截面回归检验方法: 学习横截面回归方法进行CAPM检验。
- 使用 Python 进行 CAPM 实证检验: 熟悉使用 Python 进行数据获取、回归分析和结果分析。
- 检验 CAPM 模型在中国股票市场的适用性: 使用中国股票市场数据,实证检验 CAPM 模型是否在中国市场成立。
- 理解 CAPM 模型的局限性: 通过实证结果,加深对 CAPM 模型局限性的理解。
4.2 实验原理
4.2.1 CAPM 模型回顾
资本资产定价模型 (CAPM) 描述了风险资产的预期收益率与系统性风险之间的关系。其核心公式为证券市场线 (SML):
\(E(R_i) = R_f + \beta_i[E(R_m) - R_f]\)
其中:
- \(E(R_i)\):资产 \(i\) 的期望收益率
- \(R_f\):无风险利率
- \(\beta_i\):资产 \(i\) 的 Beta 系数,衡量系统性风险
- \(E(R_m)\):市场组合的期望收益率
- \([E(R_m) - R_f]\):市场风险溢价
4.2.2 CAPM 模型的实证检验方法
本实验将采用投资组合分组法 (Portfolio Grouping) 对 CAPM 模型进行实证检验:
投资组合分组法 (Portfolio Grouping)
为解决个股Beta估计误差大的问题,我们采用投资组合分组法进行CAPM检验。这种方法最早由Black, Jensen, and Scholes (1972)提出,能够显著减少Beta估计误差对检验结果的影响。
实证模型如下:
\(\overline{R}_{p} = \gamma_{0} + \gamma_{1} \overline{\beta}_p + \eta_{p}\)
- \(\gamma_{0}\) 代表零 Beta 组合的预期收益率,如果 CAPM 成立,\(\gamma_{0}\) 应接近无风险利率 \(R_f\)。
- \(\gamma_{1}\) 代表风险溢价,如果 CAPM 成立,\(\gamma_{1}\) 应等于市场风险溢价 \(E(R_m) - R_f\),且显著为正。
- \(\overline{R}_{p}\) 和 \(\overline{\beta}_p\) 分别是投资组合的平均收益率和平均Beta值。
步骤:
- 第一步:时间序列回归估计个股Beta值:使用时间序列回归模型,对每只股票 \(i\) 估计其 Beta 值 \(\beta_i\)。
- 第二步:按Beta大小分组:根据Beta大小将股票分成多个投资组合(本实验分为10组)。
- 第三步:计算组合特征:计算每个投资组合的平均Beta和平均收益率。
- 第四步:组合级别回归:使用投资组合的平均Beta和平均收益率进行横截面回归。
与直接使用个股数据进行横截面回归相比,投资组合分组法有以下优势: - 降低Beta估计误差的影响,提高检验结果的稳健性 - 分散非系统性风险,更好地捕捉Beta与平均收益率之间的关系 - 减少极端值对回归结果的影响 - 更符合投资者实际持有多样化投资组合的实践
4.3 实验数据
本实验使用中国 A 股市场数据进行检验。
- 股票数据: 选取上证A股作为研究对象。
- 市场指数: 使用上证指数作为市场组合的代理。
- 无风险利率: 使用中国国债利率作为无风险利率的代理。
- 数据区间: 选择2015年至2024年的历史数据。
- 数据频率: 使用周度数据。
数据来源: CSMAR(学校IP登陆)
4.4 实验步骤
4.4.1 导入必要的 Python 库
import pandas as pd
import numpy as np
import statsmodels.api as sm
import os
from glob import glob
import matplotlib.pyplot as plt
4.4.2 读取市场指数数据
###################################
# 第一步:读取市场指数数据
# 市场指数代表整体市场的表现,是CAPM模型中的市场组合
###################################
= "./data/mktret_weekly/TRD_Weekm.xlsx"
index_files = pd.read_excel(index_files, header=0, skiprows=3, names=['Markettype', 'Trdwnt', 'Wretwdos', 'Dnstkcal'])
market_data
# 选择上证A股市场(Markettype=1)的流通市值加权平均回报率(Wretwdos)
# 流通市值加权平均回报率更能代表真实的市场表现,因为它考虑了不同股票的市场规模
= market_data[market_data['Markettype'] == 1]
market_data
# 把Trdwnt(YYYY-WW)转换为两个变量:year和week
'year'] = market_data['Trdwnt'].str[:4]
market_data['week'] = market_data['Trdwnt'].str[5:]
market_data[
# 筛选year为2015至2024的数据
# 选择近10年数据,既保证样本量充足,又能反映当前市场特征
= market_data[market_data['year'] >= '2015']
market_data = market_data[market_data['year'] <= '2024'] market_data
4.4.3 读取无风险利率数据
###################################
# 第二步:读取无风险利率数据
# 无风险利率是CAPM模型的重要组成部分,代表投资者在不承担风险时的最低收益要求
###################################
= "./data/rf_weekly/TRD_Nrrate.xlsx"
rf_files = pd.read_excel(rf_files, header=0, skiprows=2, names=['Nrr1', 'Clsdt', 'Nrrdaydt'])
rf_data
# 把rf从百分数单位转换为小数
# 统一单位便于后续计算超额收益率
'Nrrdaydt'] = rf_data['Nrrdaydt'] / 100
rf_data[
# 把Clsdt转换为日期格式
'Clsdt'] = pd.to_datetime(rf_data['Clsdt'])
rf_data[
# 把Clsdt转换为交易周份(周数从1开始)
# 这一步是为了将日度数据转换为周度数据,与股票和市场数据保持一致
'Trdwnt'] = rf_data['Clsdt'].dt.strftime('%Y-%U').apply(
rf_data[lambda x: f"{x.split('-')[0]}-{int(x.split('-')[1]) + 1:02d}"
)
# 把Trdwnt转换为两个变量:year和week
'year'] = rf_data['Trdwnt'].str[:4]
rf_data['week'] = rf_data['Trdwnt'].str[5:]
rf_data[
# 对rf在year和week上算平均值
# 由于一周内可能有多个无风险利率数据,取平均值作为该周的代表值
= rf_data.groupby(['year', 'week']).agg({'Nrrdaydt': 'mean'}).reset_index()
rf_data
# 筛选year为2015至2024的数据
= rf_data[rf_data['year'] >= '2015']
rf_data = rf_data[rf_data['year'] <= '2024'] rf_data
4.4.4 读取所有股票回报率数据
###################################
# 第三步:读取所有股票回报率数据(2015-2024)
# 个股回报率是CAPM检验的核心数据,用于估计Beta和验证模型预测
###################################
# 读取stockret_weekly中所有excel表格
= "./data/stockret_weekly"
stockret_folders = glob(os.path.join(stockret_folders, "*.xlsx"))
stock_files print(f"找到股票数据文件: {stock_files}")
# 创建一个空的DataFrame来存储所有股票数据
= []
stock_data_list
# 循环读取文件
for file in stock_files:
= pd.read_excel(file, header=0, skiprows=3, names=['Stkcd', 'Trdwnt', 'Wretwd', 'Markettype'])
temp_data
stock_data_list.append(temp_data)
# 合并所有股票数据
= pd.concat(stock_data_list, ignore_index=True)
stock_data
# 只选择上证A股市场(Markettype=1)
# 保持与市场指数一致,确保样本的一致性
= stock_data[stock_data['Markettype'] == 1]
stock_data
# 把Trdwnt转换为两个变量:year和week
'year'] = stock_data['Trdwnt'].str[:4]
stock_data['week'] = stock_data['Trdwnt'].str[5:]
stock_data[
# 筛选year为2015至2024的数据
= stock_data[stock_data['year'] >= '2015']
stock_data = stock_data[stock_data['year'] <= '2024']
stock_data
# 检查Stkcd的唯一值的数量
# 了解样本中包含多少只股票,样本规模越大,结果越具有代表性
print(f"Stkcd的唯一值的数量: {stock_data['Stkcd'].nunique()}")
4.4.5 合并数据框
###################################
# 第四步:合并数据框
# 将股票、市场和无风险利率数据合并,为后续分析做准备
###################################
# 将 stock_data 与 market_data 按 ['year', 'week'] 合并
= pd.merge(stock_data, market_data, on=['year', 'week'], how='left')
merged_data
# 将 merged_data 与 rf_data 按 ['year', 'week'] 合并
= pd.merge(merged_data, rf_data, on=['year', 'week'], how='left')
merged_data
# 新建market_excess_return列,计算市场超额回报率
# 市场超额回报率 = 市场回报率 - 无风险利率,代表承担市场风险获得的额外收益
'market_excess_return'] = merged_data['Wretwdos'] - merged_data['Nrrdaydt']
merged_data[
# 计算市场风险溢价
# 市场风险溢价是CAPM模型中的重要参数,代表投资者承担系统性风险的补偿
= merged_data['market_excess_return'].mean()
market_risk_premium print(f"市场风险溢价: {market_risk_premium}")
4.4.6 时间序列回归估计Beta
###################################
# 第五步:时间序列回归估计Beta
# Beta是CAPM模型的核心参数,衡量个股对市场风险的敏感度
###################################
# 先对一个股票做回归测试结果
# 筛选Stkcd == 600169的数据
= merged_data[merged_data['Stkcd'] == 600169]
merged_data0
# 做回归(添加缺失值处理)
# 先删除包含缺失值的行
= merged_data0.dropna(subset=['market_excess_return', 'Wretwd'])
merged_data0
# 进行时间序列回归:R_i = α_i + β_i(R_m - R_f) + ε_i
# 这里我们直接用总回报率而非超额回报率作为因变量,这是一种变形的CAPM检验方法
= sm.OLS(merged_data0['Wretwd'], sm.add_constant(merged_data0['market_excess_return'])).fit()
model
# 展示回归结果
# 常数项代表Alpha,斜率代表Beta
print(model.summary())
###################################
# 将 merged_data 按 stkcd 分组,做回归,y 是 Wretwd, x 是 market_excess_return
# 将回归结果存储在time_series_results字典中
= {}
time_series_results for stock_code in merged_data['Stkcd'].unique():
= merged_data[merged_data['Stkcd'] == stock_code]
individual_data # 先删除包含缺失值的行
= individual_data.dropna(subset=['market_excess_return', 'Wretwd'])
individual_data # 筛选行数超过30的,否则跳过
# 样本量过小会导致回归结果不可靠
if len(individual_data) > 30:
= sm.OLS(individual_data['Wretwd'], sm.add_constant(individual_data['market_excess_return'])).fit()
model = model
time_series_results[stock_code] else:
print(f"股票{stock_code}的行数不足30,跳过")
# 提取回归结果中的beta值
# Beta值代表个股对市场风险的敏感度,Beta>1表示该股票比市场更具波动性
= [result.params[1] for result in time_series_results.values()]
beta_values
# beta_values需要与对应股票代码合并
= pd.DataFrame({'Stkcd': list(time_series_results.keys()), 'beta': beta_values}) beta_values
4.4.7 横截面回归检验CAPM模型
###################################
# 第六步:使用投资组合分组法检验CAPM模型
# 投资组合分组法通过将股票按Beta分组,计算组合平均Beta和收益率,
# 再进行回归,能够显著减少Beta估计误差的影响,提高检验结果的稳健性
###################################
# 对beta_values进行分组,从小到大等分为10组,生成一列group
# 分组可以减少个股特异性风险的影响,更好地检验系统性风险与收益的关系
'group'] = pd.qcut(beta_values['beta'], q=10, labels=False, duplicates='drop')
beta_values[
# 根据股票代码合并股票数据
= pd.merge(stock_data, beta_values, on='Stkcd', how='left')
capm_data
# 把capm_data按group分组,计算每组的股票平均回报率、beta平均值,放在一个DataFrame中
# 组合可以分散非系统性风险,更好地反映Beta与收益率的关系
= capm_data.groupby('group').agg({'Wretwd': 'mean', 'beta': 'mean'})
group_data
# 使用Wretwd对beta进行回归,计算截距和斜率
# 横截面回归模型:R_i = γ_0 + γ_1 * β_i + η_i
# γ_0应接近无风险利率,γ_1应接近市场风险溢价
= sm.OLS(group_data['Wretwd'], sm.add_constant(group_data['beta'])).fit()
model
# 展示回归结果
print(model.summary())
# 提取并解释结果
= model.params['const'] # 零Beta组合收益率
gamma_0 = model.params['beta'] # 市场风险溢价估计值
gamma_1 = model.pvalues['const'] # 零Beta组合收益率的显著性
p_value_gamma_0 = model.pvalues['beta'] # 市场风险溢价的显著性
p_value_gamma_1
print(f"\nCAPM横截面回归结果解释:")
print(f"γ0 (零Beta组合收益率) = {gamma_0:.6f}, p值 = {p_value_gamma_0:.6f}")
print(f"γ1 (市场风险溢价) = {gamma_1:.6f}, p值 = {p_value_gamma_1:.6f}")
print(f"实际市场风险溢价 = {market_risk_premium:.6f}")
# CAPM检验结论
print("\nCAPM检验结论:")
if p_value_gamma_0 < 0.05:
print(f"- γ0 ({gamma_0:.6f}) 显著,表明零Beta组合收益率存在")
else:
print(f"- γ0 ({gamma_0:.6f}) 不显著,无法确定零Beta组合收益率")
if p_value_gamma_1 < 0.05:
if gamma_1 > 0:
print(f"- γ1 ({gamma_1:.6f}) 显著为正,表明Beta风险被市场定价")
if abs(gamma_1 - market_risk_premium) < 0.01:
print(f" 且接近实际市场风险溢价 ({market_risk_premium:.6f}),支持CAPM模型")
else:
print(f" 但与实际市场风险溢价 ({market_risk_premium:.6f}) 存在差异,CAPM模型部分成立")
else:
print(f"- γ1 ({gamma_1:.6f}) 显著但为负值,与CAPM预期不符,表明Beta风险未被正确定价")
else:
print(f"- γ1 ({gamma_1:.6f}) 不显著,表明Beta风险未被市场定价,CAPM模型在此样本中不成立")
4.4.8 作图
###################################
# 第七步:画图
# 在图上画出投资组合平均Beta和收益率,与回归线
###################################
# 设置绘图风格
'ggplot')
plt.style.use(# 根据操作系统设置不同的字体
import platform
# 获取操作系统类型
= platform.system()
system # 设置 matplotlib 字体
if system == 'Windows':
'font.sans-serif'] = ['SimHei'] # Windows 使用黑体
plt.rcParams[elif system == 'Darwin':
'font.sans-serif'] = ['Songti SC'] # Mac 使用宋体
plt.rcParams[else:
'font.sans-serif'] = ['WenQuanYi Zen Hei'] # Linux 使用文泉驿正黑
plt.rcParams[# 解决负号显示问题
'axes.unicode_minus'] = False
plt.rcParams[
# 画图并保存
=(10, 6))
plt.figure(figsize'beta'], group_data['Wretwd'], alpha=0.7)
plt.scatter(group_data['beta'], gamma_0 + gamma_1 * group_data['beta'], 'r-', linewidth=2)
plt.plot(group_data['Beta与平均收益率的关系')
plt.title('Beta系数')
plt.xlabel('平均周收益率')
plt.ylabel(True, alpha=0.3)
plt.grid(f'R2 = {model.rsquared:.4f}', xy=(0.05, 0.95), xycoords='axes fraction')
plt.annotate(f'γ0 = {gamma_0:.6f}', xy=(0.05, 0.90), xycoords='axes fraction')
plt.annotate(f'γ1 = {gamma_1:.6f}', xy=(0.05, 0.85), xycoords='axes fraction')
plt.annotate('capm_scatter.png')
plt.savefig(print("已保存Beta与平均收益率的散点图")
4.5 实验结果
本实验采用投资组合分组法检验了CAPM模型。如果CAPM模型成立,我们期望:
- 截距项(\(\gamma_0\))应接近无风险利率,代表零Beta组合的收益率
- Beta系数(\(\gamma_1\))应显著为正,且接近市场风险溢价,代表承担系统性风险的补偿
下图展示了投资组合平均Beta与平均收益率的散点图及回归线:
4.6 结论与讨论
根据投资组合分组法的实验结果,我们可以分析CAPM模型在中国A股市场的适用性:
- 系统性风险的定价:
- 观察投资组合回归中Beta系数是否显著,以及其解释能力(R方)的大小
- R方值代表投资组合平均收益率中能被Beta解释的部分
- 与个股回归相比,投资组合回归的R方通常较高,因为分组降低了特异性风险的影响
- 零Beta组合收益率:
- 比较 \(\gamma_0\) 与无风险利率的差异
- 如果 \(\gamma_0\) 显著高于无风险利率,可能存在其他风险因子未被模型捕捉
- Black, Jensen, and Scholes (1972)的研究表明,零Beta组合收益率通常高于无风险利率
- 风险溢价:
- 检验 \(\gamma_1\) 是否显著为正,并与实际市场风险溢价比较
- 如果 \(\gamma_1\) 显著为正且接近市场风险溢价,支持CAPM模型
- 如果 \(\gamma_1\) 不显著或为负,表明系统性风险未被市场正确定价
- CAPM模型的局限性:
- 中国股票市场的特殊性:政策驱动、散户主导、信息不对称
- 可能需要考虑其他风险因子:规模效应、价值效应、动量效应等
- 即使使用投资组合分组法降低了估计误差,CAPM仍可能无法充分解释中国市场的收益率差异
通过这些分析,我们可以评估CAPM模型在中国市场的适用性,并讨论可能的局限性。
4.7 实验总结
本实验通过投资组合分组法检验了CAPM模型在中国A股市场的适用性。我们首先估计了个股的Beta系数,然后按Beta大小将股票分组,计算投资组合的平均Beta和平均收益率,最后检验了Beta与平均收益率之间的关系。
与直接使用个股数据相比,投资组合分组法有以下优势: 1. 降低了Beta估计误差的影响 2. 分散了非系统性风险 3. 减少了极端值对回归结果的影响 4. 更符合投资者实际持有多样化投资组合的实践
这种实证检验方法不仅帮助我们验证金融理论,也为投资决策和资产定价提供了实证依据。通过对比理论预期与实证结果,我们可以更深入地理解中国股票市场的风险-收益特征,为投资组合构建和风险管理提供指导。