第 26 章/ 45
质量因子(Quality)
主线:质量不是一个数,而是几个会计维度:盈利能力、杠杆、盈利的现金含量与稳定性。每个维度都要问分子是什么、分母是什么、两者是否来自同一份财报、那一刻市场是否已经知道。 先修:第12章(财务报表)、第23章(截面研究)、第24章(价值与时点基本面)。完成后你能:把 ROE 拆成净利率、周转与权益乘数并说明为什么 ROE 上升不一定是经营改善;区分期末与平均分母,处理负权益与近零权益;计算毛利润对资产、营业盈利能力对权益、应计项目比率与盈利稳定性;用同一份财报版本构造比率;说明金融业口径;读懂已发布的 RMW 因子收益,并说明它不检验逐股质量信号。
1. 本章目标
读者应能回答:同一家公司“质量高”是因为它真的赚得多,还是因为它借得多;一个盈利比率里的数是不是现金,是不是那一刻已经公开。
- 写出 ROE、ROA、GP/A、OP、杠杆、应计项目比率与盈利稳定性的分子、分母、单位、方向与适用行业。
- 用杜邦分解
推出杠杆对 ROE 的作用 ,并用两家公司说明“经营改善”与“加杠杆”如何给出相似的 ROE。 - 说明平均余额与期末余额的取舍,负权益或近零权益使 ROE 失去意义的机制,以及为什么平均两个年末权益之前要先要求两者为正。
- 说明“同一份财报版本”规则:分子与分母必须来自同一次提交,重述后的分子不能除以重述前的分母。
- 用构造面板复现:净利润口径的指标混有应计项目;ROE 的横截面方差大部分来自杠杆;近零权益可严重扰动 Pearson IC;Spearman 对极端幅度较稳健,但排序及样本过滤仍会影响它;两个不相关标准化维度的 Pearson 合成恒等式,并与构造秩IC作量级比较。
- 读取已发布的 RMW 月收益,报告均值、
、分期、回撤与相关,并说明它描述聚合因子收益,不构成对任何逐股质量信号的检验。
完成标准:任何质量信号都附一张字段表,写明每个字段的报告期、available_at、ingested_at、单位,以及它在哪些行业没有定义。
2. 为什么 Quant 需要它
价值因子回答“价格相对基本面便宜吗”;质量因子回答“基本面本身好不好”。两者合起来提供识别价值陷阱的更多事前信息:便宜但亏损、杠杆过高、盈利靠应计项目撑起来的公司,在价值信号上排前面,在质量信号上排后面。第28章把两者合成,合成之前每一个质量指标自己就要先站得住。
质量指标的难处与价值相同,但更隐蔽:分子来自利润表(一个时期的流量),分母来自资产负债表(一个时点的存量),时间口径天然不同;分母常是接近零甚至为负的权益;金融业的利润与负债含义不同;净利润里含有现金没有支撑的部分;财报会被重述。第24章的时点选择规则在这里直接复用,并增加一条:一个比率的分子与分母必须来自同一版本。
本章的逻辑链:先给出各指标的定义(5 节),推导杠杆为什么改变 ROE(6.1),再处理分母(6.2)、不直接含融资乘数的度量(6.3)、应计项目(6.4)、稳定性的估计误差(6.5)、同版本规则(6.6)与金融业(6.7),然后量化独立维度如何合成(6.8),最后在构造面板与已发布的 RMW 上做报告(10–11 节)。
3. 直觉
两家公司去年都把 ROE 从 15% 提高到 20%。甲公司把成本压低、卖得更好,每一块钱销售的利润更高;乙公司借钱回购了股票,权益变小,同样的利润除以更少的权益。ROE 都升了,但甲公司的经营改善了,乙公司只是承担了更多风险。把 ROE 拆成“利润率 × 周转 × 杠杆”才看得清它们的区别。
另一件容易混淆的是利润与现金。净利润是会计数字,一部分来自应收账款、存货与应计费用的变动,并没有对应的现金流入。利润与现金的差若来自以后需要回转的应计,可能伴随后续盈利回落;投资、营运资本季节性与会计分类也会造成差额,方向须检验。把净利润减去经营活动现金流量净额再除以平均资产,就是应计项目比率;它是会计利润与经营现金流之间的差,不是“现金”本身。
第三件事是时间。一份财报在财年结束后几周才发布,发布时的数字可能后来被修订。质量信号用到的每一个数字,都要问:在信号月末,有谁已经看得到它。
简化的边界:本章的构造面板把这些现象用最小的结构表示(一个潜在的质量因子、一个应计倾向、随机杠杆),真实公司的会计要复杂得多。
3.1 一份可得财报先给出完整手算行
4. 数学基础
记号。 对公司
平均余额。 财年内的利润对应的是财年内的平均资产,常用
杜邦恒等式。 当
三个因子的名称:净利率(net margin)、资产周转(asset turnover)、权益乘数(equity multiplier,也称财务杠杆)。
样本标准差的精度。 对
两个信号的合成。 设标准化后的信号
频率约定:会计量按财年,信号与收益按月;signal_time 是月末;信号取最近一个已可得的财年。
5. 正式定义
净资产收益率(Return on Equity, ROE)。
总资产收益率(Return on Assets, ROA)。
毛利润对资产(Gross Profitability, GP/A)。
营业盈利能力(Operating Profitability, OP)。 Fama 与 French(2015,表 8 说明)的定义:
杠杆(leverage)。 本章用
应计项目比率(accrual ratio)。
盈利稳定性(earnings stability)。 最近
同一份版本(same-version)规则。 构造一个比率用的分子与分母来自同一份、同一版本的财报;重述发布后,用重述后的所有数字重新计算,在重述发布之前仍用原数字。
RMW(Robust Minus Weak)。 已发布的盈利因子价差:
质量(quality)的使用约定。 本章的“质量”只指上述可计算的会计维度,不涉及管理层、护城河等不可计量的概念。不同研究对“质量”有不同的组合方式;本章不把任何一种组合称为“质量因子的标准定义”。
6. 推导
6.1 杜邦分解与杠杆对 ROE 的作用
用一家借债
推导用到的恒等式是
杠杆的代价:同样的
6.2 分母:平均、期末与近零权益
ROE 对分母的敏感度。
负权益。
平均余额陷阱。 年末权益 average_balance(..., floor=0.0))。
接近零的权益。 即使为正,也要设阈值:例如要求权益大于资产的 5%(floor 参数)。阈值是研究者的选择,要写入计划并报告被剔除的比例。
期末与平均的选择。 利润是财年内累计的流量,平均余额更匹配;期末余额对应财年末,取值较简单;这不代表财年末已经对外披露或本地入库。平均与期末两种口径都须等同份报告的available_at/ingested_at满足截止。两者在稳定增长的公司里差别不大,在权益快速变化的公司里差别可能很大(8.3 节:
6.3 毛利润对资产为什么不直接含融资乘数
净利润
6.4 应计项目:利润与现金之差
经营活动现金流量净额
6.5 盈利稳定性的估计误差
稳定性用
6.6 同一份版本规则:重述造成的假变化
设公司原先报告 latest_available_values 一次取出整行,所有分子分母同来自这一行;包括上一年年末余额(同一份 10-K 里的比较余额),这就是 assets_prev、equity_prev 同列保存的原因。
6.7 金融业
银行、保险的资产负债表以金融负债为主,利息既是成本也是经营收入,没有销售成本这一概念:GP/A、OP 的分子没有定义;EV 与 EBITDA 同样没有意义(第24章)。银行的权益乘数通常在 10 倍上下,非金融公司 1 到 2 倍,所以同一个 ROE 数值在两个行业含义不同。处理方式:对 GP/A、OP 把金融行业置 NaN(Novy-Marx 的作者稿也剔除了金融公司);对 ROE、ROA 在行业内排序;报告每个指标的覆盖率。本章构造面板里银行的权益乘数中位数约 11.2,非金融公司约 1.5。
6.8 两个标准化维度合成:相关的算术
以下为同一配对样本上标准化Pearson变量的恒等式;不相关不等于独立,给定相关矩阵还须合法且合成方差非零。设
7. 金融解释
质量是多个维度。 盈利水平(GP/A、OP、ROA、ROE)回答“赚得多不多”;杠杆回答“靠借钱多不多”;应计项目回答“利润里现金多不多”;稳定性回答“利润波动大不大”。不同维度可能相反:高杠杆公司 ROE 高但脆弱;高应计项目公司利润高但现金少。把它们压成一个“质量分”之前,要知道每个维度在测什么。
ROE 混杂了杠杆。 构造面板里 ROE 的横截面方差是 ROA 的 5.6 倍(0.0233 对 0.0042);每个日期按 ROE 排序的最高五分位的平均杠杆
净利润口径的指标含有应计项目。 构造面板里利润
质量与价值的关系是经验问题。 Fama 与 French(2015,第 7 节)发现,无条件地看价值股往往盈利较弱;控制其他因子后,价值股表现得像盈利稳健的股票。本章构造面板里质量与价值被设计成独立,不能用来回答这个问题;已发布的 RMW 与 HML 的相关系数是 0.095(11 节)。
RMW 的经济含义。 已发布的 RMW 平均每月 0.25%(
可得时间。 与第24章相同:Novy-Marx 作者稿把某财年的会计数据从下一日历年的 6 月末起使用;Fama–French 的 RMW 也用 t−1 财年数据于 t 年 7 月形成。研究者需要写明他们的滞后规则。
7.1 盈利为什么可能与下期收益有关
盈利强并不自动意味着股票回报高:若价格已经充分支付这些现金流,买方没有免费超额收益。风险假说可问高盈利公司是否风险较低、所需收益也较低,或不同质量维度在坏状态怎样暴露;误定价假说可问投资者是否低估盈利持续性或对现金与应计差异反应不足;代理假说可问指标是否只是行业、规模、资产计量或杠杆的替身。这些问题需要分别设计事前观测与检验,不能由“质量”一词直接推出正溢价。
应计差额尤其需要经济判断。扩张公司增加应收账款可能使经营现金流暂时低于利润,也可能是收入确认激进;同样的应计率并不识别原因。课程构造面板把应计倾向对收益的负效应写进生成器,演示该假设下信号怎样工作;它没有替真实公司裁决机制。Sloan、Piotroski等原文仍未读,不增加其经验结论。RMW则是已发布组合价差,无法替代单股GP/A或OP字段,也不检验本例会计质量。
8. 数值例题:手算每一步
所有数字由 hand_examples() 产生,并由 qa/a5/claude/recompute/ch26_recompute.py 用不同实现复核。公司为教学构造。
8.1 同样的 ROE 提升:经营改善与加杠杆
本节为期末余额杜邦简化,区别于第3.1节P4平均余额。单位百万美元。甲公司与乙公司去年相同:净利润 60,销售 1000,总资产 600,权益 400。ROE
甲公司今年净利润 90,其余不变:净利率 9%,周转 1.667,乘数 1.5,ROE
8.2 杠杆对 ROE 的作用
8.3 分母:期末、平均与近零权益
| 公司 | 年初权益 | 年末权益 | 期末 ROE | 平均权益 | 平均 ROE | 年末权益不足资产 2% 时 |
|---|---|---|---|---|---|---|
| 稳定 | 400 | 400 | 10.0% | 400 | 10.0% | 保留 |
| 权益缩水 | 500 | 100 | 40.0% | 300 | 13.3% | 保留 |
| 权益近零 | 300 | 2 | 2000% | 151 | 26.5% | 期末 ROE 置 NaN( |
| 年末为负 | 100 | NaN | 35(未保护) | 114.3%(未保护) | 先要求两个余额为正:NaN |
读法:期末与平均的差别在权益快速变化的公司最大;平均余额会把负的年末权益掩盖成正数(114%);设阈值后近零权益变为 NaN,被剔除的比例要报告。
8.4 毛利润、营业盈利能力与应计项目
某公司(本简化例期初期末资产相同,因此下列800也是同版本平均资产;应计例500亦同):销售 1000,销售成本 600,销售管理费用 200,利息 20,总资产 800,账面权益 400。GP/A
8.5 版本混用造成的假变化
原先
8.6 稳定性的估计误差
8.7 两个维度合成
9. Python 实现
字段定义在新模块 quality.py;同一版本的基本面选取复用第24章的 latest_available_values(可以一次取出多列,整行来自同一版本);排序、IC 复用第23章 factor_research.py。
| 函数 | 输入 | 输出与边界 |
|---|---|---|
average_balance |
年末与年初余额 | floor 时任一余额不高于它就为 NaN(权益用 floor=0.0) |
guarded_ratio |
分子、分母、floor(数或表) |
分子保留符号;分母不高于 floor 为 NaN |
dupont |
净利润、销售、资产、权益 | 净利率、周转、权益乘数与 ROE(三者之积);净利率要求销售为正;周转要求销售/资产为正;乘数要求资产/权益为正;净利润保留符号;ROE要求销售、资产、权益都为正 |
gross_profit_to_assets |
收入、销售成本、资产 | |
operating_profitability |
收入、销售成本、销售管理费用、利息、账面权益 | Fama–French(2015)的 OP;四项分子缺一为 NaN,权益须为正 |
accrual_ratio |
净利润、经营活动现金流量净额、资产 | |
leverage_ratio |
债务、资产 | |
earnings_stability |
年度比率历史(行为财年) | 最近 min_years 为 NaN |
roe_from_leverage |
默认离线入口(从 I3 阅读包根目录运行):
.venv/bin/python run_notebook.py notebooks/ch26_quality.ipynb
默认 RUN_FRENCH=False。稳定性示例是100000个worlds、种子11(正文历史示例200000);合成示例400000样本且每个r12重置种子5(正文600000且连续rng);quality_world/quality_study默认种子20261107。 runner另写 runs/execution_<stem>_<uuid>/ 的执行副本与状态。Notebook英语语言层及整包路径在BIL23装配;下列为历史完整CLI与测试参考,并非默认离线命令:
.venv/bin/python code/ch26_quality.py
.venv/bin/python -m pytest tests/test_ch26_quality.py tests/test_ch26_workflow.py -q
默认只建章级RUN_DIR,不写CSV/PNG/JSON或生成历史图库。
频率:会计量年度,信号与收益月度;signal_time 是月末的最后一刻(UTC);取版规则见第24章 6.4 节的 latest_available_values;每份提交同时保存当年与上年的资产负债表余额,所以平均余额来自同一份提交。CLI 种子 20261107。
代码段一:杜邦分解与杠杆效应(8.1、8.2 节)。
import pandas as pd
from quant_course.quality import dupont, roe_from_leverage
idx = pd.period_range("2026-06", periods=1, freq="M")
mk = lambda v: pd.DataFrame([[v]], index=idx, columns=["f"])
before = dupont(mk(60.0), mk(1000.0), mk(600.0), mk(400.0))
ops = dupont(mk(90.0), mk(1000.0), mk(600.0), mk(400.0)) # operations improve: margin 6% -> 9%
lev = dupont(mk(56.25), mk(1000.0), mk(600.0), mk(300.0)) # buy back 100 of equity with debt; after-tax interest 3.75
r = lambda d, k: float(d[k].iloc[0, 0])
assert abs(r(before, "roe") - 0.15) < 1e-12 and abs(r(ops, "roe") - 0.225) < 1e-12 and abs(r(lev, "roe") - 0.1875) < 1e-12
assert r(lev, "net_margin") < r(before, "net_margin") and r(lev, "equity_multiplier") > r(before, "equity_multiplier") # ROE rose although margin fell
assert abs(roe_from_leverage(0.08, 0.03, 1.0) - 0.13) < 1e-12 and abs((16 - 0.03 * 100) / 100 - 0.13) < 1e-12 # balance-sheet check
print(round(r(ops, "roe"), 4), round(r(lev, "roe"), 4))
代码段二:分母规则(8.3 节)。
import numpy as np
import pandas as pd
from quant_course.quality import average_balance, guarded_ratio
idx = pd.period_range("2026-06", periods=1, freq="M")
cols = ["steady", "shrinking", "tiny", "negative"]
ni = pd.DataFrame([[40.0] * 4], index=idx, columns=cols)
end = pd.DataFrame([[400.0, 100.0, 2.0, -30.0]], index=idx, columns=cols)
prev = pd.DataFrame([[400.0, 500.0, 300.0, 100.0]], index=idx, columns=cols)
assets = pd.DataFrame([[1000.0] * 4], index=idx, columns=cols)
roe_end = guarded_ratio(ni, end).iloc[0]
roe_avg_unguarded = guarded_ratio(ni, average_balance(end, prev)).iloc[0]
roe_avg = guarded_ratio(ni, average_balance(end, prev, floor=0.0)).iloc[0]
roe_floor = guarded_ratio(ni, end, floor=0.02 * assets).iloc[0]
assert abs(roe_end["tiny"] - 20.0) < 1e-12 and np.isnan(roe_end["negative"]) # 2000%, and no ratio for negative equity
assert abs(roe_avg_unguarded["negative"] - 40 / 35) < 1e-12 # the average hides the negative balance: 114%
assert np.isnan(roe_avg["negative"]) and abs(roe_avg["shrinking"] - 40 / 300) < 1e-12
assert np.isnan(roe_floor["tiny"]) and abs(roe_floor["shrinking"] - 0.4) < 1e-12 # 2 < 2% of assets (20): dropped
print(pd.DataFrame({"ending": roe_end, "average, unguarded": roe_avg_unguarded, "average, both > 0": roe_avg, "ending, 2% floor": roe_floor}).round(3))
代码段三:GP/A、OP、应计项目、杠杆(8.4 节)。
import pandas as pd
from quant_course.quality import accrual_ratio, gross_profit_to_assets, operating_profitability
idx = pd.period_range("2026-06", periods=1, freq="M")
mk = lambda *v: pd.DataFrame([list(v)], index=idx, columns=["low_debt", "high_debt"][: len(v)])
rev, cogs, sga = mk(1000.0, 1000.0), mk(600.0, 600.0), mk(200.0, 200.0)
interest, assets, equity = mk(20.0, 60.0), mk(800.0, 800.0), mk(400.0, 400.0)
gpa = gross_profit_to_assets(rev, cogs, assets).iloc[0]
op = operating_profitability(rev, cogs, sga, interest, equity).iloc[0]
assert (gpa == 0.5).all() # GP/A ignores interest
assert abs(op["low_debt"] - 0.45) < 1e-12 and abs(op["high_debt"] - 0.35) < 1e-12 # OP falls when interest rises
acc = accrual_ratio(mk(50.0, 50.0), mk(80.0, 20.0), mk(500.0, 500.0)).iloc[0]
assert abs(acc["low_debt"] + 0.06) < 1e-12 and abs(acc["high_debt"] - 0.06) < 1e-12 # cash-backed vs not (columns reused as labels)
print(gpa.to_dict(), op.round(3).to_dict(), acc.round(3).to_dict())
代码段四:同一版本规则与重述(8.5 节)。
import pandas as pd
from quant_course.factor_signals import latest_available_values
from quant_course.quality import guarded_ratio
versions = pd.DataFrame({
"security_id": ["S1", "S1"], "period_end": pd.to_datetime(["2022-12-31", "2022-12-31"]),
"available_at": pd.to_datetime(["2023-03-15 16:05", "2023-11-20 16:05"], utc=True),
"ingested_at": pd.to_datetime(["2023-03-15 16:12", "2023-11-20 16:12"], utc=True),
"net_income": [40.0, 28.0], "equity": [400.0, 350.0]}) # restated NI and equity
months = pd.period_range("2023-10", "2023-11", freq="M")
pit = latest_available_values(versions, months, ["net_income", "equity"])
same_version = guarded_ratio(pit["net_income"], pit["equity"])["S1"]
old_equity = latest_available_values(versions, months[:1], ["equity"])["equity"]["S1"].iloc[0] # the equity known in October, kept after the restatement
mixed_nov = pit["net_income"]["S1"].iloc[1] / old_equity
assert abs(same_version.iloc[0] - 0.10) < 1e-12 and abs(same_version.iloc[1] - 0.08) < 1e-12
assert abs(mixed_nov - 0.07) < 1e-12 # new numerator over the old denominator: a false -3 pp change
print(same_version.round(3).to_dict(), round(mixed_nov, 3))
代码段五:盈利稳定性的估计误差与排序反转概率(6.5、8.6 节)。
import numpy as np
rng = np.random.default_rng(11)
worlds = 200_000
from scipy import stats
for n in (5, 10):
expected = float(stats.f.sf((0.06 / 0.04) ** 2, n - 1, n - 1)) # exact: s1^2/s2^2 = (sigma1/sigma2)^2 F(n-1, n-1); 0.226 and 0.121
s1 = rng.normal(0.0, 0.04, (worlds, n)).std(axis=1, ddof=1) # true sigma 4%
s2 = rng.normal(0.0, 0.06, (worlds, n)).std(axis=1, ddof=1) # true sigma 6% (50% higher)
reversed_share = float((s1 > s2).mean()) # sample ranking opposite to the truth
assert abs(reversed_share - expected) < 0.01 # the lognormal approximation (0.209, 0.112) is a little low
print(n, round(reversed_share, 3), round(1 / np.sqrt(2 * (n - 1)), 3))
代码段六:两个不相关信号合成后的 IC(6.8、8.7 节)。
import numpy as np
rng = np.random.default_rng(5)
rho1, rho2, n = 0.0241, 0.0418, 600_000
def composite_ic(r12: float) -> float:
"""Correlation of z1 + z2 with y when corr(z1, y) = rho1, corr(z2, y) = rho2 and corr(z1, z2) = r12."""
cov = np.array([[1.0, r12, rho1], [r12, 1.0, rho2], [rho1, rho2, 1.0]])
z1, z2, y = rng.multivariate_normal(np.zeros(3), cov, size=n).T
return float(np.corrcoef(z1 + z2, y)[0, 1])
zero, half = composite_ic(0.0), composite_ic(0.5)
assert abs(zero - (rho1 + rho2) / np.sqrt(2)) < 0.004 and abs(half - (rho1 + rho2) / np.sqrt(2 * 1.5)) < 0.004
assert half < zero # a correlated second signal shrinks the gain
print(round(zero, 4), round(half, 4))
10. 图形解读
图 26-1:杠杆、ROE 与近零权益。 左图:税后债务成本 3%(虚线)时,三条线是营业收益率

图 26-2:不同质量指标的 IC。 构造面板 300 家非金融公司、132 个月,固定种子 20261107。条形是平均 Spearman IC(竖线约 ±2 倍标准误),依次是 ROE(期末)、ROE(平均权益)、ROE(平均权益且权益大于资产 5%)、ROA、GP/A、OP、

图 26-3:已发布的 RMW。 左图:RMW 月度价差复利后的累计值(对数纵轴,起点为 1,1963-07 至 2026-08);右图:滚动 120 个月平均 RMW(% 每月)。读法:长期缓慢向上、大部分时间为正,2000 年前后有一次回撤(最大回撤约 42%,发生在 2000-02)。已发布的聚合价差,不含成本与融资。

11. 历史市场案例
数据与限制。 使用 2026-10-05 冻结的 French 五因子(2×3)月度数据:Ken French 数据库 F-F Research Data 5 Factors 2x3,处理后 CSV SHA-256 833bd925b49651428781c789c13a6ac7454d4676449e6d119782050046a57eea,758 个月(1963-07 至 2026-08),单位小数月度价差收益;公开可下载不等于可任意再分发。RMW 的构造见 5 节与来源笔记:按 t−1 财年的营业盈利能力与规模形成 6 个价值加权组合,7 月形成并持有至次年 6 月,只含权益为正且收入与成本项可得的公司。它是已发布的聚合收益,不是逐股盈利信号,不是时点版本,不是可交易成交。
描述性结果。
| 期间 | 月数 | 平均(% 每月) | 标准差(% 每月) | |
|---|---|---|---|---|
| 1963-07 至 1999-12 | 438 | 0.173 | 2.26 | 1.60 |
| 2000-01 至 2026-08 | 320 | 0.359 | 2.16 | 2.98 |
| 2007-01 至 2020-12 | 168 | 0.234 | 1.90 | 1.59 |
| 全样本 | 758 | 0.251 | 3.03 | 2.29 |
复利累计最大回撤 41.8%,发生在 2000-02。与市场超额收益的相关系数
能说什么,不能说什么。 可以说:已发布的盈利价差在 1963–2026 平均为正,两个分期的均值都为正但标准差差一倍;与 SMB 呈负相关;与 HML 的相关接近零。不能说:(1)任何一个会计指标(GP/A、OP、ROE、应计项目)是更好的质量度量,RMW 只用营业盈利能力一种;(2)逐股高 OP 的公司会有更高收益,RMW 是 6 个价值加权组合的算术差;(3)质量与价值的关系:Fama–French(2015,第 7 节)的无条件与条件关系描述适用于其样本,本章没有逐股数据检验;(4)可交易性、容量与成本。
逐股质量研究缺什么。 需要:每家公司每个财年的收入、销售成本、销售管理费用、利息、净利润、经营现金流、总资产、权益、债务的各个版本与 available_at;历史股票池与退市;行业分类的历史版本;价格与市值。第24章列出的 SEC“财务报表数据集”提供 2009 年以来 XBRL 财报的 as-filed 数值与受理时间,但标签(可扩展商业报告语言 eXtensible Business Reporting Language, XBRL 的标签)与会计概念的映射、销售成本等科目的覆盖率与一致性需要另行核对,且没有价格与退市收益。本章没有下载这些数据;Project 4 准备层的缺口表逐项列出。
12. 常见错误
错误 1:把 ROE 上升当作经营改善。 8.1 节:乙公司净利率下降而 ROE 上升。修复:始终报告杜邦三个因子,或在经营和资产固定的比较中使用不直接扣利息的GP/A,并报告杠杆;ROA用已扣利息的净利润,仍含融资信息。
错误 2:用平均余额掩盖负权益。 8.3 节:年末
错误 3:近零权益进入排序。 构造面板里 Pearson IC:未保护的 ROE(期末)0.0017(
import numpy as np
import pandas as pd
from quant_course.factor_research import information_coefficient
from quant_course.quality import guarded_ratio
rng = np.random.default_rng(8)
n = 300
quality = rng.standard_normal(n)
equity = np.exp(rng.normal(5.0, 0.3, n)); equity[:6] = [1.0, 0.5, 2.0, 0.8, 1.5, 3.0] # six near-zero equity firms
income = 40 + 15 * quality + rng.normal(0, 5, n)
ret = 0.01 * quality + 0.03 * rng.standard_normal(n)
idx = pd.period_range("2026-06", periods=1, freq="M")
frame = lambda v: pd.DataFrame([v], index=idx, columns=range(n))
raw = guarded_ratio(frame(income), frame(equity)).iloc[0].to_numpy() # no floor: ROE of ~4000% for the tiny firms
guard = guarded_ratio(frame(income), frame(equity), floor=frame(np.full(n, 20.0))).iloc[0].to_numpy()
p_raw, p_guard = information_coefficient(raw, ret, method="pearson"), information_coefficient(guard, ret, method="pearson")
s_raw = information_coefficient(raw, ret, method="spearman")
assert p_guard > p_raw + 0.1 and abs(s_raw - information_coefficient(guard, ret, method="spearman")) < 0.1
print(round(p_raw, 3), round(p_guard, 3), round(s_raw, 3))
错误 4:版本混用。 8.5 节:新分子除以旧分母得到下降3个百分点,而真实下降2个百分点,混用额外多降1个百分点。修复:一次取出整行。
错误 5:对金融公司计算 GP/A 或 OP。 银行没有销售成本;用利息收入代替收入、利息支出代替成本会得到另一个含义的数。修复:这两个指标对金融行业置 NaN,ROE 与 ROA 行业内排序,并报告覆盖率。构造面板里银行的 GP/A 全部为 NaN(CLI 断言)。
错误 6:把净利润当现金。 8.4 节:两家净利润相同的公司,经营净现金流分别为80与20,这不是逐笔识别利润的现金收款。修复:单独计算应计项目比率;净利润口径的指标要与应计项目一起读。
错误 7:用很少几年的数据排序稳定性。 6.5 与 8.6 节:5 年标准差的相对误差 35%,真实相差 30% 的两家公司样本排序颠倒的概率约 30%;真实相差 50%(26-E06)时,
错误 8:用测试期调合成权重。 8.7 节与 6.8 节的合成假设等权、事先固定;若按测试期的 IC 调权,合成 IC 被事后选择夸大(第22章)。修复:权重在计划里固定,或只在训练期估计。
13. 量化研究应用
质量字段契约(Project 4 信号层)。
| 字段 | 来源 | 时间字段 | 单位 | 缺失与口径 |
|---|---|---|---|---|
revenue、cogs、sga、interest |
利润表 | period_end、available_at、ingested_at |
报告货币 | 金融行业 cogs 缺失,GP/A 与 OP 置 NaN |
net_income、cfo(经营现金流,cash flow from operations) |
利润表、现金流量表 | 同上 | 报告货币 | 与资产同版本 |
assets、assets_prev、equity、equity_prev、debt |
资产负债表(当年与上年比较余额) | 同上 | 报告货币 | 当前P4平均权益先要求两个年末权益为正,再要求平均权益高于平均资产5%;平均资产另按接口取值 |
gp_a、neg_accrual、roe_avg |
当前P4信号计算 | signal_time |
小数 | 同版本整行计算;OP/ROA/杠杆另作教材指标 |
industry |
分类版本 | available_at |
标签 | 行业内排序用 |
研究流程(构造面板)。 冻结计划:假设“同日毛利润对资产更高、应计项目比率更低的公司下月收益更高”,信号为两个维度各自的同日百分位的等权平均,主指标 Rank IC 的
对 Project 4 的约束。 (1) 所有比率从 latest_available_values 一次取整行;(2) 每个指标报告覆盖率与被剔除比例;(3) 金融行业另行处理;(4) 净利润口径的指标与应计项目、杠杆一起报告;(5) 稳定性要求足够年数;(6) 合成权重事先固定,多个维度进入第28章的因子相关与拥挤分析;(7) 所有变体进第22章账本。
13.1 指标映射与覆盖:先问“还有谁能被排序”
当前P4质量字段只有 gp_a、neg_accrual、roe_avg,公式分别对应第3.1节的44.4444%、+3.3333%、25.7143%。当前七字段不含OP;章节独立 quality.operating_profitability 函数可供教学使用,存在这个函数不等于P4已经接入。论文gross profits-to-assets、课程平均资产GP/A与French权益OP是三个明确口径,不能互相替代。RMW是按French口径形成的组合收益,不是逐公司的OP输入。
P4 roe_avg 的实际保护分两步:average_balance(equity, equity_prev, floor=0.0)先排除任一非正年末权益;guarded_ratio(..., floor=0.05 * assets_avg)再排除平均权益不高于平均资产5%的记录,等于阈值也排除。这是预先固定的接口阈值,不是ROE的普遍定义。第8.3节2%与26-E03的期末5%是另外的教学政策例,不能搬来称作P4规则。
构造一个四公司截面,平均资产均1000、净利润均40,权益(年初,年末)分别为A(80,120)、B(10,30)、C(120,-20)、D(40,60)。A平均100>50,有效ROE40%;B平均20<=50排除;C年末非正先排除(未经保护平均恰50);D平均50等于阈值也排除。完整输入4家中只有1家通过,ROE有效覆盖25%、损失75%;应计或GP/A能否计算仍按自己的字段判断。缺失不是低质量零分。规模/行业别覆盖和各失败原因应报告,因为过滤可能系统地改变股票池。
原始质量字段进入第24章13.1节的一次通用缩尾、行业/规模残差化、标准化与合成流程。金融行业GP/A缺失会使可用维度改变,风格内重新归一不能恢复缺失的经济证据。质量评分残差与最终权重行业投影仍是两个层次,旧A6支持外权重问题与当前C13限定修正须区分:listed支持集投影、gross恢复2及60名门槛已接受,执行后漂移残差另报,见第24章13.1节的当前接口入口;真实资格、借券、费用、容量与完整真实P4仍未完成,不把旧输出说成已重跑。
14. 本章小结
质量由几个会计维度构成。ROE 是净利率、周转与杠杆的乘积,杠杆上升可以使 ROE 上升而经营没有改善:
15. 练习
26-E01(手算) 某公司第一年净利润 36、销售 600、总资产 400、权益 150;第二年净利润 39.6、销售 660、总资产 440、权益 110(用债务回购了股票)。分别计算两年的净利率、周转、权益乘数与 ROE,并说明 ROE 变化的来源。
26-E02(手算) 营业收益率
26-E03(手算) 净利润 24,资产 800。三家公司(年初权益,年末权益):(200, 180)、(150, 6)、(80,
26-E04(手算) 收入 2400,销售成本 1500,销售管理费用 500,利息 100,总资产 2000(同版本期初期末均为2000,所以也是平均资产),账面权益 800。求 GP/A 与 OP;若利息为 300,两者各为多少?净利润 120、经营现金流 60、平均资产 1000 时应计项目比率是多少?经营现金流 180 时呢?
26-E05(手算) 原先净利润 55、权益 500;重述后净利润 44、权益 440。求同版本 ROE 的两个值、新分子除旧分母的混用值,以及混用造成的假变化。
26-E06(计算) 5 年与 10 年的标准差的相对标准误各是多少?两家公司真实标准差 4% 与 6%,用 5 年、10 年样本排序,顺序颠倒的概率各约多少?
26-E07(计算) 两个标准化信号,与标签的相关
26-E08(概念与手算) 银行 ROA 1%、权益乘数 11;软件公司 ROA 8%、权益乘数 1.5。求两者 ROE,并说明按 ROE 在全市场排序为何不合适;改用什么?
26-N01(财报版本与覆盖) 重算第3.1节五项比率并标注符号、分母与年度单位;说明2月为何不可用、French OP为何不改成平均权益。用第13.1节四家公司核对P4 ROE门槛、覆盖损失,并说明GP/A与RMW不能互换的理由。
16. 编程练习
26-P01 用 dupont 复现 26-E01,断言 ROE 的变化全部来自权益乘数(净利率与周转不变)。验收:三个因子的断言通过。
26-P02 构造含一次重述的版本表,用 latest_available_values 一次取出分子与分母,断言重述前、后的 ROE,并断言“新分子除旧分母”的混用值不同于两者。验收:三条断言通过。
26-P03 模拟两个相关系数为 0.5 的信号,其与标签的相关为 0.03 与 0.02,计算等权合成 IC,并与公式
17. 挑战题
26-C01(推导) 推广 6.1 节:营业利润(税前,earnings before interest and taxes, EBIT)
26-C02(开放) 为 Project 4 写质量信号的数据缺口表与冻结计划:列出每个字段在本课程现有数据中是否存在、真实可得的来源、时间字段与缺失规则;说明哪些结论在缺少数据时不能写;给出质量与价值合成前必须先检验的三件事(相关、覆盖、时点)。评分:缺口表 30,冻结计划 30,不能写的结论 20,合成前检验 20。
18. 完整答案与参考实现
26-E01 第一年:净利率
26-E02
26-E03 期末 ROE:
26-E04 GP/A
26-E05 同版本:原
26-E06 相对标准误
26-E07 不相关:
26-E08 银行 ROE
26-P01 参考实现
import pandas as pd
from quant_course.quality import dupont
idx = pd.period_range("2026-06", periods=1, freq="M")
mk = lambda v: pd.DataFrame([[v]], index=idx, columns=["f"])
y1 = dupont(mk(36.0), mk(600.0), mk(400.0), mk(150.0))
y2 = dupont(mk(39.6), mk(660.0), mk(440.0), mk(110.0))
g = lambda d, k: float(d[k].iloc[0, 0])
assert abs(g(y1, "roe") - 0.24) < 1e-12 and abs(g(y2, "roe") - 0.36) < 1e-12
assert abs(g(y1, "net_margin") - g(y2, "net_margin")) < 1e-12 and abs(g(y1, "asset_turnover") - g(y2, "asset_turnover")) < 1e-12
assert g(y2, "equity_multiplier") > g(y1, "equity_multiplier") # all of the ROE change is leverage
26-P02 参考实现
import pandas as pd
from quant_course.factor_signals import latest_available_values
from quant_course.quality import guarded_ratio
versions = pd.DataFrame({
"security_id": ["S1", "S1"], "period_end": pd.to_datetime(["2022-12-31", "2022-12-31"]),
"available_at": pd.to_datetime(["2023-03-15 16:00", "2023-09-10 16:00"], utc=True),
"ingested_at": pd.to_datetime(["2023-03-15 16:10", "2023-09-10 16:10"], utc=True),
"net_income": [55.0, 44.0], "equity": [500.0, 440.0]})
months = pd.period_range("2023-08", "2023-09", freq="M")
pit = latest_available_values(versions, months, ["net_income", "equity"])
roe = guarded_ratio(pit["net_income"], pit["equity"])["S1"]
old_equity = pit["equity"]["S1"].iloc[0]
mixed = pit["net_income"]["S1"].iloc[1] / old_equity
assert abs(roe.iloc[0] - 0.11) < 1e-12 and abs(roe.iloc[1] - 0.10) < 1e-12
assert abs(mixed - 0.088) < 1e-12 and mixed != roe.iloc[0] and mixed != roe.iloc[1]
26-P03 参考实现
import numpy as np
rng = np.random.default_rng(6)
n, r12, rho1, rho2 = 600_000, 0.5, 0.03, 0.02
cov = np.array([[1.0, r12, rho1], [r12, 1.0, rho2], [rho1, rho2, 1.0]]) # corr(z1,y)=rho1, corr(z2,y)=rho2, corr(z1,z2)=r12
z1, z2, y = rng.multivariate_normal(np.zeros(3), cov, size=n).T
sim = np.corrcoef(z1 + z2, y)[0, 1]
assert abs(sim - (rho1 + rho2) / np.sqrt(2 * (1 + r12))) < 0.005
print(round(sim, 4), round(0.05 / np.sqrt(3.0), 4))
26-C01 假定统一税率、利息可扣除且税盾可在当期使用,资产/债务及经营回报/利率口径固定;亏损与税盾递延须另建模。税前营业利润
26-C02(评分依据与参考) 本课程现有数据里:French 的 RMW 是聚合价差;valuation.py 的构造公司只有极少量字段;没有逐股的收入、销售成本、销售管理费用、利息、经营现金流版本;也没有价格、股票池、行业历史。SEC 财务报表数据集可补充 2009 年以来 as-filed 的数值与 filed、accepted 时间,但销售成本等科目的 XBRL 标签映射、行业分类、价格与退市收益需另取(并需遵守该数据的访问限制,见第24章)。不能写的结论:任何逐股质量因子的收益、与价值的相关、成本后的表现。合成前的三件事:质量与价值指标的同日秩相关(决定合成增益)、各指标的覆盖率与剔除比例(决定样本是否偏)、每个字段的 available_at 与滞后规则(决定是否前视)。评分按原稿四项。
26-N01完整答案。 平均资产900、平均权益350;GP/A44.4444%、ROE25.7143%、原始应计率-3.3333%而其相反数+3.3333%、债务/期末资产30%;另算French OP为180/400=45%,用平均权益的51.4286%是变式。GP/A、ROE、应计率与OP取固定财年的流量/余额口径;债务/期末资产是期末存量/存量比;均不是月投资收益。2月报告尚未发布和入库;3月末才可得。四家公司只A通过,B低于平均资产5%、C年末非正、D等于阈值;覆盖25%、损失75%。GP/A是公司会计特征,RMW是规模/OP组合的聚合价差,既不同对象也不同分母,不能替代或验证彼此。
19. 延伸阅读
- Must Know:Fama–French(2015)表 8 说明与第 7 节,读 OP 的定义与价值、盈利的无条件关系。
- Must Know:Ken French 数据库五因子说明页,读 RMW 的构造与样本条件。
- Should Know:Novy-Marx 的 The Other Side of Value 作者稿(2012 年 6 月版),读引言与数据说明,理解以资产为分母和排除金融公司的理由;期刊版未读。
- Should Know:本课程第12章(财务报表)关于净利润、现金流与应计项目的定义。
- Advanced:Sloan(1996)关于应计项目、Piotroski(2000)关于基本面评分的原文,核对应计项目与稳定性指标的经验结果;本章未读。
- Optional:任何一份公开 10-K 的“Selected Financial Data”,练习找出每个字段的报告期与比较余额。
20. 参考资料
引用键与读取范围见 source_notes.md。
- CH26_FF5_PAGE:Kenneth R. French Data Library,“Description of Fama/French 5 Factors (2x3)”。
- CH26_FF2015:Fama, E. F., French, K. R. (2015). Journal of Financial Economics 116(1),表 8 说明(印刷页 14)、第 7 节(页 12–13)。
- CH26_NOVYMARX2012:Novy-Marx, R. “The Other Side of Value: The Gross Profitability Premium”,作者稿 2012 年 6 月版;期刊版 JFE 108(1) 未读。
- CH24_SEC_FSDS:SEC 财务报表数据集说明(见第24章来源笔记)。
- FRENCH:French 五因子冻结快照(
data/manifests/french_factors_2026-10-05.json)。