pythonipo模型_【python量化】Fama-French三因子回归A股实证(附源码)

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定义

总结一下

每年五月末,将股票按市值等分为两组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。作者提到之所以规模分两组,账面市值比分三组是考虑到账面市值比的效果更强(黄色部分)。

计算这六个组合的市值加权收益率(Value-weighted return) ,作者提到计算市值加权收益率一方面是为了最小化方差,另一方面可以捕捉到大市值和小市值股票的不同特征。

HML、SMB因子定义如下

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

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股,让结果稳健一些,当然也可以不剔。

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_SIZE

split_BM

实现,通过

apply

groupby

得到每一期的分组。

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月为起点,计算这六个组合的市值加权收益率

# 计算六个组合的月度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因子拼在一起。

# 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个投资组合,过程和上面类似。

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。

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.

觉得不错就点个好看吧

你可能感兴趣的:(pythonipo模型)