WorldQuant 101 Alpha 因子的流批一体实现

挖掘和计算 alpha 因子在量化金融领域有着重要的意义。在WorldQuant 发表的论文 101 Formulaic Alphas 中,Kakushadze 列出了101个 alpha 因子的公式。为方便用户在 DolphinDB 中计算因子,本文使用 DolphinDB 脚本实现了所有101个因子的函数,并封装在 DolphinDB 模块wq101alpha (wq101alpha.dos) 中。

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

  • 实现简单:在 DolphinDB 中基本可以实现原公式的一对一直接翻译。
  • 性能优越:性能远优于传统的 Python 实现方式,平均性能是Python的250倍,中位数是15.5倍。
  • 批流一体:模块中定义的因子函数,既可以用于历史计算,又可以用于流式增量计算。
本教程包含的所有代码兼容 DolphinDB 2.00.8,1.30.20 及以上版本。

本教程包含内容:因子的存储、性能对比、正确性验证、实时流计算实现等内容

点击右侧目录快速查看相应内容


1. 函数及参数的命名与用法规范

1.1 函数介绍

wq101alpha 模块中供用户直接调用的函数为WQAlpha#, 如WQAlpha1WQAlpha2。每个因子的计算方式和输入参数都不同,相关说明请参考 返回数据格式总结 和 输入字段介绍。用户也可以根据自己需求在模块中做修改。

WQAlpha#函数的输入与输出根据因子特点可分为三类:

  • 不含行业信息的截面因子:

输入:面板数据

输出:面板数据

  • 无状态因子:

输入:向量

输出:向量

  • 行业信息因子:

输入:数据表

输出:数据表

1.2 输入字段介绍

表格中的参数名称即为模块中的标准字段名称。

参数名称 / 标准字段名称参数含义
tradetime交易时间
securityid股票代码
open开盘价
close收盘价
high最高价
low最低价
vol交易量
vwap成交量加权平均价格
cap市值
indclass行业类型

数据表中若缺失特定的数据列(如缺失 vwap 数据),将会导致部分因子无法计算。因此计算某个因子前,应保证相应字段的数据存在。以下为三大类因子中各个因子所需的参数。

  • 不含行业信息的截面因子
因子序号所需参数因子序号2所需参数3因子序号4所需参数5
1, 9, 10, 19, 24, 29, 34, 46, 49, 51close22.vol, high, close66.vwap, open, high, low
2, 14vol, open, close23.high71.vwap, vol, open, close, low
3, 6vol, open25, 47, 74vwap, vol, close, high72, 77vwap, vol, high, low
4.low27, 50, 61, 81vwap, vol73.vwap, open, low
5.vwap, open, close28, 35, 55, 60, 68, 85vol, high, low, close75, 78vwap, vol, low
7, 12, 13, 17, 21, 30, 39, 43, 45vol, close31, 52vol, close, low83.vwap, vol, close, high, low
8, 18, 33, 37, 38open, close32, 42, 57, 84vwap, close88, 92, 94vol, open, close, high, low
11, 96vwap, vol, close36, 86vwap, vol, open, close95.vol, open, high, low
15, 16, 26, 40, 44vol, high53.close, high, low65, 98vwap, vol, open
20.open, close, high, low62, 64vwap, vol, open, high, low99.vol, high, low
  • 无状态因子
因子序号所需参数
41vwap, high, low
54, 101open, close, 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

1.3 因子类型介绍

如上所述,本文将 wq101alpha 模块中的因子分为三大类:不涉及行业信息的截面因子,无状态因子,涉及行业信息及行业中性化步骤的因子。本小节将对这三类因子做详细介绍。

  • 不涉及行业信息的截面因子
    所谓截面因子,指的是因子在计算过程中,在纵向(时间序列上)或者横向(各个股票之间)或者两个维度上均进行了计算和比较。以 world quant alpha 1 号因子为例,公式为:
 rank(Ts_ArgMax(SignedPower((returns<0?stddev(returns,20):close), 2), 5))-0.5


其中,stddevTs_ArgMax在时间序列的纵向维度上进行了计算和比较,rank是在股票之间的横向维度上计算和比较。

  • 无状态因子
    指的是不含任何截面操作的因子,即计算该因子用到的数据仅仅依赖于当前时刻、当前股票的信息。不涉及任何与其他时间点、其他股票的比较和计算。如 world quant alpha 41 号因子:
 (((high * low)^0.5) - vwap)


由公式可见,因子的计算结果仅仅取决于当前时刻当前股票的 high,low,vwap 值。

  • 涉及行业信息及行业中性化过程的因子
    这些因子的计算除了可能涉及时间序列和股票之间的横向比较这两个截面维度,还涉及到行业分类信息,并在这些维度上进行计算。以二维的面板数据入参并不能满足这类因子的计算需求,所以此类因子以表入参,调用 DolphinDB 特有的 context by 语句,即可以对任意维度进行分组计算。以 world quant alpha 70 号因子为例:
 ((rank(delta(vwap, 1.29456))^Ts_Rank(correlation(IndNeutralize(close, IndClass.industry), adv50, 17.8256), 17.9171)) * (-1))


其中,IndNeutralize 是在行业分类的维度上进行计算,correlation, Ts_Rank, adv,delta是在时间序列的纵向维度上计算和比较,rank 是在股票之间的横向维度上计算和比较。

1.4 流程图示

下图演示了利用 wq101alpha 模块计算因子的过程。

除了该模块中的函数,用户也可能需要调用(可选) 因子计算准备函数模块 prepare.dos 中的函数以完成某个因子的计算,具体用法见 使用范例 。

1.5 返回数据格式总结

返回形式因子序号
表(含行业信息)48,56,58,59,63,67,69,70,76,79,80,82,87,89,90,91,93,97,100
表(无状态因子)41,54,101
面板数据/矩阵(截面因子)其余所有因子

1.6 环境配置

把附件的 wq101alpha.dos 放在节点的 [home]/modules 目录下,[home] 目录由系统配置参数 home 决定,可以通过 getHomeDir() 函数查看。初次使用模块文件时,[home] 目录没有 modules 目录,手动创建 modules 目录,然后把 wq101alpha.dos 模块文件放入该目录即可。

DolphinDB 模块使用的教程文档:DolphinDB 教程:模块 。

2. 使用范例

该模块的开发基于模拟生成的日频数据,对于日频数据 rawData 的生成可参考 日频数据及分钟频数据建库建表 ;对于基本面信息 infoData,可参考 基本面数据建库建表 。

完整计算和存储101个因子的脚本可参考 Alpha101计算存储全流程代码汇总 。使用该全流程汇总脚本前,需将 prepare.dos 和 wq101alpha.dos 按照 1.6 环境配置 放在 [home]/modules 目录下。

2.1 计算前准备

计算前,需调用载入模块和数据。为保证现有的数据表中的字段名与模块中字段名一致,并可将日频信息和基本面信息做一个表连接,此处调用一个整体准备函数 prepareData, 具体函数定义可查看 因子计算准备函数模块 prepare.dos :

如若用户采用的数据字段名与  1.2节输入字段 中的标准字段名一致,无需调用准备函数  prepareData 。
prepareData(rawData, startTime, endTime, securityidName=NULL, tradetimeName=NULL, openName=NULL, closeName=NULL, highName=NULL, lowName=NULL, volumeName=NULL, vwapName=NULL, infoSecurityidName=NULL, capName=NULL, indclassName=NULL, infoData=NULL)

载入模块和数据方法如下:

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", infoSecurityidName="securityid", capName="cap", indclassName="indclass", infoData=infoData)

上方的 data 即为准备好的数据。不同因子的计算方式和返回得到的数据格式可能不同,因子计算逻辑和返回格式见 流程图示 和 返回数据格式总结

2.2 面板数据入参计算的截面因子

在 wq101alpha 模块中,大多数因子计算都涉及横向与纵向的截面计算。对于这样的因子,常规情况下用户需先准备面板数据,再调用对应的 WQAlpha# 函数,返回的结果为面板数据。由于不同因子计算时用到的参数不同,用户需通过查询 输入字段 来确定所需的参数。方法如下:

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

为了更加便于用户计算,省去查询参数这一步骤,所有面板数据入参计算的截面因子所用到的准备函数 prepare# 和计算函数 calAlpha# 均包含在 因子计算准备函数模块 prepare.dos 中。用户可从该脚本中复制对应的函数;亦可将其作为模块导入,从而更方便地使用其中的准备函数 prepare# 和计算函数 calAlpha#

以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)

2.3 表入参计算行业信息因子

部分因子涉及到了股票的行业分类信息。对于所有包含 IndNeutralize 这一步骤的因子,用户只需以表入参,返回的结果为数据表。以 world quant alpha 48 号因子为例:

use wq101alpha

res = WQAlpha48(data)

亦可使用 因子计算准备函数模块 prepare.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)
需要注意的是,现有的股票行业分类不存在统一的标准。为了便于通用,本文对公式进行了一定的调整,不再考虑行业标准的分类等级,而是通过  context by 操作对  infoData 表中的行业字段进行分组,然后计算对应分组的数据。

2.4 向量入参计算无状态因子

对于不涉及截面计算的无状态因子,用户需用 SQL 语句中调用对应的的 WQAlpha# 函数,直接用向量入参,故返回的结果为数据表。以 world quant alpha 101 号因子为例:

use wq101alpha

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

亦可使用 因子计算准备函数模块 prepare.dos 中的计算函数。

def calAlpha101(data, startTime, endTime){
    return select tradetime, securityid, `alpha101 as factorname, WQAlpha101(close, open, high, low) as val from data where tradetime between startTime : endTime
}

//调用方法如下:
use prepare101

res = calAlpha101(data, startTime, endTime)

3. 因子的存储

因子计算之后如有需要则要将因子存储入库,因子的存储可以参考因子最佳实践中的因子存储章节。wq101alpha 模块计算的因子返回的数据格式有面板数据及表两种形式,本章节将以这两种形式介绍两种存储的方法。本章节将以宽表形式存储因子为例,完整代码可参考Alpha101计算存储全流程代码汇总

3.1 面板数据格式的因子存储

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

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

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

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

3.2 表形式的因子存储

wq101alpha 模块中返回的表为纵表,可以直接存入单值模型(纵表)。若要将其存入宽表,可以用 pivot by 按照两个维度将纵表重新排列(tradetimefactorname 为行,securityid 为列。以 WorldQuant alpha 101 号因子为例,计算并存储,代码如下:

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

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

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

4. 性能对比

本章用一年模拟日频数据对比了 wq101alpha 模块和 101因子的Python实现 计算101个因子的性能。完整的性能对比脚本可参考 wq101alpha 模块性能测试 和 Python alpha 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 tqdm(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')

测试使用的服务器CPU为Intel(R) Xeon(R) Silver 4216 CPU @ 2.10GHz,操作系统为 64 位 CentOS Linux 7 (Core)。

在同一设备同一环境运行两个性能测试脚本,可得到 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 的实现。Python 实现的耗时平均是 DolphinDB 实现的250倍,中位数为15.5倍。

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

5. 正确性验证

目前101个alpha因子不存在统一的实现方式。本文参考了 101因子的Python实现 中的实现方式,用稍作调整的python模块代码,部分验证了该模块的正确性。由于在验证过程中发现诸多问题,该小节中将分类、分布验证并探讨该模块实现方式与 Python 实现方式的不同。

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

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

5.1 包含截面计算的因子验证

验证仅含纵向截面计算的因子的正确性,即截面计算只包含时间顺序上计算的因子。通过比较结果,发现 wq101alpha 模块与 Python 实现有以下差异:

  • 空值处理: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 无状态因子验证

wq101alpha 中仅有 WorldQuant alpha 41,54,101号因子为无状态因子。DolphinDB 的结果与 python 的结果均一致。

5.3 含行业中性化因子验证

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

5.4 小结

虽然现有的 Python 实现方式存在诸多不统一、不正确之处,但通过上述分类、分布的讨论,DolphinDB 中的 alpha101 模块对比现有的 Python 实现有着如下几点优越性:

  • 更直观wq101alpha 模块中的函数均直接对照标准公式实现,错误率低,同样便于直接修改和检查。
  • 更标准wq101alpha 模块中所用到的函数均为 DolphinDB 的标准函数,最大程度避免因子之间实现方式和标准的差异。
  • 易用性更高wq101alpha 模块中凡涉及到截面计算的部分均用面板数据入参,保证了截面计算简单明了,无需多余的分组处理,进一步降低错误率。

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 因子,具有简单准确、高效快速、批流一体的特点。用户通过将该模块导入 DolphinDB, 可以一站式实现数据的计算、存储、查询。

8. 附件汇总

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值