从 Fama-French 到神经网络:Python 量化人必看的因子投资进化史
从 Fama-French 到神经网络:Python 量化人必看的因子投资进化史
原创 数据科学实战 数据科学实战 数据科学实战 2026年5月25日 09:02 四川 2人
用 Python 揭秘均值回归策略:你的收益从何而来?
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含 500 篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
引言
如果你正在学习 Python 量化投资,那么「因子投资」一定是绕不开的话题。从 1992 年 Fama 和 French 提出三因子模型,到 2020 年 Gu、Kelly、Xiu 用机器学习横扫资产定价领域,再到神经网络自动构造因子,这个领域已经发生了翻天覆地的变化。
最近读到一篇非常硬核的实战梳理文章,作者把 60 年的因子投资学术文献串起来,从 CAPM 一路讲到神经网络,还附带了 Python 实现思路。今天我把核心内容整理成中文版,并配上 Python 代码示例,希望能帮你快速理清这条技术主线。
一、因子投资到底在做什么
CAPM(资本资产定价模型)告诉我们:股票收益只由市场组合驱动,其它都是噪声。但 1981 年 Banz 发现小盘股有超额收益,CAPM 解释不了,这种现象被称为「异象」(anomaly)。
Ross 在 1976 年提出 APT(套利定价理论),把资产收益写成多个因子的线性组合:
后续最有名的实现就是 Fama-French 系列:
|
模型 |
因子 |
提出年份 |
|---|---|---|
|
FF 三因子 |
市场、SMB(规模)、HML(价值) |
1993 |
|
Carhart 四因子 |
加 WML(动量) |
1997 |
|
FF 五因子 |
加 RMW(盈利)、CMA(投资) |
2015 |
数据可以直接从 Kenneth French Data Library 下载,Python 里一行代码就能搞定:
# 使用 pandas_datareader 读取 Fama-French 五因子数据
import pandas_datareader.data as web
import pandas as pd
# 拉取 1963 年至今的月度五因子数据
ff5 = web.DataReader(
"F-F_Research_Data_5_Factors_2x3", # 五因子数据集名称
"famafrench", # 数据源
start="1963-07-01" # 起始日期
)[0]
# 单位是百分比,需要除以 100 转成小数
ff5 = ff5 / 100
print(ff5.head()) # 查看前几行:MKT-RF、SMB、HML、RMW、CMA、RF
二、如何发现一个新因子
学术界发现因子主要有两种方法。
方法一:投资组合排序(Portfolio Sorting)
按某个特征把股票分成 5 组或 10 组,跟踪每组未来收益,看头尾两组差异是否显著。
# 一个简化的投资组合排序示例
import numpy as np
import pandas as pd
def sort_portfolios(df, characteristic, n_groups=10):
"""
按某个特征对股票分组并计算各组平均收益
df: 包含 stock_id、date、return、characteristic 的 DataFrame
characteristic: 排序使用的特征列名
n_groups: 分组数,默认 10 组(十分位数)
"""
# 每个时间点根据特征分组
df["group"] = df.groupby("date")[characteristic].transform(
lambda x: pd.qcut(x, n_groups, labels=False) # 等频分组
)
# 计算各组每期平均收益
group_returns = df.groupby(["date", "group"])["return"].mean().unstack()
# 多空组合:买入第一组,卖空最后一组
long_short = group_returns[0] - group_returns[n_groups - 1]
return long_short
# 多空组合的 t 统计量决定因子是否「显著」
方法二:Fama-MacBeth 回归
这是 1973 年的经典方法,分两步:第一步用时间序列回归估计每只股票的因子载荷 beta;第二步在每个时点做横截面回归,估计因子风险溢价 gamma。
import statsmodels.api as sm
import numpy as np
def fama_macbeth(returns, factors):
"""
Fama-MacBeth 两步回归
returns: 各资产收益矩阵,形状为 (T, N)
factors: 因子收益矩阵,形状为 (T, K)
"""
T, N = returns.shape
K = factors.shape[1]
# 第一步:时间序列回归,估计每只股票的 beta
betas = np.zeros((N, K))
X = sm.add_constant(factors) # 加截距项
for i in range(N):
model = sm.OLS(returns[:, i], X).fit()
betas[i, :] = model.params[1:] # 取因子系数,不要截距
# 第二步:每期横截面回归,估计因子溢价 gamma
gammas = np.zeros((T, K))
Xb = sm.add_constant(betas)
for t in range(T):
model = sm.OLS(returns[t, :], Xb).fit()
gammas[t, :] = model.params[1:]
# 风险溢价是 gamma 的时间均值
risk_premia = gammas.mean(axis=0)
# t 统计量用于显著性检验
t_stats = risk_premia / (gammas.std(axis=0) / np.sqrt(T))
return risk_premia, t_stats
Sheppard(2023)用这套方法在 25 个 Fama-French 组合上跑出来:市场溢价约 6.66%、SMB 约 2.87%、HML 约 2.81%(年化)。但 J 检验统计量高达 95.29,说明三因子模型作为完整描述其实是被拒绝的。
三、p 值陷阱:你以为显著的因子可能根本不存在
这是很多 Python 量化新手最容易栽的坑。p 值是 P(D|H),即「假设原假设成立时观察到当前数据的概率」,但我们想知道的是 P(H|D),即「在数据下原假设成立的概率」。
Harvey(2017)提出了贝叶斯化 p 值(Bayesianised p-value):
import numpy as np
def bayesianised_p_value(t_stat, prior_odds):
"""
Harvey (2017) 提出的贝叶斯化 p 值
t_stat: 回归得到的 t 统计量
prior_odds: 你对原假设为真的先验赔率,p/(1-p)
比如先验认为 86% 概率原假设成立,则 prior_odds = 0.86/0.14 ≈ 6
返回值:原假设为真的后验概率
"""
# 公式:Bpv = exp(-t²/2) * prior / (1 + exp(-t²/2) * prior)
likelihood_ratio = np.exp(-t_stat ** 2 / 2)
bpv = likelihood_ratio * prior_odds / (1 + likelihood_ratio * prior_odds)
return bpv
# 案例:t = 2(约 5% 的传统 p 值),先验赔率 6
bpv = bayesianised_p_value(t_stat=2.0, prior_odds=6)
print(f"贝叶斯化 p 值:{bpv:.3f}") # 约 0.448
# 也就是说原假设为真的概率仍有 44.8%,远不是「显著」
Harvey、Liu、Zhu(2016)整理了文献中超过 300 个因子,其中很多在样本外都无法复现。Chen 和 Zimmermann(2020)估计已发表收益的「出版偏差」约 12%,也就是说论文里报的 8% 收益,真实可能只有 7%。
💡 教训:看到一个新因子不要急着上车,至少把 t 值门槛提到 3 以上再说。
四、当因子集体翻车:2007 年 Quant 大屠杀
Khandani 和 Lo(2007)记录了一段惨痛历史:2007 年 8 月,一家量化基金因次贷损失被迫平仓。由于所有 quant 基金都「在同一个池塘里钓鱼」(持仓高度相似),强制抛售引发了多米诺骨牌:
-
1. 「便宜」股票被进一步砸盘
-
2. 「贵」股票被买入回补
-
3. 做市商持续亏损停止报价
-
4. 流动性蒸发,恶性循环
这告诉我们一个残酷事实:因子投资在正常时期是分散化的,在危机时期是高度相关的。杠杆 + 流动性蒸发是因子策略最大的杀手。
五、机器学习真的有用吗?
Gu、Kelly、Xiu(2020)做了迄今最全面的机器学习对比实验:
-
• 数据:30000 只股票 × 60 年 × 900 多个预测变量
-
• 方法:OLS、Lasso、Ridge、Elastic Net、PCR、PLS、随机森林、GBDT、神经网络(1—5 层)
关键结论:
|
方法 |
月度 R² |
备注 |
|---|---|---|
|
OLS(900 + 变量) |
严重为负 |
过拟合到爆 |
|
Elastic Net |
0.11% |
正则化救回来了 |
|
PCR / PLS |
0.26% / 0.27% |
降维有效 |
|
树模型 / 神经网络 |
0.33%—0.40% |
非线性带来提升 |
|
神经网络(最佳层数) |
3 层 |
更深反而下降 |
# 一个 Gu et al. 风格的浅层神经网络示例
import torch
import torch.nn as nn
class StockReturnNet(nn.Module):
"""预测股票月度收益的浅层神经网络"""
def __init__(self, n_features, hidden_dims=[32, 16, 8]):
super().__init__()
layers = []
in_dim = n_features
# 三层隐藏层就够了,再深效果反而变差
for h in hidden_dims:
layers.append(nn.Linear(in_dim, h)) # 全连接层
layers.append(nn.ReLU()) # 激活函数
layers.append(nn.BatchNorm1d(h)) # 批归一化稳定训练
in_dim = h
layers.append(nn.Linear(in_dim, 1)) # 输出层:预测收益
self.net = nn.Sequential(*layers)
def forward(self, x):
return self.net(x)
# 注意:金融数据信噪比极低,不要照搬 CV 领域那种 50 层网络
model = StockReturnNet(n_features=94, hidden_dims=[32, 16, 8])
💡 重要发现:线性模型与非线性模型的差距,主要来自变量之间的交互作用,而不是单个变量的非线性变换。
六、神经网络不止能选因子,还能造因子
Fang 等(2020)提出了 NNAFC 框架,思路是用神经网络从原始 OHLCV 数据自动构造因子。
核心创新点:
-
1. 目标函数用 Spearman 相关(Rank IC) :对异常值更稳健
-
2. rank() 不可微 → 用 sigmoid 核近似:让梯度能反向传播
-
3. 预训练注入金融先验:先让网络学会复现 MA、MACD、RSI 等技术指标
import torch
def differentiable_rank_kernel(x, p=1.83):
"""
可微的排序近似函数
x: 输入张量
p: 控制锐度的超参数,p=1.83 时 95% 数据落在 ±2 std 内
"""
mean = x.mean()
std = x.std()
# 用 sigmoid 函数平滑替代不可微的 rank()
return 1.0 / (1.0 + torch.exp(-p * (x - mean) / (2 * std)))
def rank_ic_loss(factor_values, returns):
"""
Rank IC 损失函数:用于神经网络因子构造
factor_values: 网络输出的因子值
returns: 实际收益
"""
rank_f = differentiable_rank_kernel(factor_values)
rank_r = differentiable_rank_kernel(returns)
# 计算两个 rank 序列的相关系数
rank_f_centered = rank_f - rank_f.mean()
rank_r_centered = rank_r - rank_r.mean()
cov = (rank_f_centered * rank_r_centered).mean()
std_f = rank_f_centered.std()
std_r = rank_r_centered.std()
# 因为是损失函数所以取负号(要最大化相关性)
return -cov / (std_f * std_r + 1e-8)
实验结果(中国 A 股数据):
|
方法 |
IC |
多样性 |
|---|---|---|
|
遗传规划(GP) |
0.072 |
17.53 |
|
全连接网络 |
0.124 |
22.15 |
|
LSTM |
0.170 |
24.47 |
|
Transformer |
0.111 |
25.26 |
把 50 个 NNAFC 因子和 50 个专家因子组合,LSTM 版本年化收益 29.9%、最大回撤 15.0%、夏普比率 3.289,全方位碾压纯专家因子。
七、ESG 因子靠谱吗
简单一句话:目前还说不清。
主要原因是不同 ESG 数据商对同一家公司的评分差距巨大(Berg、Koelbel、Rigobon,2020)。在数据标准化之前,相关研究的结论可信度都要打折扣。
总结
读完这些材料,我有几点核心收获分享给学 Python 的朋友:
-
1. 因子投资是真实存在的,但「真实」不等于「无风险」「永久有效」。因子会被拥挤、会衰减、会在杠杆与流动性同时枯竭时暴力反转。
-
2. 机器学习确实有效,但不是因为它「深」,而是因为它能捕捉变量间的非线性交互。金融信号信噪比极低,浅层架构(3 层左右)通常优于深层网络。
-
3. 神经网络构造因子是真正的范式升级。NNAFC 比遗传规划在信息含量和多样性上都更胜一筹,预训练注入领域知识的思路很值得借鉴。
-
4. 永远保持怀疑。出版偏差普遍存在,p 值经常被误读,很多发表的异象在样本外失效。学会用贝叶斯化 p 值思考,比闭眼相信 5% 显著性靠谱得多。
如果你正在用 Python 做量化研究,建议从 Kenneth French 数据库的因子开始,先把 Fama-MacBeth 回归手撸一遍,再尝试 sklearn 里的 ElasticNet 和 PCA,最后用 PyTorch 实现简单的浅层神经网络做因子预测。这条路径走完,你对量化研究会有完全不同的理解。
更多推荐

所有评论(0)