Carhart四因子模型A股实证(附源码)

2020-11-17 15:51:17 浏览数 (1)

01

说明

接上一篇《Fama-French三因子回归A股实证》,继续写Carhart四因子模型,整个过程比较容易,还是基于Fama三因子的框架,多加进去一个动量因子进行回归。全文的代码数据论文获取请在后台回复“C4"。

贴上论文原文对于模型的说明如下

三行公式,第一行为CAPM,第二行为Fama三因子模型,第三行为四因子模型,其中,RMRF是市场因子,也就是我们上一篇中的MKT,PR1YRt是作者构建的动量因子,本文我们用UMD表示。

对于动量因子的定义,原文说明如下,黄字部分

解释一下,每月末计算所有股票前2-12个月末的收益率,构建投资组合:前30%的等权组合,后30%的等权组合,二者收益率的差值定义为动量因子,组合月度调整

从定义来看,和Fama三因子的差异主要有两点,首先是组合是等权的,Fama三因子中的组合都是市值加权的,其次组合是月度构建的,三因子中的组合的年度构建的。最后需要说明一点,这里用2-12个月的收益率实际上是剔除了最近1个月的收益率,原因主要在于美股市场是短期反转,长期动量的,剃掉最近1个月的反转,可以得到比较纯粹的动量。

之后的部分和Fama三因子差不多了,因变量相同,自变量多一个动量因子,这里我们沿用上一篇的方式进行回归,构建25个投资组合,做25次回归(原文回归的过程不是这样做的,细节可以参考原文)。

02

实证部分

数据还是之前用的那些,没有变化,先给出动量因子的计算过程。首先每个月末把所有上市公司按前2-12个月末的收益率分为三层

代码语言:javascript复制
# 动量因子UMD
def split_UMD(x):
    x.loc[x['UMD'] >= x.UMD.quantile(0.7),'group_UMD'] = 'UMD_H'
    x.loc[x['UMD'] < x.UMD.quantile(0.3),'group_UMD'] = 'UMD_L'
    return x

UMD = price.pivot(index = 'tradedate',columns = 'stockcode',values = 'price').pct_change(11).shift(1).fillna(0)
UMD = UMD.stack().reset_index()
UMD = UMD.rename(columns = {UMD.columns[2]:'UMD'})

f['group_UMD'] = 'UMD_M'
f = pd.merge(f,UMD,left_on =['stockcode','tradedate'],right_on =['stockcode','tradedate'])
f =f .groupby(['ym']).apply(split_UMD)

计算最大组和最小组的等权收益率差值作为动量因子

代码语言:javascript复制
# UMD因子
UMD_ret = f.groupby(['tradedate','group_UMD']).apply(lambda x:x.ret.mean())
UMD_ret = UMD_ret.reset_index()
UMD_ret = UMD_ret.rename(columns = {UMD_ret.columns[-1]:'ret'})
 
UMD_ret_pivot = UMD_ret.pivot(index = 'tradedate',columns = 'group_UMD',values = 'ret')
UMD = UMD_ret_pivot['UMD_H'] - UMD_ret_pivot['UMD_L']

最后再把UMD因子和fama三因子进行合并,fama三因子的计算过程略去,可以看文章开头的链接

代码语言:javascript复制
Carhart4 = pd.concat([SMB,HML,UMD],axis = 1)
Carhart4 = Carhart4.reset_index()
Carhart4.columns = ['tradedate','SMB','HML','UMD']
Carhart4['ym'] = Carhart4.tradedate.apply(lambda x:x.year*100   x.month)

首先看看四个因子的收益曲线

可以明显看出,A股是没啥动量的,反转要显著一点。

个人粗浅的理解,反转一定程度上可以表示投机性的程度。A股的反转比较显著,这和A股的投资者结构关系比较大,主要为个人投资者,投机性更强一些。而且反转因子具有明显的收益集中在空头的特征,实际上和投机的心理非常一致:投资者非常可能在获得很高的收益后马上平仓,规避风险,而在股票暴跌很多时候去抄底承担风险的,虽然也有,但肯定比前者要少很多。当然,反转的不对称性也和A股不能做空有很大的关系。

接下来看看四个因子的相关性情况

动量因子和其他三个因子的相关性都很低。

接下来做四因子回归,代码和之前也是基本类似的

代码语言:javascript复制
### 四因子回归
x = f25.loc[:,['SMB','HML','UMD','mkt_rf','Intercept']].values
  
r2 = []
betas = []
t = []
p = []
for i in range(25):# i = 0
    y = f25.loc[:,f25.columns[i 1]].values
    mod = sm.OLS(y,x).fit()

    r2.append([f25.columns[i 1],mod.rsquared])

    betas.append([f25.columns[i 1]]   list(mod.params))
    t.append([f25.columns[i 1]]   list(mod.tvalues))
    p.append([f25.columns[i 1]]   list(mod.pvalues))


 


p = pd.DataFrame(p,columns = ['group','SMB','HML','UMD','mkt_rf','Intercept'])
t = pd.DataFrame(t,columns = ['group','SMB','HML','UMD','mkt_rf','Intercept'])
betas = pd.DataFrame(betas,columns = ['group','SMB','UMD','HML','mkt_rf','Intercept'])

r2 = pd.DataFrame(r2,columns = ['group','r2'])

p_percent_car4 = (p.iloc[:,1:]<0.05).mean()


alpha = betas[['group','Intercept']]
alpha['g_BM'] = alpha.group.apply(lambda x:x[2])
alpha['g_SIZE'] = alpha.group.apply(lambda x:x[-1])

alpha = alpha.pivot(index = 'g_SIZE',columns = 'g_BM',values = 'Intercept')
alpha = alpha.reset_index()

alpha = alpha.rename(columns = {'g_SIZE':'',
                                    alpha.columns[1]:'small BM',
                                    alpha.columns[-1]:'big BM'})
    

alpha.iloc[0,0] = 'small SIZE'
alpha.iloc[-1,0] = 'big SIZE'
alpha

返回回归的p值、beta、r2、alpha。

对于四因子模型,主要说明三点:

首先,看回归的系数beta:

大部分回归里,动量的系数为负,再次说明主要是反转的贡献,不是动量。

其次,四因子回归系数的p值

统计其中5%显著性显著的回归个数

与三因子回归中显著的回归个数进行对比

可以看出,在加入UMD因子之后,系数的显著性没有降低。

类似的,统计三因子和四因子的25次回归里整体F检验的p值

也能看出,加入动量因子前,92%的回归是显著的,加入后,96%的回归是显著的,这说明动量因子确实包含有一些三因子模型无法解释的信息,可以提升三因子模型的效果

03

最后

最后,也可以类比《基于Fama三因子的因子构建》一文,去构建基于四因子的相关因子,但从相关券商报告的结论来看,效果没有非常好的改善,所以也没有做过多的尝试,如果有兴趣,欢迎交流。

参考文献

Carhart M M. On persistence in mutual fund performance[J]. The Journal of finance, 1997, 52(1): 57-82.

0 人点赞