前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Fama-French三因子回归A股实证(附源码)

Fama-French三因子回归A股实证(附源码)

作者头像
量化小白
发布2023-04-03 20:26:42
2.2K2
发布2023-04-03 20:26:42
举报

01

三因子回归模型

Fama-French三因子回归是量化中最经典的模型之一,最早提出是在论文《Common risk factors in the returns on stocks and bonds》中,FAMA三因子回归模型可表示如下

其中,rt为投资组合的收益率,rf为无风险收益率,SMB为规模因子,HML为账面市值比因子,MKT为市场因子。

Fama-French三因子回归通过计算上述的三个因子,对股票的收益来源进行了分解。本文基于这篇论文,在A股上实现Fama-French三因子回归全流程。论文及源码数据的获取方式见文末

02

解释变量

解释变量为三个因子SMB、HML、MKT,先贴上论文中因子定义原文,当然如果你不想看英语,可以跳过看后面我给的说明。

股票按规模分组

股票按账面市值比分组

分组后计算SMB、HML

MKT定义

总结一下

  1. 每年五月末,将股票按市值等分为两组Big(B)、Small(S),将账面市值比按30%、40%、40%分为三组Low(L)、Middle(M)、High(H)。上述分组组合之后可以得到六个组合:B/L、B/M、B/H、S/L、S/M、S/H。作者提到之所以规模分两组,账面市值比分三组是考虑到账面市值比的效果更强(黄色部分)。
  2. 计算这六个组合的市值加权收益率(Value-weighted return) ,作者提到计算市值加权收益率一方面是为了最小化方差,另一方面可以捕捉到大市值和小市值股票的不同特征
  3. HML、SMB因子定义如下

公式中左边代表每个组合的市值加权收益率,HML、SMB分别刻画了规模因子和账面市值比因子的风险溢价。

  1. MKT因子定义为RM-RF,其中RM为全市场股票市值加权收益率,RF是无风险利率

以上是解释变量的定义。

03

被解释变量

被解释变量为投资组合的收益率,作者使用doublesort的方法构建了25个投资组合(关于doublesort可以看往期推文:因子评估——双重排序)。

还是先给出论文的定义

总结一下,其实和前文自变量分组的方式是一样的,每年的5月末进行分组,只不过这一次对市值和账面市值比都分别分成5等分,组合之后得到25个投资组合,并计算这25个投资组合的市值加权收益率,作为因变量。

以上就是本文模型部分的全部说明,论文中还讨论了一些其他处理细节,有兴趣可以看看。

在定义好了自变量和因变量之后,就可以做25次回归,对结果进行分析。接下来用A股数据进行实证分析。

04

FF3因子的A股实证

先说明使用的数据

  • HML、SMB、因变量:使用2009年-2019年全A股月度数据进行计算(用其他频率也可)
  • MKT:MKT的计算比较简单,直接使用中国资产管理研究中心提供的数据了,当然如果想自己算的话,RM可以考虑用中证全指的收益率,RF可以用10年期国债到期收益率。

数据格式如下,如果你有其他数据源,处理成如下的形式,可以直接使用本文的代码

价格数据

估值数据

市值数据

MKT因子

接下来是实证部分,首先把账面市值比BM和市值mkt数据拼在一起,然后剔除新股和ST股,让结果稳健一些,当然也可以不剔。

代码语言:javascript
复制
price = pd.read_csv('复权价格.csv')
ST = pd.read_csv('ST.csv')
pb = pd.read_csv('PB.csv')
mkt = pd.read_csv('mkt.csv')
ipodate = pd.read_csv('上市日期.csv')

# 日期处理
price['tradedate'] = pd.to_datetime(price.tradedate)
ST['entry_dt'] = pd.to_datetime(ST.entry_dt)
ST['remove_dt'] = pd.to_datetime(ST.remove_dt)
mkt['tradedate'] = pd.to_datetime(mkt.tradedate)
pb['tradedate'] = pd.to_datetime(pb.tradedate)
ipodate['ipodate'] = pd.to_datetime(ipodate.ipodate)

BM = pb.copy()
BM['BM'] = 1/BM.pb
BM = BM.drop(['pb'],axis = 1)

f = pd.merge(BM,mkt,left_on = ['tradedate','stockcode'],right_on = ['tradedate','stockcode'])
# 踢PB为负的
f = f.loc[f.BM > 0].reset_index(drop = True)
# 踢新股:上市不满一年
ipodate['entry_date'] = ipodate.ipodate + datetime.timedelta(365)
f = pd.merge(f,ipodate,left_on = ['stockcode'],right_on = ['stockcode'])
f = f.loc[f.tradedate >= f.entry_date].reset_index(drop = True)
f = f.drop(['ipodate','entry_date'],axis = 1)


# 剔ST

res =[]

for dates in f.tradedate.unique(): # dates = f.tradedate.unique()[10]
    fuse = f.loc[f.tradedate == dates]
    
    st_use = ST.loc[ST.entry_dt <= dates]
    st_use = st_use.loc[(st_use.remove_dt > dates) | pd.isnull(st_use.remove_dt)]

    scode_notst = set(fuse.stockcode).difference(set(st_use.stockcode))
    
    fuse = fuse.set_index(['stockcode']).loc[scode_notst].reset_index()
    res.append(fuse)

res = pd.concat(res,axis = 0)
f = res.reset_index(drop = True)

接下来生成6个投资组合:每年5月末,按市值分为两组,按账面市值比生成三组。以上过程通过函数split_SIZEsplit_BM实现,通过applygroupby得到每一期的分组。

代码语言:javascript
复制
f['ym'] = f.tradedate.apply(lambda x:x.year*100 + x.month)

f_5 = f.loc[f.ym %10 ==5].copy()

def split_BM(x):
    x.loc[x['BM'] >= x.BM.quantile(0.7),'group_BM'] = 'H'
    x.loc[x['BM'] < x.BM.quantile(0.3),'group_BM'] = 'L'
    return x

def split_SIZE(x):
    x.loc[x['mkt'] >= x.mkt.median(),'group_SIZE'] = 'B'
    return x

f_5['group_BM'] = 'M'
f_5 = f_5.groupby(['ym']).apply(split_BM)
f_5 = f_5.reset_index(drop = True)


f_5['group_SIZE'] = 'S'
f_5 = f_5.groupby(['ym']).apply(split_SIZE)
f_5 = f_5.rename(columns = {'ym':'portfolio_dates'})
f_5 = f_5[['stockcode','portfolio_dates','group_BM','group_SIZE']]



f['portfolio_dates'] = f.tradedate.apply(lambda x:x.year*100 + 5 if x.month > 5 else  (x.year - 1)*100 + 5)

f = pd.merge(f,f_5,left_on =['stockcode','portfolio_dates'],right_on =['stockcode','portfolio_dates'])
f = f.reset_index(drop = True)

f['portfolio_name'] = f.group_SIZE + '/' + f.group_BM

运行结果如下

其中,portfolio_name代表股票对应的组别。以2009年6月为起点,计算这六个组合的市值加权收益率

代码语言:javascript
复制
# 计算六个组合的月度value weighted return

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


sdate = datetime.date(2009,5,1)
f = f.loc[f.tradedate >= sdate].reset_index(drop = True)

f = pd.merge(f,ret,left_on =['stockcode','tradedate'],right_on =['stockcode','tradedate'])
f.loc[f.tradedate == datetime.date(2009,5,27),'ret'] = 0


port_ret = f.groupby(['tradedate','portfolio_name']).apply(lambda x:(x.ret*x.mkt).sum()/x.mkt.sum())
port_ret = port_ret.reset_index()
port_ret = port_ret.rename(columns = {port_ret.columns[-1]:'ret'})

看一下六个组合收益曲线

明显可以看出,小市值股票(S)表现更好,低账面市值比(L)表现最差,接下来计算SMB、HML因子,并和MKT因子拼在一起。

代码语言:javascript
复制
# SMB因子
# SMB = 1/3(SL + SM + SH) - 1/3(BL + BM + BH)
SMB = (port_ret_pivot['S/L'] + port_ret_pivot['S/M'] + port_ret_pivot['S/H'])/3 - (port_ret_pivot['B/L'] + port_ret_pivot['B/M'] + port_ret_pivot['B/H'])/3

# HML因子 HML = (SH + BH)/2 - (SL + BL)/2
HML = (port_ret_pivot['S/H'] + port_ret_pivot['B/H'])/2 - (port_ret_pivot['S/L'] + port_ret_pivot['B/L'])/2 

ff3 = pd.concat([SMB,HML],axis = 1)
ff3 = ff3.reset_index()
ff3.columns = ['tradedate','SMB','HML']
ff3['ym'] = ff3.tradedate.apply(lambda x:x.year*100 + x.month)

# RM:全市场流通市值加权指数收益率
# RF:无风险利率
# 这里直接用中国资产管理研究中心的数据
d = pd.read_csv('fivefactor_monthly.csv')
 

RM_RF = d[['trdmn','mkt_rf','rf']].copy()
RM_RF = RM_RF.rename(columns = {'trdmn':'ym'})

ff3 = pd.merge(ff3,RM_RF,left_on = ['ym'],right_on = ['ym'])

看看每个因子的累计收益率

三因子的相关性如下

可以看出,因子相关性不高,适合做回归。

接下来构造因变量25个投资组合,过程和上面类似。

代码语言:javascript
复制
groups = 5
f_5 = f.loc[f.ym %10 ==5,['stockcode','tradedate','ym','BM','mkt']].copy()

f_5['g_BM'] = f_5.BM.groupby(f_5.tradedate).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))
f_5['g_SIZE'] = f_5.mkt.groupby(f_5.tradedate).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))

f_5 = f_5.rename(columns = {'ym':'portfolio_dates'})
f_5 = f_5[['stockcode','portfolio_dates','g_BM','g_SIZE']]
f['portfolio_dates'] = f.tradedate.apply(lambda x:x.year*100 + 5 if x.month > 5 else  (x.year - 1)*100 + 5)

f = pd.merge(f,f_5,left_on =['stockcode','portfolio_dates'],right_on =['stockcode','portfolio_dates'])
f = f.reset_index(drop = True)

这25个组合的平均市值

从上往下,SIZE增大,从左往右,BM增大。平均市值从上往下依次增大,可以验证分组没有算错。

每个组别的股票个数如下

接下来这张:每个分组的平均收益率

这张可以分析出很多信息,实际上是一个标准的doublesort操作。从上往下,市值增大,平均收益减小。从左往右,账面市值比增大,平均收益也增大。

接下来生成这25个组合的收益率数据,用于回归,数据格式如下

横轴25行代表25个组合,纵轴代表时间,累计收益率如下

将25个组合数据和前面生成的三因子数据合并,进行三因子回归,记录回归的beta、p值、t值、r2。

代码语言:javascript
复制
f25 = pd.merge(f25,ff3,left_on = ['tradedate'],right_on = ['tradedate'])

f25['Intercept'] = 1
x = f25.loc[:,['SMB','HML','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','mkt_rf','Intercept'])
t = pd.DataFrame(t,columns = ['group','SMB','HML','mkt_rf','Intercept'])
betas = pd.DataFrame(betas,columns = ['group','SMB','HML','mkt_rf','Intercept'])

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

运行结果如下,首先看R2

对于金融数据来说,算是比较高的水平了,拟合的还不错。t值和p值代表的含义差不多,看p值更直观一些。

记录了每次回归每个系数的p值,可以看出,规模因子HML非常显著,几乎每次回归都显著,其他因子的显著性一般。

接下来看beta

beta是回归的系数,这里能看出来,MKT、SMB的系数为正,HML系数有负有正,前面p值只有SMB是非常显著的,并且SMB的正系数也是符合常理的。

这里需要细致分析的是截距项,把截距项转换成上面双重排序的格式来看

截距项表示的是股票收益中不能被三个因子解释的部分,也就是alpha的部分。从截距项的大小来看,也是随着SIZE增大,alpha减小,随着BM增大,alpha增大。表明小市值的股票和高账面市值比的股票更容易获得超额回报,非常符合常理。

除此外,也可以对残差项进行分析,这里略去。

以上是Fama三因子模型的全过程,之后会有一些基于Fama三因子的因子构建。

05

参考文献

Fama E F, French K R. Common risk factors in the returns on stocks and bonds[J]. Journal of, 1993.

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 量化小白躺平记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档