4月27日晚七点半,我们围绕高频因子计算,以快照、逐笔成交等 Level 2 行情因子为例,为大家介绍了 DolphinDB 与 Python 的脚本差异,并展示了如何在 DolphinDB 中实现高频因子流式计算,从数据分析层面总结了一套 Python 与 DolphinDB 的转换攻略,吸引了众多量化同行参加。错过直播也没关系,本文将带你回顾直播精彩内容!
点击获取完整直播回放: 打破Python束缚:Level2 因子的脚本优化实践
更多完整因子代码和性能对比,请参考知乎教程:DolphinDB:DolphinDB 处理 Level 2 行情数据实例
DolphinDB 的定位是一款基于高性能时序数据库,支持数据分析和流数据处理的低延时平台。传统的量化流程包括数据存储、策略研发、实时计算环节,通常使用“数据库+数据分析工具+流数据处理工具”的框架。传统的框架以存储数据源为中心,需要高精尖的 IT 技术人才,并需要运营和维护多套系统。
相比之下,DolphinDB 用足以高效覆盖全流程的强大功能,实现了向以数据、计算为重心的转移,让量化研究员将更多精力投入到业务上,大大提高了整个量化流程的效率。
DolphinDB 计算表达十分简洁,并提供了大量适配金融场景的函数,数据分析速度相较于传统解决方案可以提升100倍以上。此外,DolphinDB 实现了流批一体,研发与实盘可以共用一套代码,开发成本可以减少90%。
高频行情数据与因子的存储
Level 2 行情数据反映了市场的微观结构,具有很重要的研究价值,包含分钟 K 线、行情快照、逐笔成交、逐笔委托数据等。A 股市场高频数据每日增量达30 GB 以上,考虑全市场的所有高频数据增量更是达10 TB 以上。
在 DolphinDB 中,可以使用 TSDB 引擎存储,按照“时间+标的”组合分区,并合理设置排序键,实现分区内分块索引。
对于高频多因子,我们推荐大家使用窄表模式和 SSD 硬盘进行存储,以日期 Value +因子 Value 作为分区方案。下表展示了基于5000只标的10000因子1个月数据进行增删改查的一系列性能。
高频因子计算:Python vs. DolphinDB
A 股 Level 2 行情快照数据一天的数据量超过10G,因此金融量化工程师们非常关注 Level 2 行情快照数据的高频因子计算性能。这里列举了5个较受关注的高频因子:时间加权订单斜率、加权平均订单失衡率因子、成交价加权净委买比例、十档净委买增额、十档买卖委托均价线性回归斜率。
我们在 DolphinDB 中以流批一体的方式实现了这五个因子的计算逻辑,并可以通过如下代码进行性能测试:
timer {
res=select SecurityID,DateTime,timeWeightedOrderSlope(bidPrice[0],bidOrderQty[0],OfferPrice[0],OfferOrderQty[0]) as TimeWeightedOrderSlope,
level10_InferPriceTrend(bidPrice,OfferPrice,bidOrderQty,OfferOrderQty,60,20) as Level10_InferPriceTrend,
level10_Diff(bidPrice, bidOrderQty, true,20) as Level10_Diff,
traPriceWeightedNetBuyQuoteVolumeRatio(bidPrice[0],bidOrderQty[0],OfferPrice[0],OfferOrderQty[0],TotalValueTrade,
totalVolumeTrade) as TraPriceWeightedNetBuyQuoteVolumeRatio,
wavgSOIR( bidOrderQty,OfferOrderQty,20) as HeightImbalance
from loadTable(dbName,snapshotTBname) where date(DateTime)=idate context by SecurityID csort DateTime map
}
以下为基于快照数据计算一天全市场所有股票的5个因子的耗时,可以发现耗时基本与 CPU 数量成正比。
相比于 Python,DolphinDB 支持函数式编程和强大的向量化计算,原生分布式计算框架,因此实现因子计算逻辑的代码更加简洁,性能更强。此外,由于 DolphinDB 中支持流批一体,无需转写代码,可直接无缝接入实盘生产环境,这大大降低了研究员们的运维成本。
高频因子的流式实现
DolphinDB 封装了多个流数据处理引擎,并支持多个引擎的流水线处理,Stream Engine Parser 解析器可以自动将计算逻辑分解成多个内置流计算引擎的流水线。例如,对于一些行计算函数,Stream Engine Parser 会自动分配到横截面引擎进行计算;对于 rolling 函数,会解析到时序聚合引擎中;对其他函数,则一律解析到响应式状态引擎。
此外,DolphinDB 还对许多算子进行了优化,包括各类窗口计算函数、序列相关函数等。这些算子都可以直接在响应式状态引擎中调用,并且计算速度非常快。
在高频因子流式实现中,最重要的环节就是将因子的计算逻辑用代码实现出来,这一步的效率决定了整个流计算的效率。这里需要区分两种不同类型的函数:无状态函数和状态函数。
无状态函数指不需要回溯历史数据,仅根据当前时刻传入的参数即可获得计算结果的函数;在 DolphinDB 中,写无状态函数需要注意以下几点:
- 注意参数的数据类型和函数支持的数据类型是否匹配;
- 传入无状态函数的参数都是向量。
状态函数是指计算中不仅用到当前数据,还会用到历史数据的函数;在 DolphinDB 中,写状态函数需要注意以下几点:
- 状态函数需要用 @state 声明;
- 状态函数内只支持赋值语句,return 语句和 if-else 语句。
注意事项
下面为大家总结了在 DolphinDB 中实现高频因子流计算的几个注意事项。
(1)状态和无状态拆分
建议将复杂截面计算拆分到无状态函数中;状态函数里保留一些自定义函数的调用和有关历史数据的操作,比如 m 系列、tm 系列、 fill 相关、迭代等。
以移动平均买卖压力因子为例,需要先计算买卖压力指标,再使用 mavg 计算过去 lag 行的移动平均买卖压力指标。我们对上交所 100 只股票某日的 Level2 快照数据进行测试,拆分和不拆分的耗时分别为578ms与1492ms。
(2)if-else
- 无状态函数:支持 if-else 语句。但是 if-else 的 condition 结果必须是标量;否则需要使用 iif 函数替代。
- 状态函数:if-else 只支持 condition 是一个无关上游表格数据的标量;否则需要使用 iif 函数替代或者把 if-else 的逻辑封装成自定义的无状态函数。
(3)历史数据访问
DolphinDB 内置了丰富的计算函数来帮助用户在状态函数里面实现各种涉及历史数据的计算。比如:滑动窗口系列(m 系列)、时序滑动窗口系列(tm 系列)、累计窗口系列(cum 系列)、ffill 等函数。
除此之外,还有 movingWindowData 和 tmovingWindowData 可以直接返回变量历史值组成的向量,方便用户实现更多的自定义计算。
虽然状态函数内不支持函数自身调用的写法,但是 DolphinDB 提供了 conditionalIterate、stateIterate、genericStateIterate、genericTStateIterate 等函数来支持迭代逻辑的实现以及其他对函数结果历史值的复杂处理。
(4)循环
建议按照状态函数和无状态函数拆分的原则把循环逻辑封装在自定义的无状态函数中。
无状态函数支持 for / while 等循环语句,也支持使用 each / loop 等函数实现循环逻辑。为了更低的计算延时和更优的计算性能,在没有使用 JIT 优化因子代码的情况下,因子代码里面不建议使用 for / while 循环。尽量通过向量化计算实现因子计算逻辑或者可以使用 each / loop 等函数实现循环。
状态函数不支持 for / while 等循环语句,支持使用 each / loop 等函数实现循环逻辑。
流式实现的优化
在 DolphinDB 中,我们可以通过两种方式降低流计算的延时:使用数组向量(Array Vector)和即时编译(JIT)。
DolphinDB 中的Array Vector是一种特殊的向量,用于存储可变长度的二维数组。这种存储方式可显著简化某些常用的查询与计算。Level 2 高频因子往往需要对十档量价数据进行频繁的操作,使用数组向量存储可以提高压缩比,提升查询速度,方便因子计算逻辑的向量化实现。
另一方面,DolphinDB 底层由 C++ 实现,脚本中的一次函数调用会转化为多次 C++ 内的虚拟函数调用。DolphinDB 中的JIT 功能,在运行时将代码翻译为机器码,能够显著提高 for 循环、while 循环和 if-else 等语句的运行速度,特别适合于无法使用向量化运算但又对运行速度有极高要求的场景,例如高频因子计算、实时流数据处理等。
同样以移动平均买卖压力因子为例,测试上交所 100 只股票的某日的 Level2 快照数据,统计从数据灌入引擎开始到所有指标计算结束的总共耗时。可以看到,使用这两种方式可以显著降低流计算的延时。
攻略:从 Python 到 DolphinDB
既然 DolphinDB 功能和性能都如此强大,可以说在各个方面都可圈可点,那么,要如何更高效地将代码从 Python 转写为 DolphinDB 呢?这里我们也为大家整理了一份从数据分析角度出发的转写攻略,快来查收吧!
程序框架
首先,从整体的程序框架来看,Python 与 DophinDB 本质上是一致的,都是从定义因子开始,用脚本语言实现因子的计算逻辑,然后通过特定的方式调用因子。工程化层面的差别在于,DolphinDB 库表是分区表,可通过 submitJob 等后台任务提交作业,实现多因子并行计算。
编程范式
编程范式上,Python 使用较多的主要还是命令式编程,虽然 DolphinDB 也同样支持,但 DolphinDB 有更强大的函数化编程和向量化编程。因此在 DolphinDB 中,不同的数据结构之间可以通过函数相互转化,并且可以对部分数据结构进行水平、垂直的灵活切片,各类单目函数、双目函数以及窗口函数,都可以应用于各种不同的数据结构。实现一段程序时,也无须繁琐的表内赋值,DolphinDB 用函数化的方式,让用户可以搭积木一般搭建出大框架。
函数层面
Python 中有非常多的库包,包含了许多用于做数据分析的函数;DolphinDB 也内置了1500+ 函数,包含了常用的统计函数等。具体的函数映射可以参考我们在知乎发布的官方教程:《Python 函数到 DolphinDB 函数的映射》。在 DolphinDB 中,函数更多、效率更高,且没有 Python 库包之间的转换。
除此之外,我们的数据分析负责人毛忻玥老师还从许多微观层面为大家进行了详细的代码转换教学,包括:
- 数据结构
- 行列操作取数
- 数据的准备与清洗(空值处理、离散化处理、数据一览、随机重排列和随机抽样、计算指标/虚拟变量……)
- 多维分析与数据透视
- 数据整合
- 窗口计算
- 循环和迭代
- 时间转换与采样
- ……
范围非常广泛,涉及了 Python 中 panda.cut、describe、numpy.random.permutation、groupby、pivot、pandas.merge、pandas.rolling、reduce、for 循环等各种函数和语句。
这些用法要怎么转写到 DolphinDB 呢?我们将会发布一篇完整的攻略进行详细介绍,大家可以期待一下~
本次直播的演示材料已经发布在因子挖掘交流群中,还有没进群的小伙伴嘛?快快添加小助手(dolphindb1),领取你的攻略礼包吧!