别再死记硬背Fama-French模型了!用Python实战带你搞懂因子投资的核心逻辑
用Python实战拆解Fama-French模型:从数据获取到策略回测的全流程指南
当我在第一次接触Fama-French三因子模型时,面对那些晦涩的学术论文和复杂的数学公式,最大的困惑是:这些理论到底如何转化为可执行的量化策略?本文将通过Python代码,带你一步步实现从数据获取、因子构建到策略回测的完整流程,让你真正掌握因子投资的核心逻辑。
1. 环境准备与数据获取
在开始因子建模前,我们需要搭建Python分析环境。推荐使用Anaconda创建独立环境:
conda create -n factor_investing python=3.8
conda activate factor_investing
pip install pandas numpy statsmodels matplotlib tushare akshare
数据源选择是因子投资的第一步。对于A股市场,Tushare和AKShare是两个常用的免费数据接口。以下是使用Tushare获取股票基础数据的示例:
import tushare as ts
pro = ts.pro_api('你的token')
# 获取全A股列表
stock_list = pro.stock_basic(exchange='', list_status='L')
# 获取日线行情
df_daily = pro.daily(ts_code='600519.SH', start_date='20200101', end_date='20221231')
提示:在实际应用中,建议将获取的数据存储到本地数据库(如SQLite)中,避免重复请求接口。
完整的因子分析需要以下几类数据:
- 价格数据:日/周/月收益率
- 财务数据:市值、账面市值比(BM)、盈利指标等
- 宏观经济数据:无风险利率、市场收益率
我们可以用Pandas构建统一的数据处理管道:
def clean_finance_data(raw_df):
"""财务数据清洗函数"""
df = raw_df.copy()
# 处理缺失值
df = df[df['total_mv'].notna()]
# 计算BM比率
df['bm_ratio'] = df['total_share'] * df['navps'] / df['total_mv']
return df
2. 因子构建与组合排序
Fama-French模型的核心在于三个因子:市场因子(MKT)、规模因子(SMB)和价值因子(HML)。让我们看看如何用Python构建这些因子。
2.1 市值因子(SMB)构建
市值因子通过做多小市值股票、做空大市值股票构建。具体步骤如下:
- 每月末根据流通市值将股票分为三组:小(S)、中(M)、大(B)
- 计算S组和B组的等权平均收益率
- SMB = (S组收益率 - B组收益率)
def build_smb_factor(data):
"""构建SMB因子"""
# 按市值三分位分组
data['size_group'] = data.groupby('trade_date')['total_mv'].transform(
lambda x: pd.qcut(x, 3, labels=['S', 'M', 'B']))
# 计算各组收益率
group_ret = data.groupby(['trade_date', 'size_group'])['ret'].mean().unstack()
smb = group_ret['S'] - group_ret['B']
return smb
2.2 价值因子(HML)构建
价值因子通过做高BM股票、做空低BM股票构建:
- 每月末根据BM比率将股票分为三组:高(H)、中(M)、低(L)
- 计算H组和L组的等权平均收益率
- HML = (H组收益率 - L组收益率)
def build_hml_factor(data):
"""构建HML因子"""
# 排除BM为负的股票
valid_data = data[data['bm_ratio'] > 0].copy()
# 按BM三分位分组
valid_data['bm_group'] = valid_data.groupby('trade_date')['bm_ratio'].transform(
lambda x: pd.qcut(x, 3, labels=['L', 'M', 'H']))
# 计算各组收益率
group_ret = valid_data.groupby(['trade_date', 'bm_group'])['ret'].mean().unstack()
hml = group_ret['H'] - group_ret['L']
return hml
2.3 双重排序与组合构建
为了提高因子纯度,实践中常采用双重排序法。以下代码展示如何同时按市值和BM进行分组:
def double_sort_portfolio(data, var1, var2, n_groups=3):
"""双重排序构建投资组合"""
# 第一次排序
data['group1'] = data.groupby('trade_date')[var1].transform(
lambda x: pd.qcut(x, n_groups, labels=False))
# 第二次排序
data['group2'] = data.groupby(['trade_date', 'group1'])[var2].transform(
lambda x: pd.qcut(x, n_groups, labels=False))
# 计算组合收益率
port_ret = data.groupby(['trade_date', 'group1', 'group2'])['ret'].mean()
return port_ret.unstack().unstack()
3. 模型检验与统计分析
构建完因子后,我们需要检验这些因子是否真的能解释股票收益。Fama-MacBeth回归是常用的检验方法。
3.1 时间序列回归
首先对每只股票进行时间序列回归,估计因子暴露:
import statsmodels.api as sm
def time_series_regression(stock_ret, factors):
"""单只股票的时间序列回归"""
X = sm.add_constant(factors)
model = sm.OLS(stock_ret, X)
results = model.fit()
return results.params
3.2 Fama-MacBeth回归
接着进行两步Fama-MacBeth回归:
def fama_macbeth_regression(stock_rets, factors):
"""Fama-MacBeth回归"""
# 第一步:时间序列回归获取因子暴露
betas = stock_rets.apply(lambda x: time_series_regression(x, factors))
# 第二步:截面回归
lambda_vals = []
for date in stock_rets.index:
X = betas.T
y = stock_rets.loc[date]
model = sm.OLS(y, X)
results = model.fit()
lambda_vals.append(results.params)
# 计算因子溢价
lambda_df = pd.DataFrame(lambda_vals, index=stock_rets.index)
return lambda_df.mean(), lambda_df.std() / np.sqrt(len(lambda_df))
3.3 Newey-West调整
由于金融数据常存在自相关性,我们需要使用Newey-West调整标准误:
def newey_west_adjustment(residuals, max_lags=3):
"""Newey-West标准误调整"""
n = len(residuals)
X = np.column_stack([np.ones(n), residuals])
# 计算白噪声方差
sigma = np.dot(residuals, residuals) / n
# 计算自相关项
for lag in range(1, max_lags+1):
gamma = np.dot(residuals[lag:], residuals[:-lag]) / n
sigma += 2 * (1 - lag/(max_lags+1)) * gamma
# 计算调整后的协方差矩阵
cov = np.linalg.inv(X.T @ X) * sigma
return np.sqrt(np.diag(cov))
4. 策略回测与绩效评估
最后,我们需要评估基于这些因子的投资策略表现。
4.1 多因子组合构建
结合多个因子信号构建投资组合:
def build_multifactor_portfolio(data, factors):
"""构建多因子组合"""
# 标准化因子值
z_scores = data.groupby('trade_date')[factors].transform(
lambda x: (x - x.mean()) / x.std())
# 计算综合得分
data['composite_score'] = z_scores.mean(axis=1)
# 按得分分组
data['portfolio'] = data.groupby('trade_date')['composite_score'].transform(
lambda x: pd.qcut(x, 5, labels=False))
# 计算组合收益率
port_ret = data.groupby(['trade_date', 'portfolio'])['ret'].mean().unstack()
return port_ret
4.2 绩效评估指标
计算常见的绩效指标:
def performance_metrics(returns, rf=0.02/12):
"""计算策略绩效指标"""
excess_ret = returns - rf
stats = {
'年化收益率': returns.mean() * 12,
'年化波动率': returns.std() * np.sqrt(12),
'夏普比率': excess_ret.mean() / excess_ret.std() * np.sqrt(12),
'最大回撤': (returns.cumsum() - returns.cumsum().cummax()).min()
}
return pd.Series(stats)
4.3 可视化分析
使用Matplotlib进行结果可视化:
import matplotlib.pyplot as plt
def plot_cumulative_returns(returns, title):
"""绘制累计收益率曲线"""
cum_ret = (1 + returns).cumprod()
plt.figure(figsize=(10, 6))
cum_ret.plot()
plt.title(title)
plt.ylabel('Cumulative Return')
plt.grid(True)
plt.show()
5. 实战中的关键问题与解决方案
在实际应用中,我发现有几个关键问题需要特别注意:
因子衰减问题:许多传统因子(如市值因子)在A股市场的有效性正在下降。解决方案是结合更多元化的因子,如质量因子、动量因子等。
数据频率选择:虽然日频数据包含更多信息,但月频数据通常能获得更稳健的结果。我的经验是从月频开始,验证因子有效性后再考虑更高频率。
参数过拟合:避免在因子构建和组合优化中使用过多参数。一个实用的方法是保留20%的数据作为样本外测试集。
交易成本影响:高频调仓会导致交易成本侵蚀收益。可以通过以下方式降低影响:
- 延长调仓周期(如季度调仓)
- 设置调仓阈值(如权重变化超过5%才调整)
- 使用换手率约束优化
def portfolio_optimization_with_tcost(expected_ret, cov_matrix, current_weights, tcost=0.001):
"""考虑交易成本的组合优化"""
from cvxpy import Variable, Minimize, quad_form, sum as cvx_sum
n = len(expected_ret)
w = Variable(n)
# 目标函数:风险调整后收益 - 交易成本
ret = expected_ret.T @ w
risk = quad_form(w, cov_matrix)
turnover = cvx_sum(abs(w - current_weights)) * tcost
objective = Maximize(ret - 0.5 * risk - turnover)
# 约束条件
constraints = [cvx_sum(w) == 1, w >= 0]
prob = Problem(objective, constraints)
prob.solve()
return np.array(w.value).flatten()
通过完整的Python实现流程,我们不仅理解了Fama-French模型的数学原理,更重要的是掌握了将其转化为实际投资策略的方法论。记住,因子投资不是简单的套用公式,而是需要不断验证、迭代和创新的过程。
更多推荐


所有评论(0)