国内某量化私募新来的研究员从离职同事那里接过一份 SMB 构建脚本。她在沪深300成分股之外把范围扩到中证全样本,2010 年之后跑出来,SMB 年化 7.4%,t 值高于 4——惊人,因为 LSY-3 的规模因子在国内学术样本里多年只在 2-3% 附近徘徊,Fama-French 美股 SMB 也长期在 2-3%。投委会的资深基金经理脑子里跑了三步诊断:「断点用的 沪深主板 还是全样本?」「全样本十分位。」「市值加权还是等权?」「等权,跟原论文一样。」「有没有剔除微小盘?低于 30 亿 RMB 的?」「没剔——全样本嘛。」基金经理指了指桌上摊开的 Hou-Xue-Zhang 2020 复现论文:「用沪深主板断点、市值加权、剔除 30 亿以下的股票再跑一遍。溢价会落在学术 Fama-French SMB 上下 100-300 bps 区间内。」她照做。SMB 年化掉到 2.6%,t 值 1.9。原本那 7.4% 是小盘股通胀造成的假象。本节课要教的就是 L2 异象阅读清单第 (2) 项背后的构建工艺——能够把真实溢价与噪声放大选项区分开来的 FF 规范化方法学。
FF 构建的四个规范化选择
每一个因子组合,都由四个互相独立的构建选择来定义,每一个都有 Fama-French 的规范默认值:
1. sort_variable (firm characteristic — BE/ME for HML, market cap for SMB, 12-1 return for UMD,
OP/BE for RMW, Inv/A for CMA)
2. breakpoints (breakpoint-quintile / breakpoint-decile / breakpoint-30-70 — FF canonical)
3. weighting (value_weighted — academic standard)
4. rebalance (annual_June_end for size/value/profitability/investment, monthly for momentum)
四个选择标签、FF 默认值、以及「非规范化选择通常会把溢价吹高 100-300 bps,解读时要相应通胀修正」这条规则,跨地区逐字一致。每一项都有后果。排序变量,是用来排名股票的公司特征。断点(breakpoints)决定谁算「小盘」、谁算「大盘」,谁算「高 B/M」、谁算「低 B/M」——FF 规范的做法是 只用 纽交所上市股票(1963 年后样本约 1300 只)算分位数门槛,然后把这些门槛应用到 断点宇宙 + 其余 的全样本上。加权(weighting)决定多空收益是按市值加权(每只股票按其在桶内的市值占比加权)还是等权(每只股票贡献相同)。换仓(rebalance)决定多久重组桶:FF 对规模、价值、盈利能力、投资因子用每年 6 月末换仓(配对前一财年末的账面权益,作为 BE/ME 的分子),对动量因子用月度换仓。元规则是:任何偏离 FF 规范默认值的做法,通常都会把溢价吹高 100-300 个基点,解读时必须相应做通胀修正。
FF 二维 2x3 排序,逐步走法
SMB 与 HML 的构建用二维 2x3 排序。逐步走法:
1. compute_market_cap_at_June_end_year_t
2. compute_BE_to_ME = prior_FY_book_equity / June_end_market_equity
3. size_split = 2_buckets_at_breakpoint_median_market_cap
4. BM_split = 3_buckets_at_breakpoint_30th_70th_percentile_BE_to_ME
5. intersect_2x3 = 6_value_weighted_portfolios_(SL_SM_SH_BL_BM_BH)
6. SMB = mean(SL,SM,SH) - mean(BL,BM,BH)
7. HML = mean(SH,BH) - mean(SL,BL)
七个步骤、断点数值(断点宇宙中位数 与 30-70 百分位)、组合标签(SL/SM/SH/BL/BM/BH)、以及「二维排序把规模与价值正交化」这条规则,跨地区逐字一致;分别在每个区域的应用案例中,断点宇宙名称差异(美股用纽交所,A 股用 沪深主板),L4 及之后会在沪深300范围内具体说明。
操作流程展开来讲。在 t 年 6 月末,你用 6 月末股价乘以流通股数,算出每只股票的市值。你用上一财年的账面权益(取自最近披露的年报)除以 6 月末市值,算出每只股票的 BE/ME。然后只在断点宇宙股票上算两套断点:市值中位数(用来把规模分成小盘 / 大盘两桶)与 BE/ME 的 30 与 70 百分位(把价值分成低 / 中 / 高三桶)。把这三个门槛应用到全样本,把每只股票分配到六个 2x3 交叉桶之一:SL(小盘 + 低 BM)、SM(小盘 + 中 BM)、SH(小盘 + 高 BM)、BL(大盘 + 低 BM)、BM(大盘 + 中 BM)、BH(大盘 + 高 BM)。算每个桶在 t 年 7 月到 t+1 年 6 月的市值加权月收益,然后在下一个 6 月末重组桶。SMB 等于三个小盘桶收益的平均减去三个大盘桶收益的平均;HML 等于两个高 BM 桶收益的平均减去两个低 BM 桶收益的平均。
为什么要二维、不直接单变量?如果只对 BE/ME 单变量排序,小盘股会被大量推入高 BM 桶,因为小公司平均 BE/ME 更高。这样得到的「价值」溢价就会把规模与价值混在一起。先按规模分桶就消去了这种混淆;经过这一步,HML 就是一份相对干净、去掉规模影响后的纯价值溢价。
Fama-MacBeth 两阶段回归
截面定价检验用 Fama-MacBeth 1973 两阶段做:
1. stage_1_per_t = OLS(r_{i,t}, add_constant(char_vector_i)).fit() for each rebalance date t
(giving time series of slopes lambda_k_t)
2. stage_2_mean = mean(lambda_k_t over t)
3. stage_2_hac_se = NeweyWest_SE(lambda_k_t, lag=6)
4. t_stat_FMB = stage_2_mean / stage_2_hac_se
5. pass_iff t_stat_FMB > L2_threshold (2 conventional / 3 HLZ-deflated)
五个步骤、HAC 滞后规则(lag=6)、以及「FMB 测的是特征作为定价风险;TS 测的是因子作为组合;在真实信号上两者在符号上一致」这条规则,跨地区逐字一致。第一阶段在每个换仓日 t,把 N 只股票的截面收益对 K 个公司特征(BE/ME、规模、动量、盈利能力、资产增长率等)做 OLS,得到 K 个斜率 lambda_1_t, ..., lambda_K_t。每个斜率可以读为:「特征每高一个单位,t 期多赚 lambda_k_t 的额外收益」。把所有 T 期的 lambda 摞起来,算时间序列均值(平均风险溢价)与滞后 6 的 Newey-West HAC 标准误(吸收月度自相关),t 值就是均值除以 HAC 标准误。
L2 的通胀修正在这里同样适用。常规 FMB 在 t 值 > 2 时声明特征 被定价;HLZ 通胀修正后的门槛要求 t 值 > 3。再进阶一点的提醒——当截面回归用 预估 beta 而非原始特征作为自变量(常见变体)时,标准误需要按 Shanken 1992 做 EIV(变量误差)修正;从业者的快捷做法是直接用特征,绕开 EIV 问题。
特征 vs 协方差检验
实证资产定价文献中有一场关键之争:价值溢价到底是 BE/ME 特征 高的股票赚的(内在的公司层面属性),还是 HML beta 高的股票赚的(对价值因子的协方差暴露)?Daniel 与 Titman 1997 在 JF 上主张特征主导;Davis、Fama 与 French 2000 同样在 JF 上主张协方差主导。五步检验:
1. compute_pre_estimated_beta_per_stock (rolling 60-month regression of stock excess return on the factor)
2. compute_residual_characteristic = characteristic - OLS_proj(characteristic, factor_beta)
(partial-out the factor-beta loading)
3. sort_on_residual_characteristic_into_deciles
4. compute_long_short_return = top_decile - bottom_decile
5. interpret: if residual long-short > 0 with t-stat > 2 -> characteristic dominates
(Daniel-Titman 1997 result on 1973-1993);
if residual long-short ≈ 0 -> covariance dominates
(Davis-FF 2000 result on 1929-1997).
五个步骤、两篇论文的规范化结果、以及「两者在不同时间尺度上都有用——特征用于选股,协方差用于风险分解」这条规则,跨地区逐字一致。Daniel-Titman 在 1973-1993 窗口上的结果:即使把因子 beta 偏回归掉,残差特征仍然有显著的多空溢价——支持特征解释(价值溢价是 公司层面 属性)。Davis-Fama-French 在更长的 1929-1997 窗口上重做同一检验:残差特征的溢价收敛到接近零——支持协方差解释(价值溢价是 风险载荷)。后续研究中分歧一直没消;实操立场是 两者 都重要,只是在不同时间尺度上:选股看特征,做风险分解看协方差。
四类小盘股通胀陷阱
非规范化的构建选项,会以机械的方式把溢价吹高。四类规范化陷阱:
1. equal_weighting_inflation_100_to_300_bps (vs value-weighting)
2. full_universe_breakpoint_inflation_100_to_200_bps (vs main-board-only or 沪深主板-only)
3. micro_cap_inclusion_inflation (stocks < $300M in US / < 30亿 in CN —
high return but high cost — Fama 1998 recommends excluding)
4. survivorship_bias_inflation_100_to_300_bps_per_year (use CRSP / Qlib survivorship-bias-free per 4.1.1 L4)
四个陷阱标签、通胀量级、以及 FF 规范实践规则(断点宇宙断点 + 市值加权 + 排除微小盘 + 无幸存者偏差重组),跨地区逐字一致。陷阱 (1):等权把同样的钱放进每只股票,相对于市值加权会大幅过权微小盘——同一特征下,等权多空溢价通常比市值加权年化高 100-300 bps。陷阱 (2):全样本断点会把 70-80% 的股票推进小盘桶(创业板与北交所偏小盘股,A 股的中小创与北交所、低价股扮演同样角色),机械性地把任何与规模相关的溢价吹高,沪深主板 之外的 ChiNext / STAR 是常见污染来源。陷阱 (3):微小盘——美股低于 $300M 市值或 A 股低于 30 亿 RMB 的股票——账面收益高,但交易成本极高、容量极小、数据稳定性差;Fama 1998 明确建议把它们从因子构建中剔除。陷阱 (4):幸存者偏差——用「当前上市」的数据流而非 PIT 无幸存者偏差宇宙——把年化收益吹高 100-300 bps,因为退市股票(往往是表现差的)会被悄悄地从历史里抹掉。
FF 规范实践就是这四件事的捆绑:沪深主板 断点、市值加权、剔除微小盘、按 4.1.1 L4 的无幸存者偏差宇宙做重组。任何报告偏离这一捆绑的因子,解读前都应当按对应量级做通胀修正。
Formula Explorer
\text{SMB}_t = \frac{1}{3}(SL_t + SM_t + SH_t) - \frac{1}{3}(BL_t + BM_t + BH_t)代码:FF 二维 2x3 排序
def ff_bivariate_sort(prices: pd.DataFrame, market_cap: pd.DataFrame,
book_equity: pd.DataFrame, breakpoint_universe_filter: pd.Series,
rebalance_month: int = 6) -> dict:
"""Fama-French 1993 规模 x 账面市值比 二维 2x3 排序。
prices : 月度收盘价, date x symbol
market_cap : 月度市值, date x symbol
book_equity : 上一财年末账面权益(向前 carry forward), date x symbol
breakpoint_universe_filter : Boolean Series, True 表示属于断点宇宙的股票
(沪深主板)
rebalance_month : 年度换仓月份(默认 6 = 6 月)
返回 dict, 键为 SMB, HML, portfolio_returns_dict (六只 SL/SM/SH/BL/BM/BH 序列).
"""
import pandas as pd
bm = book_equity / market_cap
june_dates = market_cap.index[market_cap.index.month == rebalance_month]
portfolio_returns_dict = {k: [] for k in ['SL', 'SM', 'SH', 'BL', 'BM', 'BH']}
for sort_date in june_dates:
bp = breakpoint_universe_filter.reindex(market_cap.columns).fillna(False)
size_med = market_cap.loc[sort_date, bp].median()
bm_30 = bm.loc[sort_date, bp].quantile(0.30)
bm_70 = bm.loc[sort_date, bp].quantile(0.70)
# ... 把全样本股票分配到 SL/SM/SH/BL/BM/BH 六个桶,使用 (size_med, bm_30, bm_70)
# ... 计算 sort_date + 1 月 至 sort_date + 12 月 的市值加权月收益
# (完整实现见 scripts/factor_construction/ff_bivariate.py)
SMB = None # mean(SL, SM, SH) - mean(BL, BM, BH)
HML = None # mean(SH, BH) - mean(SL, BL)
return {'SMB': SMB, 'HML': HML, 'portfolio_returns_dict': portfolio_returns_dict}
函数名 ff_bivariate_sort、参数名 prices / market_cap / book_equity / breakpoint_universe_filter / rebalance_month、默认 rebalance_month=6、以及返回 dict 键 SMB / HML / portfolio_returns_dict,跨地区逐字一致。
代码:Fama-MacBeth 两阶段回归
def fama_macbeth_test(returns_panel: pd.DataFrame, characteristics_panel: pd.DataFrame,
hac_lag: int = 6) -> dict:
"""Fama-MacBeth 1973 两阶段截面回归。
returns_panel : 长格式 DataFrame, 含 date, symbol, return 列
characteristics_panel : 长格式 DataFrame, 含 date, symbol, 与 K 列特征
hac_lag : 第二阶段 Newey-West HAC 滞后(月频默认 6)
返回 dict, 键: lambda_means, lambda_hac_ses, lambda_t_stats, lambda_p_values.
"""
import pandas as pd
import statsmodels.api as sm
merged = returns_panel.merge(characteristics_panel, on=['date', 'symbol'])
char_cols = [c for c in characteristics_panel.columns if c not in ('date', 'symbol')]
lambdas = [] # 第一阶段各日斜率
for date, sub in merged.groupby('date'):
X = sm.add_constant(sub[char_cols])
fit = sm.OLS(sub['return'], X).fit()
lambdas.append(fit.params)
lambda_panel = pd.DataFrame(lambdas)
# 第二阶段:时间序列 HAC 均值
lambda_means = lambda_panel.mean()
lambda_hac_ses = {}
lambda_t_stats = {}
lambda_p_values = {}
from scipy.stats import norm
for c in lambda_panel.columns:
m = sm.OLS(lambda_panel[c], [1] * len(lambda_panel)).fit(
cov_type='HAC', cov_kwds={'maxlags': hac_lag})
lambda_hac_ses[c] = m.bse[0]
lambda_t_stats[c] = m.tvalues[0]
lambda_p_values[c] = 2 * (1 - norm.cdf(abs(m.tvalues[0])))
return {
'lambda_means': lambda_means.to_dict(),
'lambda_hac_ses': lambda_hac_ses,
'lambda_t_stats': lambda_t_stats,
'lambda_p_values': lambda_p_values,
}
函数名 fama_macbeth_test、参数名 returns_panel / characteristics_panel / hac_lag、默认 hac_lag=6、以及返回 dict 键 lambda_means / lambda_hac_ses / lambda_t_stats / lambda_p_values,跨地区逐字一致。
练习
Exercise
你拿到股票级面板 prices_panel(date x symbol)、market_cap_panel(date x symbol)、book_equity_panel(date x symbol,上一财年末账面权益向前 carry forward),以及 breakpoint_universe_filter Series(沪深主板 上市股票为 True,其余为 False),范围沪深300成分股,2018-01 至 2023-12,T+1 结算约束生效,涨跌停 日的样本已剔除,数据通过 CFFEX 清算口径下的标准日线(SSE 与 SZSE)交叉核对。
(i) 跑 ff_bivariate_sort(prices_panel, market_cap_panel, book_equity_panel, breakpoint_universe_filter, rebalance_month=6);报告实证 SMB 与 HML 的年化收益、年化波动、夏普。
(ii) 用 breakpoint_universe_filter = all_True(全样本断点——非规范化选项)重做一遍构建;报告实证溢价并计算通胀 delta_SMB = SMB_full - SMB_mainboard 与 delta_HML = HML_full - HML_mainboard(单位:bps 年化)。
(iii) 用等权而非市值加权重做一遍;报告实证溢价并算出对比市值加权的通胀(bps 年化)。
(iv) 为每只股票构造 12-1 动量 特征(过去 12 个月减过去 1 个月收益);跑 fama_macbeth_test(returns_panel, characteristics_panel = pd.concat([standardise(BE_to_ME), standardise(mom_12_1), standardise(log_market_cap)], axis='columns'), hac_lag=6);报告三个特征各自的 lambda 均值、HAC SE、t 值。
(v) 应用 L2 多重检验调整门槛——只在 lambda t 值 > 3 时声明特征 被定价;t 值在 [2, 3] 区间的特征声明为可疑;按此分类。
提示
提示
下一节
下一节《风险与错误定价:因子动物园的经济学》追问 L1 + L2 + L3 铺好的更深层问题:存活下来的因子聚类 为什么 能拿到溢价?文献里有两种竞争性的经济解释——风险解释下的 ICAPM 传统(Merton 1973、Lettau-Ludvigson 2001 的 cay 条件化、Hou-Xue-Zhang q 理论),与行为金融传统(Daniel-Hirshleifer-Subrahmanyam 1998 的过度自信、Barberis-Shleifer-Vishny 1998 的保守性、Hong-Stein 1999 的新闻观察者与动量交易者)。你会学到套利限制(limits to arbitrage)文献为什么能让行为错误定价持续存在、能把每个存活聚类归类的风险 vs 行为诊断、以及把 L1 + L2 + L4 合成单一上线决策规则的生产级因子清单。
参考卡
本课元素:inline-code listing(代码清单)、fenced ```python(代码围栏)、Exercise(练习)、FormulaExplorer。术语:因子模型、因子暴露、动量、价值因子、规模因子、质量因子、低波动因子、协方差矩阵、普通最小二乘、正交。
A 股区域参考
本节示例在 A 股语境下默认使用以下数据与机构参考(均为 CN 区域专属,与美股语境不重合):数据库 Wind、Qlib、JoinQuant、RiceQuant、Tushare、JQData、Choice;监管机构 CSRC、PBoC、SAFE、CBIRC;交易所 SSE、SZSE、BJEX、CFFEX;基准指数 CSI300、CSI500、CSI1000、ChinaA50;券商与基金 CICC、CITIC、HuaTai、Guosen、GuoTai、JunAn、HaiTong、ZhongJin、JiaShi、EFunds、BOSC、Bosera;研究锚定文献 LSY、Liu、Stambaugh、Yuan(LSY 三因子)、ShiChuan《因子投资》、WuFeixiang《量化投资》、DingPeng《量化投资策略》;本地术语 ChiNext、STAR、Beijing、RMB、PIT、BIPC。