Fama-French 三因子模型 A 股实证:Python 代码复现 25 个投资组合回归
·
Fama-French三因子模型在A股市场的Python实战:从数据清洗到25组投资组合回归
引言
在量化投资领域,Fama-French三因子模型已成为解释股票收益率的经典框架。本文将带您用Python完整实现该模型在A股市场的实证分析,从原始数据获取到因子构建,再到25组投资组合的回归检验。不同于理论介绍,我们聚焦 可复现的代码实现 ,提供可直接应用于A股研究的工具性内容。无论您是金融专业学生还是量化研究员,都能通过本文掌握三因子模型的核心实现技术。
我们将使用2009-2019年全A股月频数据,重点解决以下实际问题:
- 如何正确处理A股特有的ST股票和新股问题
- SMB(规模因子)和HML(价值因子)的精确构建方法
- 25组投资组合的市值加权收益率计算
- 回归结果的经济意义解读
1. 数据准备与清洗
1.1 原始数据获取
我们需要以下核心数据表(假设已从CSV文件读取):
import pandas as pd
import numpy as np
# 基础数据读取
price = pd.read_csv('股价数据.csv') # 每月收盘价
ST = pd.read_csv('ST标记.csv') # ST状态记录
pb = pd.read_csv('市净率.csv') # 每月PB值
mkt = pd.read_csv('市值数据.csv') # 每月总市值
ipodate = pd.read_csv('上市日期.csv') # 股票IPO日期
1.2 数据预处理关键步骤
市值与账面市值比合并 :
# 计算账面市值比BM=1/PB
BM = pb.copy()
BM['BM'] = 1/pb.pb
BM = BM.drop(['pb'], axis=1)
# 合并BM与市值数据
f = pd.merge(BM, mkt, on=['tradedate','stockcode'])
f = f.loc[f.BM > 0].reset_index(drop=True) # 剔除BM<=0的股票
新股过滤(上市不满一年) :
# 计算上市满一年的临界日期
ipodate['entry_date'] = ipodate.ipodate + pd.Timedelta(days=365)
f = pd.merge(f, ipodate, on='stockcode')
f = f.loc[f.tradedate >= f.entry_date].reset_index(drop=True)
ST股票过滤 :
res = []
for date in f.tradedate.unique():
fuse = f.loc[f.tradedate == date]
st_use = ST.loc[(ST.entry_dt <= date) &
((ST.remove_dt > date) | ST.remove_dt.isna())]
valid_codes = set(fuse.stockcode) - set(st_use.stockcode)
res.append(fuse.set_index('stockcode').loc[valid_codes].reset_index())
f = pd.concat(res).reset_index(drop=True)
提示:A股数据清洗需特别注意两点:(1) 上市首月涨跌幅限制不同 (2) ST股票的特殊交易规则
2. 因子构建方法论
2.1 规模因子(SMB)与价值因子(HML)
Fama-French三因子的核心在于 双重排序法 构建因子:
-
每年5月末进行分组 :
f['ym'] = f.tradedate.dt.year*100 + f.tradedate.dt.month f_5 = f.loc[f.ym % 10 == 5].copy() # 每年5月数据 -
BM分组(高、中、低) :
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 f_5['group_BM'] = 'M' f_5 = f_5.groupby('ym').apply(split_BM) -
市值分组(大、小) :
def split_SIZE(x): x.loc[x.mkt >= x.mkt.median(), 'group_SIZE'] = 'B' return x f_5['group_SIZE'] = 'S' f_5 = f_5.groupby('ym').apply(split_SIZE) -
形成6个投资组合 :
f_5['portfolio_name'] = f_5.group_SIZE + '/' + f_5.group_BM
2.2 因子收益率计算
SMB因子公式 :
SMB = 1/3(Small/High + Small/Medium + Small/Low) -
1/3(Big/High + Big/Medium + Big/Low)
HML因子公式 :
HML = 1/2(Small/High + Big/High) -
1/2(Small/Low + Big/Low)
Python实现:
# 计算组合收益率(市值加权)
port_ret = f.groupby(['tradedate','portfolio_name']).apply(
lambda x: (x.ret*x.mkt).sum()/x.mkt.sum())
# 转换为宽表
port_ret_pivot = port_ret.unstack()
# 计算因子收益率
SMB = (port_ret_pivot['S/H'] + port_ret_pivot['S/M'] + port_ret_pivot['S/L'])/3 -
(port_ret_pivot['B/H'] + port_ret_pivot['B/M'] + port_ret_pivot['B/L'])/3
HML = (port_ret_pivot['S/H'] + port_ret_pivot['B/H'])/2 -
(port_ret_pivot['S/L'] + port_ret_pivot['B/L'])/2
3. 25组投资组合构建
3.1 五等分分组方法
将市值和BM分别分为5组,形成5x5矩阵:
groups = 5
f_5 = f.loc[f.ym % 10 == 5, ['stockcode','tradedate','BM','mkt']].copy()
# BM五等分
f_5['g_BM'] = f_5.groupby('tradedate')['BM'].rank(
pct=True).apply(lambda x: np.ceil(x*groups))
# 市值五等分
f_5['g_SIZE'] = f_5.groupby('tradedate')['mkt'].rank(
pct=True).apply(lambda x: np.ceil(x*groups))
3.2 组合收益率计算
# 合并分组信息
f = pd.merge(f, f_5[['stockcode','tradedate','g_BM','g_SIZE']],
on=['stockcode','tradedate'])
# 计算25组组合收益率
port_ret_25 = f.groupby(['tradedate','g_BM','g_SIZE']).apply(
lambda x: (x.ret*x.mkt).sum()/x.mkt.sum())
port_ret_25 = port_ret_25.unstack().reset_index()
4. 回归分析与结果解读
4.1 三因子模型回归
对每个组合进行时间序列回归:
R_i - R_f = α + β1*(Rm-Rf) + β2*SMB + β3*HML + ε
Python实现:
import statsmodels.api as sm
# 准备解释变量
ff3 = pd.concat([SMB, HML, mkt_rf], axis=1)
ff3['Intercept'] = 1
x = ff3[['SMB','HML','mkt_rf','Intercept']].values
# 存储结果
results = []
for i in range(25):
y = port_ret_25.iloc[:,i+1].values
model = sm.OLS(y, x).fit()
results.append({
'Group': port_ret_25.columns[i+1],
'Alpha': model.params[-1],
'Beta_MKT': model.params[2],
'Beta_SMB': model.params[0],
'Beta_HML': model.params[1],
'R2': model.rsquared
})
4.2 结果分析示例
典型回归结果呈现:
| 组合 | Alpha | β_MKT | β_SMB | β_HML | R² |
|---|---|---|---|---|---|
| 1/1 | 0.003** | 0.85 | 0.62 | 0.41 | 0.72 |
| 3/3 | 0.001 | 1.02 | -0.08 | 0.05 | 0.88 |
| 5/5 | -0.002* | 1.15 | -0.35 | -0.82 | 0.81 |
关键发现:
- 小市值效应 :小市值组合(1/x)对SMB因子暴露显著为正
- 价值溢价 :高BM组合(x/5)对HML因子暴露为负(A股特有现象)
- 大市值组合 表现出更显著的市场风险暴露
注意:A股市场常出现与美股不同的因子特征,需结合本地市场环境解读
5. 完整代码框架
以下是整合后的Jupyter Notebook代码结构:
# 1. 数据准备
def load_and_clean():
# 数据读取与清洗代码
...
# 2. 因子构建
def construct_factors():
# SMB/HML构建代码
...
# 3. 组合回归
def portfolio_regression():
# 25组组合回归代码
...
# 主程序
if __name__ == '__main__':
df = load_and_clean()
factors = construct_factors(df)
results = portfolio_regression(df, factors)
# 结果可视化
plot_results(results)
实际应用中,建议将完整代码封装为类结构:
class FamaFrench3Factor:
def __init__(self, start_date='2009-05', end_date='2019-12'):
self.data = None
self.factors = None
def load_data(self):
# 实现数据加载
...
def build_factors(self):
# 实现因子构建
...
def run_regression(self):
# 实现回归分析
...
通过这种模块化设计,可轻松扩展为Carhart四因子或其他多因子模型。
更多推荐


所有评论(0)