import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
print('依赖库加载完毕')
依赖库加载完毕
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
第一部分:三大财务报表——各自解决什么问题?¶
在开始计算任何指标之前,先要理解三张报表的底层逻辑。 它们互为补充,缺少任何一张都会形成盲点。
📄 损益表(Income Statement / P&L)¶
核心问题:这家公司在这一段时间内赚了多少钱?
营业收入 (Revenue)
- 营业成本 (COGS)
= 毛利润 (Gross Profit)
- 运营费用 (OPEX)
= 息税前利润 (EBIT)
- 利息费用
- 所得税
= 净利润 (Net Income)
量化分析的关注点:
- 净利率 = 净利润 / 营业收入:每赚 1 元收入能留下多少钱?
- EPS(每股收益) = 净利润 / 总股数:摊到每股上的盈利,是 P/E 的分母
🏦 资产负债表(Balance Sheet)¶
核心问题:这家公司现在拥有什么(资产),欠了多少(负债),股东实际拥有多少(权益)?
资产 = 负债 + 股东权益
(现金、存货、固定资产…)= (银行贷款、债券…)+(实收资本、留存收益…)
量化分析的关注点:
- 账面价值(Book Value) = 股东权益:公司清算后股东能拿到多少
- P/B = 市值 / 账面价值:市场愿意为账面净资产支付几倍溢价?
- 负债率 = 总负债 / 总资产:财务杠杆水平,越高越危险
💰 现金流量表(Cash Flow Statement)¶
核心问题:公司实际流进流出了多少真实现金?
经营活动现金流 (CFO) ← 最重要!主业赚的真实现金
投资活动现金流 (CFI) ← 购置资产、投资
筹资活动现金流 (CFF) ← 借款、还款、分红
为什么现金流最重要?
会计利润可以被操纵(通过应计利润等手段)。 现金是最难造假的——账上如果没有现金,公司无论利润多高都可能违约。
关键检验:CFO ≫ 净利润 → 盈利质量高 如果净利润很高但 CFO 为负 → 警告信号!
# 构建模拟财务数据:50 只股票(代表不同质量级别的公司)
np.random.seed(42)
n = 50
# ── 损益表数据 ──────────────────────────────────
revenue = np.random.uniform(20e8, 200e8, n) # 营业收入(元)
cogs = revenue * np.random.uniform(0.5, 0.75, n) # 营业成本
gross = revenue - cogs # 毛利润
opex = gross * np.random.uniform(0.4, 0.8, n) # 运营费用
net_income = gross - opex # 净利润(简化)
net_income = np.maximum(net_income, 0)
# ── 资产负债表数据 ───────────────────────────────
total_assets = np.random.uniform(50e8, 500e8, n)
total_liab = total_assets * np.random.uniform(0.3, 0.8, n)
equity = total_assets - total_liab # 股东权益(账面价值)
shares = np.random.uniform(1e8, 10e8, n) # 总股数
book_value = equity # 账面净资产 = 股东权益
current_assets = total_assets * np.random.uniform(0.3, 0.6, n)
current_liab = total_liab * np.random.uniform(0.4, 0.7, n)
# ── 现金流量表数据 ───────────────────────────────
operating_cf = net_income * np.random.uniform(0.5, 1.8, n) # CFO(有时比利润多,有时少)
long_term_debt = total_liab * np.random.uniform(0.3, 0.7, n)
long_term_debt_py = long_term_debt * np.random.uniform(0.8, 1.2, n) # 上一年
# ── 市场数据 ─────────────────────────────────────
# P/E = 10~40 倍,市值 = 股价 × 股数
pe_ratio = np.random.uniform(8, 45, n)
market_cap = np.maximum(net_income * pe_ratio, equity * 0.5) # 市值
df = pd.DataFrame({
'revenue': revenue,
'net_income': net_income,
'total_assets': total_assets,
'total_liab': total_liab,
'equity': equity,
'book_value': book_value,
'shares': shares,
'market_cap': market_cap,
'operating_cf': operating_cf,
'current_assets': current_assets,
'current_liab': current_liab,
'long_term_debt': long_term_debt,
'long_term_debt_py': long_term_debt_py,
}, index=[f'STK{i:03d}' for i in range(n)])
print(f'模拟了 {n} 只股票的完整财务数据')
print('\n示例(STK000):')
sample = df.iloc[0]
print(f' 营业收入: {sample.revenue/1e8:.1f} 亿元')
print(f' 净利润: {sample.net_income/1e8:.1f} 亿元')
print(f' 净利率: {sample.net_income/sample.revenue:.1%}')
print(f' 股东权益: {sample.equity/1e8:.1f} 亿元')
print(f' 经营CFO: {sample.operating_cf/1e8:.1f} 亿元')
模拟了 50 只股票的完整财务数据 示例(STK000): 营业收入: 87.4 亿元 净利润: 13.2 亿元 净利率: 15.1% 股东权益: 173.8 亿元 经营CFO: 8.4 亿元
第二部分:核心财务指标——为什么这么设计?¶
每一个指标背后都有深刻的经济学逻辑。在计算之前,先理解它量化的是什么现象,为什么值得量化。
📊 ROE(净资产收益率)——股东最关心的指标¶
$$ROE = \frac{\text{净利润}}{\text{股东权益}}$$
为什么 ROE 最重要?
股东的钱(即股东权益)投入公司之后,能产生多少回报——这才是股东真正关心的事。 如果银行存款利率是 3%,而一家公司的 ROE 只有 2%,那么投资这家公司不如存银行。
巴菲特长期寻找 ROE > 15% 且稳定的公司。 护城河强大的公司(如可口可乐),ROE 可以持续高于 20%。
ROE 的 DuPont 拆解: $$ROE = \underbrace{\frac{\text{净利润}}{\text{营业收入}}}_{\text{净利率}} \times \underbrace{\frac{\text{营业收入}}{\text{总资产}}}_{\text{资产周转率}} \times \underbrace{\frac{\text{总资产}}{\text{股东权益}}}_{\text{财务杠杆}}$$
这告诉我们提高 ROE 的三种路径:提高利润率、提高周转率、or 加杠杆。
📊 ROA(总资产收益率)——管理层效率指标¶
$$ROA = \frac{\text{净利润}}{\text{总资产}}$$
为什么 ROA 和 ROE 不同?
ROE 看股东投入的回报,但公司还用了债权人(银行贷款)的钱。 ROA 看所有资产(包括借来的)的回报效率——更能反映管理层真实的运营能力。
一家公司可以靠高杠杆(借很多钱)提高 ROE,但 ROA 会更客观地揭示真相。
ROA 低但 ROE 高 → 公司高度依赖杠杆,财务风险较高
📊 EPS(每股收益)——每股能生产多少利润¶
$$EPS = \frac{\text{净利润}}{\text{总股数}}$$
为什么用每股而不是总量?
两家公司净利润都是 10 亿,但一家有 10 亿股,一家有 1 亿股。 对于持有 1 股的投资者来说,后者的投资价值更高。 EPS 使不同规模的公司具有可比性。
📊 P/E(市盈率)——市场愿意为利润付多少钱¶
$$P/E = \frac{\text{股价}}{\text{每股收益}} = \frac{\text{市值}}{\text{净利润}}$$
如何理解 P/E?
P/E = 20 意味着:你现在花 20 元,每年能分到 1 元利润(假设利润不增长)。 换句话说,需要 20 年 才能通过利润回本(不考虑增长情形下)。
P/E 越高代表什么?
- 市场预期未来利润增速高(愿意为成长付溢价)
- 或者市场情绪乐观(泡沫)
P/E 越低代表什么?
- 公司可能被低估(价值投资机会)
- 或者市场预期未来利润下滑(价值陷阱)
当整体市场 P/E 远超历史均值时,往往是熊市的前兆。
📊 P/B(市净率)——市场愿意为净资产付多少钱¶
$$P/B = \frac{\text{股价}}{\text{每股账面净资产}} = \frac{\text{市值}}{\text{股东权益}}$$
P/B 的直觉:如果今天公司清算,股东能按账面价值拿回净资产。 P/B = 1 表示市场价格 = 账面价值。 P/B < 1 意味着市场认为公司实际值比账面价值更少(可能是财务有问题)。
为什么 Fama-French 把低 P/B 作为价值因子? 因为历史上低 P/B(= 高账面市值比 B/M)的股票长期跑赢高 P/B 股票, 这被称为价值溢价(Value Premium)。
# 计算所有核心财务指标
df['ROE'] = df['net_income'] / df['equity']
df['ROA'] = df['net_income'] / df['total_assets']
df['EPS'] = df['net_income'] / df['shares']
df['PE'] = df['market_cap'] / df['net_income'].replace(0, np.nan)
df['PB'] = df['market_cap'] / df['book_value']
df['PS'] = df['market_cap'] / df['revenue'] # 市销率
df['leverage'] = df['total_liab'] / df['total_assets'] # 资产负债率
df['net_margin'] = df['net_income'] / df['revenue'] # 净利率
df['asset_turnover'] = df['revenue'] / df['total_assets'] # 资产周转率
df['current_ratio'] = df['current_assets'] / df['current_liab'] # 流动比率
# DuPont 验证: ROE ≈ 净利率 × 资产周转率 × 财务杠杆
df['ROE_decomposed'] = (df['net_margin'] * df['asset_turnover'] *
(df['total_assets'] / df['equity']))
print('核心财务指标统计:')
metrics_to_show = ['ROE', 'ROA', 'PE', 'PB', 'leverage', 'net_margin', 'current_ratio']
print(df[metrics_to_show].describe().round(3))
print()
print('DuPont 分解验证(ROE 与拆解结果之差,应接近0):')
print(f' 平均绝对误差: {(df.ROE - df.ROE_decomposed).abs().mean():.6f}')
核心财务指标统计:
ROE ROA PE PB leverage net_margin current_ratio
count 50.000 50.000 50.000 50.000 50.000 50.000 50.000
mean 0.166 0.067 25.329 4.322 0.558 0.153 1.628
std 0.127 0.049 10.033 3.996 0.155 0.054 0.806
min 0.020 0.007 9.065 0.500 0.303 0.067 0.737
25% 0.072 0.029 16.905 1.379 0.419 0.111 0.927
50% 0.157 0.051 25.129 2.780 0.586 0.136 1.324
75% 0.211 0.093 31.643 5.888 0.670 0.193 2.140
max 0.628 0.203 44.261 18.482 0.787 0.286 3.613
DuPont 分解验证(ROE 与拆解结果之差,应接近0):
平均绝对误差: 0.000000
# 可视化各指标分布及其经济学含义
fig, axes = plt.subplots(2, 3, figsize=(16, 8))
plots = [
('ROE', 'ROE 净资产收益率\n(股东最重要的指标,>15% 是优秀企业的门槛)', 'steelblue', 0.15),
('ROA', 'ROA 总资产收益率\n(衡量全部资源的使用效率,排除杠杆干扰)', 'darkorange', 0.08),
('PE', 'P/E 市盈率\n(越低=估值越便宜,但要警惕利润下滑的「价值陷阱」)', 'green', 20),
('PB', 'P/B 市净率\n(Fama-French 价值因子分母,<1 可能存在危机或机会)', 'red', 2.0),
('leverage', '资产负债率\n(越高=杠杆越高=财务风险越大,>70% 需警惕)', 'purple', 0.70),
('net_margin', '净利率\n(每赚 1 元收入保留多少,软件公司 >30%,制造业通常 <10%)', 'gray', 0.10),
]
for ax, (col, title, color, threshold) in zip(axes.flat, plots):
data = df[col].dropna()
ax.hist(data, bins=20, color=color, alpha=0.65, edgecolor='white')
ax.axvline(data.median(), color='red', linestyle='--', linewidth=2,
label=f'中位数={data.median():.2f}')
ax.axvline(threshold, color='black', linestyle=':', linewidth=1.5,
label=f'参考线={threshold}')
ax.set_title(title, fontsize=9)
ax.legend(fontsize=7)
ax.grid(alpha=0.3)
plt.suptitle('50 只模拟股票:核心财务指标分布', fontsize=13)
plt.tight_layout()
plt.show()
第三部分:Piotroski F-Score——为什么这 9 个条件?¶
背景:Joseph Piotroski 在 2000 年发表的论文发现, 许多低 P/B 的公司(传统「价值股」)其实是业绩持续恶化的「价值陷阱」。 他设计了一个 9 维财务健康评分来区分真正的低估值 vs 正在衰退的困境股。
每一条评分标准都对应一个具体的财务恶化信号。下面逐一解释为什么:
盈利能力(4 项)
| 指标 | 含义 | 为什么是 1 或 0 |
|---|---|---|
| F1: ROA > 0 | 总资产有正回报 | 公司最基本的生存线——连资产回报都为负,是严重危险信号 |
| F2: 经营 CFO > 0 | 主业产生真实现金 | 利润可以造假,现金流更难造假。CFO 为负意味着公司靠外部融资维持 |
| F3: ROA 同比改善 | 盈利能力好转 | 捕捉「公司是否走出低谷」。改善中的公司比持续恶化的公司更值得关注 |
| F4: CFO > 净利润(应计项目为负) | 现金质量高 | 若净利润高但 CFO 低,差距是「应计利润」——应收账款未收、存货积压等。应计项目多意味着未来利润可能被修正 |
杠杆与流动性(3 项)
| 指标 | 为什么 |
|---|---|
| F5: 长期负债比率下降 | 公司在去杠杆(降低财务风险),是努力改善财务结构的信号 |
| F6: 流动比率改善 | 流动比率 = 流动资产/流动负债,改善意味着短期偿债能力变强,不太可能发生流动性危机 |
| F7: 未增发新股 | 增发股票会稀释现有股东权益。不增发说明公司有内部资金来源,不需要「圈钱」 |
运营效率(2 项)
| 指标 | 为什么 |
|---|---|
| F8: 毛利率改善 | 毛利率 = (收入 - 成本)/收入,改善意味着产品定价权或成本控制在变好 |
| F9: 资产周转率改善 | 同样资产规模,能产生更多收入,说明运营效率提升 |
评分规则:满足 = 1 分,不满足 = 0 分,满分 9 分
- ≥ 8 分:财务基本面持续改善 → 候选多头
- ≤ 2 分:财务基本面全面恶化 → 候选空头(或回避)
# 计算 Piotroski F-Score(逐项说明)
# F1: ROA > 0(总资产收益率为正)
df['F1'] = (df['ROA'] > 0).astype(int)
# F2: 经营 CFO > 0(主业产生真实现金流)
df['F2'] = (df['operating_cf'] > 0).astype(int)
# F3: ROA 同比改善(用均值的 90% 模拟上期)
df['ROA_prev'] = df['ROA'] * np.random.uniform(0.7, 1.2, n) # 模拟上期 ROA
df['F3'] = (df['ROA'] > df['ROA_prev']).astype(int)
# F4: 应计利润率 < 0(CFO > 净利润,现金质量好)
# 应计利润率 = (净利润 - CFO) / 总资产
df['accruals'] = (df['net_income'] - df['operating_cf']) / df['total_assets']
df['F4'] = (df['accruals'] < 0).astype(int)
# F5: 长期负债率下降(去杠杆)
df['LTD_ratio'] = df['long_term_debt'] / df['total_assets']
df['LTD_ratio_py'] = df['long_term_debt_py'] / df['total_assets']
df['F5'] = (df['LTD_ratio'] < df['LTD_ratio_py']).astype(int)
# F6: 流动比率改善(模拟上期流动比率略低)
df['current_ratio_py'] = df['current_ratio'] * np.random.uniform(0.8, 1.1, n)
df['F6'] = (df['current_ratio'] > df['current_ratio_py']).astype(int)
# F7: 未增发新股(随机模拟:80% 未增发)
df['F7'] = np.random.choice([0, 1], n, p=[0.2, 0.8])
# F8: 毛利率改善
df['gross_margin'] = (df['revenue'] - df['revenue']*0.6) / df['revenue']
df['gross_margin_py'] = df['gross_margin'] * np.random.uniform(0.85, 1.05, n)
df['F8'] = (df['gross_margin'] > df['gross_margin_py']).astype(int)
# F9: 资产周转率改善
df['turnover_py'] = df['asset_turnover'] * np.random.uniform(0.85, 1.1, n)
df['F9'] = (df['asset_turnover'] > df['turnover_py']).astype(int)
# 汇总 F-Score
df['F_score'] = df[['F1','F2','F3','F4','F5','F6','F7','F8','F9']].sum(axis=1)
# 查看各条件通过率
print('各 F-Score 条件通过率:')
fscore_cols = {'F1':'ROA>0', 'F2':'CFO>0', 'F3':'ROA改善', 'F4':'现金质量',
'F5':'去杠杆', 'F6':'流动性改善', 'F7':'未增发', 'F8':'毛利率改善', 'F9':'周转率改善'}
for col, desc in fscore_cols.items():
rate = df[col].mean()
bar = '█' * int(rate * 20)
print(f' {col}({desc:10s}): {bar:<20s} {rate:.0%}')
各 F-Score 条件通过率: F1(ROA>0 ): ████████████████████ 100% F2(CFO>0 ): ████████████████████ 100% F3(ROA改善 ): ██████████████ 72% F4(现金质量 ): ███████████ 58% F5(去杠杆 ): ████████████ 64% F6(流动性改善 ): ████████████████ 80% F7(未增发 ): ████████████████ 82% F8(毛利率改善 ): ██████████████ 72% F9(周转率改善 ): █████████████ 66%
# F-Score 分布与策略验证
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 左图:F-Score 分布
df['F_score'].hist(bins=range(0, 11), ax=axes[0], color='steelblue',
alpha=0.75, edgecolor='white', rwidth=0.8)
axes[0].axvspan(7.5, 10, alpha=0.15, color='green', label='高质量(多头区 ≥8)')
axes[0].axvspan(-0.5, 2.5, alpha=0.15, color='red', label='低质量(空头区 ≤2)')
axes[0].axvline(df['F_score'].median(), color='black', linestyle='--',
lw=2, label=f'中位数={df.F_score.median():.0f}')
axes[0].set_title('Piotroski F-Score 分布(0~9分)')
axes[0].set_xlabel('F-Score')
axes[0].set_ylabel('股票数量')
axes[0].legend()
# 右图:F-Score 与财务指标的关系
score_groups = df.groupby('F_score')[['ROE', 'ROA', 'net_margin']].mean()
colors = ['red' if i <= 2 else ('green' if i >= 7 else 'gray') for i in score_groups.index]
axes[1].bar(score_groups.index, score_groups['ROE'], color=colors, alpha=0.8)
axes[1].set_title('F-Score vs 平均 ROE\n(验证:高分公司 ROE 是否确实更高?)')
axes[1].set_xlabel('F-Score')
axes[1].set_ylabel('平均 ROE')
axes[1].grid(alpha=0.3)
plt.suptitle('Piotroski F-Score:财务健康综合评分', fontsize=13)
plt.tight_layout()
plt.show()
high_f = df[df['F_score'] >= 7]
low_f = df[df['F_score'] <= 2]
mid_f = df[(df['F_score'] >= 3) & (df['F_score'] <= 6)]
print('\nF-Score 分层统计:')
print(f'高分(≥7): {len(high_f):2d} 只, 平均 ROE = {high_f.ROE.mean():.3f},'
f' 平均 ROA = {high_f.ROA.mean():.3f}')
print(f'中分(3-6): {len(mid_f):2d} 只, 平均 ROE = {mid_f.ROE.mean():.3f},'
f' 平均 ROA = {mid_f.ROA.mean():.3f}')
print(f'低分(≤2): {len(low_f):2d} 只, 平均 ROE = {low_f.ROE.mean():.3f},'
f' 平均 ROA = {low_f.ROA.mean():.3f}')
print()
print('结论:F-Score 高的公司,ROE 和 ROA 也系统性地更高,验证了指标的有效性。')
F-Score 分层统计: 高分(≥7): 35 只, 平均 ROE = 0.146, 平均 ROA = 0.062 中分(3-6): 15 只, 平均 ROE = 0.211, 平均 ROA = 0.078 低分(≤2): 0 只, 平均 ROE = nan, 平均 ROA = nan 结论:F-Score 高的公司,ROE 和 ROA 也系统性地更高,验证了指标的有效性。
🎯 练习¶
- 对 A 股上市公司(可用 akshare 或 adata 获取财务数据),计算所有成份股的 F-Score,观察 F-Score ≥ 8 的公司在下一年的平均涨幅是否优于 F-Score ≤ 2 的公司。
- 用 DuPont 公式将贵州茅台和中国石化的 ROE 分别拆解为净利率、资产周转率、杠杆三个分量,解释两家公司 ROE 相似但商业模式截然不同的原因。
- 将 F4(应计利润率)条件改为「CFO增长率 > 0」,对比两种定义下 F-Score 分布的差异。
下一节 → 02_value_factors.ipynb