Fama-Macbeth回归及因子统计

引言

本文介绍的因子统计方法基于1973年Fama和Macbeth为验证CAPM模型而提出的Fama-Macbeth回归,该模型现如今被广泛用被广泛用于计量经济学的panel data分析,而在金融领域在用于多因子模型的回归检验,用于估计各类模型中的因子暴露和因子收益(风险溢价)。

Fama-Macbeth与传统的截面回归类似,本质上也与是一个两阶段回归,不同的是它用了巧妙的方法解决了截面相关性的问题,从而得出更加无偏,相合的估计。

时间序列回归

Fama-Macbeth模型与传统截面回归相同,第一步都是做时间序列回归。在因子分析框架中,时间序列回归是为了获得个股在因子上的暴露。如果模型中的因子是 portfolio returns(即使用投资组合收益率作为因子,例如Fama-French三因子模型中的SMB,HML和市场因子),那么可以通过时间序列回归(time-series regression)来分析E[Ri]E[R_i]E[Ri​]和βi\beta_iβi​在截面上的关系。(本文举例的因子都是portfolio returns)

令ftf_tft​为因子组合在t期的收益率,RitR_{it}Rit​为个股iii在t期的收益率,用ftf_tft​对每只股票的RitR_{it}Rit​回归,即可得到每支股票的全样本因子暴露βi\beta_iβi​。
Rit=αi+βift+εit,t=1,2,...,T∀iR_{it}=\alpha_i+\beta_if_t+\varepsilon_{it},t=1,2,...,T \forall i Rit​=αi​+βi​ft​+εit​,t=1,2,...,T∀i
也可滚动计算某个时间段的因子暴露βit\beta_{it}βit​,体现个股随市场的变化设置时间段长度为periodperiodperiod
Rik=αi+βitfk+εik,k=t−period,2,...,t∀iR_{ik}=\alpha_i+\beta_{it}f_k+\varepsilon_{ik},k=t-period,2,...,t \forall i Rik​=αi​+βit​fk​+εik​,k=t−period,2,...,t∀i

截面回归

截面回归的第一步就是通过时间序列回归得到个股暴露,与Fama-Macbeth回归相同,第二步回归体现了传统截面回归和Fama-Macbeth回归最大的不同

对时序回归中回归式在时间序列上取均值,在E[ε]=0E[\varepsilon]=0E[ε]=0的假设下可以得出:
E[Ri]=αi+βiE[f]E[R_{i}]=\alpha_i+\beta_iE[f] E[Ri​]=αi​+βi​E[f]
上式正是个股的期望收益与因子暴露在截面上的关系,截距αi\alpha_iαi​为个股的错误定价

那么便可通过截面回归找到因子的期望收益率E[f]E[f]E[f],方法是最小化个股定价错误αi\alpha_iαi​的平方和。对个股的的收益在时序上取均值得到个股期望收益E[Ri]E[R_i]E[Ri​],用全样本的个股因子暴露对个股期望收益做无截距回归。
E[Ri]=βiλ+αiE[R_{i}]=\beta_i\lambda+\alpha_i E[Ri​]=βi​λ+αi​
回归残差αi\alpha_iαi​为个股的错误定价,λ\lambdaλ为因子的期望收益率。

截面回归最大的缺陷在于忽略了截面上的残差相关性,使得OLS给出的标准误存在巨大的低估。

Fama-Macbeth回归

与截面回归相同,第一步都是通过时间序列回归得到因子暴露值,不同的是,第二步中,Fama-Macbeth在每个t上都做了一次无截距截面回归,:
Rit=βiλt+αit,i=1,2,...,N∀tR_{it}=\beta_i\lambda_t+\alpha_{it},i=1,2,...,N \forall t Rit​=βi​λt​+αit​,i=1,2,...,N∀t
上式中的βi\beta_iβi​为全样本β\betaβ,当然若使用滚动回归数据,也可以在不同截面的回归上使用对应时期的βi,t\beta_{i,t}βi,t​

Fama-Macbeth回归相当于在每个t上做一次独立的截面回归,这T次回归的参数取均值作为回归的估计值:
λ^=1T∑t=1Tλ^t,αi^=1T∑t=1Tα^it\hat{\lambda}=\frac{1}{T} \sum_{t=1}^{T} \hat{\lambda}_{t},\hat{\alpha_i}=\frac{1}{T} \sum_{t=1}^{T} \hat{\alpha}_{it} λ^=T1​t=1∑T​λ^t​,αi​^​=T1​t=1∑T​α^it​
上述方法的巧妙之处在于它把 T 期的回归结果当作 T 个独立的样本。参数的 standard errors 刻画的是样本统计量在不同样本间是如何变化的。在传统的截面回归中,我们只进行一次回归,得到λ\lambdaλ和αi\alpha_iαi​的一个样本估计。而在 Fama-MacBeth 截面回归中,我们把T期样本点独立处理,得到 T 个λ\lambdaλ和αi\alpha_iαi​的样本估计。

若使用全样本因子暴露βi\beta_iβi​进行估计,截面回归和Fama-Macbeth的估计结果相同,当使用滚动窗口进行估计时(Fama and MacBeth (1973)中作者使用了滚动窗口),截面回归和Fama-Macbeth回归会得到完全不同的估计结果。

Fama-Macbeth回归很好的解决了截面相关性的问题,但对于时间序列上的相关性仍然无力。

因子统计

Fama-Macbeth回归为本文所讲的因子统计框架提供了大量参数,包括每次截面回归的斜率λt\lambda_tλt​和每次回归系数的t值tvaluettvalue_ttvaluet​

图中的betatbeta_tbetat​就是每个截面上的λt\lambda_tλt​

Python3代码

本文将所有时序回归,截面回归和Fama-macbeth回归都封装在一个类里,方便调用。因为要进行多次的回归,最多N*T次,故没有使用第三方库,而是用OLS的矩阵解析式直接计算得到回归参数,测试出速度大概能比第三方库快3~4倍。代码已经尽笔者所能优化到了最快的速度,欢迎各位大佬搬运测试。

初始化
'''
@Time    : 2020/8/5 13:33
@Author  : hjz
@Email   : acejasonhuang@163.com'''
class Fama_regression:def __init__(self, return_data, factor_data, frequency='d'): # return_data:T*N factor_data:T*1 frequency='d' , 'm' ,'y'self.T, self.N = return_data.shape[0], return_data.shape[1]self.stock = return_data.columns #股票池self.time = return_data.index   #时间self.return_data = return_data  # 股票收益率矩阵self.factor_data = factor_data  # 因子收益率序列if frequency=='d':              #频率(日,月,年)self.time_period=250elif frequency=='m':self.time_period=12else:self.time_period=1
时间序列回归(全样本)
    def time_series_regression_all_sample(self):  # 全样本时间序列回归def time_series_regression(Y):Y = Y.valuesY[np.isnan(Y)] = 0X = self.factor_data.valuesconstant = np.array([[1]] * len(self.time))X = np.hstack((constant, X))beta = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(Y).T[1]  # OLS矩阵求解式return betaself.factor_loading_allsample = self.return_data.apply(time_series_regression)return
时间序列回归(滚动窗口)

若参数Forecast为true表示预测,会将当期因子暴露记录在下一次,则之后的fama-macbeth回归中,当期因子暴露与下期收益回归,验证因子对收益的预测能力

    def time_series_regression_rolling(self, period=22,Forecast = False):# 滚动时间窗口时间序列回归,# period表示滚动窗口的长度# Forecast为true表示预测,用于验证因子对收益的预测能力,即后面的fama-macbeth回归用当期暴露对下期收益回归,则在此函数中将当期暴露记录在下期# Forecast为False表示后面的fama-macbeth回归用当期暴露对当期收益回归,表示对当期收益的归因if period > len(self.time):exit("Period is too large")returndef time_series_regression(Y):if not hasattr(time_series_regression,'count'):time_series_regression.count=0time_series_regression.count+=1constant = np.array([[1]] * period)Y[np.isnan(Y)] = 0X = factor_data_temp.iloc[0:period, :].valuesX = np.hstack((constant, X))beta = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(Y).T[1]  # OLS矩阵求解式if time_series_regression.count==len(self.stock):factor_data_temp.drop(factor_data_temp.iloc[0, :].name, axis=0, inplace=True)time_series_regression.count = 0return betafactor_data_temp=self.factor_datarolling_data = self.return_data.rolling(window=period).apply(time_series_regression)if Forecast:rolling_data=rolling_data.iloc[period-1:-1]rolling_data.index = (self.time[period:])else:rolling_data=rolling_data.iloc[period-1:]self.df_factor_loading_rolling=rolling_datareturn

这一块的速度一直很慢,dataframe.rolling迭代效率太低,若各位大佬有更快的方法欢迎指教

截面回归
    def section_regression_without_intercept(self):  # 截面回归Er = self.return_data.apply(np.mean)N = len(Er)Y = np.array([Er.values.tolist()]).TX = np.array([self.factor_loading_allsample.values.tolist()]).Tbeta = (X.T.dot(Y)) / (X.T.dot(X))[0][0]epsilon = Y - beta * XT_test = beta / np.sqrt(epsilon.T.dot(epsilon) / (X.T.dot(X)) / (N - 1))[0][0]self.error_of_section_reg = pd.Series(epsilon.T[0], index=self.stock)self.beta_of_section_reg = betaself.tvalue_of_section_reg = T_testreturn
Fama-Macbeth回归

参数data_type表示使用全样本因子暴露还是滚动窗口因子暴露

    def fama_macbeth_regression_without_intercept(self,data_type='rolling'):  # Fama-macbeth回归#data_type为rolling(滚动回归数据),allsample(全样本回归数据)def section_regression_epsilion(Y):if data_type=='rolling':X = self.df_factor_loading_rolling.loc[Y.name]else:X = self.factor_loading_allsampleN = len(X)X = np.array([X.values.tolist()]).TY = np.array([Y.values.tolist()]).Tbeta = ((X.T.dot(Y)) / (X.T.dot(X)))[0][0]epsilon = Y - beta * Xepsilon = epsilon.T[0]return pd.Series(epsilon, index=self.stock)def section_regression_beta(Y):if data=='rolling':X = self.df_factor_loading_rolling.loc[Y.name]else:X = self.factor_loading_allsampleN = len(X)X = np.array([X.values.tolist()]).TY = np.array([Y.values.tolist()]).Tbeta = ((X.T.dot(Y)) / (X.T.dot(X)))[0][0]epsilon = Y - beta * XT_test = beta / np.sqrt(epsilon.T.dot(epsilon) / (X.T.dot(X)) / (N - 1))[0][0]return pd.Series([beta, T_test], index=['beta', 'tvalue'])time_list = self.df_factor_loading_rolling.indexreturn_data = self.return_data.loc[time_list]self.epsilon_mat = return_data.apply(section_regression_epsilion, axis=1)self.epsilon_mean = self.epsilon_mat.apply(np.mean)self.beta_tvalue = return_data.apply(section_regression_beta, axis=1)self.beta_fama, self.tvalue_fama = self.beta_tvalue['beta'], self.beta_tvalue['tvalue']return
因子计算及显示
 def compute_factor_characteristic(self):def autocor(X):if not hasattr(autocor,'last'):autocor.last=X.values.copy()return 0else:result=np.corrcoef(X.values,autocor.last)autocor.last=X.values.copy()return result[0][1]def IC_rank(X):R=self.return_data.loc[X.name]mat=pd.DataFrame([X,R]).Treturn mat.corr('spearman').values[0][1]self.autocor = self.df_factor_loading_rolling.apply(autocor, axis=1)[1:]self.IC_rank=self.df_factor_loading_rolling.apply(IC_rank,axis=1)self.IC_rank_mean=self.IC_rank.mean()self.IC_IR_rank=self.IC_rank.std()self.factor_return_annual=self.beta_fama.mean()*self.time_periodself.factor_vol_annual = self.beta_fama.std() * np.sqrt(self.time_period)self.factor_sharpe_ratio=self.factor_return_annual/self.factor_vol_annualself.factor_tvalue=self.beta_fama.mean()/(self.beta_fama.mean()*np.sqrt(len(self.beta_fama)))self.mean_tvalue=self.tvalue_fama.mean()self.mean_abs_tvalue=np.mean(np.abs(self.tvalue_fama))self.tvalue_morethan2=(self.tvalue_fama.abs()>2).sum()/len(self.tvalue_fama)def show_factor_characteristic(self):print("因子截面相关性: ",round(self.autocor.mean(),2))print("因子IC:",round(self.IC_rank_mean,2))print("因子IC_IR:", round(self.IC_IR_rank, 2))print("因子年化收益:", round(self.factor_return_annual, 2))print("因子年化波动率:", round(self.factor_vol_annual, 2))print("因子夏普比率: ",round(self.factor_sharpe_ratio, 2))print("因子t值: ", round(self.factor_tvalue, 2))print("平均t值: ", round(self.mean_tvalue, 2))print("平均绝对t值: ", round(self.mean_abs_tvalue, 2))print("绝对t值>2占比: ", round(self.tvalue_morethan2, 2))return
测试结果

本文选用沪深300成分股2020年的日数据对市场因子beta进行测试
输出结果:

因子截面相关性:  -0.04
因子IC: -0.0
因子IC_IR: 0.06
因子年化收益: 0.46
因子年化波动率: 0.25
因子夏普比率:  1.82
因子t值:  0.09
平均t值:  1.23
平均绝对t值:  9.93
绝对t值>2占比:  0.83

时间开销(单位秒):

时间开销(单位:秒):
时序回归全样本:  0.04852179628497339
时序回归全滚动窗口:  11.162792468957404
截面回归:  0.039669358541182476
Fama-Macbeth回归:  0.1491620773626554
因子统计计算:  1.4921370042123563

在滚动窗口时序回归上要花费大量时间,我测试过很多方法去替代dataframe.rolling,但结果都不理想,欢迎各位大佬提出改进检验。

参考

股票多因子模型的回归检验——石川
《长江证券-金融工程专题-覃川桃郑起-高频因子(三):高频因子研究框架》

初探多因子选股:基于Fama-Macbeth回归的因子分析框架 (附Python3代码)相关推荐

  1. 【交通建模】基于模型的自主交通仿真框架附matlab代码

    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信.

  2. python面板数据回归_Python中的Fama Macbeth回归(Pandas或Statsmodels)

    编辑:新建库 已存在可通过以下命令安装的更新库:pip install finance-byu 新的库包括Fama Macbeth回归实现,速度得到了提高,并且更新了Regtable类.新的图书馆还包 ...

  3. 基于TextRank算法的文本摘要(附Python代码)

    基于TextRank算法的文本摘要(附Python代码): https://www.jiqizhixin.com/articles/2018-12-28-18

  4. 智能窗帘传感器c语言程序,基于单片机的智能窗帘控制系统设计(附程序代码)

    基于单片机的智能窗帘控制系统设计(附程序代码)(论文18000字,程序代码) 摘要:二十一世纪初以来,科学技术不断发展,智能家居涌现于各家各户,人们越来越重视生活质量的提高.但是传统的手动开合窗帘耗时 ...

  5. 基于YOLOv4的目标检测系统(附MATLAB代码+GUI实现)

    摘要:本文介绍了一种MATLAB实现的目标检测系统代码,采用 YOLOv4 检测网络作为核心模型,用于训练和检测各种任务下的目标,并在GUI界面中对各种目标检测结果可视化.文章详细介绍了YOLOv4的 ...

  6. python数据分析及可视化(十七)聚宽(双均线分析、因子选股策略、多因子选股策略、均值回归理论、布林带策略、PEG策略、权重收益策略)

    聚宽 聚宽是一个做金融量化的网站,https://www.joinquant.com,登录注册,如果你写的文章.策略被别人采纳,增加积分,积分用于免费的回测时长.在我的策略,进入策略列表,里面有做好的 ...

  7. 初探多因子选股:多因子筛选与因子正交化

    多因子筛选与因子正交化 引言 在多因子研究框架中,如果已经检验出多个有效的因子,而在实际因子选股的过程中,各个有效的因子可能会相互影响,而高度相关的两个有效因子,即使都有不错的获取alpha的能力,但 ...

  8. 玩转树莓派---详解树莓派的系统烧录,基础使用及基于树莓派制作手势控制的小车(附详细代码及演示效果)

    目录 一.写在前面 二.系统实现: Author:qyan.li Date:2022.6.10 Topic:详解树莓派的使用及基于树莓派制作手势控制的小车 Reference:如何给树莓派安装操作系统 ...

  9. 【天文】基于matlab实现GPS卫星运动仿真附matlab代码

    1 内容介绍 基于matlab实现GPS卫星运动仿真 2 部分代码 %time是个时间参数利用它可以画出一个看起来旋转的地球 function DrawEarth(time) r=6400; j1=[ ...

最新文章

  1. 华为平板电脑_当5G遇上平板电脑,华为MatePad Pro 5G带来了什么?
  2. matlab与acess连接问题
  3. 原生JS和jQuery实现banner图滚动那些事
  4. flex与java间用json传输数据,如何在Java中使用flexjson通过@JSON注释控制序列化?
  5. IDEA 断点调试高级玩法 |debug高手必看!
  6. 转载:数据库表结构设计方法及原则
  7. 第21课 田忌赛马 《小学生C++趣味编程》
  8. Python编写抽奖式随机提问程序
  9. 素数c分解语言程序,PTA|《C语言程序设计实验与习题指导(第3版)》实验4-2-3 验证“哥德巴赫猜想” (20分)...
  10. EWF在win7_x86_x64系统中配置
  11. 【学习感受】初入老男孩Linux运维班的心得分享
  12. 下载新版火狐后无法同步书签_Ubuntu解决火狐浏览器无法同步书签的问题【推荐】...
  13. Essential Netty in Action 《Netty 实战(精髓)》
  14. 解决Chrome浏览器主页被hao123、360和2345篡改简单有效方法
  15. 小学生数量成为楼市风向标?
  16. 清除FreeIPA SSSD缓存
  17. 《人月神话》作者去世,我们都曾读过他的书
  18. 解决Word2019使用卡顿问题
  19. 百度AI攻略:人体关键点识别
  20. html5音乐播放器格式midi,HTML5 Audio時代的MIDI音樂文件播放

热门文章

  1. 拜托,有一个python画的生日蛋糕超酷的好吧~
  2. php声音转换工具,音乐格式转换工具(AudioRetoucher 5)
  3. 安全知识普及--总结什么是网络安全
  4. ros 与 sony ps3joy
  5. BIOS无法识别SSD,但希望用SSD启动系统的解决方法
  6. 金字塔的思维---先总后分与结论先行
  7. 解决GitHub中灰色文件夹问题
  8. 1.docker基础用法
  9. 魅族MX4安装Ubuntu Touch 16.04
  10. 解决Nginx 404 not found问题