6 实践:构建和回测中国特色三因子模型(CH-3)
6.1 数据说明
6.1.1 数据来源
本实验所使用的数据来自CSMAR(国泰安) http://data.csmar.com/,主要包括:
- 基本信息: 公司研究系列 -> 上市公司基本信息 -> 上市公司基本信息年度表
- 股票市场信息: 股票市场系列 -> 股票市场交易 -> 个股交易数据 -> 月个股回报率文件
- 无风险利率: 股票市场系列 -> 股票市场交易 -> 汇率及利率 -> 无风险利率文件
- 多因子: 因子研究系列 -> Fama-French因子 -> 五因子模型指标(月)
- 财务数据: 公司研究系列 -> 财务指标分析 -> 披露财务指标
6.1.2 样本与变量选择
- 时间范围: 2007年5月至2019年2月
- 样本选择: 上海与深圳A股非金融、非公用事业上市企业
- 主要变量:
- 基本信息: 股票代码、行业代码、上市日期
- 股票市场信息: 考虑现金红利再投资的月个股回报率、月个股总市值
- 无风险利率: 月度化无风险利率
- 五因子: 市场风险溢价因子、市值因子、账面市值比因子(均为总市值加权)
- 财务信息: 归属于上市公司股东的扣除非经常性损益的净利润、加权平均净资产收益率
6.1.3 实验步骤概要
- 导入整理数据,保证各数据库间的变量及单位等保持一致
- 选取A股非金融、非公用事业上市公司
- 合并上市公司股票信息与财务信息,并保留市值处于最大的70%的上市企业
- 每月根据市值与价值对企业进行排序分组,计算每个投资组合的市值加权平均收益率
- 构造中国三因子,并计算中国三因子在中国市场的解释力度
- 构造ROE异象,比较CAPM、中国三因子与Fama-French三因子模型的解释能力
- 分析结果并得出结论
6.2 本次实验内容
- 数据准备和预处理: 学习如何获取和处理中国股票数据
- 构建中国特色规模因子: 掌握中国特色规模因子(SMB)的构建方法
- 构建中国特色价值因子: 掌握基于市盈率的价值因子(VMG)的构建方法
- 回测因子收益: 学习如何对因子进行回测和性能评估
- 因子有效性分析: 了解如何评估因子的有效性和统计显著性
6.3 背景:中国特色三因子模型(CH-3)
根据Liu, Stambaugh和Yuan (2019)的”Size and Value in China”研究,中国市场具有独特的特点,传统Fama-French三因子模型需要进行调整:
壳价值效应:中国市场最小30%的股票存在明显的壳价值现象,其价格受到被用作反向收购壳公司潜在价值的影响,而非传统风险因素
规模因子调整:需要剔除最小30%市值股票,构建SMB(Small Minus Big)因子,以避免壳价值的干扰
价值因子调整:市盈率(E/P)在中国市场的表现优于账面市值比(B/M),因此使用E/P构建VMG(Value Minus Growth)价值因子
超强解释力:调整后的CH-3模型在解释中国市场异象方面显著优于传统FF-3模型
本实验将带领大家一步步构建中国特色三因子模型,并通过对ROE异象的解释力分析来验证该模型的有效性。我们将使用真实的中国股票市场数据,从数据预处理开始,经过因子构建、回测到最终的统计分析。
6.4 实践:构建和回测中国特色三因子模型
6.4.1 第一步:导入必要的库
首先,我们需要导入本实验所需的Python库。这些库包括数据处理的pandas和numpy,可视化的matplotlib和seaborn,以及统计分析的statsmodels和scipy等。
# 导入必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
from scipy import stats
from pandas.tseries.offsets import MonthEnd
import seaborn as sns
from statsmodels.regression.rolling import RollingOLS
from statsmodels.stats.sandwich_covariance import cov_hac
# 设置绘图风格
'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[
6.4.2 第二步:读入数据
接下来,我们需要读取中国股票市场的相关数据。这些数据包括:公司基本信息、财务指标、交易数据、无风险利率和市场因子数据。在实际应用中,这些数据通常来自于CSMAR、Wind等专业金融数据库。
# 基本信息
= pd.read_excel("data/basic_info/STK_LISTEDCOINFOANL.xlsx", skiprows=3,
cominfo =["Symbol", "ShortName", "EndDate", "ListedCoID", "SecurityID",
names"IndustryName", "IndustryCode", "IndustryNameC", "IndustryCodeC",
"IndustryNameD", "IndustryCodeD", "RegisterAddress", "OfficeAddress",
"Zipcode", "Secretary", "SecretaryTel", "SecretaryFax", "SecretaryEmail",
"SecurityConsultant", "SocialCreditCode", "Sigchange", "Lng", "Lat",
"ISIN", "FullName", "LegalRepresentative", "EstablishDate", "Crcd",
"RegisterCapital", "Website", "BusinessScope", "RegisterLongitude",
"RegisterLatitude", "EMAIL", "LISTINGDATE", "PROVINCECODE", "PROVINCE",
"CITYCODE", "CITY", "MAINBUSSINESS", "LISTINGSTATE"])
# 财务指标(ROE)
= pd.read_excel("data/ROE/FI_T2.xlsx", skiprows=3,
comfin =["Stkcd", "ShortName", "Accper", "nr", "npexnr", "ROE", "ROE_exnr"],
names={'Stkcd': str, 'nr': float, 'npexnr': float, 'ROE': float, 'ROE_exnr': float})
dtype
# 个股交易数据
= pd.read_excel("data/stk_monthly/TRD_Mnth.xlsx", skiprows=3,
trdmnth =["Stkcd", "Trdmnt", "Opndt", "Mopnprc", "Clsdt",
names"Mclsprc", "Mnshrtrd", "Mnvaltrd", "Msmvosd",
"Msmvttl", "Ndaytrd", "Mretwd", "Mretnd",
"Markettype", "Capchgdt", "Ahshrtrd_M", "Ahvaltrd_M"])
# 无风险利率
= pd.read_excel("data/rf_monthly/TRD_Nrrate.xlsx", skiprows=3,
nrrate =["Nrr1", "Clsdt", "Nrrmtdt"])
names
# 五因子
= pd.read_excel("data/five_factors/STK_MKT_FIVEFACMONTH.xlsx", skiprows=3,
facmnth =["MarkettypeID", "TradingMonth", "Portfolios",
names"RiskPremium1", "RiskPremium2", "SMB1", "SMB2",
"HML1", "HML2", "RMW1", "RMW2", "CMA1", "CMA2"])
6.4.3 第三步:整理数据
数据读入后,我们需要对数据进行整理和初步分析。这包括:行业分类、过滤非A股和金融公用事业公司等。
# 行业信息
= (cominfo.groupby(['IndustryName', 'IndustryCode'])
indinfo
.size()
.reset_index()={0: 'count'})
.rename(columns'IndustryCode'))
.sort_values(
# A股、非金融公用事业行业
= cominfo.copy()
comA 'EndDate'] = pd.to_datetime(comA['EndDate'])
comA['year'] = comA['EndDate'].dt.year
comA['LISTINGDATE'] = pd.to_datetime(comA['LISTINGDATE'])
comA[
# 过滤条件:上市后数据 + 非金融非公用事业 + A股
= comA[
comA 'EndDate'] >= comA['LISTINGDATE']) &
(comA[~comA['IndustryCode'].str.startswith('J')) &
(~comA['IndustryCode'].str.startswith('D')) &
(~comA['Symbol'].astype(str).str[:3].isin(['900', '200']))
( ]
6.4.4 第四步:财务数据处理
这一步我们处理财务数据,包括与公司信息的合并、填充缺失值等。由于财务数据的披露通常有滞后性,我们需要确保使用最新可获得的财务数据。我们还需要处理一些特殊情况,例如:
- 对财务数据进行向下填充,确保在新数据公布前使用最近期的有效数据
- 将交易数据转换为适当的格式,并调整收益率计算方式
# 财务数据处理
'Accper'] = pd.to_datetime(comfin['Accper'])
comfin['year'] = comfin['Accper'].dt.year
comfin['month'] = comfin['Accper'].dt.month
comfin[
# 合并财务数据与公司基本信息
= pd.merge(comfin, comA, left_on=['Stkcd', 'year'],
comfin =['Symbol', 'year'], how='inner')
right_on
# 向下填充财务数据(模拟R中的fill函数)
= comfin.sort_values(['Stkcd', 'year', 'month'])
comfin 'npexnr'] = comfin.groupby('Stkcd')['npexnr'].fillna(method='ffill')
comfin['ROE'] = comfin.groupby('Stkcd')['ROE'].fillna(method='ffill')
comfin['Stkcd'] = pd.to_numeric(comfin['Stkcd'])
comfin[
# 交易数据处理
'Trdmnt'] = pd.to_datetime(trdmnth['Trdmnt'])
trdmnth['ym'] = pd.to_datetime(trdmnth['Trdmnt']).dt.to_period('M')
trdmnth['year'] = trdmnth['Trdmnt'].dt.year
trdmnth['month'] = trdmnth['Trdmnt'].dt.month
trdmnth['Mretwd'] = trdmnth['Mretwd'] * 100 # 转换为百分比
trdmnth[
# 无风险利率处理
'Clsdt'] = pd.to_datetime(nrrate['Clsdt'])
nrrate['year'] = nrrate['Clsdt'].dt.year
nrrate['month'] = nrrate['Clsdt'].dt.month
nrrate[= nrrate.groupby(['year', 'month'])['Nrrmtdt'].mean().reset_index()
rf = rf.rename(columns={'Nrrmtdt': 'rf'})
rf
# 因子数据处理
= facmnth[(facmnth['MarkettypeID'] == 'P9714') & (facmnth['Portfolios'] == 1)]
facmnth 'TradingMonth'] = pd.to_datetime(facmnth['TradingMonth'])
facmnth['ym'] = facmnth['TradingMonth'].dt.to_period('M')
facmnth[# 转换为百分比
= ['RiskPremium1', 'SMB2', 'HML2', 'RMW2', 'CMA2']
factor_cols = facmnth[factor_cols] * 100 facmnth[factor_cols]
6.4.5 第五步:合并数据集与构造变量
在这一步,我们将合并处理后的各数据集,并计算关键变量,如市值和市盈率(E/P)。特别地,为了实现中国特色三因子模型的核心思想,我们筛选出市值最大的70%的企业,以剔除可能受到壳价值影响的小市值股票。
# 应用规则到交易数据
= trdmnth.copy()
trddata # 根据月份调整年份:1-4月使用上一年,其他月份使用当前年
'year1'] = np.where((trddata['month'] >= 1) & (trddata['month'] <= 4),
trddata['year'] - 1,
trddata['year'])
trddata[# 根据月份调整财务月份:
# 5-8月对应3月(一季报),9-10月对应6月(二季报),其他月份对应9月(三季报)
'month1'] = np.select(
trddata['month'] >= 5) & (trddata['month'] <= 8),
[(trddata['month'] == 9) | (trddata['month'] == 10)],
(trddata[3, 6],
[=9
default
)
# a. 合并财务数据
= pd.merge(trddata, comfin,
trddata =['Stkcd', 'year1', 'month1'],
left_on=['Stkcd', 'year', 'month'],
right_on='inner')
how= trddata.drop(columns=['year_y', 'month_y'])
trddata = trddata.rename(columns={'year_x': 'year', 'month_x': 'month'})
trddata
# b. 计算总市值和市盈率
= trddata.sort_values(['Stkcd', 'ym'])
trddata 'size'] = trddata.groupby('Stkcd')['Mclsprc'].shift(1) * (trddata['Msmvttl'] / trddata['Mclsprc'])
trddata['EP'] = trddata['npexnr'] / trddata['size']
trddata[
# c. 筛选市值最大的70%的企业
def filter_by_size(group):
= group['size'].quantile(0.3)
size_threshold return group[group['size'] >= size_threshold]
= trddata.groupby('ym').apply(filter_by_size).reset_index(drop=True)
trddata
# d. 合并无风险利率并计算超额收益率
= pd.merge(trddata, rf, on=['year', 'month'], how='left')
trddata 'exret'] = trddata['Mretwd'] - trddata['rf']
trddata[
# e. 筛选时间范围
'ym_dt'] = trddata['ym'].dt.to_timestamp()
trddata[= trddata[(trddata['ym_dt'] >= pd.Timestamp('2007-05-01')) &
trddata 'ym_dt'] <= pd.Timestamp('2019-02-28'))] (trddata[
6.4.6 第六步:对size、value、ROE进行排序分组
按照Fama-French的经典方法,我们需要对股票进行分组以构建因子。在这一步,我们将按照市值进行2组划分,按照E/P进行3组划分,并额外对ROE进行10组划分用于后续异象分析。
# 按市值、EP、ROE进行分组
= trddata.copy()
trddata_grp
# 市值分组(2组)
'grp_size'] = trddata_grp.groupby('ym')['size'].transform(
trddata_grp[lambda x: pd.qcut(x, 2, labels=[1, 2]).astype(int)
)
# EP价值分组(3组)
def get_value_group(x):
if x.notna().any():
= x.quantile(0.3)
low_val = x.quantile(0.7)
high_val return np.select(
<= low_val, x <= high_val],
[x 1, 2],
[=3
default
)return np.nan
'grp_value'] = trddata_grp.groupby('ym')['EP'].transform(get_value_group)
trddata_grp[
# ROE分组(10组),处理空值情况
def roe_group(x):
if x.notna().any():
# 先处理空值,将空值替换为-1
= x.fillna(-1)
x_filled # 使用qcut分组,将-1单独分为一组
= pd.qcut(x_filled, 10, labels=range(1, 11), duplicates='drop')
groups # 将-1对应的组别设为NaN
= groups.where(x_filled != -1, np.nan)
groups return groups.astype('Int64') # 使用可空整数类型
return np.nan
'grp_ROE'] = trddata_grp.groupby('ym')['ROE'].transform(roe_group)
trddata_grp[
# 市值分组(10组,用于条件分组)
'grp_size1'] = trddata_grp.groupby('ym')['size'].transform(
trddata_grp[lambda x: pd.qcut(x, 10, labels=range(1, 11)).astype(int)
)
# 在每个市值组内对ROE进行条件分组,处理空值情况
def roe_con_group(x):
if x.notna().any():
# 先处理空值,将空值替换为-1
= x.fillna(-1)
x_filled # 使用qcut分组,将-1单独分为一组
= pd.qcut(x_filled, 10, labels=range(1, 11), duplicates='drop')
groups # 将-1对应的组别设为NaN
= groups.where(x_filled != -1, np.nan)
groups return groups.astype('Int64') # 使用可空整数类型
return np.nan
'grp_ROE_con'] = trddata_grp.groupby(['ym', 'grp_size1'])['ROE'].transform(roe_con_group) trddata_grp[
6.4.7 第七步:计算加权平均股票回报率与定价因子
这一步是构建因子的核心环节。我们首先计算市场超额收益率(MKT),然后构建2×3分组的投资组合,计算基于规模的SMB因子和基于价值的VMG因子。
CH-3模型采用的分组方式与经典Fama-French模型相似,但有一些关键调整: 1. 我们使用已剔除最小30%市值股票的样本进行分组 2. 按市值分为两组(小市值和大市值) 3. 按E/P比率分为三组(30%-40%-30%) 4. 形成6个投资组合:小市值低E/P(11)、小市值中E/P(12)、小市值高E/P(13)、大市值低E/P(21)、大市值中E/P(22)、大市值高E/P(23)
# 计算市场超额收益率
= trddata_grp.groupby('ym').agg(
factor1 =('exret', lambda x: np.average(x, weights=trddata_grp.loc[x.index, 'size']))
MKT
).reset_index()
# 根据size和value排序计算投资组合回报
def calc_portfolio_return(group):
return np.average(group['Mretwd'], weights=group['size'])
= trddata_grp.groupby(['ym', 'grp_size', 'grp_value']).apply(
factor2
calc_portfolio_return
).reset_index()= ['ym', 'grp_size', 'grp_value', 'vwret']
factor2.columns
# 只保留不含NaN的组合
= factor2[factor2['grp_size'].notna() & factor2['grp_value'].notna()]
factor2
# 创建组合标识并透视表
'grp'] = factor2['grp_size'] * 10 + factor2['grp_value']
factor2[= factor2.pivot(index='ym', columns='grp', values='vwret').reset_index()
factor2_pivot
# 计算SMB和VMG因子
'SMB'] = (factor2_pivot[11] + factor2_pivot[12] + factor2_pivot[13]) / 3 - \
factor2_pivot[21] + factor2_pivot[22] + factor2_pivot[23]) / 3
(factor2_pivot['VMG'] = (factor2_pivot[13] + factor2_pivot[23]) / 2 - \
factor2_pivot[11] + factor2_pivot[21]) / 2
(factor2_pivot[
# 合并所有因子
= factor2_pivot.merge(factor1, on='ym')
factor = factor.merge(facmnth[['ym', 'RiskPremium1', 'SMB2', 'HML2']], on='ym', how='left') factor
6.4.8 第八步:描述性统计和因子相关性分析
在构建完成三因子后,我们需要对因子进行统计分析,了解其基本特征和相互关系。这些分析可以帮助我们了解: - 各因子的平均收益率和波动性 - 因子的分布特性(最小值、最大值、中位数等) - 因子之间的相关性,这对了解多因子模型中的因子独立性很重要
通常,我们希望看到因子之间的相关性不太高,这样每个因子都能捕捉股票收益率中的不同风险来源。
# 描述性统计
= factor[['MKT', 'SMB', 'VMG']].describe().T
factor_stats 'P0'] = factor_stats['min']
factor_stats['P25'] = factor[['MKT', 'SMB', 'VMG']].quantile(0.25)
factor_stats['Median'] = factor[['MKT', 'SMB', 'VMG']].median()
factor_stats['P75'] = factor[['MKT', 'SMB', 'VMG']].quantile(0.75)
factor_stats['P100'] = factor_stats['max']
factor_stats[print("因子描述性统计:")
print(factor_stats[['count', 'mean', 'std', 'P0', 'P25', 'Median', 'P75', 'P100']])
# 相关系数
= factor[['MKT', 'SMB', 'VMG']].corr()
factor_corr print("\n因子相关系数:")
print(factor_corr)
6.4.9 第九步:ROE异象分析
为了验证CH-3模型的有效性,我们分析其对ROE异象的解释能力。ROE(净资产收益率)是衡量公司盈利能力的重要指标,高ROE的股票通常表现出超额收益。
我们使用两种方法构建ROE异象组合: 1. 无条件分组:直接按ROE对所有股票进行十分位分组,计算高ROE组合与低ROE组合的收益率差 2. 市值中性分组:在每个市值十分位内分别按ROE进行分组,控制规模效应的影响
两种方法的对比可以帮助我们了解ROE异象是否受到规模效应的影响。
# 基于ROE的投资组合形成(无条件分组)
def calc_roe_portfolio(trddata_grp):
# 无条件分组
= trddata_grp.groupby(['ym', 'grp_ROE']).apply(
roe_port lambda x: pd.Series({
'vwret': np.average(x['Mretwd'], weights=x['size'])
})
).reset_index()
# 只保留不含NaN的组合
= roe_port[roe_port['grp_ROE'].notna()]
roe_port
# 转为宽格式
= roe_port.pivot(index='ym', columns='grp_ROE', values='vwret').reset_index()
roe_port_wide
# 计算高减低ROE组合回报
'rmw'] = roe_port_wide[10] - roe_port_wide[1]
roe_port_wide[
return roe_port_wide[['ym', 'rmw']]
# 基于市值中性的ROE投资组合形成(条件分组)
def calc_size_neutral_roe(trddata_grp):
# 条件分组(市值中性)
= trddata_grp.groupby(['ym', 'grp_ROE_con']).apply(
sn_port lambda x: pd.Series({
'vwret': np.average(x['Mretwd'], weights=x['size'])
})
).reset_index()
# 只保留不含NaN的组合
= sn_port[sn_port['grp_ROE_con'].notna()]
sn_port
# 转为宽格式
= sn_port.pivot(index='ym', columns='grp_ROE_con', values='vwret').reset_index()
sn_port_wide
# 计算高减低ROE组合回报
'rmw_sn'] = sn_port_wide[10] - sn_port_wide[1]
sn_port_wide[
return sn_port_wide[['ym', 'rmw_sn']]
# 计算ROE投资组合
= calc_roe_portfolio(trddata_grp)
factor3 = calc_size_neutral_roe(trddata_grp)
factor4
# 合并所有因子数据
= factor.merge(factor3, on='ym').merge(factor4, on='ym') factor_all
6.4.10 第十步:使用Newey-West调整的标准误差估计因子模型
接下来,我们使用Newey-West调整的标准误差进行回归分析,比较CH-3模型与传统CAPM、FF-3模型在解释ROE异象方面的能力。
# 使用Newey-West调整的标准误差估计因子模型
def nw_regression(y, X, lags=4):
"""使用Newey-West调整的标准误差进行回归"""
# 准备数据
= sm.add_constant(X)
X
# 拟合模型
= sm.OLS(y, X).fit()
model
# 使用Newey-West调整标准误差
= cov_hac(model, nlags=lags)
cov = model.get_robustcov_results(cov_type='HAC', maxlags=lags)
model_nw
return model_nw
# 进行因子模型回归
print("\nROE异象的因子模型分析:")
# 无条件分组 - CAPM
= nw_regression(factor_all['rmw'], factor_all[['MKT']])
model1 print("\n无条件分组 - CAPM模型:")
print(model1.summary().tables[1])
# 无条件分组 - 三因子
= nw_regression(factor_all['rmw'], factor_all[['MKT', 'SMB', 'VMG']])
model2 print("\n无条件分组 - 三因子模型:")
print(model2.summary().tables[1])
# 无条件分组 - Fama-French三因子
= nw_regression(factor_all['rmw'], factor_all[['MKT', 'SMB2', 'HML2']])
model3 print("\n无条件分组 - Fama-French三因子模型:")
print(model3.summary().tables[1])
# 条件分组 - CAPM
= nw_regression(factor_all['rmw_sn'], factor_all[['MKT']])
model4 print("\n条件分组 - CAPM模型:")
print(model4.summary().tables[1])
# 条件分组 - 三因子
= nw_regression(factor_all['rmw_sn'], factor_all[['MKT', 'SMB', 'VMG']])
model5 print("\n条件分组 - 三因子模型:")
print(model5.summary().tables[1])
# 条件分组 - Fama-French三因子
= nw_regression(factor_all['rmw_sn'], factor_all[['MKT', 'SMB2', 'HML2']])
model6 print("\n条件分组 - Fama-French三因子模型:")
print(model6.summary().tables[1])
6.4.11 第十一步:绘图分析
为了直观展示因子表现,我们绘制三因子和ROE异象收益的时间序列图。
# 绘制因子收益时间序列图
=(12, 8))
plt.figure(figsize
3, 1, 1)
plt.subplot('ym'].dt.to_timestamp(), factor_all['MKT'], 'b-')
plt.plot(factor_all['市场风险溢价 (MKT)')
plt.title(True)
plt.grid(
3, 1, 2)
plt.subplot('ym'].dt.to_timestamp(), factor_all['SMB'], 'g-')
plt.plot(factor_all['规模因子 (SMB)')
plt.title(True)
plt.grid(
3, 1, 3)
plt.subplot('ym'].dt.to_timestamp(), factor_all['VMG'], 'r-')
plt.plot(factor_all['价值因子 (VMG)')
plt.title(True)
plt.grid(
plt.tight_layout()'三因子收益.png')
plt.savefig(
plt.close()
# 绘制ROE异象收益
=(12, 6))
plt.figure(figsize
2, 1, 1)
plt.subplot('ym'].dt.to_timestamp(), factor_all['rmw'], 'b-')
plt.plot(factor_all['无条件分组ROE异象 (High-Low)')
plt.title(True)
plt.grid(
2, 1, 2)
plt.subplot('ym'].dt.to_timestamp(), factor_all['rmw_sn'], 'g-')
plt.plot(factor_all['市值中性ROE异象 (High-Low)')
plt.title(True)
plt.grid(
plt.tight_layout()'ROE异象收益.png')
plt.savefig(
plt.close()
# 保存三因子数据
'中国三因子与ROE异象.csv', index=False)
factor_all.to_csv(print("\n因子数据已保存到'中国三因子与ROE异象.csv'")
6.4.12 计算绩效指标函数(用于评估)
以下是计算投资组合绩效指标的函数,可用于评估因子表现:
def calculate_performance(returns_series):
"""
计算投资组合绩效指标
参数:
returns_series: 包含收益率的Series
返回:
绩效指标字典
"""
# 计算累积收益
= (1 + returns_series).cumprod().iloc[-1] - 1
cumulative_return
# 计算年化收益率
= len(returns_series)
n_periods = 12 # 假设为月度数据
periods_per_year = (1 + cumulative_return) ** (periods_per_year / n_periods) - 1
annualized_return
# 计算年化波动率
= returns_series.std() * np.sqrt(periods_per_year)
annualized_vol
# 计算夏普比率 (假设无风险利率为0)
= annualized_return / annualized_vol if annualized_vol != 0 else 0
sharpe_ratio
# 计算最大回撤
= (1 + returns_series).cumprod()
cum_returns = cum_returns.cummax()
running_max = (cum_returns / running_max) - 1
drawdown = drawdown.min()
max_drawdown
# 计算t统计量
= stats.ttest_1samp(returns_series, 0)
t_stat, p_value
return {
'Cumulative Return': cumulative_return,
'Annualized Return': annualized_return,
'Annualized Volatility': annualized_vol,
'Sharpe Ratio': sharpe_ratio,
'Max Drawdown': max_drawdown,
't-statistic': t_stat,
'p-value': p_value
}
6.5 结果分析与讨论
通过上述分析,我们可以评估中国特色三因子模型(CH-3)的有效性。从理论和实证结果看,CH-3模型相比传统FF-3模型在中国市场有以下优势:
规模因子改进:通过剔除最小30%市值股票,避免了壳价值效应对规模因子的影响,使规模因子更能反映真实的风险溢价。
价值因子改进:使用市盈率(E/P)替代账面市值比(B/M)构建价值因子,更适合中国市场特点,提高了模型解释力。
对异象的解释能力:从ROE异象的回归结果可以看出,CH-3模型对中国市场的盈利能力异象有较强的解释能力,特别是在控制规模效应后。
稳健性:从因子收益的时间序列分析可以看出,CH-3模型的因子在不同市场环境下表现相对稳定。
这一结果印证了Liu, Stambaugh和Yuan (2019)的研究发现,即中国股票市场存在独特的壳价值现象,需要针对这一特点调整因子模型。同时,选择更适合中国市场的价值指标也是提高模型性能的关键。
6.6 实验思考题
为什么在中国市场需要剔除市值最小的30%股票?这种剔除对规模因子表现有何影响?
为什么在中国市场中E/P比B/M更有效?这与中国股票市场的哪些特点有关?
在构建CH-3模型时,你认为中国市场的反向收购机制如何影响了小市值股票的定价?
比较CH-3模型与传统FF-3模型在解释中国市场异象方面的差异,哪些异象能被CH-3模型更好地解释?
如果政策环境变化导致上市公司的壳价值发生变化,你预计这会对CH-3模型的有效性产生什么影响?
6.7 参考资料
Liu, J., Stambaugh, R. F., & Yuan, Y. (2019). Size and Value in China. Journal of Financial Economics, 134(1), 48-69.
Fama, E. F., & French, K. R. (1993). Common risk factors in the returns on stocks and bonds. Journal of Financial Economics, 33(1), 3-56.
Hu, G. X., Chen, C., Shao, Y., & Wang, J. (2019). Fama–French in China: size and value factors in Chinese stock returns. International Review of Finance, 19(1), 3-44.