4. 因子与特征构建

在Qlib的组合策略与回测流程中,因子与特征构建是决定模型上限的关键环节。无论你使用LightGBM、线性模型还是深度网络,模型看到的输入都是特征矩阵。特征的质量、覆盖度和表达力,直接决定了策略能否捕捉到有效的市场规律。本节将系统讲解量价因子、基本面因子的构建逻辑,Alpha158与Alpha360因子库的使用方式,以及如何自定义因子并接入Qlib的数据管线。

一、核心原理:因子是市场行为的数值化表达

因子的本质,是把原始行情数据(OHLCV、财务报表、交易状态等)通过数学变换,压缩成能够刻画未来收益差异的数值信号。一个完整的因子构建流程通常包含四个层次:

  • 数据层:原始行情、财务、行业分类等基础数据,Qlib通过DataHandler统一管理。
  • 变换层:对原始数据做差分、比率、滚动统计、排序等运算,生成因子表达式。
  • 标准化层:截面标准化(如Z-Score、Rank)或时序标准化,消除量纲与市场整体波动影响。
  • 特征层:将多个因子拼接为特征矩阵,供模型训练与预测使用。

量价因子关注价格与成交量的统计规律,例如动量、反转、波动率、换手率、量价背离等。基本面因子则从财务数据出发,如市盈率、市净率、ROE、营收增速、资产负债率等。两类因子的共同点是:它们都试图用可计算的数值,描述股票未来收益的横截面差异。

关键认知:因子不是越多越好。冗余因子会引入噪声、增加过拟合风险。Qlib的Alpha158和Alpha360之所以被广泛使用,正是因为它们在因子数量与信息覆盖之间做了工程化的平衡。

二、Alpha158与Alpha360因子库的使用

Qlib内置了两套经典因子库:Alpha158和Alpha360。Alpha158包含158个因子,主要基于日频量价数据,涵盖K线形态、滚动统计、量价关系等;Alpha360则包含360个因子,更侧重多周期、多窗口的时序特征,适合深度学习模型。

在Qlib中使用它们非常简单,只需在配置文件中指定handler类名:

# 使用Alpha158因子库
from qlib.contrib.data.handler import Alpha158

handler_conf = {
    "class": "Alpha158",
    "module_path": "qlib.contrib.data.handler",
    "kwargs": {
        "start_time": "2018-01-01",
        "end_time": "2022-12-31",
        "fit_start_time": "2018-01-01",
        "fit_end_time": "2020-12-31",
        "instruments": "csi300",
    }
}

# 使用Alpha360因子库
from qlib.contrib.data.handler import Alpha360

handler_conf_360 = {
    "class": "Alpha360",
    "module_path": "qlib.contrib.data.handler",
    "kwargs": {
        "start_time": "2018-01-01",
        "end_time": "2022-12-31",
        "fit_start_time": "2018-01-01",
        "fit_end_time": "2020-12-31",
        "instruments": "csi300",
    }
}

配置完成后,Qlib会自动完成因子计算、缺失值处理、标准化和特征拼接。你可以通过handler.fetch()查看生成的DataFrame,列名即为因子名称。Alpha158的因子命名通常带有窗口参数,如ROC5、MA10、STD20等,便于追溯。

因子库 因子数量 主要类型 适用场景
Alpha158 158 K线形态、滚动统计、量价关系 树模型、线性模型、快速基线
Alpha360 360 多周期时序、价格成交量组合 深度学习、端到端训练
自定义因子 按需 基本面、另类数据、行业中性 特定策略增强、因子挖掘

三、自定义因子的构建与接入

当内置因子库无法满足策略需求时,需要自定义因子。Qlib提供了灵活的扩展机制,核心是继承DataHandler或使用Expression表达式引擎。以下是一个自定义量价因子的示例:计算20日收益率与20日成交量变化率的比值,用于捕捉量价配合关系。

from qlib.data.dataset.handler import DataHandlerLP
from qlib.data.dataset.processor import DropnaLabel
from qlib.data import D

# 方式一:使用表达式引擎定义因子
fields = [
    "Ref($close, -1) / $close - 1",          # 未来一日收益率(标签)
    "($close - Ref($close, 20)) / Ref($close, 20)",  # 20日动量
    "($volume - Ref($volume, 20)) / Ref($volume, 20)", # 20日量变
    "($close - Ref($close, 20)) / Ref($close, 20) / (($volume - Ref($volume, 20)) / Ref($volume, 20) + 1e-8)", # 量价比值
]

names = ["LABEL", "MOM20", "VOL20", "PV_RATIO"]

# 方式二:继承DataHandlerLP实现自定义处理器
class MyHandler(DataHandlerLP):
    def __init__(self, instruments="csi300", start_time=None, end_time=None, **kwargs):
        fields = ["$close", "$volume", "$high", "$low"]
        names = ["close", "volume", "high", "low"]
        super().__init__(
            instruments=instruments,
            start_time=start_time,
            end_time=end_time,
            data_loader=...,  # 指定数据加载器
            infer_processors=[...],  # 推理阶段处理器
            learn_processors=[DropnaLabel()],  # 学习阶段处理器
            **kwargs
        )

实践建议:自定义因子时,优先使用Qlib的表达式引擎,因为它能自动处理时间对齐、停牌、涨跌停等细节。只有在表达式无法表达复杂逻辑时,才考虑继承DataHandler编写Python代码。

四、常见误区与复盘

在因子构建过程中,初学者容易陷入几个典型误区:

  1. 未来函数:使用Ref($close, -1)作为特征而非标签,导致回测虚高。务必确认特征只使用当前及历史数据。
  2. 标准化泄露:在训练集和测试集上分别做标准化,或使用全样本统计量。正确做法是在训练集上拟合标准化参数,再应用到测试集。
  3. 因子冗余:盲目堆砌相似因子,如同时加入5日、10日、20日动量,导致多重共线性。建议先做相关性分析,保留信息增量最大的因子。
  4. 忽略行业与市值中性化:基本面因子往往带有行业和市值暴露,直接使用可能引入风格偏差。Qlib支持在处理器中加入中性化步骤。

复盘要点:因子构建不是一次性工作。建议建立因子评估流水线,定期计算IC、ICIR、换手率、分层收益等指标。只有经过严格样本外验证的因子,才值得进入组合策略。

总结来说,量价因子和基本面因子各有适用场景,Alpha158与Alpha360提供了开箱即用的基线特征,而自定义因子则是策略差异化的来源。掌握Qlib的表达式引擎与DataHandler扩展机制,能够让你在因子构建环节既高效又可控。下一阶段,我们将把这些特征输入模型,观察它们如何转化为预测信号。