(二十六)Fama-French三因素模型及应用

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值