4、因子计算引擎
因子计算引擎是因子库的“生产车间”,负责将原始行情、财务、另类数据转化为结构化的因子值。一个高性能、可扩展的计算引擎需要同时解决架构分层、计算模式、依赖管理和性能优化四个核心问题。本章从工程实践角度拆解这些关键设计。
4.1 架构设计:分层解耦与模块职责
因子计算引擎采用经典的三层架构:数据接入层、计算编排层、结果输出层。每层职责清晰,通过标准化接口通信,便于独立扩展与维护。
架构设计原则:
- 数据与计算分离:数据接入层只负责格式转换与缓存,不参与业务逻辑。
- 计算无状态:每个因子计算任务都是纯函数,输入DataFrame/Array,输出因子序列。
- 结果可追溯:输出层记录计算元数据(版本、时间、参数),支持回滚与审计。
4.2 批处理与流式计算
因子计算场景天然分为两类:历史回测(批处理)和实时信号(流式计算)。两种模式在数据源、计算窗口、延迟要求上存在显著差异。
| 对比维度 | 批处理模式 | 流式计算模式 |
|---|---|---|
| 典型场景 | 全量历史因子计算、回测数据准备 | 盘中实时因子、T+0信号生成 |
| 数据源 | HDFS/Parquet 历史快照 | Kafka/Pulsar 实时消息 |
| 计算窗口 | 无限制(全量或指定日期区间) | 滑动窗口(如过去20个tick/分钟) |
| 延迟要求 | 分钟级到小时级 | 毫秒级到秒级 |
| 计算框架 | Spark、Dask、Pandas(单机) | Flink、Kafka Streams、RisingWave |
| 状态管理 | 无状态,每次全量计算 | 有状态,维护窗口聚合值 |
混合架构建议:使用同一套因子定义代码,通过配置切换计算模式。批处理用于每日收盘后的全量重算与校验,流式计算用于盘中实时信号。两者共享因子注册中心与依赖管理模块,避免因子逻辑不一致。
4.3 计算依赖管理
因子之间常存在依赖关系:因子B的计算需要因子A作为输入。例如,RSI 依赖 close_price 和 rolling_mean。依赖管理不当会导致重复计算、数据不一致或死循环。
采用有向无环图(DAG)管理因子依赖,核心流程如下:
- 因子注册:每个因子声明其输入依赖(原始字段或其他因子)。
- DAG构建:解析所有因子依赖关系,构建DAG,检测环。
- 拓扑排序:按依赖深度确定计算顺序,无依赖的因子优先计算。
- 增量计算:当某个因子更新时,只重新计算其下游依赖因子。
避坑指南:避免因子间循环依赖(如A依赖B,B依赖A)。在因子注册阶段必须做环检测,否则会导致计算引擎死锁或栈溢出。建议使用
networkx或graphlib库进行DAG校验。
4.4 性能优化:向量化与并行计算
因子计算通常涉及大量时间序列操作,性能瓶颈集中在循环遍历和单线程执行。核心优化手段包括向量化和并行计算。
4.4.1 向量化计算
向量化利用底层C/Fortran实现(如NumPy、Pandas),避免Python层显式循环。对于因子计算中的滚动窗口、分组聚合等操作,向量化可带来10~100倍加速。
# 低效方式:Python显式循环
def sma_loop(series, window):
result = []
for i in range(len(series)):
if i < window - 1:
result.append(None)
else:
result.append(series[i-window+1:i+1].mean())
return result
# 高效方式:向量化
def sma_vectorized(series, window):
return series.rolling(window=window).mean()
向量化加速比实测:对100万行数据计算20日滚动均值,循环方式耗时约12秒,向量化方式仅0.08秒,加速比达150倍。
4.4.2 并行计算策略
并行计算从两个维度展开:数据并行(按股票/合约拆分)和因子并行(按因子拆分)。
| 并行维度 | 拆分方式 | 适用场景 | 框架示例 |
|---|---|---|---|
| 数据并行 | 按股票代码或行业分组,每组独立计算所有因子 | 全市场因子计算,股票间无依赖 | Spark groupBy、Dask groupby |
| 因子并行 | 按因子名称拆分,每个因子独立计算 | 因子间无依赖或依赖已缓存 | Python multiprocessing、Ray |
| 混合并行 | 先按数据分组,组内再按因子并行 | 大规模多因子计算 | Spark + 内部线程池 |
性能调优口诀:“先向量化,再并行化;先数据并行,再因子并行;缓存中间结果,避免重复计算。” 使用
joblib或concurrent.futures实现轻量级并行,使用Spark/Dask实现分布式并行。
4.4.3 内存与I/O优化
因子计算中,内存占用和磁盘I/O往往是比CPU更稀缺的资源。以下优化策略可显著提升吞吐量:
- 列式存储:使用Parquet/Feather格式,只读取需要的列,减少I/O。
- 数据分块:将大表按时间或股票拆分为块,逐块计算,避免内存溢出。
- 中间结果缓存:使用LRU缓存或Redis缓存高频依赖因子(如rolling_mean_20),避免重复计算。
- 数据类型压缩:将float64降级为float32,int64降级为int32,内存减半且精度足够。
# 数据类型压缩示例
import pandas as pd
import numpy as np
def compress_dataframe(df):
for col in df.select_dtypes(include=['float64']).columns:
df[col] = df[col].astype(np.float32)
for col in df.select_dtypes(include=['int64']).columns:
df[col] = df[col].astype(np.int32)
return df
# 使用前:内存占用 1.2GB
# 使用后:内存占用 0.6GB
注意:数据类型压缩可能引入精度损失。对于价格类因子(如close_price),float32可保留小数点后4位,足够量化交易使用;但对于累计收益率等需要高精度的因子,建议保留float64。