2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含300篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
做因子研究,最大的痛点是什么?往往不是“不会写代码”,而是“想法验证的速度太慢”。每次都要重新写一遍数据分组、滚动窗口、截面对齐的代码,既容易出错,又难以复用。使用表达式引擎的核心价值就在于解决这个问题,它让你能专注于因子的数学逻辑本身。
它的主要优势可以总结为三点:
- 将研究目标从繁琐的工程细节中解耦,让你只关注因子的数学结构。
- 把重复的分组、窗口、对齐逻辑都交给引擎去处理,减少手动编写带来的错误。
- 统一了因子的定义格式,让因子复现、对比和批量迭代变得前所未有的容易。
想想看,传统的方式可能是每个因子都手写一长段 Pandas 或 DataFrame 的操作逻辑,代码又长又难以统一。而表达式的方式,一句 Rank(Ts_Mean(Close, 10)) 就能清晰表达一个复杂的因子,让你直接进入验证和迭代环节。
AKQuant 因子表达式的特色
AKQuant 的因子表达式引擎在设计上有一些鲜明的特色,理解它们能帮助你更好地使用:
- 高性能执行:底层基于 Rust + Polars 构建,充分利用了 Lazy 执行模式和并行计算能力。
- 分区语义清晰:它明确区分了 TS(时序)、CS(截面)、EL(元素级)三类计算分区。遇到像
Rank(Ts_Mean(...)) 这样的跨分区嵌套,引擎会自动拆步执行,你不用担心背后的复杂性。
- 工程化与可调试:对于复杂的表达式,你可以先验证内层结果,再叠加外层逻辑,这种分步调试的方式让定位问题变得非常直接。
- 批量计算友好:
run_batch 方法专门为统一样本池下的多因子并行评估而设计,效率很高。
- 时区与数据规范明确:默认时区为
Asia/Shanghai,如果你处理的是非 A 股市场数据,记得要显式设置时区。
5分钟实战:从零到第一个可用因子
我们来快速跑通一个完整的流程。整个过程的核心就是:准备数据 -> 创建引擎 -> 执行表达式。
首先,我们需要准备数据。这里以 A 股日线数据为例,使用 akshare 获取数据,并用 AKQuant 的 ParquetDataCatalog 进行本地化管理。
import akshare as ak
import pandas as pd
from akquant.data import ParquetDataCatalog
from akquant.factor import FactorEngine
# 初始化一个本地数据目录
catalog = ParquetDataCatalog("./data_catalog")
# 假设我们准备两只股票的数据
symbols = ["sh600000", "sz300750"]
for symbol in symbols:
df = ak.stock_zh_a_daily(
symbol=symbol,
start_date="20230101",
end_date="20230601",
adjust="hfq",
)
df["symbol"] = symbol
df["date"] = pd.to_datetime(df["date"])
df.set_index("date", inplace=True)
catalog.write(symbol, df)
# 创建因子引擎
engine = FactorEngine(catalog)
数据准备好后,就可以开始玩转因子表达式了。建议按照“由简入繁、先单后批”的顺序来操作。
# 1. 单层表达式:先确认基础计算是否正确
df_ts = engine.run("Ts_Mean(Close, 5)")
# 2. 嵌套表达式:再确认跨分区语义是否如预期
df_nested = engine.run("Rank(Ts_Mean(Close, 5))")
# 3. 批量表达式:最后进行规模化计算,一次评估多个因子
df_batch = engine.run_batch(
[
"Ts_Mean(Close, 5)",
"Rank(Volume)",
"Rank(Ts_Corr(Close, Volume, 10))",
]
)
print(df_batch)
运行批量计算后,你会得到一个结构清晰的 DataFrame,类似下面这样:
shape: (781, 5)
┌─────────────────────┬──────────┬───────────┬──────────┬──────────┐
│ date ┆ symbol ┆ factor_0 ┆ factor_1 ┆ factor_2 │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ datetime[ms] ┆ str ┆ f64 ┆ f64 ┆ f64 │
╞═════════════════════╪══════════╪═══════════╪══════════╪══════════╡
│ 2023-01-03 00:00:00 ┆ sh600000 ┆ null ┆ 0.8 ┆ null │
│ 2023-01-03 00:00:00 ┆ sh600036 ┆ null ┆ 1.0 ┆ null │
│ 2023-01-03 00:00:00 ┆ sh600519 ┆ null ┆ 0.2 ┆ null │
│ 2023-01-03 00:00:00 ┆ sh603843 ┆ null ┆ 0.6 ┆ null │
│ 2023-01-03 00:00:00 ┆ sz300750 ┆ null ┆ 0.4 ┆ null │
│ … ┆ … ┆ … ┆ … ┆ … │
│ 2023-12-27 00:00:00 ┆ sh600519 ┆ 13479.5 ┆ 0.5 ┆ 0.5 │
│ 2023-12-28 00:00:00 ┆ sh600036 ┆ 159.256 ┆ 1.0 ┆ 1.0 │
│ 2023-12-28 00:00:00 ┆ sh600519 ┆ 13568.274 ┆ 0.5 ┆ 0.5 │
│ 2023-12-29 00:00:00 ┆ sh600036 ┆ 159.498 ┆ 1.0 ┆ 1.0 │
│ 2023-12-29 00:00:00 ┆ sh600519 ┆ 13657.63 ┆ 0.5 ┆ 0.5 │
└─────────────────────┴──────────┴───────────┴──────────┴──────────┘
你看,核心的使用代码其实非常简洁:
from akquant.factor import FactorEngine
engine = FactorEngine(catalog)
# 单因子验证
df = engine.run("Rank(Ts_Mean(Close, 10))")
# 批量并行
df_batch = engine.run_batch([
"Ts_Mean(Close, 5)",
"Rank(Volume)",
"If(Close > Open, 1, 0)",
])
建议你按照这个顺序来落地你的研究:
- 准备好
catalog,确保里面包含 symbol、date 以及 OHLCV 等关键数据列。
- 用
run 方法跑单个表达式,先观察结果是否符合你的逻辑预期。
- 确认无误后,再使用
run_batch 在统一的样本池和区间内进行多因子批量计算和对比。
语法规则速览
变量与常量
- 列名不区分大小写:
Close 和 close 是等价的。
- 数值常量直接写就行,比如
1、0.5。
- 变量名必须能映射到你数据中的真实列名。如果你的数据列叫
ClosePrice,那么在表达式中写 Close 会导致执行失败。
运算符
- 算术运算:
+, -, *, /
- 比较运算:
>, <, >=, <=, ==, !=
- 逻辑组合:可以在条件中使用
& (与), | (或)
示例:
If((Close > Open) & (Volume > Ts_Mean(Volume, 20)), 1, 0)
三类核心分区语义
这是理解表达式的关键:
- TS (时序):按
symbol 分组,在时间序列上滚动计算。
- CS (截面):按
date 分组,在横截面上对所有股票进行计算。
- EL (元素级):逐元素变换,不引入任何窗口或分组。
像 Rank(Ts_Mean(...)) 这样的写法,就是一个 CS 算子嵌套了一个 TS 算子。引擎会自动拆解执行步骤,并物化中间结果,你无需手动处理。
算子速查手册
为了方便查阅,这里列出了常用的算子。
时序算子 (TS)
| 算子 |
别名 |
说明 |
示例 |
Ts_Mean(X, d) |
Mean |
过去 d 期移动平均 |
Ts_Mean(Close, 5) |
Ts_Std(X, d) |
Std |
过去 d 期移动标准差 |
Ts_Std(Close, 20) |
Ts_Max(X, d) |
Max |
过去 d 期最大值 |
Ts_Max(High, 10) |
Ts_Min(X, d) |
Min |
过去 d 期最小值 |
Ts_Min(Low, 10) |
Ts_Sum(X, d) |
Sum |
过去 d 期求和 |
Ts_Sum(Volume, 5) |
Ts_ArgMax(X, d) |
ArgMax |
过去 d 期最大值出现的距今天数 |
Ts_ArgMax(Close, 5) |
Ts_ArgMin(X, d) |
ArgMin |
过去 d 期最小值出现的距今天数 |
Ts_ArgMin(Close, 5) |
Ts_Rank(X, d) |
- |
当前值在过去 d 期中的分位排名 |
Ts_Rank(Close, 5) |
Delta(X, d) |
- |
X(t) - X(t-d) |
Delta(Close, 1) |
Delay(X, d) |
Ref |
滞后 d 期数值 |
Delay(Close, 1) |
Ts_Corr(X, Y, d) |
Corr |
过去 d 期相关系数 |
Ts_Corr(Close, Volume, 20) |
Ts_Cov(X, Y, d) |
Cov |
过去 d 期协方差 |
Ts_Cov(Close, Open, 20) |
截面算子 (CS)
| 算子 |
说明 |
示例 |
Rank(X) |
同一交易日(横截面)上的百分比排名,结果在 0~1 之间 |
Rank(Close) |
Scale(X) |
同一交易日(横截面)上的归一化,使得 sum(abs(X)) = 1 |
Scale(Close) |
逻辑与数学算子 (EL)
| 算子 |
说明 |
示例 |
If(Cond, A, B) |
条件判断 |
If(Close > Open, 1, -1) |
Sign(X) |
符号函数(返回 1, 0, -1) |
Sign(Close - Open) |
Abs(X) |
绝对值 |
Abs(Close - Open) |
Log(X) |
自然对数 |
Log(Volume) |
SignedPower(X, e) |
保持符号的幂运算 |
SignedPower(Close, 2) |
排障手册(按优先级排序)
遇到问题别慌,按以下顺序检查,大概率能快速解决。
结果全是 NaN
优先检查这三项:
- 窗口期
d 是否大于可用的历史数据长度(数据 Warmup 不足)。
- 原始数据列本身是否含有大量 NaN 值。
- 表达式中的列名是否与数据中的真实列名完全匹配。
嵌套表达式计算明显变慢
这通常是因为跨分区(如 TS 套 CS)嵌套触发了引擎的拆步物化操作。
处理方式:
- 先单独运行内层的表达式(比如先跑
Ts_Mean(Close, 5))。
- 确认内层结果合理后,再套上外层算子(比如再跑
Rank(上一步结果))。
- 在批量计算场景下,使用
run_batch 本身已经过优化。
窗口语义与预期不一致
如果你的数据存在停牌或缺失日期,引擎的滚动窗口是按数据行(而非自然日历日)推进的。这可能导致窗口的实际时间跨度与你的“自然日”理解有偏差。建议先对数据进行交易日对齐处理。
列名看着对却报错
引擎会进行列名大小写归一化处理(Close 和 close 视为一列),但它不会自动猜测别名。ClosePrice 和 Close 在它看来就是两个完全不同的列。
同一策略在不同市场回测结果漂移
先检查时区和数据本地化设置:
- 默认时区是
Asia/Shanghai。
- 处理非 A 股数据时,必须显式设置
timezone 参数。
- 确保你的时间列已经用
tz_localize 等方法赋予了正确的时区信息。
性能优化建议
- 先单后批:始终先用
run 调通单个因子逻辑,再用 run_batch 进行扩展。
- 简化嵌套:减少不必要的深层嵌套,优先采用逻辑清晰、易于解释的算子组合结构。
- 精简数据:只加载计算所需的最小数据列,有效降低 IO 和内存开销。
- 清洗数据:提前处理停牌和缺失日期,可以减少因窗口偏差带来的反复调试。
从“会写因子”到“会做研究流水线”
真正高效的因子研究,其目标不是一天内写出 100 个不同的公式,而是建立一套稳定的、可重复的研究流水线。这套流水线应该包括:
- 用单表达式快速验证因子逻辑的正确性。
- 在统一的样本池和时间内,批量评估大量因子的表现。
- 对表现异常的因子,有标准化的排查和诊断流程。
- 将经过验证的、可解释的因子组合沉淀下来,形成你自己的策略特征库。
AKQuant 的因子表达式引擎,其最大价值正是在于此:它将你的时间和精力,从重复编写“胶水代码”中解放出来,让你能够重新聚焦于“研究因子本身”这个核心创造性工作上。对于希望提升研究效率的量化从业者来说,掌握这样一款工具至关重要。如果你对利用 Python 进行智能 & 数据分析和建模感兴趣,欢迎到技术社区交流更多实战心得。
加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取本文完整研究解析、代码实现细节。