WorldQuant 101 Alpha 因子指标库

挖掘和计算 alpha 因子在量化金融领域有着重要的意义。在著名论文 101 Formulaic Alphas 中,作者给出了世界顶级量化对冲基金 WorldQuant 所使用的 101 个因子公式。为方便用户在 DolphinDB 中计算因子,本文使用 DolphinDB 脚本实现了所有101个因子的函数,并封装在 DolphinDB 模块 wq101alpha (wq101alpha.dos) 中。

该模块的实现具有三大优势:

  • 性能优越:性能远优于传统的 Python 实现方式。DolphinDB的性能中位数为Python的15.5倍,其中,27.5%的因子超100倍。
  • 批流一体:模块中定义的因子函数,既可以用于历史计算,又可以用于流式增量计算。
  • 实现简单:直接使用论文中所列公式实现,无需用户自定义函数,便于用户后续自行修改以产生定制化因子。

注意:本教程包含的所有代码兼容 DolphinDB 2.00.8,1.30.20 及以上版本。

1. 函数的命名与入参规范

  • wq101alpha 模块 中的所有函数命名规则为WQAlpha + 因子序号, 如WQAlpha1,WQAlpha2。

  • 每一个因子的入参字段有所不同,具体参考附录1-因子入参一览表。所有需要用到的输入字段如下:

参数名称 / 标准字段名称参数含义是否为行业信息字段
tradetime交易时间×
securityid股票代码×
open开盘价×
close收盘价×
high最高价×
low最低价×
vol交易量×
vwap成交量加权平均价格×
cap市值
indclass行业类型
  • 由于包含行业信息的因子计算步骤与不含行业信息的因子有所不同,wq101alpha 模块 中根据入参字段是否包括行业信息字段,将因子分为了两类:行业信息因子和非行业信息因子:
因子类型输入数据类型输出数据类型因子序号
行业信息因子48,56,58,59,63,67,69,70,76,79,80,82,87,89,90,91,93,97,100
非行业信息因子矩阵矩阵其余所有因子

2. 使用范例

本章节将通过环境配置、数据准备、计算调用方法等方面具体介绍 wq101alpha.dos 模块的具体用法。

2.1. 环境配置

把附件的 wq101alpha.dos 放在 [home]/modules 目录下,[home] 目录由系统配置参数 home 决定,可以通过 getHomeDir() 函数查看。

有关模块使用的更多细节,请参见:DolphinDB 教程:模块

2.2. 数据准备

如若没有数据,可参考 日频数据及分钟频数据建库建表 生成模拟的日频数据 ;对于行业信息表的模拟数据生成,可参考 行业信息数据建库建表

如若已有数据,需保证现有的数据表中的字段名与模块中字段名一致,并将日频信息和行业信息做一个表连接。为方便使用,本教程准备了一个辅助模块 prepare101.dos 来帮助统一字段名。若要使用辅助模块,请将 prepare101.dos 放在 wq101alpha 模块 同一目录下。辅助模块中的prepareData函数的作用是将数据与标准字段的名称对齐,其中,rawData为非行业信息表,infoData为行业信息表,startTime与endTime 为需要的数据的起始时间和结束时间,其余参数为现有字段名与标准字段的对应名称:

注意:如若用户采用的数据字段名与 输入字段 中的标准字段名一致,无需调用准备函数 prepareData 。

载入模块和数据方法如下,data 即为准备好的数据:

use wq101alpha
use prepare101
login('admin', '123456')
rawData = loadTable("dfs://k_day_level", "k_day")
infoData = select * from loadTable("dfs://info", "info_data")
startTime = timestamp(2010.01.01)
endTime = timestamp(2010.01.31)
data = prepareData(rawData=rawData, startTime=startTime, endTime=endTime, securityidName="securityid", tradetimeName="tradetime", openName="open", closeName="close", highName="high", lowName="low", volumeName="vol", vwapName="vwap", capName="cap", indclassName="indclass", infoData=infoData, infoSecurityidName="securityid")

2.3. 矩阵入参计算的非行业因子

在 wq101alpha 模块中,大多数因子计算都涉及横向与纵向的计算。对于这样的因子,用户需先准备矩阵,再调用对应的 WQAlpha# 函数,返回的结果为矩阵。由于不同因子计算时用到的参数不同,用户需通过查询 附录1-因子入参一览表 来确定所需的参数。

计算方法如下:

use wq101alpha
input1 = exec close from data where tradetime between startTime : endTime pivot by tradetime, securityid
res1 = WQAlpha1(input1)

input2 = dict(`vol`close`open, panel(data.tradetime, data.securityid, [data.vol, data.close, data.open]))
res2 = WQAlpha2(input2.vol, input2.close, input2.open)

为了更加便于用户计算,省去查询参数这一步骤,因子计算准备函数模块 prepare101.dos提供了所需的矩阵准备函数 prepare# 和计算函数 calAlpha#,用户可将其作为模块导入。prepare#的作用是调用对应模块计算函数所需要的字段,calAlpha#的作用是将prepare#以及wqAlpha#封装。

以world quant alpha 第 1 号因子为例,辅助准备模块内的函数如下:

def prepare1(data, startTime, endTime){
    p = exec close from data where tradetime between startTime : endTime pivot by tradetime, securityid
    return p
}

def calAlpha1(data, startTime, endTime){
    input = prepare1(data, startTime, endTime)
    return WQAlpha1(input)
}
//调用方法如下:
use prepare101

res = calAlpha1(data, startTime, endTime)

注意:非行业因子中的41、54、101号因子除了可以用矩阵入参之外,也可以向量入参用 SQL 调用,如101号因子也可以这样调用:

use wq101alpha 
res = select tradetime, securityid, `alpha101 as factorname, WQAlpha101(close, open, high, low) as val from data where tradetime between startTime : endTime

用户可以按需灵活选取调用方法。

2.4. 表入参计算行业信息因子

少部分因子涉及到了股票的行业分类信息。对于这些因子,用户需以表入参,返回的结果也为数据表。以 world quant alpha 第48 号因子为例:

use wq101alpha

res = WQAlpha48(data)

亦可使用 辅助准备模块 prepare101.dos 中的计算函数。

def calAlpha48(data, startTime, endTime){
    input = select * from data where tradetime between startTime : endTime
    return WQAlpha48(input)
}
//调用方法如下:
use prepare101

res = calAlpha48(data, startTime, endTime)

注意:论文中用了多种行业分类,如IndClass.subindustry,IndClass.industry, IndClass.sector等,为了简便起见,本模块统一只用IndClass这一个字段做行业中性化。

3. 因子的存储

因子的存储可以参考因子最佳实践中的因子存储章节。wq101alpha 模块计算的因子返回的数据格式有矩阵及表两种形式,本章节将以宽表形式存储因子为例,完整代码可参考Alpha101计算存储全流程代码汇总

3.1. 矩阵格式的因子存储

存储面板数据,需要先将面板数据转换为表,而后进行存储。以第 1 号因子为例,计算并存储因子,示例代码如下:

// 计算alpha 1号因子,得到的矩阵存储在res中
res = calAlpha1(data, startTime, endTime)

// 将res转换成表并存储在因子宽表中
writePanelInWideTable(res, `alpha1)

其中 writePanelInWideTable 的实现可见 Alpha101计算存储全流程代码汇总

3.2. 表形式的因子存储

wq101alpha 模块中返回的表为纵表,可以直接存入单值模型(纵表)。若要将其存入宽表,可以用 pivot by 将纵表重新排列(tradetimefactorname 为行,securityid 为列)。以第 101 号因子为例,代码如下:

// 计算world quant alpha 101号因子,得到的纵表存储在res中
res = calAlpha101(data, startTime, endTime)

// 将res转换成表并存储在因子宽表中
writeLongInWideTable(res)

其中 writeLongInWideTable 的实现可见 Alpha101计算存储全流程代码汇总

4. 性能对比

本章节将用 DolphinDB 实现的 wq101alpha 模块与 python 中用 pandas, numpy 模块实现的 WorldQuant alpha 因子分别对比计算性能。

本小节测试显示,DolphinDB 的 wq101alpha 模块的性能显著优于使用 python 的 pandas 与 numpy 模块的实现。

  • 测试设备

CPU:Intel(R) Xeon(R) Silver 4216 CPU @ 2.10GHz

操作系统: 64 位 CentOS Linux 7 (Core)

  • 使用数据

本节测试使用一年模拟日频数据进行性能对比

4.1. DolphinDB 与 Python Pandas 性能对比

本节用一年模拟日频数据对比了 wq101alpha 模块和 101因子的Pandas实现 计算101个因子的性能。

wq101alpha 模块中的函数计算并计时,核心代码如下,完整的脚本可参考 wq101alpha 模块性能测试

times = array(INT, 0)
defs()
for (i in 1:102){
    if (i in passList) times.append!(NULL)
    else{
        print(i)
        alphaName = exec name from defs() where name = "wq101alpha::WQAlpha"+string(i)
        alphaSyntax = exec syntax from defs() where name = "wq101alpha::WQAlpha"+string(i)
        function = alphaName + alphaSyntax
        t1 = time(now())
        res = parseExpr(function[0]).eval()
        t2 = time(now())
        times.append!(t2 - t1)
    }
}

用 Python 脚本计算并计时,核心代码如下,完整的脚本可参考 Python alpha 101 性能测试

times = []

nofunc = [48, 56, 58, 59, 63, 67, 69, 70, 76, 79, 80, 82, 87, 89, 90, 91, 93, 97, 100]

for i in range(1, 102):
    if i in nofunc:
        times.append('no function')
        continue
    else:
        factor = getattr(Alphas, "alpha{:03d}".format(i))
    try:
        t1 = time.time()
        res = factor(stock)
        t2 = time.time()
        times.append(t2 - t1)
    except Exception:
        times.append('error')

通过两个性能测试脚本,可得到 wq101alpha 模块和 Python 脚本计算101个因子的运行耗时,完整结果可见 性能对比结果。筛除 Python 脚本中未实现的因子和实现有误的因子后,可供比较的因子共 69 个。对比结果见下表,单位为毫秒 ms。

因子ID#DolphinDBPython耗时比(py/ddb)ddbWin因子ID#DolphinDBPython耗时比(py/ddb)ddbWin
18668,837800.43true389189,487983.38true
22292,1179.24true40732,37932.59true
31402,01814.41true4184110.13false
47589,4401,192.53true42972182.25true
51206005.true4391165,9541,823.67true
6211,76584.05true44821,91823.39true
714872,001486.49true451704,85328.55true
81121,51313.51true4635571.62true
95071414.27true472521,1564.59true
101288086.31true4933371.13true
111458986.2true502352,47510.53true
1215100.69false5136381.05true
132131,7848.38true5213191,360697.4true
141131,98717.59true5329280.97false
151472,57217.49true541751781.02true
162081,7768.54true552162,99713.88true
17177174,055983.36true6015472,081468.06true
181042,41723.24true611472,61417.78true
19724406.11true623543,2049.05true
202624391.68true641912,95615.48true
21782,29629.43true651812,96816.4true
22972,35824.31true6824781,582330.29true
246468610.72true743804,76112.53true
25965005.21true752794,46115.99true
2661182,3402,989.18true783845,20413.55true
272262,57311.38true8151961,954119.37true
28452,15547.9true832091,1075.3true
2940689,515220.48true8415280,908532.29true
308283210.15true85303184,645609.39true
32532,14640.5true8612375,681615.3true
33881481.68true94169221,0361,307.91true
342541,3825.44true9528767,899236.58true
35131249,7481,906.47true992034,75823.44true
3638892,303237.89true1019202.2true
371481,95313.2true

从上表结果可以看出,使用 DolphinDB 实现的 wq101alpha 模块的计算效率远远高于 Python 的实现。DolphinDB 的性能中位数为 Python 的15.5倍,其中,27.5%的因子超100倍。

其中因子41的实现,DolphinDB 慢于 Python。原因是 pow 函数的实现有些许不同。DolphinDB 会在后续版本中提升这个内置函数的性能。

4.2. DolphinDB 与 Python numpy 的性能对比

考虑到用 python pandas Dataframe 实现因子的计算性能效率可能不及 numpy ,为公平起见,本节抽取了11个 pandas 实现耗时特别久的因子,重新用 numpy 实现,具体代码见部分101因子的numpy实现。 DolphinDB 与 numpy 在实现这些因子的性能比较如下:

因子ID#ddbpandasnumpy耗时比(pandas/ddb)耗时比(numpy/ddb)
18668,837418800.44.9
47589,44054,4171,192.5725.6
51206002185.01.8
714872,00139,472486.5266.7
81121,51326513.52.4
95071415214.33.0
17177174,05593,704983.4529.4
2940689,51547,100220.5116.0
389189,48746,257983.4508.3
5213191,36046,715697.4356.6
832091,1074645.32.2

从结果上来看, numpy 的实现确实比 pandas dataframe 实现快一些,但是由于窗口函数 numpy 中是用 numpy.lib.stride_tricks.sliding_window_view 实现的,对于窗口计算没有优化,故性能并没有显著提升。

综合来看,DolphinDB 在实现 WorldQuant 101 alpha 因子上有非常大的优势,性能卓越。

5. 正确性验证

目前 python 101个alpha因子不存在统一的实现方式。本文基于 101因子的Python实现 中的实现方式,对部分报错代码进行调整(详情见 python模块代码),与 wq101alpha 模块进行对比,部分验证了 wq101alpha 模块的正确性。由于在验证过程中发现诸多问题,该小节中将分类验证并探讨该模块实现方式与 Python 实现方式的不同。

验证所用的数据为一年模拟日频数据中的前十支股票。用 DolphinDB 验证脚本 在 DolphinDB 及 Python 验证脚本 在 python 中跑完因子后,截取第一支股票的数据做对比。

最后的验证步骤可参考正确性验证脚本

5.1. 非行业因子验证

结果显示50%的因子DolphinDB与pandas计算结果一致。其余因子通过研究,发现 wq101alpha 模块与 Python pandas 实现有以下差异:

  • 空值处理:wq101alpha 模块中,对一个滚动 / 累积窗口长度为 k 的函数,每组最初的 (k-1) 个位置的结果均为空;Python 实现中,某些因子会默认将空值取0,某些因子不作改动。

  • Rank实现差异:如 alpha 4 号因子,在 DolphinDB 中的结果与 python 中的结果相差1,其原因是Rank的实现在 DolphinDB 中是从0开始排序,而在 python 中是从1开始排序。

  • 结果不一致:经人工检验,Python 实现中存在一定错误。例如 Python 脚本计算 alpha 27 号因子的结果全为1,经检验为 Python 实现有错误。

5.2. 行业因子验证

该类因子目前少见 Python 实现,且因该模块中考虑到实际使用的便利性,已调整论文中的实现方式,故不作比较。

6. 实时流计算实现

World Quant 101 alpha 因子中大多数因子的实现方式都较为复杂,在流计算中,需要创建多个引擎进行流水线处理来完成。DolphinDB 提供了一个解析引擎 streamEngineParser 来代替人工创建并串联多个引擎,大大提高效率。

此功能从 DolphinDB 1.30.20 和 2.00.8 开始支持。

使用 wq101alpha 模块进行流计算时,因子的定义无需修改,直接在流引擎 streamEngineParser 中调用即可。

完整World Quant 101 alpha流计算流程代码可查看 WQ101alpha流计算全流程 。这里以 WQAlpha 1 为例,演示如何调用 wq101alpha 模块,实现流计算:

首先定义输入输出的表结构:

inputSchemaT = table(1:0, ["SecurityID","TradeTime","close"], [SYMBOL,TIMESTAMP,DOUBLE])
resultStream = table(10000:0, ["SecurityID","TradeTime", "factor"], [SYMBOL,TIMESTAMP, DOUBLE])

调用 wq101alpha 模块,并在 streamEngineParser 中使用 WQAlpha1 函数:

use wq101alpha
metrics = <[WQAlpha1(close)]>
streamEngine = streamEngineParser(name="WQAlpha1Parser", metrics=metrics, dummyTable=inputSchemaT, outputTable=resultStream, keyColumn="SecurityID", timeColumn=`tradetime, triggeringPattern='perBatch', triggeringInterval=4000)

部分因子可能会创建多个引擎,可以调用 getStreamEngineStat() 查看总共串联了哪些引擎:

getStreamEngineStat()

#output
ReactiveStreamEngine->
name            user        status lastErrMsg numGroups ...
-------------   ----------- ------ ---------- --------- ...
WQAlpha1Parser0 admin OK                0         0       
WQAlpha1Parser2 admin OK                0         0       

CrossSectionalEngine->
name            user  status lastErrMsg numRows ...
--------------- ----- ------ ---------- ------- ...
WQAlpha1Parser1 admin OK                0       2          

将数据注入引擎,即可在 resultStream 输出表中查看结果:

streamEngine.append!(data)
//check the result
res = exec factor from resultStream pivot by TradeTime, SecurityID

7. 小结

本教程详细介绍了 wq101alpha 模块的实现原理和用法。该模块用 DolphinDB 内置函数实现了 WorldQuant 101 alpha 因子,具有简单准确、高效快速、批流一体的特点。

8. 附录

8.1. 附录1-因子入参一览表

  • 非行业信息因子
因子序号所需参数因子序号所需参数因子序号所需参数
1, 9, 10, 19, 24, 29, 34, 46, 49, 51close23high71vwap, vol, open, close, low
2, 14vol, open, close25, 47, 74vwap, vol, close, high72, 77vwap, vol, high, low
3, 6vol, open27, 50, 61, 81vwap, vol73vwap, open, low
4low28, 35, 55, 60, 68, 85vol, high, low, close75, 78vwap, vol, low
5vwap, open, close31, 52vol, close, low83vwap, vol, close, high, low
7, 12, 13, 17, 21, 30, 39, 43, 45vol, close32, 42, 57, 84vwap, close88, 92, 94vol, open, close, high, low
8, 18, 33, 37, 38open, close36, 86vwap, vol, open, close95vol, open, high, low
11, 96vwap, vol, close41vwap, high, low65, 98vwap, vol, open
15, 16, 26, 40, 44vol, high53close, high, low99vol, high, low
20, 54, 101open, close, high, low62, 64vwap, vol, open, high, low
22vol, high, close66vwap, open, high, low
  • 行业信息因子
因子序号所需参数因子序号所需参数
48close, indclass76, 89vwap, vol, low, indclass
56close, cap80vol, open, high, indclass
58, 59vwap, vol, indclass82vol, open, indclass
63, 79vwap, vol, open, close, indclass90vol, close, indclass
67vwap, vol, high, indclass97vwap, vol, low, indclass
69, 70, 87, 91, 93vwap, vol, close, indclass100vol, close, high, low, indclass

8.2. 附录2