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没有经济含义,你没法说一个亏损的公司是「便宜」还是「贵」。
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,才认为这个因子值得进一步研究。
3.3 分层回测:直观展示因子的区分度
IC分析告诉我们因子有没有预测能力,但没告诉我们这个能力有多强。分层回测就是用来解决这个问题的。
做法很简单:
- 在每个调仓日,把所有股票按因子值从小到大排序。
- 分成N组(通常是5组或10组)。第1组是因子值最小的(最便宜),第N组是因子值最大的(最贵)。
- 每组等权或市值加权构建投资组合。
- 持有到下个调仓日,计算每组的收益。
- 重复以上步骤,得到每组的历史收益曲线。
如果因子有效,我们会看到:第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组)能稳定赚钱,这个因子就有价值。
最后,我想说一句:估值因子虽然经典,但绝不是万能的。在牛市中,估值因子往往跑输成长因子。在熊市中,估值因子又可能因为「价值陷阱」而暴跌。所以,因子投资不是找到一个因子就一劳永逸,而是要持续跟踪、动态调整。
下一章,我们会继续深入单因子测试,聊聊动量因子和反转因子的构建与检验。到时候,我会分享一些我在实盘中踩过的坑,希望能帮你少走弯路。