4、因子计算引擎:向量化计算、多因子并行计算、因子计算框架设计
好,咱们今天聊点硬核的——因子计算引擎。
说实话,我见过太多团队在因子计算上栽跟头。有的用纯Python循环算,一个因子跑半小时;有的把几百个因子串在一起算,改一个就得重跑全部。说白了,就是没把计算引擎当回事。
我个人习惯,做因子库之前,先把计算引擎的架构想清楚。这就像盖楼前先打地基,地基稳了,后面怎么折腾都行。
4.1 向量化计算:告别for循环
先说说向量化。你想想看,我们做量化最常处理的是什么?是时间序列数据。几千只股票,每只几百个交易日,数据量轻松上百万行。
如果用for循环去算,比如计算过去20天的均值:
# 千万别这么写
for i in range(len(close_prices)):
if i >= 20:
ma20[i] = np.mean(close_prices[i-20:i])
这种写法,数据量一上来,慢得你想砸电脑。我在项目中遇到过,用纯Python循环算50个因子,跑了整整一个晚上。
向量化计算,说白了就是利用NumPy、Pandas这些库的底层C语言优化,一次性处理整个数组。比如:
import pandas as pd
import numpy as np
# 向量化计算20日均线
close = pd.Series(close_prices)
ma20 = close.rolling(window=20).mean()
这一行代码,底层用的是C语言循环,速度比Python快几十倍。嗯,这里要注意:向量化不是银弹。有些复杂的逻辑,比如条件判断、分组计算,还是得用apply或者groupby。但核心原则是——能用向量化就别用手动循环。
4.2 多因子并行计算:别让CPU闲着
单个因子算得快还不够。一个因子库可能有几百个因子,如果串行计算,一个接一个跑,时间成本太高。
我曾经接手过一个项目,300多个因子,串行跑一次要40分钟。每次调参都要等这么久,人都等傻了。
后来我改成了并行计算。思路很简单:把因子分成多个批次,同时计算。Python里常用的工具有concurrent.futures、joblib、或者Dask。
from concurrent.futures import ProcessPoolExecutor
import pandas as pd
def calculate_factor(factor_name, data):
# 假设每个因子有自己的计算函数
return factor_name, factor_func_dict[factor_name](data)
# 并行计算所有因子
with ProcessPoolExecutor(max_workers=8) as executor:
futures = [executor.submit(calculate_factor, name, data)
for name in factor_list]
results = [f.result() for f in futures]
这里有个坑,我踩过好几次——内存爆炸。并行计算时,每个进程都会复制一份数据。如果数据量太大,比如全市场日线数据,8个进程同时跑,内存直接爆掉。
另外,并行计算不是核数越多越好。我测试过,8核和16核的加速比大概在1.5倍左右,因为Python的GIL锁和进程间通信有开销。建议根据实际数据量,选4-8个worker就够了。
4.3 因子计算框架设计:让系统可扩展
好了,向量化和并行都搞定了,接下来是重头戏——因子计算框架。
我见过最糟糕的设计,是把所有因子计算逻辑写在一个几千行的文件里。新增一个因子,得从头到尾看一遍,生怕改错什么。这种代码,维护成本极高。
一个好的因子计算框架,应该满足三个要求:
- 可扩展:新增因子只需写一个函数,不用改框架代码
- 可配置:因子参数、计算频率、数据源都能通过配置文件调整
- 可复用:公共计算逻辑(比如去极值、标准化)能统一调用
我个人习惯用注册器模式来设计。每个因子是一个独立的类或函数,通过装饰器注册到框架中。
# 因子注册器
class FactorRegistry:
_factors = {}
@classmethod
def register(cls, name):
def wrapper(func):
cls._factors[name] = func
return func
return wrapper
@classmethod
def get_factor(cls, name):
return cls._factors.get(name)
# 注册一个因子
@FactorRegistry.register('ma5_ratio')
def ma5_ratio(data):
close = data['close']
ma5 = close.rolling(5).mean()
return close / ma5 - 1
这样设计的好处是:新增因子时,只需要写一个函数,加个装饰器就行。框架自动识别、自动调度。我曾经用这个模式管理过500多个因子,新增一个因子不超过5分钟。
下面是我常用的因子计算框架流程图,你可以参考一下:
这个框架的核心思想是分层解耦。数据层只管数据清洗,注册层只管因子定义,计算层只管调度。每一层各司其职,互不干扰。
4.4 实战中的几个细节
最后,分享几个我在实战中总结的细节:
- 缓存中间结果:很多因子会用到公共计算,比如过去20天的波动率。把这些中间结果缓存起来,能省不少时间。我一般用lru_cache或者自定义缓存字典。
- 因子依赖管理:有些因子依赖其他因子。比如Alpha因子依赖基础因子。这时候需要拓扑排序,确保依赖的因子先算。我踩过这个坑,后来加了个依赖声明机制。
- 错误隔离:一个因子算崩了,不能影响其他因子。我习惯用try-except包裹每个因子的计算,失败时记录日志,继续算下一个。
嗯,因子计算引擎这块,说白了就是三个字——快、稳、省。向量化让你快,并行让你稳,框架设计让你省心。把这三点做好,你的因子库就能支撑起后续的回测和实盘了。
好了,这一章的内容就到这里。记住,计算引擎是因子库的心脏,值得你花时间好好打磨。
公众号:蓝海资料掘金营,微信deep3321