3. 单因子构建与测试(上):估值因子(PE、PB、PS)的构建、IC分析、分层回测

好,我们正式开始动手了。

前面两章聊了因子投资的底层逻辑和行业轮动的框架。现在,咱们得把袖子撸起来,真正去构建一个因子,然后看看它到底有没有用。

这一章,我选估值因子作为切入点。为什么?因为估值因子是最直观、最经典的因子之一。说白了,就是买便宜货。但便宜货怎么定义?怎么量化?怎么验证它真的能赚钱?这就是我们今天要解决的问题。

3.1 估值因子的构建:PE、PB、PS

估值因子,核心思想就是「低买高卖」。但「低」和「高」是相对的,我们需要一个标尺。

常用的标尺有三个:市盈率(PE)、市净率(PB)、市销率(PS)。

  • PE(市盈率):市值 / 净利润。最常用,但净利润容易受非经常性损益影响。我见过不少公司,靠卖一套房子把PE做得很低,结果第二年主业亏损,PE直接变负。所以,用PE的时候,我一般会用「扣非净利润」来算。
  • PB(市净率):市值 / 净资产。适合银行、保险这类重资产行业。但轻资产公司(比如互联网)的净资产很少,PB会很高,参考意义不大。
  • PS(市销率):市值 / 营业收入。这个指标不容易被财务操纵。对于还没盈利的成长型公司,PS比PE更靠谱。

构建因子时,我们通常取这些指标的倒数,或者做行业中性化处理。为什么?因为不同行业的估值水平天然不同。银行业的PE常年个位数,科技股的PE动辄几十倍。如果不做处理,你选出来的全是银行股,那就不是行业轮动了,是行业集中。

我个人习惯的做法是:先计算每个股票在行业内的估值百分位。比如,某只银行股的PE在全部银行股中排前10%,那它的估值百分位就是0.1。然后,用1减去这个百分位,得到「估值得分」。得分越高,说明越便宜。

嗯,这里要注意:对于PE为负的公司,直接剔除。负PE没有经济含义,你没法说一个亏损的公司是「便宜」还是「贵」。

避坑指南:我曾经在构建PB因子时,没有剔除净资产为负的公司。结果回测收益奇高无比。后来一查,全是ST股和濒临破产的公司。这些公司虽然PB极低,但风险极大,根本不是我们想要的「便宜货」。

3.2 IC分析:因子有效性的第一道检验

因子构建好了,怎么知道它有没有预测能力?

IC(信息系数)就是干这个的。它衡量的是因子值与未来收益之间的相关性。

具体来说,我们计算每个时间点(比如每月末)的因子值与下个月收益的秩相关系数(Spearman Rank Correlation)。

为什么用秩相关而不是皮尔逊相关?因为秩相关对异常值不敏感。你想想看,万一某只股票因为黑天鹅事件暴跌,它的收益会是一个极端值,皮尔逊相关会被这个值带偏,但秩相关只看排名,影响小得多。

IC的解读很简单:

  • IC > 0:因子值与未来收益正相关。因子值越大,未来收益越高。
  • IC < 0:因子值与未来收益负相关。因子值越小,未来收益越高。
  • IC = 0:没有预测能力。

对于估值因子,我们通常期望IC为负。因为估值越低(因子值越小),未来收益越高。这就是所谓的「价值溢价」。

但光看IC的均值还不够。我们还要看IC的稳定性。一个因子,如果IC均值是0.05,但标准差是0.1,那它的IR(信息比率,IC均值/IC标准差)只有0.5,说明预测能力很不稳定。我个人一般要求IR > 0.5,才认为这个因子值得进一步研究。

实战经验:我在做A股估值因子时,发现PE因子的IC在每年4月和10月会明显变弱。后来一查,4月是年报披露季,10月是三季报披露季。财报披露后,市场会重新定价,估值因子的预测能力会暂时失效。所以,做因子择时也是很有必要的。

3.3 分层回测:直观展示因子的区分度

IC分析告诉我们因子有没有预测能力,但没告诉我们这个能力有多强。分层回测就是用来解决这个问题的。

做法很简单:

  1. 在每个调仓日,把所有股票按因子值从小到大排序。
  2. 分成N组(通常是5组或10组)。第1组是因子值最小的(最便宜),第N组是因子值最大的(最贵)。
  3. 每组等权或市值加权构建投资组合。
  4. 持有到下个调仓日,计算每组的收益。
  5. 重复以上步骤,得到每组的历史收益曲线。

如果因子有效,我们会看到:第1组的收益最高,第N组的收益最低,且各组收益单调递减。这就是所谓的「单调性」。

如果第1组和第2组收益差不多,但第5组收益明显低,说明因子只能区分极端便宜的股票,对中间区域的区分度不够。这种因子也可以用,但效果会打折扣。

下面是一个简单的分层回测代码示例:

import pandas as pd
import numpy as np

def分层回测(factor_df, return_df, n_groups=5):
    """
    factor_df: 因子值,index为日期,columns为股票代码
    return_df: 未来收益,index为日期,columns为股票代码
    """
    results = {}
    for date in factor_df.index:
        # 获取当前日期的因子值和收益
        factors = factor_df.loc[date].dropna()
        returns = return_df.loc[date]
        
        # 按因子值排序并分组
        sorted_factors = factors.sort_values()
        groups = np.array_split(sorted_factors.index, n_groups)
        
        # 计算每组收益
        for i, group in enumerate(groups):
            group_returns = returns[group].mean()
            results.setdefault(f'Group_{i+1}', []).append(group_returns)
    
    # 转换为DataFrame
    result_df = pd.DataFrame(results, index=factor_df.index)
    return result_df

# 使用示例
# result = 分层回测(pe_factor, forward_returns, 5)
# result.cumsum().plot()  # 绘制累计收益曲线

这段代码虽然简单,但已经能跑通整个流程了。实际项目中,你还需要处理停牌、涨跌停、ST股等特殊情况。嗯,这些细节我会在后面的章节中逐步展开。

3.4 本章小结:估值因子的实战要点

好了,我们来捋一捋今天的内容。

估值因子的构建,核心在于「可比性」。不同行业的估值不能直接比,要做行业中性化。不同时间点的估值也不能直接比,要做时间序列标准化。

IC分析是因子有效性的第一道检验。IC的均值、标准差、IR,这三个指标要一起看。IC均值高但标准差大,说明因子不稳定,容易翻车。

分层回测是因子有效性的直观展示。单调性是最理想的情况,但实际中往往只能做到「两端分化」。这没关系,只要多空组合(第1组减第N组)能稳定赚钱,这个因子就有价值。

最后,我想说一句:估值因子虽然经典,但绝不是万能的。在牛市中,估值因子往往跑输成长因子。在熊市中,估值因子又可能因为「价值陷阱」而暴跌。所以,因子投资不是找到一个因子就一劳永逸,而是要持续跟踪、动态调整。

下一章,我们会继续深入单因子测试,聊聊动量因子和反转因子的构建与检验。到时候,我会分享一些我在实盘中踩过的坑,希望能帮你少走弯路。

个人建议:刚开始做因子研究时,不要追求复杂的模型。先把PE、PB、PS这三个最基础的因子吃透,理解它们的逻辑和局限。等你对因子有了直觉,再去尝试机器学习、深度学习这些高阶方法,会事半功倍。
估值因子构建与测试流程 数据准备 获取PE/PB/PS数据 因子构建 行业中性化 + 标准化 IC分析 计算秩相关系数 分层回测 分组计算收益曲线 结果评估 检查单调性 & 多空收益 决策 因子有效 → 纳入因子库 图:估值因子从构建到验证的完整流程

蓝海数据掘金营,专注资料整理