因子计算框架:从逻辑到性能的实战拆解

因子计算,说白了就是把原始行情数据变成有预测能力的信号。这个环节做得好不好,直接决定了你后面所有工作的质量。我见过太多人花大把时间调模型,结果回头一看,因子计算那步就埋了雷。

今天咱们就聊聊因子计算的核心逻辑,以及怎么用向量化计算把性能拉满。

因子计算的核心逻辑

因子计算本质上就三步:输入数据 → 计算逻辑 → 输出因子值。听起来简单,但实际落地时坑不少。

我个人习惯把因子计算拆成三个层次来看:

  • 数据层:原始行情、财务数据、另类数据等,需要做清洗和对齐
  • 逻辑层:具体的计算公式,比如动量、反转、波动率等
  • 执行层:用什么工具、什么方式去算,这直接影响效率

举个例子,一个简单的动量因子:

# 伪代码逻辑
动量因子 = (当前收盘价 - N天前收盘价) / N天前收盘价

这个逻辑看着简单,但放到全市场几千只股票、每天几万条数据上,怎么算得快、算得准,就是门学问了。

核心要点:因子计算不是写公式,而是设计一套可复用的计算框架。好的框架能让你快速迭代、批量计算、避免重复劳动。

向量化计算 vs 循环计算:性能差距有多大?

我记得刚入行那会儿,写因子计算全用 for 循环。跑一个简单的均线因子,3000只股票、500个交易日,愣是跑了快10分钟。后来换成向量化计算,同样的逻辑,3秒搞定。

为什么会这样?

Python 的 for 循环是解释执行的,每循环一次都要做类型检查、内存分配。而 NumPy 和 Pandas 的向量化操作底层是 C 语言实现的,一次操作处理整个数组,省掉了大量 Python 层面的开销。

咱们直接看对比:

计算方式 代码量 可读性 执行速度(100万行数据)
纯 Python 循环 少 差 ~8-12秒
列表推导式 中等 中等 ~5-8秒
Pandas 向量化 少 好 ~0.3-0.8秒
NumPy 向量化 中等 好 ~0.1-0.3秒

我曾经在一个项目中,用循环计算了50多个因子,每次回测光计算就要等半小时。改成向量化后,整个因子库的更新压缩到2分钟以内。嗯,那感觉就像从绿皮火车换成了高铁。

避坑指南:我曾经在计算滚动窗口统计量时,不小心在循环里重复创建 DataFrame,导致内存暴涨。向量化虽然快,但也要注意中间变量的内存管理,尤其是处理全市场数据时。

用 Pandas 和 NumPy 进行因子计算

实际工作中,我主要用 Pandas 做数据操作,NumPy 做数值计算。两者配合起来,基本能覆盖90%的因子计算场景。

Pandas 常用操作

  • groupby + apply:按股票分组计算
  • rolling / expanding:滚动窗口和扩展窗口
  • shift:数据对齐,避免未来信息
  • pct_change:收益率计算

举个例子,计算每个股票的20日滚动波动率:

import pandas as pd
import numpy as np

# 假设 df 是 MultiIndex 格式,索引为 (date, stock)
# 列包含 close 收盘价

# 计算日收益率
df['ret'] = df.groupby('stock')['close'].pct_change()

# 计算20日滚动波动率(年化)
df['vol_20d'] = df.groupby('stock')['ret'].rolling(20).std() * np.sqrt(252)

# 重置索引
df = df.reset_index(level='stock', drop=True)

你看,三行代码就搞定了。如果用循环写,至少得十几行,还容易出错。

NumPy 加速技巧

有些计算 Pandas 做起来比较慢,比如复杂的矩阵运算、自定义函数等。这时候我会把数据转成 NumPy 数组来处理。

# 假设需要计算一个自定义的加权移动平均
def weighted_ma(arr, window=20):
    weights = np.exp(np.linspace(-1, 0, window))
    weights /= weights.sum()
    
    result = np.full_like(arr, np.nan)
    for i in range(window-1, len(arr)):
        result[i] = np.dot(arr[i-window+1:i+1], weights)
    return result

# 应用到每个股票
df['wma_20'] = df.groupby('stock')['close'].transform(
    lambda x: weighted_ma(x.values, 20)
)

这里的关键是 .values,它把 Pandas Series 转成 NumPy 数组,计算效率能提升一个数量级。

小技巧:我习惯在因子计算前,先把数据按日期排序,然后用 groupby 分组。这样能保证每个股票内部的时间序列是连续的,避免计算滚动统计量时出现错位。

因子计算框架的整体流程

下面这张图是我自己总结的因子计算框架,你可以参考一下:

因子计算框架流程图 因子计算框架流程图 原始数据输入 数据清洗与对齐 因子计算逻辑 向量化计算 循环计算 因子值输出 存储与回测 推荐优先使用向量化 性能提升10-100倍

这个流程里,最关键的决策点就是「向量化计算 vs 循环计算」。我的建议是:能用向量化就别用循环。只有在逻辑特别复杂、无法向量化的时候,才考虑用循环,而且一定要用 numba 或 cython 加速。

实战中的几个坑

最后分享几个我踩过的坑,希望能帮你少走弯路:

  1. 未来信息泄露:计算因子时一定要用 shift 对齐,确保只用历史数据。我曾经在计算均线时忘了 shift,结果因子值包含了当天的信息,回测结果漂亮得离谱,实盘直接打脸。
  2. 分组计算时的索引问题:groupby 之后记得重置索引,否则后续操作容易出错。
  3. 缺失值处理:因子计算中经常出现 NaN,比如滚动窗口初期、停牌日等。一定要想好怎么处理,是填充、删除还是保留。
  4. 内存爆炸:全市场数据量很大,中间变量不要随意复制。能用 inplace=True 就用,能删的临时变量就删。

总结一下:因子计算框架的核心就是「数据清洗 → 向量化计算 → 结果输出」。把这三步做好,你的因子库就有了坚实的基础。至于性能,记住一句话:能用 NumPy 就别用 Pandas,能用向量化就别用循环。