用Python拆解Fama-French三因子模型:从理论到量化策略实战
当你在基金报告中看到"阿尔法收益"或"贝塔系数"时,是否好奇这些专业术语背后的数学本质?实际上,这些让普通投资者望而生畏的概念,本质上只是多元线性回归中的截距项和斜率系数。1993年由诺贝尔经济学奖得主Fama和French提出的三因子模型,将这种回归思维发挥到极致——用三个关键因子解释股票收益,成为量化投资领域的里程碑式框架。本文将用Python带你亲手实现这个经典模型,你会发现那些看似高深的金融理论,本质上都是可以亲手验证的数据科学问题。
1. 三因子模型核心原理拆解
三因子模型的创新之处在于,它突破了传统CAPM模型仅用市场风险解释收益的局限,发现了另外两个显著影响股票回报的因素:
- 市场风险溢价(Rmt):市场组合收益率减去无风险利率,反映系统性风险补偿
- 市值因子(SMB):小市值股票组合与大市值股票组合的收益率之差
- 价值因子(HML):高账面市值比股票与低账面市值比股票的收益率之差
模型数学表达式为:
E(Ri) - Rf = α + β1(Rmt) + β2(SMB) + β3(HML) + ε其中α就是传说中的"阿尔法收益",而β系数则衡量股票对各因子的敏感程度。这个看似简单的线性模型,在实证研究中展现出惊人的解释力——Fama和French发现它能解释70%-90%的股票收益差异。
关键理解误区澄清:
- 阿尔法≠超额收益:α是剔除三因子影响后的"纯净"超额收益
- 贝塔系数具有时变性:不同时期回归得到的β可能差异显著
- 因子暴露≠因子收益:β反映敏感性,因子溢价(如SMB)才是实际收益
2. 数据获取与预处理实战
2.1 因子数据获取方案对比
| 数据源 | 更新频率 | 历史深度 | 获取方式 | 适用场景 |
|---|---|---|---|---|
| 央财金融学院 | 不定期 | 10年+ | CSV下载 | 学术研究 |
| Wind终端 | 每日 | 20年+ | API接口 | 专业机构 |
| Baostock | 每日 | 5年 | Python库 | 个人开发者 |
| 聚宽量化平台 | 实时 | 15年 | 在线平台 | 策略回测 |
我们使用Baostock获取市场数据,配合央财的因子数据进行演示:
# 安装必要库 !pip install baostock statsmodels seaborn import baostock as bs import pandas as pd import numpy as np import statsmodels.api as sm # 登录Baostock系统 lg = bs.login()2.2 数据清洗关键步骤
- 日期对齐:确保股票数据与因子数据时间范围完全匹配
- 收益率计算:使用对数收益率避免简单收益率的链式偏差
- 异常值处理:3σ原则剔除极端值,防止回归结果失真
# 获取中国平安2022年日线数据 rs = bs.query_history_k_data_plus("sh.601318", "date,close", start_date='2022-01-01', end_date='2022-12-31', frequency="d") data = rs.get_data() data['close'] = data['close'].astype(float) data['return'] = np.log(data['close']/data['close'].shift(1))3. 模型构建与结果解读
3.1 回归分析完整流程
# 合并因子数据 full_data = pd.merge(factors, data, on='date') # 构建回归模型 X = sm.add_constant(full_data[['mkt_rf', 'smb', 'hml']]) model = sm.OLS(full_data['return'], X).fit() # 输出结果摘要 print(model.summary())典型回归结果输出示例:
OLS Regression Results ============================================================================== Dep. Variable: return R-squared: 0.642 Model: OLS Adj. R-squared: 0.631 Method: Least Squares F-statistic: 58.37 Date: Thu, 01 Jun 2023 Prob (F-statistic): 3.21e-25 Time: 14:30:45 Log-Likelihood: 347.21 No. Observations: 242 AIC: -686.4 Df Residuals: 238 BIC: -672.9 Df Model: 3 Covariance Type: nonrobust ============================================================================== coef std err t P>|t| [0.025 0.975] ------------------------------------------------------------------------------ const 0.0002 0.000 0.718 0.473 -0.000 0.001 mkt_rf 0.8914 0.072 12.305 0.000 0.749 1.034 smb -0.4271 0.132 -3.241 0.001 -0.687 -0.168 hml 0.3568 0.109 3.266 0.001 0.141 0.572 ==============================================================================3.2 关键指标解读指南
- 阿尔法(α):const项系数,本例0.0002且不显著(p=0.473),说明无显著超额收益
- 市场贝塔:0.8914表示股价波动小于市场整体
- SMB系数:-0.4271说明具有大盘股特征
- HML系数:0.3568显示偏向价值型股票
注意:当因子之间存在多重共线性时(如VIF>10),需要采用岭回归等改进方法
4. 多股票分析与策略构建
4.1 批量处理函数设计
def analyze_stock(stock_code, start_date, end_date): # 获取股票数据 rs = bs.query_history_k_data_plus(stock_code, "date,close", start_date=start_date, end_date=end_date) stock_data = rs.get_data() # 计算收益率 stock_data['close'] = stock_data['close'].astype(float) stock_data['return'] = np.log(stock_data['close']/stock_data['close'].shift(1)) # 合并因子数据 merged = pd.merge(factors, stock_data, on='date') # 回归分析 X = sm.add_constant(merged[['mkt_rf','smb','hml']]) model = sm.OLS(merged['return'], X).fit() return { 'alpha': model.params['const'], 'beta': model.params['mkt_rf'], 'smb_beta': model.params['smb'], 'hml_beta': model.params['hml'], 'rsquared': model.rsquared }4.2 组合分析实战案例
我们对比五只不同风格股票的表现:
| 股票代码 | 名称 | α值 | 市场β | SMBβ | HMLβ | R² |
|---|---|---|---|---|---|---|
| 600519 | 贵州茅台 | 0.0003 | 0.62 | -0.31 | 0.28 | 0.51 |
| 601318 | 中国平安 | 0.0001 | 0.89 | -0.43 | 0.36 | 0.64 |
| 600036 | 招商银行 | -0.0001 | 1.12 | -0.52 | 0.41 | 0.68 |
| 300750 | 宁德时代 | 0.0004 | 1.45 | 0.38 | -0.22 | 0.59 |
| 000858 | 五粮液 | 0.0002 | 0.78 | -0.25 | 0.33 | 0.54 |
从结果可以看出:
- 宁德时代表现出明显的高β、小盘股(正SMBβ)和成长型(负HMLβ)特征
- 传统金融股(平安、招行)具有显著的大盘价值属性
- 消费龙头(茅台、五粮液)市场敏感性较低
4.3 简单策略思路
基于因子暴露的特征,可以设计如下策略框架:
因子择时策略:
- 当小盘股溢价显著时(SMB因子收益为正),增配高SMBβ股票
- 当价值风格占优时(HML因子收益为正),选择高HMLβ组合
阿尔法增强组合:
# 筛选α显著为正的股票 alpha_stocks = [code for code in results if results[code]['alpha'] > 0 and results[code]['alpha_p'] < 0.05]风险平价配置:
# 根据β系数反向配置权重 total_beta = sum(1/abs(results[code]['beta']) for code in selected_codes) weights = {code: (1/abs(results[code]['beta']))/total_beta for code in selected_codes}
在实际回测中,我发现三因子模型对消费和金融板块的解释力较强(R²通常>0.6),但对科技股的解释度相对较低,这时可能需要引入动量因子或行业因子进行补充。另一个实用技巧是采用滚动回归(如60交易日窗口)来捕捉因子暴露的动态变化,这对捕捉风格轮动特别有效。