Fama-French三因素模型简介
Fama-French三因素模型在CAPM的基础上增加了两个因子:市值因子和账面市值比因子。其中市场风险溢酬因子E(RM)-Rf对应了市场投资组合的收益率,市值因子SMB对应了做多市值较小的公司与做空市值较大的公司的投资组合带来的收益率(规模因子),账面市值比因子HML对应的是做多高book-to-market ratio公司、做空低book-to-market ratio公司的投资组合带来的收益率(做多价值股做空成长股,价值因子)。三因素模型的形式为:
其中Rit为我们要研究的投资组合或个股收益率序列;Rmt为市场投资组合收益率序列,常用合适的大盘指数来替代;αi代表超额收益。
方法很简单,通过多元线性回归估计出各个因子前的系数就可得到完整的三因素模型。在投资组合管理中,透过系数可以看出该投资组合的投资风格,比如估计出的hi为负,就代表该投资组合在做空价值股、做多成长股;若Rf前的系数为负,代表该策略在加杠杆买大盘;超额收益率α越大代表投资组合越优秀等等。
实际上难点在于SMB和HML的计算。类似与Fama-French(1993)的构造方法,首先,我们按照公司的市值与帐面市值比的大小形成6个组合;然后我们利用这6个组合来模拟“规模因子”与“价值因子” 带来的收益率。具体方法如下:
第一步,在每个交易日内对所有的股票按其市值进行排序,用中位数把样本内的股票分成两个两组,即小的(S)与大(B)的两组。同样我们也按账面市值比的大小进行排序,按最小的%30(L)、中间的40%(M)、最大的30%(H)来取分界点。这样我们通过把上面的两种分类方法就可以构造出6个组合(S/L,S/M,S/H,B/L,B/M,B/H),以等权重来计算出6个组合的收益。
第二步,利用已经构造的6个组合来计算SMB与HML,计算方法如下:
新引入函数介绍
query()函数对数据框进行挑选行的操作,主要格式为按条件提取df.query(‘(a列==“b”) and (b列 >=“c”)’)或列之间的比较df.query(‘a列<b列’) 。尤其注意多条件提取时内外引号不能相同,比如里面单引号外面双引号,否则会报错。
map()将一个自定义函数应用于Series结构中的每个元素,表现为一种映射关系。比如df[‘a’] = df[‘a’].map(lambda x :"%.3f"%x)表示将df数据框的a列改成保留小数点后三位。
apply()将一个函数作用于DataFrame中的每个行或者列,也就是说fun的参数x指的是某列或某行。比如想对数据框中某两行或两列相加:
df = pd.DataFrame({'a1':np.arange(5),'a2':np.arange(5,10),'key1':['a','a','b','b','a']})
df['key1']=df['key1'].map({'b':'c','a':'d'});df#map函数修改key1列的数据
Out[7]:
a1 a2 key1
0 0 5 d
1 1 6 d
2 2 7 c
3 3 8 c
4 4 9 d
#对列元素(每行)求和
df['rowsum']=df[['a1','a2']].apply(lambda x:sum(x),axis=1);df
Out[9]:
a1 a2 key1 rowsum
0 0 5 d 5
1 1 6 d 7
2 2 7 c 9
3 3 8 c 11
4 4 9 d 13
#对行元素(每列)求和
df.loc['colsum']=df[['a1','a2']].apply(lambda x:sum(x),axis=0);df
a1 a2 key1 rowsum
0 0.0 5.0 d 5.0
1 1.0 6.0 d 7.0
2 2.0 7.0 c 9.0
3 3.0 8.0 c 11.0
4 4.0 9.0 d 13.0
colsum 10.0 35.0 NaN NaN
下面以研究个股收益率为例建立三因素模型。
step1:计算SMB和HML
引入需要的库:
import pandas as pd
import tushare as ts
import datetime
pro=ts.pro_api('你的token')#登录tushare
定义计算SMB和HML的函数。先根据流通市值进行大市值小市值分组,再求出每一个公司的账面市值比,并分为三组。然后组合成六组并求每一组的按市值加权平均收益率,代入公式即可。
def cal_smb_hml(df):
# 根据流通市值circ_mv划分大小市值公司,映射到SB列
df['SB'] = df['circ_mv'].map(lambda x: 'B' if x >= df['circ_mv'].median() else 'S')
# 求账面市值比:PB的倒数
df['BM'] = 1 / df['pb']
# 划分高、中、低账面市值比公司
border_down, border_up = df['BM'].quantile([0.3, 0.7])
df['HML'] = df['BM'].map(lambda x: 'H' if x >= border_up else 'M')
df['HML'] = df.apply(lambda x: 'L' if x['BM'] <= border_down else x['HML'], axis=1)#这里的x可看做df,按行处理
# 组合划分为6组
df_SL = df.query('(SB=="S") & (HML=="L")')
df_SM = df.query('(SB=="S") & (HML=="M")')
df_SH = df.query('(SB=="S") & (HML=="H")')
df_BL = df.query('(SB=="B") & (HML=="L")')
df_BM = df.query('(SB=="B") & (HML=="M")')
df_BH = df.query('(SB=="B") & (HML=="H")')
# 计算各组按市值加权平均收益率:Σ百分比变化×流通市值/Σ流通市值,除以100是因为pct_chg乘了100
R_SL = (df_SL['pct_chg'] * df_SL['circ_mv'] / 100).sum() / df_SL['circ_mv'].sum()
R_SM = (df_SM['pct_chg'] * df_SM['circ_mv'] / 100).sum() / df_SM['circ_mv'].sum()
R_SH = (df_SH['pct_chg'] * df_SH['circ_mv'] / 100).sum() / df_SH['circ_mv'].sum()
R_BL = (df_BL['pct_chg'] * df_BL['circ_mv'] / 100).sum() / df_BL['circ_mv'].sum()
R_BM = (df_BM['pct_chg'] * df_BM['circ_mv'] / 100).sum() / df_BM['circ_mv'].sum()
R_BH = (df_BH['pct_chg'] * df_BH['circ_mv'] / 100).sum() / df_BH['circ_mv'].sum()
# 计算SMB, HML
smb = (R_SL + R_SM + R_SH - R_BL - R_BM - R_BH) / 3
hml = (R_SH + R_BH - R_SL - R_BL) / 2
return smb, hml
挑选出样本区间内的所有股票信息,包括每日行情和每日指标,计算出SMB和HML:
data = []
#取出交易日信息,选出上交所的交易日
df_cal = pro.trade_cal(start_date='20190101', end_date='20200101');df_cal.head()
Out[4]:
exchange cal_date is_open
0 SSE 20190101 0
1 SSE 20190102 1
2 SSE 20190103 1
3 SSE 20190104 1
4 SSE 20190105 0
df_cal = df_cal.query('(exchange=="SSE") & (is_open==1)')
for date in df_cal.cal_date:
df_daily = pro.daily(trade_date=date)#通过日期取出某一天所有股票的行情数据
df_basic = pro.daily_basic(trade_date=date)#通过日期取某一天的全部基本指标
#根据股票代码合并数据
df = pd.merge(df_daily,df_basic,on='ts_code',how='inner')
smb, hml = cal_smb_hml(df)
data.append([date,smb,hml])
print(date,smb,hml)
'''20190102 0.012469284877061207 -0.0030494986538882407
20190103 -0.00208332807907743 0.015438815398099406
20190104 0.002506068642702431 3.796338743873237e-05......'''
df_tfm = pd.DataFrame(data, columns=['trade_date', 'SMB', 'HML'])
df_tfm['trade_date'] = pd.to_datetime(df_tfm.trade_date)
#将日期设为索引之前先转换为日期格式
df_tfm = df_tfm.set_index('trade_date')
df_tfm.to_csv('df_three_factor_model.csv')
df_tfm.head()
Out[11]:
SMB HML
trade_date
2019-01-02 0.012469 -0.003049
2019-01-03 -0.002083 0.015439
2019-01-04 0.002506 0.000038
2019-01-07 0.010351 -0.005321
2019-01-08 0.001529 0.000247
step2:挑选股票和基准指数
这里研究的个股是贵州茅台,选取的市场指数是国证A指。
maotai = pro.daily(ts_code='600519.SH',start_date='20190101',end_date='20200101')
gzA = pro.index_daily(ts_code='399317.SZ',start_date='20190101',end_date='20200101')
#用日期作为index并按照日期升序排序
stock_list = [maotai,gzA]
for stock in stock_list:
stock.index = pd.to_datetime(stock.trade_date)
df_stock = pd.concat([stock.pct_chg/100 for stock in stock_list],axis=1)
#concat下的两个数据框的index取并集合并
df_stock.columns = ['maotai','gzA']
df_stock = df_stock.sort_index(ascending=True)
df_stock.head()
Out[13]:
maotai gzA
trade_date
2019-01-02 0.015203 -0.009442
2019-01-03 -0.014992 -0.003467
2019-01-04 0.020339 0.024723
2019-01-07 0.005797 0.012932
2019-01-08 -0.001156 -0.001923
step3:合并数据并回归
df = pd.merge(df_stock,df_tfm,left_index=True,right_index=True,how='inner')
#left_index=True,right_index=True表示合并索引
df = df.fillna(0)
rf = 1.032**(1/360)-1#每一天的Rf
df.maotai=df.maotai-rf;df.gzA=df.gzA-rf
df.head()
Out[14]:
maotai gzA SMB HML
trade_date
2019-01-02 0.015115 -0.009530 0.012469 -0.003049
2019-01-03 -0.015080 -0.003555 -0.002083 0.015439
2019-01-04 0.020251 0.024635 0.002506 0.000038
2019-01-07 0.005709 0.012844 0.010351 -0.005321
2019-01-08 -0.001244 -0.002011 0.001529 0.000247
import statsmodels.api as sm
model=sm.OLS(df['maotai'],sm.add_constant(df[['gzA', 'SMB', 'HML']])).fit()
result=model.summary()
result
<class 'statsmodels.iolib.summary.Summary'>
"""
OLS Regression Results
==============================================================================
Dep. Variable: maotai R-squared: 0.565
Model: OLS Adj. R-squared: 0.559
Method: Least Squares F-statistic: 103.8
Date: Thu, 06 Feb 2020 Prob (F-statistic): 4.24e-43
Time: 21:35:56 Log-Likelihood: 721.01
No. Observations: 244 AIC: -1434.
Df Residuals: 240 BIC: -1420.
Df Model: 3
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const -0.0013 0.001 -1.413 0.159 -0.003 0.000
x1 0.9123 0.065 13.950 0.000 0.784 1.041
x2 -1.1363 0.114 -10.010 0.000 -1.360 -0.913
x3 -0.9687 0.142 -6.838 0.000 -1.248 -0.690
==============================================================================
Omnibus: 19.671 Durbin-Watson: 2.001
Prob(Omnibus): 0.000 Jarque-Bera (JB): 26.343
Skew: 0.561 Prob(JB): 1.90e-06
Kurtosis: 4.154 Cond. No. 175.
==============================================================================
model.params
Out[15]:
const -0.001258
x1 0.912341
x2 -1.136315
x3 -0.968734
dtype: float64
注意在fama的研究中是先按t-1时的数据分组再计算t时间的加权收益率,本文为了说明大致思路做了一个简化,未体现如此轮换。由上述结果可得茅台集团股票收益率的Fama-French三因素模型表达式为Ri=-0.0013+0.9123x1-1.1363x2-0.9687x3。
参考文献
https://zhuanlan.zhihu.com/p/55071842