4. 基础因子计算(一):估值类因子

估值因子是量化选股中最经典、最基础的因子类别。它们通过比较公司市值与财务指标(如净利润、净资产、营业收入、现金流)的相对关系,衡量证券的“贵贱”程度。本章将系统讲解四大核心估值因子——PE、PB、PS、PCF 的计算逻辑、行业中性化处理与极值处理方法。

4.1 四大估值因子定义与计算逻辑

每个估值因子均由“价格”与“基本面指标”的比值构成。下表总结了各因子的核心公式与业务含义:

因子名称 英文缩写 计算公式 核心含义
市盈率 PE 总市值 / 归母净利润(TTM) 每单位净利润对应的股价,反映盈利估值
市净率 PB 总市值 / 归母净资产(最新报告期) 每单位净资产对应的股价,反映资产估值
市销率 PS 总市值 / 营业总收入(TTM) 每单位营业收入对应的股价,反映营收估值
市现率 PCF 总市值 / 经营活动现金流净额(TTM) 每单位经营现金流对应的股价,反映现金流估值
TTM 说明:TTM(Trailing Twelve Months)即滚动十二个月数据。计算时使用最近四个季度的财务数据加总,确保财务指标与股价在时间窗口上对齐。对于最新报告期数据,若季报已披露则使用季报,否则使用年报。

4.2 计算逻辑详解

在实际因子计算中,需注意以下关键步骤与数据对齐规则:

  1. 市值数据对齐:使用计算日当天的总市值(股价 × 总股本),确保与财务数据日期匹配。
  2. 财务数据获取:从数据库提取最近四个季度的净利润、净资产、营业收入、经营现金流。净资产使用最新报告期(非TTM),其余使用TTM。
  3. 缺失值处理:若财务数据缺失或为0(如净利润为负),则对应因子值设为NaN,后续在分层时剔除。
  4. 负值处理: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 行业中性化,步骤如下:

  1. 将股票按申万一级行业(或自定义行业分类)分组。
  2. 在每个行业内,计算因子值的均值(μ)与标准差(σ)。
  3. 对每个股票,计算行业中性化后的因子值:
    Z = (X - μ) / σ,其中 X 为原始因子值。
  4. 若行业样本数过少(如少于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法 处理估值因子,因其对异常值鲁棒性更强。具体步骤:

  1. 计算因子值的中位数(M)和中位数绝对偏差(MAD)。
  2. 设定阈值倍数(通常为3~5倍)。
  3. 将超出 [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 估值因子处理流程总览

下图展示了从原始数据到最终可用因子的完整处理流水线:

估值因子处理流程 原始数据 因子计算 缺失值处理 行业中性化(Z-Score) 极值处理(MAD) 最终可用因子 处理顺序说明: 1. 先计算原始因子值(PE/PB/PS/PCF) 2. 剔除财务数据缺失或为0的样本 3. 进行行业中性化 → 再执行极值处理(推荐顺序)

4.6 常见问题与避坑指南

  • 市值加权问题:计算行业均值时,是否使用市值加权?建议使用等权均值,因为市值加权会使中性化结果偏向大盘股,削弱因子选股能力。
  • 负值因子处理:PE为负时,Z-Score中性化会将其映射到负值区域,但排序逻辑仍然有效(负PE代表亏损,估值逻辑不同)。建议保留负值,不做特殊截断。
  • 行业分类时效性:行业分类每年可能调整,需使用因子计算日当天的行业分类,避免使用历史分类导致中性化偏差。
  • 极值处理参数:MAD倍数建议在3~5之间选择。倍数过小会丢失有效信息,倍数过大则极值处理效果不足。可通过回测对比不同参数下的因子IC表现来确定最优值。
实战建议:在构建因子库时,建议同时保留原始因子值、行业中性化后因子值、极值处理后因子值三个版本。后续分层试验时,可以对比不同处理步骤对因子选股效果的影响,从而确定最优处理流程。