文章目录
- 一、起源
- 二、构建因子
- 三、投资组合的净值归因
- 1. 市场因子
- 2. 规模因子
- 3.价值因子
- 4. 基于净值的归因方法
- 三、代码实现
一、起源
在多因子模型推出之前,CAPM模型被视为资产定价的第一标准。随着市场不断发展,发现了越来越多CAPM模型无法解释的现象,包括盈利市值比效应、小市值效应等等。但这些单一异象并未动摇CAPM模型的地位,直到Fama-French三因子模型出现,拉开多因子模型的序幕。Fama三因子模型在CAPM的基础上加入了价值和规模两个因子,提出了三因子模型。
E ( r i ) = r f + β i , M K T E ( r m − r f ) + β i , S M B E ( S M B ) + β i , H M L E ( H M L ) E(r_{i})=r_{f}+\beta_{i,MKT}E(r_m-r_f)+\beta_{i,SMB}E(SMB)+\beta_{i,HML}E(HML) E(ri)=rf+βi,MKTE(rm−rf)+βi,SMBE(SMB)+βi,HMLE(HML)
其中 E ( R i ) E(R_{i}) E(Ri)表示股票第i期的预期收益率, r f r_{f} rf是无风险收益率,为市场组合预期收益率, E ( S M B ) E(SMB) E(SMB)和 E ( H M L ) E(HML) E(HML)分别为规模因子(SMB)以及价值因子(HML)的预期收益率, β i , M K T \beta_{i,MKT} βi,MKT、 β i , S M B \beta_{i,SMB} βi,SMB和 β i , H M L \beta_{i,HML} βi,HML是个股在因子上的暴露。
通俗版解释:若该模型成立,那么个股的超额收益就能通过这三个因子预测出来,在选股时,只需要找到对应因子最大的股票,即可认为是“优质股”.
二、构建因子
选择BM和市值两个指标,进行独立双重排序。先按照市值将股票分为大市值组合小市值组,再按照BM的30%分位数和70%分位数分组,共6组,分别记为:S/H、S/M、S/L、B/H、B/M、B/L。再将每组的股票收益率按照市值加权得到6个投资组合,构建两个因子。
S M B = S H + S M + S L 3 − B H + B M + B L 3 SMB=\frac{SH+SM+SL}{3}-\frac{BH+BM+BL}{3} SMB=3SH+SM+SL−3BH+BM+BL
H M L = S H + B H 2 − S L + B L 2 HML=\frac{SH+BH}{2}-\frac{SL+BL}{2} HML=2SH+BH−2SL+BL
分析SMB和HML表达式可以看出,SMB是用三个小市值组合减去三个大市值组合,HML是用两个高BM组合减去两个低BM组合。
三、投资组合的净值归因
1. 市场因子
市场因子收益率为市场指数区间收益率减去同期的无风险收益率。
2. 规模因子
多头:对全 A 股市场的股票按照市值排序,取市值较小的 50%的股票等权重配置构成小盘风格组合。
空头:对全 A 股市场的股票按照市值排序,取市值较大的 50%的股票等权重配置构成大盘风格组合。
3.价值因子
多头:对全 A 股市场的股票按照市净率 PB(MRQ)和市值进行双重排序,选取市净率 PB(MRQ)较低的 30%的股票。根据市值中位数对该股票池分割,构成小市值低估值和大市值低估值组合。两个组合内部股票等权重配置,并且两个组合之间也等权重配置构成低估值组合。
空头:对全 A 股市场的股票按照市净率 PB(MRQ)和市值进行双重排序,选取市净率 PB(MRQ)较高的 30%的股票。根据市值中位数对该股票池分割,构成小市值高估值和大市值高估值组合。两个组合内部股票等权重配置,并且两个组合之间也等权配置重构成高估值组合。
4. 基于净值的归因方法
需提前确定的参数:
- 评估时间段:1M、3M、1Y、3Y、5Y
- 无风险收益率:一年定存利率、一年期国债收益率等
- 市场指数:沪深300指数、上证综指等
基于净值的多因子模型通过使用因子收益的时间序列对组合收益的时间序列进行线性回归运算,将组合的收益和风险分解在各因子上:
R p t − R F t = α + ∑ i = 0 n β i F i t + ϵ t R_{pt}-RF_{t}=\alpha+\sum_{i=0}^{n}\beta_{i}F_{it}+\epsilon_{t} Rpt−RFt=α+i=0∑nβiFit+ϵt
其中 R p t R_{pt} Rpt为组合在t时间段的收益率, R F t RF_t RFt为t时间段的无风险收益率, F i t F_{it} Fit为因子i在t时间段的收益率。 β i \beta_{i} βi为待拟合的系数。 α \alpha α为待拟合的截距项。 ϵ t \epsilon_{t} ϵt为t时间的残差项。
三、代码实现
本代码基于wind数据提供的API,需先下载WIND并开通API权限。
from WindPy import w
import pandas as pd
import statsmodels.api as sm# 连接Wind的API接口
w.start() # 默认命令超时时间为120秒,如需设置超时时间可以加入waitTime参数,例如waitTime=60,即设置命令超时时间为60秒
w.isconnected() # 判断WindPy是否已经登录成功# 构建因子
def fama_three_factor(startDate, endDate, rfree_code, index_code, fund_code):# 基金数据fund = w.wsd(fund_code, fields=['return_m'], beginTime=startDate, endTime=endDate, usedf=True, Period='M')[1]# 市场因子free_data = w.wsd(rfree_code, fields=['close'], beginTime=startDate, endTime=endDate, usedf=True, Period='M',Fill='Previous')[1]index = w.wsd(index_code, fields=['pct_chg'], beginTime=startDate, endTime=endDate, usedf=True, Period='M')[1]df = pd.merge(free_data, index, on=free_data.index, left_index=True)df['MKT'] = df['PCT_CHG'] - df['CLOSE']df['y'] = fund['RETURN_M'] - df['CLOSE']# 规模因子SMB_list = []HML_list = []date = df.index.tolist()# 遍历每个交易日市值大小for i in date:stock_list = w.wset("sectorconstituent", date=i, windcode=index_code, usedf=True)[1]stock = stock_list['wind_code'].tolist() # 获取指数成分股# 获取当前时点下,全部成分股收益率、市值returns1 = w.wss(stock, fields=['ev'], annualized=0, tradeDate=i, usedf=True)[1]returns2 = w.wss(stock, fields=['pb_mrq'], annualized=0, tradeDate=i, usedf=True)[1]returns = pd.merge(returns1, returns2, on=returns1.index,left_index=True,right_index=True)# 按照市值排序returns.sort_values(by=['EV'], ascending=True, inplace=True)# 取前50%作为Slist,取后50%作为BlistSlist = returns.head(int(len(returns) * 0.5)).index.tolist()Blist = returns.tail(int(len(returns) * 0.5)).index.tolist()# 计算SMBSmall = w.wsd(Slist, "pct_chg", i, i, "Period=M", usedf=True)[1]Big = w.wsd(Blist, "pct_chg", i, i, "Period=M", usedf=True)[1]SMB = Small.mean() - Big.mean()SMB_list.append(SMB.values[0])# 取选取市净率PB(MRQ)较低的30%的股票,市值中位数对该股票池分割returns.sort_values(by=['PB_MRQ'], ascending=True, inplace=True)# 取PB低的30%和PB高的30%Lowlist = returns.head(int(len(returns) * 0.3))Highlist = returns.tail(int(len(returns) * 0.3))# 按照市值继续分割:小市值低估值、大市值低估值Lowlist.sort_values(by=['EV'], ascending=True, inplace=True)Low_small_list = Lowlist.head(int(len(Lowlist) * 0.5)).index.tolist()Low_big_list = Lowlist.tail(int(len(Lowlist) * 0.5)).index.tolist()Highlist.sort_values(by=['EV'], ascending=True, inplace=True)High_small_list = Highlist.head(int(len(Highlist) * 0.5)).index.tolist()High_big_list = Highlist.tail(int(len(Highlist) * 0.5)).index.tolist()# 计算HMLHigh_small = w.wsd(High_small_list, "pct_chg", i, i, "Period=M", usedf=True)[1]High_big = w.wsd(High_big_list, "pct_chg", i, i, "Period=M", usedf=True)[1]Low_small = w.wsd(Low_small_list, "pct_chg", i, i, "Period=M", usedf=True)[1]Low_big = w.wsd(Low_big_list, "pct_chg", i, i, "Period=M", usedf=True)[1]HML = (High_small.mean() + High_big.mean())/2 - (Low_small.mean() + Low_big.mean())/2HML_list.append(HML.values[0])# 将规模因子合并至原数据集df['SMB'] = SMB_listdf['HML'] = HML_listdf = df[['y', 'MKT', 'SMB', 'HML']]return df# 计算beta
def cal_beta(df):# 添加常数项作为截距x = df[['MKT', 'SMB', 'HML']]y = df['y']X_with_intercept = sm.add_constant(x)# 构建模型并估计参数model = sm.OLS(y, X_with_intercept)results = model.fit().params# 输出贡献度print("市场因子贡献度%.4f" % (results['MKT']))print("规模因子贡献度%.4f" % (results['SMB']))print("价值因子贡献度%.4f" % (results['HML']))return results['MKT'], results['SMB'], results['HML']# 输入参数:开始时间、结束时间、无风险利率标准、市场指数、基金指数
startDate = '2023-01-01'
endDate = '2023-12-30'
rfree_code = "SHIBOR1M.IR"
index_code = "000300.SH"
fund_code = "167702.OF"
df = fama_three_factor(startDate, endDate, rfree_code, index_code, fund_code)
beta_MKT, beta_SMB, beta_HML = cal_beta(df)