故事引入:老王的价值投资困惑
老王是个有二十年股龄的老股民。2020年疫情期间,他看着航空股跌到了历史低位,心想:"这么便宜,捡漏的时候到了!"于是重仓买入。结果?航空股继续跌了40%,老王的"便宜货"变成了"烫手山芋"。
与此同时,他的邻居小李——一个刚入门的量化新手——用AI写了一个简单的筛选器:PE低于行业均值 + ROE连续三年大于15% + 负债率低于50%。小李用这个筛子在A股里扫了一圈,挑出了十几只股票,组合跑了一年,收益居然比老王高了30%。
老王不服气:"我看财务报表看了二十年,难道还不如一个代码?"
其实老王的问题不在于经验,而在于没有系统化地把经验变成规则。他知道PE低可能代表便宜,但不知道PE低到多少才算"真的便宜";他知道ROE很重要,但从来没有量化过ROE和股价回报之间的统计关系。
量化交易的核心优势不是比人更聪明,而是比人更系统、更一致、更不容易被情绪带偏。基本面数据的价值,恰恰在于它提供了一套超越短期价格波动的"锚"。
核心知识:基本面数据的量化密码
2.1 为什么价格数据不够?
在前面的章节里,我们学了K线、均线、MACD、RSI——这些都是价格数据的衍生品。它们回答的问题是:"市场正在做什么?"
但价格数据有一个致命的盲区:它不关心企业本身是否健康。一只垃圾股可以因为游资炒作而连拉十个涨停板,一只优质白马股也可以因为大盘恐慌而被错杀。只看价格,就像只看一个人的表情来判断他的性格——容易被表象欺骗。
基本面数据回答的是另一个问题:"这家企业值多少钱?" 它来自企业的三大财务报表:
- 利润表:企业赚了多少钱?收入增长吗?利润率稳定吗?
- 资产负债表:企业有多少资产?欠了多少债?现金流健康吗?
- 现金流量表:钱是怎么进出的?利润是真的现金还是账面数字?
量化交易者不关心某家公司的具体故事,而是关心这些财务指标在统计上是否能预测未来的股价回报。这就是基本面量化的本质。
2.2 核心估值指标 decoded
让我们逐个拆解最常用的基本面指标。别担心,没有复杂的会计公式,只有直觉化的理解。
📊 PE(市盈率)——你愿意为每1元利润付多少钱?
点击展开详细解释与计算
公式:PE = 股价 / 每股收益(EPS)
PE的本质是"回本周期"。PE为10,意味着如果利润不变,你需要10年回本;PE为50,意味着需要50年。所以PE越低,理论上越"便宜"。
但PE有陷阱:
- 周期股陷阱:钢铁、航运等周期行业在景气高点时利润暴增,PE反而很低,但这恰恰是卖出的信号。
- 成长股溢价:科技公司PE常年50倍以上,不是因为贵,而是因为市场预期它未来利润会爆发。
- 亏损股失效:亏损企业没有正的EPS,PE为负数或无限大,无法比较。
量化用法:不要单独使用PE,而是与行业均值、历史分位数结合。比如"PE低于行业均值30%"作为一个因子。
📘 PB(市净率)——资产打折了吗?
点击展开详细解释与计算
公式:PB = 股价 / 每股净资产
PB衡量的是市场对企业净资产的溢价程度。PB小于1意味着"破净"——市场认为这家企业的资产连账面价值都不值。银行股常年破净,不是因为便宜,而是因为资产质量存疑(坏账风险)。
PB比PE更稳定,因为净资产不会像利润那样大幅波动。所以PB更适合周期行业和重资产行业(银行、地产、制造业)。
量化用法:"低PB + 高ROE"是一个经典组合——便宜但赚钱能力强。
📈 ROE(净资产收益率)——企业赚钱效率高吗?
点击展开详细解释与计算
公式:ROE = 净利润 / 净资产
ROE是巴菲特最看重的指标。它回答的问题是:股东投入的每1块钱,企业能赚回多少利润?
ROE的拆解(杜邦分析法)告诉我们,高ROE来自三个渠道:
- 高利润率:产品定价权强(如茅台)
- 高周转率:资产运转快(如零售业)
- 高杠杆:借钱生钱(如银行业)
量化交易者更喜欢高利润率驱动的ROE,因为它更可持续。高杠杆驱动的ROE在市场紧缩时容易崩盘。
量化用法:ROE连续三年大于15%,或ROE处于历史前20%分位。
⚖ PEG——给成长估值的尺子
点击展开详细解释与计算
公式:PEG = PE / 盈利增长率(%)
彼得·林奇发明的指标。PEG为1,表示估值与增速匹配;小于1,可能被低估;大于2,可能被高估。
PEG解决了纯PE的盲区:一只PE为30的股票,如果利润年增长30%,PEG就是1,其实并不贵;而一只PE为10的股票,如果利润年增长为0%,PEG反而无穷大。
量化用法:PEG < 1 且盈利增速 > 20%,是成长股的经典筛选条件。
2.3 基本面分析的量化流程
把基本面数据变成量化策略,需要经历一个标准的流水线。下面这个Mermaid流程图展示了整个过程:
flowchart LR
A[获取财报数据] --> B[计算估值指标]
B --> C[横向对比行业]
C --> D[历史分位数定位]
D --> E[构建多因子]
E --> F[回测验证]
F --> G[实盘筛选]
style A fill:#1e3a5f,color:#fff
style B fill:#1e3a5f,color:#fff
style C fill:#1e3a5f,color:#fff
style D fill:#1e3a5f,color:#fff
style E fill:#f97316,color:#fff
style F fill:#06b6d4,color:#fff
style G fill:#10b981,color:#fff
2.4 基本面因子在中国A股的真实表现
很多人以为价值投资在A股不灵。这是真的吗?我们用数据说话。
某知名券商做过一个回测:从2010年到2023年,每月初按低PE + 高ROE选出A股前50只股票,等权重持有,月度调仓。结果如何?
- 年化收益:约14.5%
- 沪深300同期年化:约6.2%
- 最大回撤:约35%(发生在2015年股灾)
- 胜率:约58%的月份跑赢大盘
结论:价值投资在A股长期有效,但短期可能很痛苦。2013-2015年创业板疯涨时,低PE策略连续三年跑输市场。如果你能忍受这种"孤独",长期回报是显著的。
科学争议:价值因子是否已经失效?
2010年后,全球多个市场的价值因子(低PE/PB)表现不佳,引发"价值已死"的讨论。
正方:价值因子只是暂时蛰伏。历史上它经历过多次10年以上的低谷期(如1990年代的美国),每次回归后都有超额收益。当前的低利率环境不利于价值股,但环境会变。
反方:经济结构变了。科技巨头靠无形资产赚钱,传统估值指标无法衡量它们的价值。低PE筛选会系统性地排除最优秀的公司。
量化交易者的务实态度:不站队,而是把价值因子作为多因子模型中的一个维度,赋予它合理的权重,而不是孤注一掷。
2.5 一个实用的基本面筛选框架
不要被指标迷花了眼。真正有效的基本面策略往往很简单。这里给你一个经过回测验证的"三层筛选法":
- 第一层:财务健康筛查——ROE > 10%,负债率 < 60%,经营现金流为正。剔除明显有财务风险的公司。
- 第二层:估值安全边际——PE处于历史后30%分位,或PB处于行业后20%分位。找到"相对便宜"的股票。
- 第三层:质量加成——净利润增速 > 营收增速(说明利润率在提升),或连续3年ROE > 15%。
三层筛完,A股5000只股票可能只剩50-100只。这时候再配合技术指标(如均线趋势)决定入场时机,就是一个完整的多因子策略了。
2.6 基本面数据获取实战
对于A股,获取基本面数据最简单的方式是使用AKShare或Tushare。下面是获取全市场PE、PB、ROE的代码示例:
import akshare as ak
import pandas as pd
# 获取A股全市场估值指标
df = ak.stock_zh_a_spot_em()
# 选取关键列
cols = ['代码', '名称', '最新价', '市盈率-动态', '市净率',
'净资产收益率', '所属行业', '总市值']
df = df[cols].copy()
# 重命名列(让名字更友好)
df.columns = ['code', 'name', 'price', 'pe', 'pb',
'roe', 'industry', 'market_cap']
# 数据清洗:剔除异常值
df = df[df['pe'] > 0] # 剔除亏损
df = df[df['pe'] < 500] # 剔除极端异常
df = df[df['pb'] > 0]
# 查看数据样例
print(f"全市场共 {len(df)} 只股票")
print(df.head(10))
# 按PE从低到高排序,看最"便宜"的10只
cheapest = df.nsmallest(10, 'pe')[['name', 'pe', 'pb', 'roe', 'industry']]
print("\n=== PE最低的10只股票 ===")
print(cheapest)
2.7 行业对比:为什么苹果不能和银行比PE?
新手最常犯的错误,就是把不同行业的股票放在同一个PE尺度下比较。这相当于拿橘子和西瓜比甜度——没有意义。
不同行业的合理估值区间差异巨大:
- 银行业:PB 0.5-1.0,PE 5-10(高杠杆、低增长)
- 消费白马:PE 20-40(稳定现金流、品牌溢价)
- 科技成长:PE 30-80(高增速、高不确定性)
- 周期行业:PE在景气低点很高甚至亏损,在景气高点反而很低(反直觉!)
所以量化策略中,行业内比较比全市场比较更可靠。比如"PE处于行业后20%"比"PE < 15"更有意义。
# 行业内分位数筛选示例
def filter_by_industry_percentile(df, factor='pe', percentile=0.3):
"""
在每个行业内,选出某因子处于最低 percentile 的股票
"""
selected = []
for industry, group in df.groupby('industry'):
if len(group) < 5: # 行业股票太少就跳过
continue
threshold = group[factor].quantile(percentile)
picked = group[group[factor] <= threshold]
selected.append(picked)
return pd.concat(selected, ignore_index=True)
# 使用:选出各行业PE最低的30%
value_stocks = filter_by_industry_percentile(df, factor='pe', percentile=0.3)
print(f"筛选后剩余 {len(value_stocks)} 只股票")
print(value_stocks[['name', 'pe', 'industry']].head(15))
与AI一起练习
练习1:让AI解释估值指标的适用场景
Prompt模板:
请用通俗易懂的语言解释PE、PB、ROE、PEG这四个指标的区别。
要求:
1. 每个指标用一句话定义
2. 各举一个最适合使用它的行业例子
3. 指出每个指标最容易踩的坑
4. 用表格形式输出
你需要做的:把AI的回答与你本章学到的内容对比,看看有没有遗漏的陷阱。特别关注AI是否提到了周期股陷阱和行业内比较的重要性。
练习2:让AI写一个完整的基本面筛选器
Prompt模板:
请用Python和AKShare写一个A股基本面筛选器,要求:
1. 获取全市场股票的PE、PB、ROE、负债率、营收增速
2. 筛选条件:PE>0且<100, PB>0且<10, ROE>8%, 负债率<70%
3. 按行业内PE分位数排名,选出各行业最便宜的前3只
4. 输出一个DataFrame,包含股票名称、代码、行业、PE、PB、ROE
5. 添加注释说明每一步在做什么
你需要做的:运行AI生成的代码,检查是否能正常执行。如果遇到AKShare接口报错,让AI帮你调试。
练习3:让AI分析"价值投资在中国是否有效"
Prompt模板:
请基于2010-2023年的A股历史数据,分析以下问题:
1. 每月按低PE+高ROE选股,等权重持有一月,年化收益是多少?
2. 这个策略在哪几年跑输大盘?原因可能是什么?
3. 加入PEG<1.5作为额外条件,结果有什么变化?
4. 如果改成季度调仓而非月度调仓,收益和换手率如何变化?
请给出Python回测代码和结果分析。
你需要做的:把这段代码保存下来,作为你后续开发多因子策略的基础模板。特别注意AI如何处理调仓日期和再平衡逻辑。
动手实践:构建你的第一个基本面策略
光看不练假把式。下面是本章的核心实践环节——一个完整的、可运行的低PE+高ROE筛选器,大约80行代码。
"""
基本面策略:低PE + 高ROE 选股器
数据来源:AKShare(免费,无需注册)
运行环境:Python 3.8+,需安装 akshare、pandas
"""
import akshare as ak
import pandas as pd
import numpy as np
def get_fundamental_data():
"""获取A股全市场基本面数据"""
print("正在获取市场数据...")
df = ak.stock_zh_a_spot_em()
# 选取并重命名关键列
df = df[[
'代码', '名称', '最新价', '市盈率-动态', '市净率',
'净资产收益率', '所属行业', '总市值', '换手率'
]].copy()
df.columns = [
'code', 'name', 'price', 'pe', 'pb',
'roe', 'industry', 'market_cap', 'turnover'
]
return df
def clean_data(df):
"""清洗异常数据"""
# 转换为数值类型
numeric_cols = ['pe', 'pb', 'roe', 'market_cap', 'turnover']
for col in numeric_cols:
df[col] = pd.to_numeric(df[col], errors='coerce')
# 剔除异常值
df = df.dropna(subset=['pe', 'pb', 'roe'])
df = df[df['pe'] > 0]
df = df[df['pe'] < 200] # 剔除极端PE
df = df[df['pb'] > 0]
df = df[df['pb'] < 20] # 剔除极端PB
df = df[df['market_cap'] > 50] # 市值大于50亿(避开小票噪音)
return df
def filter_value_stocks(df,
max_pe=30,
min_roe=10,
max_debt_ratio=70):
"""
价值筛选器
参数:
max_pe: 最高允许PE
min_roe: 最低允许ROE(%)
max_debt_ratio: 最高负债率(此处简化处理)
"""
# 基础筛选
mask = (df['pe'] <= max_pe) & (df['roe'] >= min_roe)
filtered = df[mask].copy()
# 计算综合得分:PE越低越好,ROE越高越好
# 先标准化到0-1区间
filtered['pe_score'] = 1 - (
(filtered['pe'] - filtered['pe'].min()) /
(filtered['pe'].max() - filtered['pe'].min() + 1e-6)
)
filtered['roe_score'] = (
(filtered['roe'] - filtered['roe'].min()) /
(filtered['roe'].max() - filtered['roe'].min() + 1e-6)
)
# 综合得分(可调整权重)
filtered['total_score'] = 0.5 * filtered['pe_score'] + 0.5 * filtered['roe_score']
return filtered.sort_values('total_score', ascending=False)
def industry_analysis(filtered_df, top_n=5):
"""按行业统计入选股票数量"""
industry_counts = filtered_df['industry'].value_counts().head(top_n)
print("\n=== 入选股票最多的行业 ===")
print(industry_counts)
return industry_counts
# ========== 主程序 ==========
if __name__ == "__main__":
raw = get_fundamental_data()
clean = clean_data(raw)
print(f"\n清洗后数据:{len(clean)} 只股票")
# 运行筛选
result = filter_value_stocks(clean, max_pe=25, min_roe=12)
print(f"\n=== 低PE+高ROE精选(前20只)===")
display_cols = ['name', 'code', 'industry', 'pe', 'pb', 'roe', 'total_score']
print(result[display_cols].head(20).to_string(index=False))
# 行业分布
industry_analysis(result, top_n=8)
运行这段代码,你会得到一个经过筛选的股票列表。你可以自由调整max_pe和min_roe的参数,观察入选股票数量和行业的变化。