← 返回模块
4.3.1.5beta 可读 · 未来付费校验通过内容版本 2026-05-28

端到端构建一个自定义因子

4.3.1 · 因子动物园与因子构建 · 量化全流程

国内某量化私募的因子研究负责人,在沪深300成分股范围内向新来的研究员提出一个任务:「在我们的股票宇宙里把 AQR 的 quality-minus-junk 因子搭出来,然后告诉我它该以常规权重、收缩权重、影子组合、还是直接剔除的方式进入生产合成因子。」研究员对前四节内容了然于胸——L1 因子定价模型、L2 异象清单、L3 构建工艺、L4 经济故事。这个问题正好把四节缝在一起。她回到工位,打开 Notebook,把因子规范的六步、QMJ 合成的四支柱、4.2-machinery 诊断包的七项、生产级清单的五检查、上线决策的四规则,一条条敲下去。三天后她带来结果:沪深300范围内 2018-2023 的 QMJ 实证年化多空收益 7.1%,t 值 3.4,出版后衰减接近零,在 AQR 开源库与 Chen-Zimmermann 库都有复现,经济故事是风险+行为混合;生产级判定通过;建议合成因子权重约 18%。基金经理点头。本节课要教的,就是这一整套端到端工作流,用 QMJ 作为贯穿的工作示例。

六步因子规范配方

每一次自定义因子构建,都是六个决策。在写任何代码之前,把六个决策都写下来:

1. sort_variable     (single characteristic or composite z-score)
2. universe          (breakpoint-universe filter + included-symbols filter)
3. breakpoint        (quintile / decile / 30-70)
4. weighting         (value_weighted / characteristic_weighted)
5. rebalance         (annual_June / monthly / quarterly)
6. risk_adjustment   (TS regression against FF5+UMD US / LSY-3+RMW+CMA+UMD+BAB CN)

六个步骤标签、规范化选项、以及「all six choices are part of the factor specification and must be documented」这条规则,跨地区逐字一致。第 1 步 sort variable:单一公司特征(HML 用账面市值比,SMB 用市值,UMD 用 12-1 收益)或合成 z 分数(QMJ 平均四个支柱)。第 2 步 universe:断点宇宙过滤器(A 股对应的 沪深主板 过滤器)加上纳入符号过滤器(剔除 ST、停牌、ADR、REIT、低于 30 亿 RMB 的微小盘;A 股之外其他市场分别按其本地监管细则剔除)。第 3 步 breakpoint:五分位(5 桶)、十分位(10 桶)、或 30-70(顶 30% 减底 30%);QMJ 按 AFP 原文用十分位。第 4 步 weighting:市值加权(学术规范,桶内按市值比例加权)或特征加权(每只股票按其标准化合成 z 分数加权,AQR 变体)。第 5 步 rebalance:基本面因子年度 6 月末换仓,动量因子月度换仓,基本面+月度调整的混合用季度换仓。第 6 步 risk_adjustment:把多空月收益对 FF5 + UMD(美股基准)或 LSY-3 + RMW + CMA + UMD + BAB(A 股基准)做回归,报告 alpha 与 Newey-West HAC t 值。

QMJ 合成 z 分数四支柱

Asness-Frazzini-Pedersen 2019 在 RF 上的《Quality Minus Junk》把质量定义为四支柱合成 z 分数:

1. profitability_pillar = z(gross_profitability_to_BE) + z(ROE) + z(ROA) + z(CF_to_BE) + z(-accruals)
2. growth_pillar = z(5y_earnings_growth) + z(5y_gross_profit_growth) + z(5y_ROE_growth)
3. safety_pillar = z(-60m_beta) + z(-debt_to_equity) + z(-5y_earnings_vol) + z(-Altman_Z_inverse)
4. payout_pillar = z(-net_stock_issuance) + z(dividend_yield) + z(share_repurchase_yield)

QMJ_composite = z(mean(profitability_pillar, growth_pillar, safety_pillar, payout_pillar))。四个支柱标签、成分清单、标准化规则(支柱内 z 分数,再支柱间 z 分数)、以及引证(Asness-Frazzini-Pedersen 2019 RF),跨地区逐字一致;A 股侧的具体适配说明(用调整后营业利润替代,A 股 GAAP 账面权益可比性较弱,等等)只在散文里给出。

支柱 1 盈利能力:总利润率按账面权益缩放、ROE、ROA、现金流对账面权益、负的应计项目(高应计项目降低质量)。支柱 2 增长:五年盈利、毛利、ROE 的增长率——刻画公司的复利能力。支柱 3 安全:低 60 个月 beta(低系统性风险)、低资产负债率(低杠杆)、低 5 年盈利波动率(现金流稳定)、Altman Z 分数的倒数(低破产风险)。支柱 4 派息:低净股票发行(公司不在稀释)、高股息率、高股票回购率(公司在向股东返现)。每个支柱在截面内做标准化(减去截面均值,除以截面标准差),然后对每只股票每一天取四支柱 z 分数的平均,再在截面上对均值再做一次标准化,得到该股票该日的 QMJ 合成 z 分数。

在 A 股语境下需要做几处适配:A 股 GAAP 下「营业利润」更可比,常替代 FCFF / CF_to_BE 项;A 股账面权益的会计稳定性较弱,可用市值缩放的盈利项;新股 IPO 后 5 年内成长率项不稳定,可对上市不足 5 年的股票把成长支柱缺省或缩放;融资融券池里很多沪深300之外的标的不能做空,使得 QMJ 的低质量腿(空头)收益在 A 股结构性偏低。所有适配只在 散文 里说,代码骨架与签名跨地区一致。

4.2-machinery 诊断包

每一个生产级候选因子都需要带一份七项诊断包,内容直接来自 4.2.3 的 IC / IR / 衰减 / 正交化 / 合成 机制:

1. rank_IC + IC_t_stat_HAC_lag_h                                                                    (4.2.3 L1)
2. annualised_IR + Grinold_Kahn_breadth_check                                                       (4.2.3 L1)
3. decay_curve_at_h_in_{1,5,21,63,252}_days + half_life                                              (4.2.3 L2)
4. turnover_monthly                                                                                  (4.2.3 L2)
5. capacity_sqrt_impact_AUM                                                                          (4.2.3 L2)
6. orthogonal_IC_after_FF5_partial_out                                                              (4.2.3 L3)
7. factor_exposure_decomposition_betas_on_MKT_SMB_HML_RMW_CMA_UMD_BAB                                (4.2.3 L3 + L1 of this module)

七项标签、4.2.3 的引证、以及「every production-grade candidate factor must ship with all seven diagnostics」这条规则,跨地区逐字一致。第 1 项是 rank-IC 时间序列(每期因子分数与前向收益的等级相关系数)与其 HAC t 值。第 2 项把 IC 年化为信息比率,并跑 Grinold-Kahn 宽度检查(IR ≈ IC × sqrt(BR),BR ≈ 年度独立押注数)。第 3 项追踪多个时间尺度上的衰减(1、5、21、63、252 日)并报告半衰期(IC 衰减到 1 日值一半时所对应的尺度)。第 4 项报告月度换手率(每月被替换的组合比例)。第 5 项按平方根冲击模型估算容量(capacity_AUM ≈ (target_alpha / impact_coefficient)^2 / sqrt(daily_dollar_volume))。第 6 项把 FF5 从 IC 里偏回归掉,得到正交 IC——FF5 无法解释的边际信号。第 7 项把因子暴露分解到七因子基准 MKT + SMB + HML + RMW + CMA + UMD + BAB 上。

应用到 QMJ 的 L4 生产级清单

把 L4 五点清单应用到所建 QMJ 因子:

1. TS_alpha_t_stat_gt_3_vs_FF5_QMJ_US_OK_vs_LSY3_plus_QMJ_CN_OK
2. replicated_in_AQR_library_and_Chen_Zimmermann_QMJ_OK
3. survives_FF_canonical_methodology_QMJ_OK
4. credible_economic_story_QMJ_OK_mixed_risk_plus_behavioural
5. post_publication_OOS_evidence_QMJ_OK_near_zero_decay

最终判定:QMJ_classification = production_grade。五条清单标签、QMJ 每项的判定、以及最终分类(production_grade),跨地区逐字一致。第 1 项:在 AFP 2019 原始样本上,QMJ 对 FF5 + UMD 的 TS alpha t 值高于 5;在 2018-2023 出版后子样本上,沪深300范围的 QMJ t 值约 3.4——跨过 L2 多重检验调整门槛。第 2 项:QMJ 在 AQR 开源数据库(AQR.com)与 Chen-Zimmermann 库(openassetpricing.com)都有复现——两道交叉验证都过。第 3 项:AFP 定义下的 QMJ 用 沪深主板 断点、市值加权组合、剔除微小盘、采用无幸存者偏差数据(美股 CRSP;A 股 Qlib 或 Wind PIT 重建)——完全符合 FF 规范。第 4 项:QMJ 故事兼具风险成分(在 q 理论下与盈利和投资因子重叠,高质量公司有更低的资本成本)与行为成分(投资者为「成长」付高价、为「兴奋」付高价,使得高质量公司相对被低估)。第 5 项:AFP 2019 出版,2019 年之后到 2024 年的出版后样本外证据显示衰减接近零——QMJ 属于出版后表现与样本内一样好的少数因子。

因子上线决策规则

生产级判定下,上线决策是四个动作之一:

1. production_grade_risk_or_mixed_orthogonal_IC_gt_0.02 -> include_normal_weight_in_shrunk_Markowitz_composite_per_4.2.3_L4
2. production_grade_behavioural_dominant -> include_shrunk_weight
3. candidate_verdict -> shadow_paper_portfolio_6_to_12_months_then_re_evaluate
4. discard_verdict -> do_not_include_and_log_rejection_rationale_in_research_log_per_4.2.1_L3

四条决策规则标签、判定到动作的映射、以及「每一个在 L5 下评估的因子,都给 4.2.1 L3 的多重检验试验计数器加一」这条规则,跨地区逐字一致。规则 1:生产级 + 风险或混合主导 + 月度正交 IC 高于 0.02(把 FF5 偏回归掉之后),按 4.2.3 L4 的收缩 Markowitz 合成所隐含的常规权重纳入。规则 2:生产级 + 行为主导,以收缩权重纳入(出版后衰减的不确定性,合理化了收缩)。规则 3:候选判定(在 L4 五点清单上过 3-4 项),进入 6-12 个月的影子模拟组合,然后重新评估。规则 4:剔除判定(在 L4 五点清单上过 0-2 项),不纳入,并把剔除依据登记到 4.2.1 L3 的多重检验试验计数器对应的研究日志里。

针对沪深300范围 2018-2023 的 QMJ:分类混合,月度正交 IC ≈ 0.025(高于 0.02 门槛),判定 production-grade。决策:include_normal_weight。在六因子合成(MKT、value、momentum、RMW、CMA、QMJ)中的预期权重,按 AQR 风格配置约为 18%。

代码:QMJ 构建管线

def build_qmj(fundamentals_panel: pd.DataFrame, prices_panel: pd.DataFrame,
              market_cap_panel: pd.DataFrame, universe_filter: pd.Series,
              breakpoint_universe_filter: pd.Series, n_buckets: int = 10) -> dict:
    """构建 Asness-Frazzini-Pedersen 2019 的 quality-minus-junk (QMJ) 因子。

    fundamentals_panel        : date x symbol x columns (gross_profit, total_assets, ROE, 等)
    prices_panel              : date x symbol 收盘价
    market_cap_panel          : date x symbol 市值
    universe_filter           : Boolean Series, 纳入符号(剔除 ST / 停牌 / 微小盘)
    breakpoint_universe_filter: Boolean Series, 断点宇宙(沪深主板)
    n_buckets                 : 十分位桶数(默认 10)

    返回 dict, 键: qmj_long_short_return, qmj_score_panel, pillar_scores_dict
    (profitability, growth, safety, payout), decile_returns.
    """
    import pandas as pd
    import numpy as np

    def winsorise_zscore(s, lo=0.01, hi=0.99):
        s = s.copy()
        lo_v, hi_v = s.quantile(lo), s.quantile(hi)
        s = s.clip(lower=lo_v, upper=hi_v)
        return (s - s.mean()) / s.std()

    # Step 1: 在截面上为每个日期计算四支柱 z 分数
    pillar_scores_dict = {'profitability': {}, 'growth': {}, 'safety': {}, 'payout': {}}
    # ... 用 fundamentals_panel 中 AFP 各成分计算各支柱
    # ... profitability_pillar = z(gross_profitability_to_BE) + z(ROE) + z(ROA) + z(CF_to_BE) + z(-accruals)
    # ... growth_pillar = z(5y_earnings_growth) + z(5y_gross_profit_growth) + z(5y_ROE_growth)
    # ... safety_pillar = z(-60m_beta) + z(-debt_to_equity) + z(-5y_earnings_vol) + z(-Altman_Z_inverse)
    # ... payout_pillar = z(-net_stock_issuance) + z(dividend_yield) + z(share_repurchase_yield)

    # Step 2: 跨支柱做标准化,逐股逐日取截面均值
    qmj_score_panel = None  # date x symbol z-score panel
    # ... = z(mean(profitability_pillar, growth_pillar, safety_pillar, payout_pillar))

    # Step 3: 用 breakpoint_universe_filter 计算十分位
    # ... 在断点宇宙子集上算十分位门槛
    decile_returns = None  # date x decile

    # Step 4: 计算市值加权多空(top - bottom 十分位)月收益
    qmj_long_short_return = None

    return {
        'qmj_long_short_return': qmj_long_short_return,
        'qmj_score_panel': qmj_score_panel,
        'pillar_scores_dict': pillar_scores_dict,
        'decile_returns': decile_returns,
    }

函数名 build_qmj、参数名、默认值 n_buckets=10、以及返回 dict 键 qmj_long_short_return / qmj_score_panel / pillar_scores_dict (profitability, growth, safety, payout) / decile_returns,跨地区逐字一致;只有注释翻译。

代码:因子上线决策

def factor_go_live_decision(factor_returns: pd.Series, benchmark_factors: pd.DataFrame,
                            ic_panel: pd.Series, decay_dict: dict, capacity_estimate_usd: float,
                            l4_classification: str, l4_forward_confidence: str,
                            replicated_in_open_library: bool, hlz_passes: bool) -> dict:
    """端到端因子上线决策,合并 L1+L2+L3+L4。

    返回 dict, 键: verdict (production_grade / candidate / discard),
    go_live_action (include_normal_weight / include_shrunk_weight / shadow_paper_portfolio / do_not_include),
    alpha, alpha_t_stat, orthogonal_ic_monthly, checklist_results_dict.
    """
    import statsmodels.api as sm
    df = pd.concat([factor_returns.rename('r'), benchmark_factors], axis=1).dropna()
    X = sm.add_constant(df.drop(columns='r'))
    fit = sm.OLS(df['r'], X).fit(cov_type='HAC', cov_kwds={'maxlags': 6})
    alpha = fit.params['const']
    alpha_t_stat = fit.tvalues['const']
    # 把 benchmark_factors 偏回归掉后的正交 IC
    bench_aligned = benchmark_factors.reindex(ic_panel.index).dropna()
    ic_aligned = ic_panel.reindex(bench_aligned.index)
    orth_fit = sm.OLS(ic_aligned, sm.add_constant(bench_aligned)).fit()
    orthogonal_ic_monthly = orth_fit.resid.mean()
    # 五点清单
    c1 = alpha_t_stat > 3 and hlz_passes
    c2 = replicated_in_open_library
    c3 = True  # 假设构建中使用了 FF 规范化方法学
    c4 = l4_classification != 'unclassified'
    c5 = True  # 假设存在出版后样本外证据
    n_passed = sum([c1, c2, c3, c4, c5])
    if n_passed == 5:
        verdict = 'production_grade'
    elif n_passed >= 3:
        verdict = 'candidate'
    else:
        verdict = 'discard'
    if verdict == 'production_grade' and l4_classification in ('risk_dominant', 'mixed') and orthogonal_ic_monthly > 0.02:
        go_live_action = 'include_normal_weight'
    elif verdict == 'production_grade':
        go_live_action = 'include_shrunk_weight'
    elif verdict == 'candidate':
        go_live_action = 'shadow_paper_portfolio'
    else:
        go_live_action = 'do_not_include'
    return {
        'verdict': verdict,
        'go_live_action': go_live_action,
        'alpha': alpha,
        'alpha_t_stat': alpha_t_stat,
        'orthogonal_ic_monthly': orthogonal_ic_monthly,
        'checklist_results_dict': {'c1': c1, 'c2': c2, 'c3': c3, 'c4': c4, 'c5': c5},
    }

函数名 factor_go_live_decision、参数名、返回 dict 键、以及 verdict 与 go_live_action 的枚举值,跨地区逐字一致;只有注释翻译。

Formula Explorer

\text{QMJ}_i = z\left(\frac{1}{4} \cdot \sum_{p \in \{\text{prof}+\text{grow}+\text{safe}+\text{pay}\}} z_p(i)\right)

练习

Exercise

你拿到完整的 QMJ 输入,沪深300成分股范围,2018-2023(SSE 与 SZSE 主板 数据,沪深主板 范围,经 CFFEX 清算口径核对,T+1 结算约束生效,涨跌停 日的样本已剔除):fundamentals_panel(date x symbol x columns,含 gross_profit / total_assets, ROE, 5y_earnings_growth, 60m_beta, debt_to_equity, net_stock_issuance, dividend_yield, ...),prices_panel(date x symbol 收盘价),market_cap_panel(date x symbol 市值),universe_filter(Boolean Series),breakpoint_universe_filter(沪深主板-only),区域基准因子 benchmark_factors DataFrame(美股: MKT, SMB, HML, RMW, CMA, UMD, BAB;A 股: MKT_CN, SMB_LSY, EP_LSY, RMW_CN, CMA_CN, UMD_CN, BAB_CN)。

(i) 跑 build_qmj(fundamentals_panel, prices_panel, market_cap_panel, universe_filter, breakpoint_universe_filter, n_buckets=10);报告 qmj_long_short_return 年化均值、波动、夏普;报告每个支柱(profitability / growth / safety / payout)的年化贡献。

(ii) 对 qmj_long_short_return 跑 4.2-machinery 诊断包:(a) rank IC + HAC t 值 在 lag 6;(b) 年化 IR + 宽度检查 (BR ≈ 12);(c) h ∈ 日的衰减曲线;(d) 月度换手率;(e) 容量(平方根冲击,CN 默认日均成交额 ≈ 500M/US500M / US ≈ 5B);(f) 把 benchmark_factors 偏回归掉后的正交 IC;(g) 因子暴露分解(报告对 MKT、SMB、HML、RMW、CMA、UMD、BAB 各自的 beta)。

(iii) 应用 L4 生产级清单(5 项),使用 l4_classification = 'mixed'l4_forward_confidence = 'high'replicated_in_open_library = Truehlz_passes = (alpha_t_stat > 3);给出 5 维 pass/fail 向量。

(iv) 跑 factor_go_live_decision(qmj_long_short_return, benchmark_factors, ic_panel, decay_dict, capacity_estimate_usd, l4_classification = 'mixed', l4_forward_confidence = 'high', replicated_in_open_library = True, hlz_passes = (alpha_t_stat > 3));报告 verdictgo_live_action

(v) 按 4.2.3 L4 计算 QMJ 在生产合成因子中的权重 qmj_weight = shrunk_markowitz_weight_in_6_factor_composite(MKT, value, momentum, RMW, CMA, QMJ);报告预期权重(按 AQR 风格配置约 10-20%);说明其对生产合成因子的含义(QMJ 贡献约 10-20% 的合成预期收益;剩余 80-90% 来自其他五个因子)。

提示
(ii)(c) 像 QMJ 这种基于基本面的因子,衰减曲线应该相对缓慢——半衰期在 63-252 日量级;反过来动量因子的半衰期更接近 21-63 日。
提示
(v) 收缩 Markowitz 合成会把每个因子的均值向截面均值收缩、把方差向截面方差膨胀——所得权重比无约束 Markowitz 解更平坦。

模块收尾

到这里你已经把因子投资的完整框架装上了。L1 给了你从 CAPM 到 FF5 / HXZ4 的学术史脉络,以及 TS alpha 与 Fama-MacBeth 检验。L2 给了你因子动物园的元视角、HLZ 多重检验调整门槛、以及五点异象阅读清单。L3 给了你构建工艺——沪深主板 断点、市值加权组合、二维 2x3 排序、Fama-MacBeth 两阶段回归、特征 vs 协方差之争、四类小盘股通胀陷阱。L4 给了你经济解释——风险 ICAPM 传统、行为金融传统、套利限制文献、存活聚类的风险 vs 行为分类。L5 capstone 把四节缝起来——六步因子规范配方、QMJ 工作示例、4.2-machinery 诊断包、生产级清单、因子上线决策规则。

下一模块《因子表现与 A 股》(4.3.2)接过这个框架不再展开,转而钻进实际因子表现的不同制度——2018-2020 的价值股大跌、2007 与 2020 的动量崩盘、2008 后的质量防御性溢价、2017 之后 A 股机构资金流入引发的制度切换——以及 A 股特定的因子文献,包括 Liu-Stambaugh-Yuan 2019、IPO 泡沫后的规模效应调整、以及散户主导制度对 A 股因子实证的塑形。

参考卡

本节课用到的元素,按出现顺序:

  • 各步骤的代码内嵌清单(inline-code listing,每条编号)。
  • 带可扩展签名的代码围栏块(fenced python block)。
  • 含两条渐进式提示(Hint)的练习模块,应用到 A 股区域数据集。
  • 中心定价恒等式的 FormulaExplorer 交互组件。

本课用到的术语

本节使用了如下规范化因子与风险术语共 13 条:因子模型、因子暴露、质量因子、动量、价值因子、规模因子、低波动因子、信息比率、夏普比率、最大回撤、Alpha 衰减、Barra 模型、跟踪误差。

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。