跳转至

因子表达式引擎 (Factor Expression Engine)

AKQuant 内置了高性能的因子表达式引擎 (akquant.factor),允许用户通过简洁的字符串公式定义复杂的 Alpha 因子。底层基于 Rust 实现的 Polars 库,提供极高的计算性能和并行处理能力。

核心特性

  • 极速计算: 基于 Polars Lazy API,自动优化查询计划,利用 Rust 多线程并行计算。
  • 简洁语法: 使用类 WorldQuant Alpha101 的公式语法,如 Rank(Ts_Mean(Close, 5))
  • 防止未来函数: 封装好的时序算子(如 Ts_Mean)自动处理窗口和偏移,减少手写代码引入未来数据的风险。
  • 自动对齐: 引擎自动处理面板数据(Panel Data)的对齐和分组(Group By Symbol/Date)。

快速开始

1. 准备数据

因子引擎默认使用 ParquetDataCatalog 读取数据。为了确保因子引擎能正确工作,您需要将数据整理为面板(Panel)格式,并确保包含以下关键列:

  • symbol (String): 标的代码,用于区分不同的资产(分组计算)。
  • date (Datetime): 日期或时间戳,用于时间序列排序和对齐。
  • OHLCV 字段: 如 open, high, low, close, volume 等,用于因子计算。

示例:使用 AKShare 获取 A 股数据并写入

AKQuant 与 AKShare 完美兼容。你可以使用 AKShare 获取真实的历史行情数据,并将其整理为 AKQuant 所需的格式。

import akshare as ak
import pandas as pd
from akquant.data import ParquetDataCatalog

# 1. 初始化数据目录
catalog = ParquetDataCatalog("./data_catalog")

# 2. 准备股票列表 (例如: 贵州茅台, 宁德时代, 招商银行)
symbols = ["sh600519", "sz300750", "sh600036"]

print("开始下载数据...")
for symbol in symbols:
    print(f"Downloading {symbol}...")

    # 使用 AKShare 获取日线数据 (需安装: pip install akshare)
    # adjust="hfq" 表示后复权,适合回测
    df = ak.stock_zh_a_daily(symbol=symbol, start_date="20230101", end_date="20231231", adjust="hfq")

    if df.empty:
        print(f"Warning: No data for {symbol}")
        continue

    # 添加 symbol 列 (akquant 需要此列进行分组计算)
    df["symbol"] = symbol

    # 确保 date 列是 datetime 类型并设为索引
    # AKShare 返回的数据包含 open, high, low, close, volume 等标准字段
    df["date"] = pd.to_datetime(df["date"])
    df.set_index("date", inplace=True)

    # 写入 Parquet 目录
    catalog.write(symbol, df)

print("数据准备完成!目录: ./data_catalog")

2. 计算因子

from akquant.factor import FactorEngine

# 初始化引擎
engine = FactorEngine(catalog)

# 计算单个因子
# 返回包含 [date, symbol, factor_value] 的 DataFrame
df = engine.run("Rank(Ts_Mean(Close, 10))")
print(df.head())

# 批量计算因子
df_batch = engine.run_batch([
    "Ts_Mean(Close, 5)",
    "Rank(Volume)",
    "If(Close > Open, 1, 0)"
])

算子参考 (Operators)

表达式支持变量(列名,不区分大小写,如 Close, open)和常数。

时序算子 (Time Series)

在时间维度上对每个标的(Symbol)独立计算。

算子 说明 示例
Ts_Mean(X, d) 过去 d 天的移动平均 Ts_Mean(Close, 5)
Ts_Std(X, d) 过去 d 天的移动标准差 Ts_Std(Close, 20)
Ts_Max(X, d) 过去 d 天的最大值 Ts_Max(High, 10)
Ts_Min(X, d) 过去 d 天的最小值 Ts_Min(Low, 10)
Ts_Sum(X, d) 过去 d 天的求和 Ts_Sum(Volume, 5)
Ts_Corr(X, Y, d) 过去 d 天 X 和 Y 的相关系数 Ts_Corr(Close, Volume, 20)
Ts_Cov(X, Y, d) 过去 d 天 X 和 Y 的协方差 Ts_Cov(Close, Open, 20)
Delay(X, d) 滞后 d 天的值 (Ref) Delay(Close, 1)
Delta(X, d) 差分: X(t) - X(t-d) Delta(Close, 1)

截面算子 (Cross Sectional)

在同一时间截面上对所有标的进行计算。

算子 说明 示例
Rank(X) 百分比排名 (0 到 1) Rank(Ts_Mean(Close, 5))
Scale(X) 归一化,使 sum(abs(X)) = 1 Scale(Close)

逻辑与数学算子 (Math & Logic)

算子 说明 示例
Log(X) 自然对数 Log(Volume)
Abs(X) 绝对值 Abs(Return)
Sign(X) 符号函数 (1, 0, -1) Sign(Close - Open)
SignedPower(X, e) 保持符号的幂运算 SignedPower(Close, 2)
If(Cond, A, B) 条件判断 (If-Else) If(Close > Open, 1, -1)

基础运算

支持加减乘除 +, -, *, / 以及比较运算符 >, <, >=, <=, ==, !=

示例:

(Close - Open) / Open

进阶技巧 (Advanced Tips)

1. 数据对齐与填充 (Alignment & Padding)

因子引擎底层使用 Polars 的 LazyFrame 进行计算。在进行时序计算(如 Ts_Mean)时,Polars 会对每个 symbol 分组内的数据进行操作。 注意: 如果某些日期的交易数据缺失(例如停牌),rolling 窗口计算可能会基于物理行数(Row-based)而非时间(Time-based)。为了确保精确性,建议在写入数据前对数据进行日历填充(Reindex)。

2. 内存优化 (Memory Optimization)

FactorEngine 采用 Lazy Evaluation(惰性求值)模式: 1. engine.run() 时并不会立即加载所有数据到内存。 2. Polars 会构建查询计划,只读取计算所需的列(Projection Pushdown)。 3. 例如计算 Ts_Mean(Close, 5) 时,引擎只会读取 symbol, date, close 列,忽略 open, high 等其他列,极大节省内存。

3. 复合因子示例

你可以将多个算子嵌套使用,构建复杂的 Alpha 因子:

  • 量价背离: Ts_Corr(Close, Volume, 20) (价格与成交量的相关性)
  • 动量反转: Rank(Ts_Mean(Close, 5)) - Rank(Ts_Mean(Close, 20)) (短期动量减去长期动量)
  • 波动率调整后的动量: Ts_Mean(Close, 20) / Ts_Std(Close, 20)

常见问题 (FAQ)

Q: 支持日内数据(分钟线)吗? A: 支持。只要 date 列包含时间戳即可。时序算子(如 Ts_Mean)是基于窗口长度(行数)计算的,对于分钟线,d=60 代表过去 60 个分钟 bar。

Q: 如何处理停牌数据? A: 建议在数据入库前填充停牌日的记录(使用前值填充或 NaN)。如果数据中直接缺失该日期,rolling 函数会跳过该日期,直接取上一行数据作为 t-1,这在逻辑上可能不符合预期。

Q: 为什么我的结果全是 NaN? A: 1. 检查窗口大小 d 是否大于数据长度(Warmup 期)。 2. 检查数据中是否包含大量 NaN。 3. 检查列名是否拼写正确(如 Close vs close,引擎会自动转小写,但如果数据列名是 ClosePrice 则无法匹配)。