1. 这节究竟解决什么实际问题?¶
7.3 质量因子:盈利能力、应计利润与 QV 策略 的具体任务是:从盈利、应计利润和资产负债表质量识别经营质量。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
理解利润表、资产负债表、现金流量表以及收益率。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
教学样本可能为模拟或固定快照。必须区分报告期、公告日、可获取日、修订日和实际交易日。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 7.3 质量因子:盈利能力、应计利润与 QV 策略 的输入、计算步骤、输出和局限;尤其能说明:从盈利、应计利润和资产负债表质量识别经营质量。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
报告期结束日、公告日和数据实际可获取日不同。交易信号必须按可获取日而非报告期对齐。
财务比率的分子与分母须来自同一可得时点;因子形成后再给予合理交易延迟。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
Pandas 便于按公告日对齐;数据服务返回的字段并不天然具有可交易时点。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“从盈利、应计利润和资产负债表质量识别经营质量。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
按财报所属季度直接交易、使用修订后数据、忽略负值和行业差异、混用市值时点。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
错误的 Point-in-Time 对齐会产生未来函数;必须记录公告日并设置交易可用延迟。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/07_fundamental/03_quality_factors_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/07_fundamental/03_quality_factors_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:AQR 关于因子研究的资料。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 04_earnings_surprise。本节的概念会成为理解它的输入,而不是孤立的名词。
第一部分:质量的五个维度——为什么每个都重要?¶
| 维度 | 指标 | 方向 | 为什么重要 |
|---|---|---|---|
| 盈利能力 | ROE | 越高越好 | 衡量股东资本的回报效率 |
| 盈利稳定性 | ROE 波动率 | 越低越好 | 稳定 ROE=持续护城河,波动大=靠运气 |
| 现金质量 | CFO / 净利润 | 越高越好 | >1 说明真实现金超过会计利润 |
| 财务安全 | 负债率 | 越低越好 | 低负债=危机时逆势扩张的能力 |
| 盈利质量 | 应计利润率 | 越负越好 | 负值=CFO>利润,利润真实可信 |
关键洞察:为什么 ROE 波动率很重要?¶
两家公司平均 ROE 都是 15%:
- 公司 A:每年稳定 15%
- 公司 B:+30%, -5%, +25%, +10%, +5%(均值 15%,但忽上忽下)
公司 A 的 ROE 更稳定。这可能与定价权、成本优势或资本结构有关,但仅凭 ROE 不能确认竞争优势或估值高低。 而不是靠周期性或运气暂时获利。这就是巴菲特寻找「持久竞争优势」的量化体现。
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
from scipy.stats import rankdata
np.random.seed(42)
n = 300
# 模拟三类公司:高质量、中等、低质量
def gen(n, roe_mu, roe_std, cf_mu, lev_mu, acc_mu):
return pd.DataFrame({
'ROE': np.random.normal(roe_mu, roe_std, n).clip(0),
'ROE_vol': np.abs(np.random.normal(roe_std, roe_std*0.3, n).clip(0.01)),
'CF_ratio': np.random.normal(cf_mu, 0.2, n).clip(0.2, 2.5),
'leverage': np.random.normal(lev_mu, 0.1, n).clip(0.1, 0.9),
'accruals': np.random.normal(acc_mu, 0.04, n),
})
high = gen(100, 0.25, 0.04, 1.3, 0.25, -0.03)
high['label'] = '高质量'
mid = gen(100, 0.14, 0.08, 0.95, 0.45, 0.01)
mid['label'] = '中等'
low = gen(100, 0.06, 0.12, 0.6, 0.68, +0.05)
low['label'] = '低质量'
df = pd.concat([high, mid, low], ignore_index=True)
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for ax, col, label, note in zip(axes,
['ROE', 'ROE_vol', 'accruals'],
['ROE(盈利能力)', 'ROE波动率(越低=越稳定)', '应计利润率(越负=现金质量越好)'],
['高ROE公司ROE显著更高', '低ROE_vol=护城河稳定', '低质量公司应计项目多']):
for lbl, color in [('高质量','green'), ('中等','gray'), ('低质量','red')]:
df[df['label']==lbl][col].hist(bins=20, ax=ax, alpha=0.6, color=color, label=lbl)
ax.set_title(f'{label} {note}', fontsize=9)
ax.legend(fontsize=8); ax.grid(alpha=0.3)
plt.suptitle('质量因子各维度:高 vs 低质量公司分布差异', fontsize=12)
plt.tight_layout(); plt.show()
第二部分:Sloan 应计利润异象(1996)¶
应计利润的本质¶
$$\text{净利润} = \text{经营现金流(CFO)} + \underbrace{\text{应计项目(Accruals)}}_{\text{「借来」的利润}}$$
应计项目的典型来源:
- 应收账款增加:卖了货但还没收到钱,记入利润但 CFO 没有体现
- 存货增加:生产了产品但未卖出,按成本暂不记为费用
为什么应计利润多是坏事?
- 应收账款可能变成坏账,未来需要冲销 → 利润下调
- 存货跌价 → 需要计提减值损失
- 管理层可能故意通过应计项目美化当期业绩
Sloan 的实证:高应计利润组未来 12 月 系统性跑输 低应计利润组约 10%(年化)。
应计利润异象假设市场没有立即充分反映应计项目的信息。该假设需要在不同市场、时期和成本条件下重新检验。
# 验证 Sloan 应计利润异象
np.random.seed(42)
N = 500
accruals = np.random.normal(0, 0.06, N)
noise = np.random.normal(0, 0.25, N)
fwd_ret = -0.9 * accruals + noise # 高应计→未来跌
r, p = stats.spearmanr(accruals, fwd_ret)
print(f'应计利润率 Spearman IC = {-r:.4f} (p = {p:.4e})')
deciles = pd.qcut(accruals, 10, labels=[f'D{i+1}' for i in range(10)])
dec_ret = pd.Series(fwd_ret).groupby(deciles).mean()
fig, axes = plt.subplots(1, 2, figsize=(13, 4))
axes[0].scatter(accruals, fwd_ret, alpha=0.3, s=12, color='steelblue')
m, b, *_ = stats.linregress(accruals, fwd_ret)
xr = np.linspace(accruals.min(), accruals.max(), 100)
axes[0].plot(xr, m*xr+b, 'r-', lw=2, label=f'IC = {-r:.3f}')
axes[0].set_xlabel('应计利润率'); axes[0].set_ylabel('未来12月收益')
axes[0].set_title('Sloan 异象:高应计利润→未来跌'); axes[0].legend(); axes[0].grid(alpha=0.3)
bar_c = ['#2ecc71' if x > 0 else '#e74c3c' for x in dec_ret]
axes[1].bar(range(10), dec_ret.values, color=bar_c, alpha=0.85, edgecolor='white')
axes[1].axhline(0, color='black', lw=0.8)
axes[1].set_title('十分位分组:D1=低应计(好) D10=高应计(差)')
axes[1].set_ylabel('平均收益率'); axes[1].grid(alpha=0.3)
plt.tight_layout(); plt.show()
print('结论:低应计利润组(D1)系统性跑赢高应计利润组(D10)')
应计利润率 Spearman IC = 0.2802 (p = 1.7930e-10)
结论:低应计利润组(D1)系统性跑赢高应计利润组(D10)
def rnk(s, hi=True):
r = pd.Series(rankdata(s)) / len(s)
return r if hi else 1 - r
# 构建五维质量分
df['q'] = (rnk(df['ROE']) + rnk(df['ROE_vol'],hi=False) +
rnk(df['CF_ratio']) + rnk(df['leverage'],hi=False) +
rnk(df['accruals'],hi=False)) / 5
# 价值因子
df['PB'] = np.random.lognormal(np.log(2),0.7,len(df)).clip(0.3,15)
df['value'] = rnk(df['PB'], hi=False)
df['QV'] = 0.5*df['q'] + 0.5*df['value']
# 前瞻收益
noise_q = np.random.normal(0, 0.25, len(df))
fwd = 0.4*df['q'] + 0.3*df['value'] + noise_q
fig, axes = plt.subplots(1, 3, figsize=(13, 4))
for ax, col, title in zip(axes, ['q','value','QV'], ['纯质量','纯价值(低PB)','QV双因子']):
q5 = pd.qcut(df[col], 5, labels=['Q1','Q2','Q3','Q4','Q5'])
qr = fwd.groupby(q5).mean()
sp = qr.iloc[-1] - qr.iloc[0]
ax.bar(qr.index, qr.values,
color=['#e74c3c','#e67e22','#f1c40f','#2ecc71','#27ae60'],alpha=0.85,edgecolor='w')
ax.axhline(0, color='k', lw=0.8)
ax.set_title(f'{title} Q5-Q1={sp:.3f}'); ax.grid(alpha=0.3)
plt.suptitle('三种策略多空收益差:QV合成通常最稳健', fontsize=12)
plt.tight_layout(); plt.show()
练习¶
- 对 A 股前 500 大市值股票计算五维质量分,统计高质量组(Q5)未来 6 个月平均超额收益。
- AQR 的 QMJ 因子:做多高质量(Q5)、做空低质量(Q1),实现并计算年化 Sharpe,与沪深300对比。
- 用 DuPont 分解验证:挑出 5 只 ROE>25% 的 A 股公司,拆解其 ROE 来源是利润率、周转率还是杠杆?
下一节 → 04_earnings_surprise.ipynb