3、单因子构建基础:因子定义与逻辑、因子计算函数封装、因子值标准化与中性化处理

各位同学,今天咱们聊聊单因子构建。说实话,这是整个量化策略的基石。你因子挖得不好,后面再怎么调参、再怎么风控,都是白搭。我见过太多人一上来就搞复杂模型,结果因子本身逻辑都说不通,那不就是空中楼阁吗?

这一节,我会带着大家从最基础的东西开始。因子定义、计算函数封装、标准化和中性化处理。嗯,每一步都有坑,我会把我在实战中踩过的雷都告诉你。

3.1 因子定义与逻辑:别小看这一步

什么是因子?说白了,因子就是能预测股票未来收益的某种特征。比如市盈率、动量、换手率,这些都是因子。但关键不在于你选什么因子,而在于你怎么定义它。

我个人习惯,定义因子前先问自己三个问题:

  • 这个因子有经济含义吗? 比如“过去5日涨幅”,它代表短期动量。这说得通。
  • 数据源可靠吗? 你用的行情数据、财务数据,有没有幸存者偏差?
  • 计算逻辑是否可复现? 换一个人用同样的数据,能不能算出同样的结果?

举个例子。假设我们要定义一个“日内振幅因子”。逻辑很简单:

# 因子定义:日内振幅 = (最高价 - 最低价) / 前日收盘价
def amplitude_factor(high, low, prev_close):
    return (high - low) / prev_close

你看,这个因子逻辑清晰,数据也好拿。但我在项目中遇到过一个问题:如果前日收盘价为0怎么办?比如新股上市第一天。嗯,这里要注意,必须做异常值处理。

避坑指南: 我曾经因为没处理除零错误,导致整个因子矩阵出现NaN值,回测结果直接崩了。后来我养成了习惯,所有因子计算前先检查分母是否为零。

3.2 因子计算函数封装:写一次,用一辈子

你想想看,如果你每次计算因子都重新写一遍代码,那效率得多低?我建议把因子计算封装成函数,参数化、模块化。这样不仅方便复用,也便于后续的因子测试和组合。

这里我给出一个通用的因子计算框架:

import pandas as pd
import numpy as np

class FactorCalculator:
    def __init__(self, data):
        """
        data: DataFrame, 必须包含 columns=['open','high','low','close','volume','returns']
        """
        self.data = data.copy()
    
    def calc_momentum(self, window=5):
        """计算动量因子:过去N日收益率"""
        self.data['momentum'] = self.data['close'].pct_change(window)
        return self.data
    
    def calc_volatility(self, window=20):
        """计算波动率因子:过去N日收益率标准差"""
        self.data['volatility'] = self.data['returns'].rolling(window).std()
        return self.data
    
    def calc_turnover(self, window=5):
        """计算换手率因子:过去N日平均换手率"""
        # 假设 volume 是成交额,close 是收盘价,流通市值需要外部传入
        self.data['turnover'] = self.data['volume'] / self.data['market_cap']
        self.data['avg_turnover'] = self.data['turnover'].rolling(window).mean()
        return self.data

这个类的好处是什么?你只需要传入原始数据,然后调用对应的方法,因子就自动算好了。我建议每个因子都单独写一个方法,这样后期调试也方便。

个人经验: 我习惯在因子计算函数中加入参数校验。比如 window 参数必须大于0,数据类型必须是 float 等。这些小细节能帮你省去很多调试时间。

3.3 因子值标准化:让不同因子站在同一起跑线

因子值标准化,说白了就是把不同量纲的因子拉到同一个尺度上。比如市盈率可能是几十,而换手率可能是百分之零点几。如果不标准化,你没法直接比较。

常用的标准化方法有三种:

方法 公式 适用场景
Z-score 标准化 (x - μ) / σ 因子近似正态分布时
Min-Max 标准化 (x - min) / (max - min) 因子值有明确边界时
Rank 标准化 排序后映射到 [0,1] 因子分布偏态严重时

我个人最常用的是 Z-score 标准化。为什么?因为它保留了因子值的相对大小关系,而且对异常值有一定的容忍度。但要注意,如果因子值本身有严重的厚尾分布,Z-score 效果会变差。

def zscore_standardize(factor_series):
    """Z-score 标准化"""
    mean = factor_series.mean()
    std = factor_series.std()
    if std == 0:
        return factor_series * 0  # 如果标准差为0,全部置0
    return (factor_series - mean) / std

def rank_standardize(factor_series):
    """Rank 标准化:排序后映射到 [0,1]"""
    rank = factor_series.rank()
    return (rank - 1) / (len(rank) - 1)
注意: 标准化一定要在截面(同一时间点)上进行,而不是在时间序列上。你想想看,如果在时间序列上做标准化,那相当于把不同时间点的因子值混在一起,完全失去了截面比较的意义。

3.4 因子中性化处理:剔除你不想要的风格

因子中性化,是量化投资里一个非常重要的概念。说白了,就是剔除因子中与某些已知风格(如市值、行业)相关的部分。为什么要这么做?

  • 避免风格暴露: 比如你的因子在小市值股票上表现好,但这不是因子本身的能力,而是小市值风格在起作用。
  • 提高因子纯度: 中性化后,因子剩下的部分才是真正的“alpha”。
  • 便于组合管理: 中性化后的因子更容易与其他因子组合,不会产生多重共线性。

最常用的中性化方法是回归法。具体来说,就是用因子值对市值、行业等变量做线性回归,取残差作为中性化后的因子值。

import statsmodels.api as sm

def neutralize_factor(factor_series, market_cap, industry_dummies):
    """
    对因子进行市值和行业中性化处理
    factor_series: 因子值
    market_cap: 市值(对数化)
    industry_dummies: 行业哑变量(One-Hot编码)
    """
    # 合并自变量
    X = pd.concat([np.log(market_cap), industry_dummies], axis=1)
    X = sm.add_constant(X)  # 添加截距项
    
    # 回归
    model = sm.OLS(factor_series, X).fit()
    
    # 残差即为中性化后的因子
    neutralized_factor = model.resid
    return neutralized_factor

我在项目中遇到过一个问题:如果行业哑变量太多,会导致多重共线性。比如有30个行业,你放29个哑变量就够了,放30个就会出问题。嗯,这里要注意,一定要去掉一个行业作为基准。

一个小技巧: 中性化后,我习惯再检查一下残差与自变量的相关性。如果相关系数还很高,说明中性化不彻底,需要调整模型。

3.5 本章知识体系总览

说了这么多,我画了一张图,帮你把这一节的核心逻辑串起来。你一看就明白了。

单因子构建核心流程 因子定义与逻辑 经济含义 + 数据源 + 可复现 因子计算函数封装 参数化 + 模块化 + 可复用 因子值标准化 Z-score / Min-Max / Rank 因子中性化处理 回归法剔除风格暴露 常见中性化变量 市值、行业、波动率 最终输出:纯净因子 图:单因子构建四步流程——从定义到中性化,每一步都不可跳过

你看,整个流程其实就四步:定义、计算、标准化、中性化。每一步都有它的意义,缺一不可。我见过有人跳过中性化直接做回测,结果因子收益全是风格暴露带来的,一换市场环境就失效了。

核心要点回顾:

  • 因子定义要有经济含义,数据源要可靠,逻辑要可复现
  • 因子计算函数要封装成类或模块,便于复用和测试
  • 标准化要在截面上做,常用 Z-score 或 Rank 方法
  • 中性化用回归法,剔除市值和行业等风格暴露
  • 每一步都要做异常值处理,避免 NaN 或无穷值

好了,这一节的内容就到这里。记住,因子构建是量化策略的根基。根基不牢,地动山摇。下一节我们会讲因子测试与 IC 分析,到时候你会看到,今天做的这些标准化和中性化处理,在 IC 计算中会发挥多大的作用。

专注资料整理