欢迎加入专注于财经数据与量化投研的【数据科学实战】知识星球!在这里,您将获取持续更新的《财经数据宝典》和《量化投研宝典》,这两部宝典相辅相成,为您在量化投研道路上提供明确指引。 我们提供了精选的国内外量化投研的 180+ 篇高质量文章,并每日更新最新研究成果,涵盖策略开发、因子分析、风险管理等核心领域。 无论您是量化投资新手还是经验丰富的研究者,星球社区都能帮您少走弯路,事半功倍,共同探索数据驱动的投资世界!
引言
在量化金融领域,理解资产定价的驱动因素是投资决策的基础。从最初的资本资产定价模型(CAPM)到多因子模型,金融学者们不断探索能更好解释证券收益率的方法。本文将带领大家深入了解量化金融中的因子模型,从经典的 Fama-French 三因子模型到 Carhart 四因子模型,并探讨如何使用 Python 实现这些模型。
无论你是数据科学家、量化开发者还是金融分析师,掌握这些模型的原理和实现方法,都能帮助你更好地理解市场,优化投资组合,管理风险。
1. 资产定价模型的演进
1.1 CAPM 模型
资产定价的核心问题是:什么驱动了证券的收益?Sharpe、Lintner 和 Mossin 开发的资本资产定价模型(CAPM)提供了第一个严谨的框架,认为证券的预期收益是其对单一因子(市场风险溢价)敏感性的线性函数。
CAPM 模型的表达式为:
E[R_i] = R_f + β_i(E[R_m] - R_f)
其中:
然而,CAPM 模型在实证研究中存在许多问题。例如,它无法解释小市值股票和价值股票(高账面市值比)历史上获得的收益率往往高于其市场贝塔所预测的水平。
1.2 多因子模型的诞生
这些异常现象促使了多因子模型的发展,这些模型试图通过多个系统性风险因子的暴露来解释收益率。
2. Fama-French 三因子模型
2.1 数学公式
Eugene Fama 和 Kenneth French 通过增加两个因子来扩展 CAPM,以捕捉规模和价值异常。模型方程为:
R_{i,t} - R_{f,t} = α_i + β_{i,MKT}(R_{m,t} - R_{f,t}) + β_{i,SMB}SMB_t + β_{i,HML}HML_t + ε_{i,t}
其中:
- R_{i,t} - R_{f,t}:资产 i 相对于无风险利率的超额收益
- R_{m,t} - R_{f,t}:市场组合相对于无风险利率的超额收益(MKT)
- SMB_t(小市值减大市值):小市值股票组合的收益率减去大市值股票组合的收益率
- HML_t(高账面市值比减低账面市值比):高账面市值比(价值股)组合的收益率减去低账面市值比(成长股)组合的收益率
- α_i(詹森阿尔法):截距项,表示不能被因子解释的异常收益
- β_{i,MKT}、β_{i,SMB}、β_{i,HML}:因子载荷或敞口,表示资产收益率对各因子的敏感性
- ε_{i,t}:误差项(特有剩余收益),假设均值为零
2.2 因子背后的直觉
- 市场因子(MKT):捕捉广泛的市场风险。贝塔大于 1 表示波动性高于市场。
- 规模因子(SMB):代表小公司相对于大公司的历史超额表现,通常归因于较高的风险和较低的流动性。
- 价值因子(HML):代表价值股相对于成长股的历史超额表现。理论上是投资于困境(高账面市值比)公司的风险溢价。
2.3 Python 实现:估计因子载荷
下面我们将从 Ken French 的网站下载实际的因子数据,并用它来对样本资产(这里是苹果股票)进行回归分析。
import pandas as pd
import yfinance as yf
import statsmodels.api as sm
# -----------------------------
# 1. 从网站加载 Fama-French 因子数据
# -----------------------------
url = "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Research_Data_Factors_CSV.zip"
ff = pd.read_csv(url, skiprows=3)
# 当"Annual Factors"部分开始时停止
ff = ff[ff.iloc[:,0].str.strip().str.match(r"^\d{6}$", na=False)]
# 重命名和清理
ff.columns = ["Date", "Mkt-RF", "SMB"
, "HML", "RF"]
ff["Date"] = pd.to_datetime(ff["Date"], format="%Y%m") + pd.offsets.MonthEnd(0)
ff = ff.set_index("Date")
ff = ff.astype(float) / 100.0# 转换为小数
# -----------------------------
# 2. 下载资产数据
# -----------------------------
asset_ticker = "AAPL"
start_date = "2000-01-01"
asset_data = yf.download(asset_ticker, start=start_date)
# 选择调整后收盘价(如果可用),否则选择收盘价
if"Adj Close"in asset_data.columns:
asset_prices = asset_data["Adj Close"]
elif"Close"in asset_data.columns:
asset_prices = asset_data["Close"]
else:
raise KeyError(f"No 'Adj Close' or 'Close' in asset_data columns: {asset_data.columns}")
# 月度收益率
asset_returns = asset_prices.resample("M").last().pct_change().dropna()
asset_returns = pd.DataFrame(asset_returns)
asset_returns.columns = ["Asset_Return"]
# -----------------------------
# 3. 合并资产收益率和因子数据
# -----------------------------
# 强制两者都使用月末时间戳
asset_returns.index = asset_returns.index.to_period("M").to_timestamp("M")
ff.index = ff.index.to_period("M").to_timestamp("M")
merged_df = pd.merge(asset_returns, ff, how="inner", left_index=True, right_index=True)
if merged_df.empty:
raise ValueError(
f"No overlapping dates between asset returns ({asset_returns.index.min()} to {asset_returns.index.max()}) "
f"and FF factors ({ff.index.min()} to {ff.index.max()})."
)
# 计算超额收益
merged_df["Excess_Return"] = merged_df["Asset_Return"] - merged_df["RF"]
# -----------------------------
# 4. 运行 Fama-French 回归
# -----------------------------
X = merged_df[["Mkt-RF", "SMB", "HML"]]
X = sm.add_constant(X)
y = merged_df["Excess_Return"]
model = sm.OLS(y, X)
results = model.fit()
print(results.summary())
2.4 因子载荷的可视化
import matplotlib.pyplot as plt
# 绘制因子载荷(贝塔)
betas = results.params.drop('const')
std_errors = results.bse.drop('const')
fig, ax = plt.subplots(figsize=(10, 6))
betas.plot(kind='bar', yerr=std_errors, ax=ax, capsize=5, color=['blue', 'green', 'red'])
ax.axhline(0, color='black', linestyle='-', linewidth=0.5)
ax.set_ylabel('因子载荷 (Beta)')
ax.set_title('AAPL 的 Fama-French 三因子载荷及标准误差')
plt.xticks(rotation=45)
plt.show()
3. Carhart 四因子模型(加入动量因子)
Mark Carhart 通过添加动量因子扩展了 Fama-French 模型,基于这一广泛记录的异常现象:最近表现良好的股票(赢家)在短期内往往会继续表现良好,而表现不佳的股票(输家)则相反。
3.1 数学公式
R_{i,t} - R_{f,t} = α_i + β_{i,MKT}(R_{m,t} - R_{f,t}) + β_{i,SMB}SMB_t + β_{i,HML}HML_t + β_{i,MOM}MOM_t + ε_{i,t}
其中:
- MOM_t(动量):过去赢家组合的收益率减去过去输家组合的收益率。通常基于从 12 个月前到 1 个月前的收益构建(Jegadeesh 和 Titman,1993)。
3.2 Python 实现
# 1. 下载并清理动量因子(WML)数据
import requests, zipfile, io
mom_url = "https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/ftp/F-F_Momentum_Factor_CSV.zip"
response = requests.get(mom_url)
with zipfile.ZipFile(io.BytesIO(response.content)) as z:
with z.open(z.namelist()[0]) as f:
mom_df = pd.read_csv(f, skiprows=13, index_col=0, na_values=[-99.99, -999])
# 仅保留 YYYYMM 行
mom_df = mom_df[mom_df.index.astype(str).str.match(r'^\d{6}$')]
mom_df.index = pd.to_datetime(mom_df.index, format="%Y%m")
mom_df.index.name = "Date"
# 将百分比转换为小数,清理列名
mom_df = mom_df.astype(float) / 100
mom_df.columns = mom_df.columns.str.strip()
# 标准化列名
if"Mom"in mom_df.columns:
mom_df.rename(columns={"Mom": "MOM"}, inplace=True)
print("动量 DF 形状:", mom_df.shape)
print("动量列:", mom_df.columns.tolist())
# =====================================================
# 2. 与已合并的 Fama-French + 收益率数据集对齐
# =====================================================
# 将两个索引转换为 PeriodIndex(月度)
merged_df.index = merged_df.index.to_period("M")
mom_df.index = mom_df.index.to_period("M")
# 基于月度周期合并
carhart_df = pd.merge(
merged_df, mom_df, how="inner", left_index=True, right_index=True
)
print("合并后 Carhart DF 形状:", carhart_df.shape)
print("样本日期:", carhart_df.index[:5])
# =====================================================
# 3. 仅在有数据时运行 4 因子回归
# =====================================================
if carhart_df.shape[0] > 0:
Y_c = carhart_df['Excess_Return']
X_c = carhart_df[['Mkt-RF', 'SMB', 'HML', 'MOM']]
X_c = sm.add_constant(X_c)
model_carhart = sm.OLS(Y_c, X_c, missing="drop")
results_carhart = model_carhart.fit()
print(results_carhart.summary())
# 比较 R 平方
print(f"Fama-French 三因子 R 平方: {results.rsquared:.4f}")
print(f"Carhart 四因子 R 平方: {results_carhart.rsquared:.4f}")
else:
print("⚠️ 合并的 DF 和动量因子之间没有重叠的日期!")
这个比较显示 R² 有所增加,表明 MOM 因子解释了收益变化的额外部分。MOM 贝塔(β_{i, MOM})的显著性告诉我们资产是否具有显著的动量敞口。
4. 超越 HML 的价值信号
虽然 HML(账面市值比)是经典的价值因子,但量化分析师使用各种其他信号来捕捉公司的"便宜程度"或基本价值。一般构建原则是:价值 = (基本面指标) / (价格或市值)。
常用的价值信号包括:
- 现金流价格比(CF/P):经营现金流 / 市值(比收益更不容易受到会计操作的影响)
- 销售价格比(S/P)或企业价值销售比(EV/S):收入 / 市值 或 企业价值 / 收入,对于负收益公司很有用
- 企业价值与 EBITDA 比率(EV/EBITDA):行业中常用的指标,专注于核心运营盈利能力
4.1 价值因子构建实践
要在多空组合中创建类似 HML 的价值因子:
- 选择投资范围:选择一个投资范围(如标普 500 指数、所有美国股票)
- 信号计算:在特定时间点(如 6 月底)为每支股票计算所选的价值指标(如 E/P)
- 排序:根据这个价值指标对投资范围内的所有股票进行排名
- 组合形成:做多排名前 30%(价值股)并做空排名后 30%(成长股)
这个多空组合的收益率就是该特定价值信号的因子收益率时间序列。
5. 因子模型在量化金融中的应用
5.1 资产定价与收益归因
因子模型是绩效归因的主力工具。通过将基金收益率对因子进行回归,我们可以分解其表现:
- 因子载荷(β):基金的刻意或隐含风格押注。"量化基金"可能会明确目标高 β_{HML};科技成长型 ETF 自然会有负 β_{HML}
5.2 投资组合构建
- 因子倾斜:相信价值溢价的投资者可以超配高 β_{HML} 股票
- 多空策略:纯因子策略包括做多高敞口股票并做空低敞口股票,以隔离因子风险溢价,同时对冲市场和其他风险
- 投资组合优化:可以在优化器中添加约束条件,以实现对某些因子的目标敞口(例如,"保持投资组合 β_{HML} 在 0.2 和 0.4 之间")
5.3 风险管理
因子模型对于理解投资组合中的隐藏风险至关重要。一个按行业看似多元化的投资组合可能对单一因子有集中押注(例如,强烈的负动量载荷),使其容易受到动量崩溃的影响。监控因子敞口有助于识别和管理这些隐藏风险。
6. 因子动物园与稳健性挑战
Fama-French 模型的成功导致了大量"因子"的发表——所谓的因子动物园。已提出数百种因子,从投资和盈利能力(添加创建 Fama-French 五因子模型)到更奇特的因子,如"天气贝塔"。
这带来了关键挑战:
- 数据挖掘和过拟合:有了庞大的数据集和计算能力,研究人员不可避免地会发现一些恰好在过去通过随机机会预测了收益的变量。这种关系是虚假的,在样本外不会成立。
- P 值挖掘:尝试不同变量和规格组合,直到找到统计上显著(p 值 < 0.05)的结果。
- 发表偏差:期刊更有可能发表具有显著、新颖发现的论文,造成对真实因子数量的夸大印象。
6.1 解决方案和最佳实践
- 经济理由:因子应该基于合理的风险或行为经济学故事,而不仅仅是数据挖掘的相关性
-
稳健性:因子应对不同的构建方法、投资范围和交易成本假设具有稳健性
- 多重测试调整:使用更严格的显著性阈值(如 Bonferroni 校正)来考虑正在测试数百个假设的事实
总结
因子模型提供了一个强大、易于处理的框架,用于理解资产收益的驱动因素。从 CAPM 到 Fama-French 到 Carhart 的演进代表了金融思想对实证证据的响应而发展。对于量化从业者来说,通过回归实现这些模型是一项基础技能,对于阿尔法研究、投资组合管理和风险控制至关重要。
然而,实施的简便性与稳健金融发现的复杂性形成对比。在因子动物园蔓延的时代,严谨、样本外测试和经济直觉比以往任何时候都更重要,以区分持久的风险溢价和统计幻象。经典模型之所以仍然有价值,不一定是因为它们"真实",而是因为它们简洁、直观,并且被证明是解释股票横截面收益的非常持久的样本外解释。
参考文章
加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。财经数据与量化投研知识社区
核心权益如下:
- 赠送《财经数据宝典》完整文档,汇集多年财经数据维护经验
- 赠送《量化投研宝典》完整文档,汇集多年量化投研领域经验
- 赠送《PyBroker-入门及实战》视频课程,手把手学习量化策略开发
- 每日分享高质量量化投研文章(已更新 180+篇)、代码和相关资料
星球已有丰富内容积累,包括量化投研论文、财经高频数据、 PyBroker 视频教程、定期直播、数据分享和答疑解难。适合对量化投研和财经数据分析有兴趣的学习者及从业者。欢迎加入我们!