写在前面
资产管理领域有一个长期争论:超额收益究竟来自哪里?是运气、技艺,还是系统性地持有了某些"风险因子"的敞口?
EDHEC《Advanced Portfolio Construction and Analysis with Python》课程的第一个模块,正面回应了这个问题——从单因子 CAPM 到 Fama–French 多因子模型,从风格分析到 Smart Beta 构建,全程配以可运行的 Python 代码。
核心洞见:每一个持仓,本质上都是一组因子暴露的叠加,加上一块特有风险(idiosyncratic risk)。
一、什么是"因子"?
因子(Factor)是解释资产收益截面差异与时序变化的系统性维度。按来源可分为三大类:
五大经典风格因子速览:
- • HML(价值):高账面市值比股票 − 低账面市值比股票的多空组合收益
- • SMB(规模):小市值股票 − 大市值股票的多空组合收益差
- • MOM(动量):过去 12 个月(跳过最近 1 月)收益最高的股票组合
- • RMW(盈利):高营业利润率股票 − 低营业利润率股票(FF5 新增)
- • CMA(投资):低资产增速股票 − 高资产增速股票(FF5 新增)
二、第一步:CAPM 单因子模型
CAPM 是理解因子模型的基石。它假设资产的全部系统性风险可由市场组合的单一 β 来捕捉:
核心含义:
- • :扣除 敞口后的风险调整超额收益(若市场有效, 统计上应不显著区别于零)
Python 实现
import statsmodels.api as smimport pandas as pd# excess: 资产超额收益序列;mkt_rf: 市场风险溢价y = excess['AAPL'].dropna()X = mkt_rf.loc[y.index].rename('Mkt-RF')X = sm.add_constant(X)# Newey-West 稳健标准误(处理异方差与自相关)model = sm.OLS(y, X).fit( cov_type='HAC', cov_kwds={'maxlags': 3})alpha = model.params['const']beta = model.params['Mkt-RF']r2 = model.rsquaredprint(f"α = {alpha:.4f} β = {beta:.4f} R² = {r2:.3f}")print(model.summary())
实操提醒: 区分"样本内拟合"与"样本外有效性"——一个高 的模型并不意味着它在未来仍有预测力。
三、第二步:Fama–French 多因子模型
CAPM 被大量实证研究证伪——市值效应和账面市值比效应无法被单一 β 解释。多因子框架的演进历程如下:
CAPM (1964) └── FF3 因子 (1993):+ SMB + HML └── Carhart 四因子 (1997):+ MOM └── FF5 因子 (2015):+ RMW + CMA
Carhart 四因子回归方程:
Python 实现
import pandas_datareader as pdrimport statsmodels.api as sm# 下载 Fama-French 月度因子数据(需翻墙或本地存储)factors = pdr.get_data_famafrench('F-F_Research_Data_Factors', start='2018')[0] / 100# 转为小数mom = pdr.get_data_famafrench('F-F_Momentum_Factor', start='2018')[0] / 100factors['MOM'] = mom['Mom ']# 运行四因子回归factor_cols = ['Mkt-RF', 'SMB', 'HML', 'MOM']X = sm.add_constant(factors[factor_cols])result = sm.OLS(fund_excess, X).fit( cov_type='HAC', cov_kwds={'maxlags': 3})print(result.summary())# 提取关键指标exposures = result.params.drop('const')t_stats = result.tvalues.drop('const')alpha_ann = result.params['const'] * 12# 月度 → 年化
如何解读回归结果:
| |
| 风格外超额收益;是否显著区别于零是判断主动能力的关键 |
| |
| |
| |
四、第三步:风格分析(RBSA)
给投资者一份基金净值序列,不看持仓,能推断出它的风格权重吗?William Sharpe 1992 年提出的 RBSA(Return-Based Style Analysis) 正是为此而生——用带约束的回归,将基金收益"分解"到一组风格指数上。
优化问题的形式:
这两个约束使得权重可被解释为"风格配置比例",截距(残差方差)则捕捉主动管理成分。
Python 实现(scipy 二次规划)
import numpy as npfrom scipy.optimize import minimizedefrbsa(fund_ret, style_ret):""" fund_ret : 1D array,基金超额收益序列 style_ret : 2D array (T × K),风格指数收益矩阵 返回: K 维权重向量 """ K = style_ret.shape[1]# 目标:最小化残差平方和defobjective(w):return np.sum((fund_ret - style_ret @ w) ** 2) constraints = [ {'type': 'eq', 'fun': lambda w: w.sum() - 1}, # 权重和为 1 ] bounds = [(0, 1)] * K # 权重非负 w0 = np.ones(K) / K # 初始等权 res = minimize(objective, w0, bounds=bounds, constraints=constraints, method='SLSQP')return res.x# 示例调用style_names = ['Value', 'Growth', 'Small', 'Large', 'Bond']weights = rbsa(fund_excess.values, style_matrix.values)for name, w inzip(style_names, weights):print(f"{name:10s}: {w:.1%}")# 输出示例:# Value : 32.0%# Growth : 28.0%# Small : 15.0%# Large : 18.0%# Bond : 7.0%
应用场景:
- • 基金尽职调查:宣称"价值风格"但实际暴露在成长因子上
五、第四步:市值加权的局限与 Smart Beta
标准指数(如沪深300、标普500)采用流通市值加权。这在理论上有 CAPM 支撑,但现实中暴露出两个明显弱点:
缺陷一:高度集中
以标普500为例,前10只股票(约占总数2%)的权重常超过30%,HHI 集中度远高于等权组合。
缺陷二:价格驱动权重
市值 = 价格 × 股数。股价涨越多,权重越高——相当于系统性地"追涨杀跌",潜在动量偏置与估值偏离。
五种加权方式对比
Python 实现:HHI 集中度 + 风险平价
import numpy as npfrom scipy.optimize import minimize# ── HHI(赫芬达尔指数)──────────────────────────────defhhi(weights):"""HHI 越接近 1 越集中,等权时 HHI = 1/n"""return (np.array(weights) ** 2).sum()n = 300# 假设300只成分股cap_w = np.random.dirichlet(np.ones(n) * 0.3) # 模拟市值分布ew = np.ones(n) / n # 等权print(f"市值加权 HHI : {hhi(cap_w):.4f}")print(f"等权 HHI : {hhi(ew):.4f} (= 1/n = {1/n:.4f})")# ── 风险平价(Risk Parity)──────────────────────────defrisk_parity(cov):"""使各资产风险贡献相等""" n = cov.shape[0]defobjective(w): port_var = w @ cov @ w rc = w * (cov @ w) / port_var # 各资产风险贡献比例return np.sum((rc - 1/n) ** 2) # 目标:风险贡献均等 bounds = [(1e-6, 1)] * n constraints = {'type': 'eq', 'fun': lambda w: w.sum() - 1} w0 = np.ones(n) / n res = minimize(objective, w0, bounds=bounds, constraints=constraints, method='SLSQP')return res.x# 使用历史收益率协方差矩阵cov_matrix = returns.cov().values # (n × n)rp_weights = risk_parity(cov_matrix)print(f"\n风险平价 HHI : {hhi(rp_weights):.4f}")
六、常见坑与实操注意事项
理论转落地,有几处容易踩坑的地方值得特别提醒。
① 样本窗口过短估计因子暴露至少需要 36 个月以上的月度数据,窗口过短会导致 β 估计极不稳健,换手率虚高。滚动回归时窗口长度的选择是稳健性与时效性之间的 trade-off。
② 数据频率与对齐日度与月度混用、节假日缺失处理不当,都会引入幽灵 alpha。务必先统一时间轴,再做回归。
# 正确做法:先对齐,再计算aligned = pd.concat([fund_returns, factor_returns], axis=1).dropna()y = aligned.iloc[:, 0]X = aligned.iloc[:, 1:]
③ 因子共线性SMB 与 HML 历史上的相关系数并不低,小样本下会导致系数剧烈波动。可考虑正交化(Gram-Schmidt)或 Ridge 正则化处理:
from sklearn.linear_model import Ridgeridge = Ridge(alpha=0.01, fit_intercept=True)ridge.fit(X_factors, y_fund)print("Ridge 系数:", ridge.coef_)
④ 忽略容量与成本等权策略在小盘股上的理论溢价,在规模化后往往因市场冲击成本消失殆尽。回测一定要加入合理的交易费率与滑点假设:
# 简单的单边交易成本扣除(假设 bps 为每次换手的单边成本)defnet_return(gross_ret, turnover, bps=10): cost = turnover * bps / 10000return gross_ret - cost
七、完整知识框架总结
因子投资框架├── 因子识别│ ├── 宏观因子(不可交易)│ ├── 风格因子(可交易多空组合)│ └── 统计因子(PCA 提取)│├── 因子模型│ ├── CAPM(单因子)│ ├── Fama-French 3因子│ ├── Carhart 4因子(+MOM)│ └── FF5(+RMW +CMA)│├── 风格分析(RBSA)│ ├── 带约束回归:w ≥ 0, Σw = 1│ └── 应用:风格漂移检测、业绩归因│└── Smart Beta 构建 ├── 等权(Equal Weight) ├── 基本面加权(Fundamental Weight) ├── 最小方差(Minimum Variance) └── 风险平价(Risk Parity)
八、自学练习建议
- 1. 因子暴露估计:下载 Fama–French 月度数据,估计三支公募基金过去 5 年的因子暴露与年化 alpha,对比主动与被动基金的差异。
- 2. Smart Beta 对比:用 A 股数据构建等权与市值加权两组组合,对比年化收益、波动率、Sharpe 比率与 HHI 集中度。
- 3. RBSA 实战:选取一支宣称"价值风格"的基金,用 RBSA 验证其实际风格暴露是否与宣传一致。
- 4. Smart Beta 因子组合:构造"价值 + 质量"的简单多空组合,用四因子模型检验其 alpha 是否显著。
参考文献
- • Fama, E. F., & French, K. R. (1993). Common risk factors in the returns on stocks and bonds. Journal of Financial Economics.
- • Carhart, M. M. (1997). On persistence in mutual fund performance. Journal of Finance.
- • Sharpe, W. F. (1992). Asset allocation: Management style and performance measurement. Journal of Portfolio Management.
- • Ang, A. (2014). Asset Management: A Systematic Approach to Factor Investing. Oxford University Press.
- • EDHEC Business School. Advanced Portfolio Construction and Analysis with Python, Coursera, Course 2, Module 1.