4. 基础因子计算(一):估值类因子
估值因子是量化选股中最经典、最基础的因子类别。它们通过比较公司市值与财务指标(如净利润、净资产、营业收入、现金流)的相对关系,衡量证券的“贵贱”程度。本章将系统讲解四大核心估值因子——PE、PB、PS、PCF 的计算逻辑、行业中性化处理与极值处理方法。
4.1 四大估值因子定义与计算逻辑
每个估值因子均由“价格”与“基本面指标”的比值构成。下表总结了各因子的核心公式与业务含义:
| 因子名称 | 英文缩写 | 计算公式 | 核心含义 |
|---|---|---|---|
| 市盈率 | PE | 总市值 / 归母净利润(TTM) | 每单位净利润对应的股价,反映盈利估值 |
| 市净率 | PB | 总市值 / 归母净资产(最新报告期) | 每单位净资产对应的股价,反映资产估值 |
| 市销率 | PS | 总市值 / 营业总收入(TTM) | 每单位营业收入对应的股价,反映营收估值 |
| 市现率 | PCF | 总市值 / 经营活动现金流净额(TTM) | 每单位经营现金流对应的股价,反映现金流估值 |
TTM 说明:TTM(Trailing Twelve Months)即滚动十二个月数据。计算时使用最近四个季度的财务数据加总,确保财务指标与股价在时间窗口上对齐。对于最新报告期数据,若季报已披露则使用季报,否则使用年报。
4.2 计算逻辑详解
在实际因子计算中,需注意以下关键步骤与数据对齐规则:
- 市值数据对齐:使用计算日当天的总市值(股价 × 总股本),确保与财务数据日期匹配。
- 财务数据获取:从数据库提取最近四个季度的净利润、净资产、营业收入、经营现金流。净资产使用最新报告期(非TTM),其余使用TTM。
- 缺失值处理:若财务数据缺失或为0(如净利润为负),则对应因子值设为NaN,后续在分层时剔除。
- 负值处理:PE、PCF可能出现负值(亏损企业),通常保留负值作为信息,但需在极值处理时单独考虑。
以下为 Python 伪代码示例,展示 PE 因子的计算过程:
import pandas as pd
import numpy as np
def calc_pe_factor(market_cap: pd.Series, net_profit_ttm: pd.Series) -> pd.Series:
"""
计算市盈率因子
:param market_cap: 总市值序列
:param net_profit_ttm: 归母净利润TTM序列
:return: PE因子序列
"""
# 避免除零
pe = np.where(net_profit_ttm != 0, market_cap / net_profit_ttm, np.nan)
# 将无穷大替换为NaN
pe = np.where(np.isinf(pe), np.nan, pe)
return pd.Series(pe, index=market_cap.index)
关键注意点:计算时务必使用“归母净利润”而非“合并净利润”,因为市值对应的是母公司股东权益。使用合并净利润会导致PE被低估。
4.3 行业中性化处理
不同行业的估值水平天然存在巨大差异(如银行股PE普遍低于科技股)。若不进行行业中性化,因子选股结果将严重偏向低估值行业,而非行业内选股。行业中性化的核心目标是:剔除行业系统性差异,保留个股在行业内的相对估值信息。
常用方法为 Z-Score 行业中性化,步骤如下:
- 将股票按申万一级行业(或自定义行业分类)分组。
- 在每个行业内,计算因子值的均值(μ)与标准差(σ)。
- 对每个股票,计算行业中性化后的因子值:
Z = (X - μ) / σ,其中 X 为原始因子值。 - 若行业样本数过少(如少于5只),则该行业股票的中性化值设为NaN。
以下为行业中性化处理的代码示例:
def industry_neutralize(factor: pd.Series, industry: pd.Series) -> pd.Series:
"""
行业Z-Score中性化
:param factor: 原始因子值序列
:param industry: 行业分类序列
:return: 中性化后的因子值
"""
def zscore_group(group):
if len(group) < 5:
return pd.Series(np.nan, index=group.index)
return (group - group.mean()) / group.std()
neutralized = factor.groupby(industry).transform(zscore_group)
return neutralized
行业分类选择:建议使用申万一级行业(28个)或GICS行业分类。行业分类不宜过细(避免样本不足),也不宜过粗(中性化效果差)。对于综合类行业,可考虑剔除或单独处理。
4.4 极值处理
估值因子常因极端财务数据(如微利企业导致PE极高)或数据错误而产生异常值。极值处理旨在降低这些异常值对后续分层和模型训练的影响。常用方法包括:
| 方法名称 | 处理逻辑 | 适用场景 |
|---|---|---|
| MAD法 | 以中位数为中心,剔除超过 n 倍中位数绝对偏差的值 | 因子分布偏态严重时(如PE) |
| 百分位截尾法 | 将上下 x% 的极值替换为对应百分位值 | 因子分布较对称时 |
| 标准差截尾法 | 剔除超过均值 ± n 倍标准差的值 | 因子近似正态分布时 |
推荐使用 MAD法 处理估值因子,因其对异常值鲁棒性更强。具体步骤:
- 计算因子值的中位数(M)和中位数绝对偏差(MAD)。
- 设定阈值倍数(通常为3~5倍)。
- 将超出 [M - n×MAD, M + n×MAD] 的值替换为边界值(截尾)或设为NaN(剔除)。
def mad_winsorize(factor: pd.Series, n: float = 5.0) -> pd.Series:
"""
MAD法截尾处理
:param factor: 因子值序列
:param n: MAD倍数阈值
:return: 截尾后的因子值
"""
median = factor.median()
mad = np.median(np.abs(factor - median))
lower = median - n * mad
upper = median + n * mad
return factor.clip(lower, upper)
处理顺序建议:先进行行业中性化,再进行极值处理。因为中性化后的Z-Score分布更接近标准正态,极值处理效果更稳定。若先处理极值再中性化,可能破坏行业内的相对排序。
4.5 估值因子处理流程总览
下图展示了从原始数据到最终可用因子的完整处理流水线:
4.6 常见问题与避坑指南
- 市值加权问题:计算行业均值时,是否使用市值加权?建议使用等权均值,因为市值加权会使中性化结果偏向大盘股,削弱因子选股能力。
- 负值因子处理:PE为负时,Z-Score中性化会将其映射到负值区域,但排序逻辑仍然有效(负PE代表亏损,估值逻辑不同)。建议保留负值,不做特殊截断。
- 行业分类时效性:行业分类每年可能调整,需使用因子计算日当天的行业分类,避免使用历史分类导致中性化偏差。
- 极值处理参数:MAD倍数建议在3~5之间选择。倍数过小会丢失有效信息,倍数过大则极值处理效果不足。可通过回测对比不同参数下的因子IC表现来确定最优值。
实战建议:在构建因子库时,建议同时保留原始因子值、行业中性化后因子值、极值处理后因子值三个版本。后续分层试验时,可以对比不同处理步骤对因子选股效果的影响,从而确定最优处理流程。