您的位置:  首页 > 技术杂谈 > 正文

性能提升30倍丨基于 DolphinDB 的 mytt 指标库实现

2022-07-19 14:00 https://my.oschina.net/u/4865736/blog/5555568 DolphinDB智臾科技 次阅读 条评论

MyTT 是一个简单易用的 Python 库,它将通达信、同花顺、文华麦语言等指标公式最简化移植到了 Python 中,实现的常见指标包括 MACD、RSI、BOLL、ATR、KDJ、CCI、PSY 等。MyTT 全部基于 numpy 和 pandas 的函数进行封装。

为了方便用户在 DolphinDB 中计算这些技术指标,我们使用 DolphinDB 脚本实现了 MyTT 中包含的指标函数,并封装在 DolphinDB mytt module (mytt.dos)中。 相比于 Python 中的 MyTT 库,DolphinDB mytt module 中的计算函数不仅在批处理中性能有大幅提升,而且支持 DolphinDB 的流式增量计算引擎,可以直接用于实时流计算场景。

因为 DolphinDB mytt module 是基于 DolphinDB V1.30.18 和 DolphinDB V2.00.6 开发的,所以建议用户使用 DolphinDB V1.30.18 和 DolphinDB V2.00.6 及以上版本运行 mytt 指标库中的函数。


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

  • Python MyTT 库中所有函数名大写,所有参数名大写,为适应使用者的使用习惯,DolphinDB mytt module 中的函数名、参数、参数默认值均与 MyTT 保持一致。
  • 为得到有意义的计算结果,mytt 中函数的参数表示时间跨度的参数均要求至少是 2。
  • 由于 LAST 函数与 DolphinDB 中内置关键字冲突,mytt 中将此函数命名为 LAST_。

2. 环境配置

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

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

3. 使用范例

3.1 脚本中直接使用指标函数

对一个向量直接使用 mytt 模块中的 EMA 函数(指数平滑法)进行计算:

// 如果未设置自动加载 mytt module,新会话需要手动加载一次 mytt module
use mytt

close = 7.2 6.97 7.08 6.74 6.49 5.9 6.26 5.9 5.35 5.63
x = EMA(close, 5)

3.2 在 SQL 语句中分组使用

用户经常需要在数据表中对多组数据在每组内进行计算。在以下例子中,先构造了一个包含 2 个股票的数据表:

use mytt

close = 7.2 6.97 7.08 6.74 6.49 5.9 6.26 5.9 5.35 5.63 3.81 3.935 4.04 3.74 3.7 3.33 3.64 3.31 2.69 2.72
date = (2020.03.02 + 0..4 join 7..11).take(20)
symbol = take(`F,10) join take(`GPRO,10)
t = table(symbol, date, close)

对其中每只股票使用 mytt 模块中的 EMA 函数进行计算:

update t set EMA = EMA(close, 5) context by symbol

3.3 返回多个列的结果

某些函数会返回多个列的结果,例如函数 BIAS(乘离率指标)。

直接使用的例子:

use mytt

close = 7.2 6.97 7.08 6.74 6.49 5.9 6.26 5.9 5.35 5.63
bias1, bias2, bias3 = BIAS(close, L1 = 2, L2 = 4, L3 = 6)

在 SQL 语句中使用的例子:

use mytt

close = 7.2 6.97 7.08 6.74 6.49 5.9 6.26 5.9 5.35 5.63 3.81 3.935 4.04 3.74 3.7 3.33 3.64 3.31 2.69 2.72
date = (2020.03.02 + 0..4 join 7..11).take(20)
symbol = take(`F,10) join take(`GPRO,10)
t = table(symbol, date, close)
select *, BIAS(close, L1 = 2, L2 = 4, L3 = 6) as `bias1`bias2`bias3 from t context by symbol

symbol date       close  bias1    bias2    bias3
------ ---------- ----- -------- -------- --------
F      2020.03.02 7.2
F      2020.03.03 6.97 -1.623
F      2020.03.04 7.08  0.783
F      2020.03.05 6.74 -2.46     -3.68
F      2020.03.06 6.49 -1.89     -4.839
F      2020.03.09 5.9  -4.762    -9.958   -12.333
F      2020.03.10 6.26  2.961    -1.378   -4.767
F      2020.03.11 5.9  -2.961    -3.87    -7.74
F      2020.03.12 5.35 -4.889    -8.586   -12.391
F      2020.03.13 5.63  2.55     -2.679   -4.925
GPRO   2020.03.02 3.81
GPRO   2020.03.03 3.935 1.614
GPRO   2020.03.04 4.04  1.317
GPRO   2020.03.05 3.74 -3.856    -3.639
GPRO   2020.03.06 3.7  -0.538    -3.99
GPRO   2020.03.09 3.33 -5.263    -10.061 -11.417
GPRO   2020.03.10 3.64  4.448     1.041  -2.435
GPRO   2020.03.11 3.31 -4.748    -5.293  -8.732
GPRO   2020.03.12 2.69 -10.333   -17.039 -20.921
GPRO   2020.03.13 2.72  0.555    -11.974 -15.833

4. 函数计算性能

本节将以 AVEDEV 函数为例做直接使用的性能对比,同时使用真实股票日频数据对所有函数进行分组使用性能对比。

4.1 直接使用性能对比

在 DolphinDB 中:

use mytt

close = 7.2 6.97 7.08 6.74 6.49 5.9 6.26 5.9 5.35 5.63
close = take(close, 100000)
timer x = mytt::AVEDEV(close, 100)

对一个长度为 100000 的向量直接使用 mytt 模块中的 AVEDEV 函数,耗时为 25ms。

与之对应的 Python 代码如下:

import numpy as np
from MyTT import *
import time

close = np.array([7.2,6.97,7.08,6.74,6.49,5.9,6.26,5.9,5.35,5.63])
close = np.tile(close,10000)
start_time = time.time()
x = AVEDEV(close, 100)
print("--- %s seconds ---" % (time.time() - start_time))

Python MyTT 库中的 AVEDEV 函数耗时为 25000ms,是 DolphinDB mytt module 中的 AVEDEV 函数的 1000 倍。测试数据量越大,性能差异越显著。

4.2 分组使用性能对比

  • 测试数据为上海证券交易所 2020 年,全年 2919 个证券(筛选交易日大于 120)日频交易数据,总记录数为 686,104 条。
  • 计算逻辑为按照股票代码进行分组计算各指标。
  • 为了测试函数计算性能,DolphinDB 和 Python 测试代码都是单线程运行。
  • DolphinDB 测试代码
  • Python 测试代码
  • 测试数据
  • Python MyTT 库

测试结果如下表所示:

序号函数Python(ms)DolphinDB(ms)运行时间比
1RD2961618
2RET2431318
3ABS2291515
4LN2532510
5POW3113010
6SQRT2481913
7MAX3903411
8MIN3732912
9IF2822113
10REF7401743
11DIFF6622230
12STD1,2632498
13SUM1,2972258
14CONST2582211
15HHV1,2073040
16LLV1,2183139
17HHVBARS2,9524172
18LLVBARS2,8783875
19MA1,2202450
20EMA1,1712645
21SMA1,1992842
22WMA4,32220216
23DMA1,1232741
24AVEDEV176,652325,520
25SLOPE53,703291,851
26FORCAST60,321381,587
27LAST4,13238108
28COUNT1,2492062
29EVERY1,2672845
30EXIST1,4902267
31BARSLAST5591831
32BARSLASTCOUNT6071735
33CROSS2,0888026
34LONGCROSS6,0199464
35VALUEWHEN9682735
36BETWEEN4894211
37MACD3,0608635
38KDJ4,70514432
39RSI2,53910324
40WR5,63216633
41BIAS5,31813539
42BOLL3,0679034
43PSY2,5968231
44CCI163,681762,153
45ATR2,28110122
46BBI3,6676655
47DMI6,18125024
48TAQ2,2926435
49KTN3,17016419
50TRIX4,3299744
51VR2,73211723
52EMV4,43713233
53DPO2,4555941
54BRAR4,90915631
55DFMA2,8905255
56MTM1,6594338
57MASS4,6029946
58ROC2,0006331
59EXPMA1,9004938
60OBV1,7909419
61MFI3,48815822
62ASI4,17331613

从测试结果分析可知:

  • DolphinDB mytt module 中的函数计算性能远远超过 Python MyTT 库,最大的性能差距达到 5520 倍,普遍性能差距在 30 倍左右。

**Python pandas 测试核心代码 **

data.groupby("symbol").apply(lambda x: RSI(np.array(x.close), N = 24))

DolphinDB 测试核心代码

RSI = select symbol, tradedate, mytt::RSI(close, N=24) as `RSI from data context by symbol

5. 正确性验证

基于 4.2 分组使用性能对比中的测试数据和代码,验证 DolphinDB mytt module 中函数的计算结果是否和 Python MyTT 库一致。

5.1 浮点数精度问题

结果有差异的函数

  • CROSS, LONGCROSS

原因

  • 浮点数精度问题
  • 对于 CROSS 和 LONGCROSS 函数,在浮点数比较上,DolphinDB mytt module 中的处理比 Python MyTT 库更加严谨。DolphinDB mytt module 中首先会对浮点数 round 保留小数点后 6 位,然后再进行大小判断,而 MyTT 中并没有类似处理,因此对于相同大小的浮点数,Python 的判别可能会出错,如下图所示:

5.2 NULL 值的处理

结果有差异的函数

  • SUM, DMI, EMV, MASS, MFI, ASI

原因

  • 若输入向量开始包含空值,则从第一个非空位置开始计算。DolphinDB mytt module 与 Python MyTT 库的计算规则一致。
  • 对一个滚动 / 累积窗口长度为 k 的函数,每组最初的 (k-1) 个位置的结果均为空。DolphinDB mytt module 与 Python MyTT 库的计算规则一致。
  • 对一个滚动 / 累积窗口长度为 k 的函数,若一组中第一个非空值之后再有空值,Python MyTT 库会对包含 nan 的窗口计算结果都处理为 nan。DolphinDB mytt module 会对窗口内非 NULL 的元素按计算规则计算,得到一个非 NULL 的计算结果。

DolphinDB 代码与结果:

close = [99.9, NULL, 84.69, 31.38, 60.9, 83.3, 97.26, 98.67]
mytt::SUM(close, 5);

[,,,,276.87, 260.27, 357.53, 371.51]

Python 代码与结果:

close = np.array([99.9, np.nan, 84.69, 31.38, 60.9, 83.3, 97.26, 98.67])
MyTT.SUM(close,5)

array([nan, nan, nan, nan, nan,  nan, 357.53, 371.51])

以滑动窗口求和为例,close 向量的第 2 个元素为空值,DolphinDB mytt module 在计算第 5 个元素(60.9)时,回看过去 5 个窗口内的数据 [99.9, NULL, 84.69, 31.38, 60.9],对非 NULL 的元素求和,所以结果向量的第 5 个元素为 276.87。

Python MyTT 库会对包含 nan 的窗口计算结果都处理为 nan,所以结果向量的前 6 个元素都为 nan。

除上述因为浮点数精度问题和 NULL 值的处理问题导致计算结果存在差异外,其余函数计算结果的百分比误差均小于 1e-10。

6. 实时流计算案例

在 DolphinDB V1.30.3 中发布的响应式状态引擎(Reactive State Engine)是许多金融场景流批统一计算中的重要构件,DolphinDB mytt module 在开发时就对其做了适配,使得 mytt 模块中的大部分函数可以在响应式状态引擎中实现增量计算。

  • 当前无法在响应式状态引擎中使用的指标函数:RET, CONST, BARSLAST, BARSLASTCOUNT,已经规划开发。
  • 所有 mytt 中的 技术指标函数 均支持增量计算。

示例代码如下:

def cleanEnvironment(){
	try{unsubscribeTable(tableName="snapshotStream",actionName="aggr1min") } catch(ex){ print(ex) }
	try{dropStreamEngine("myttReactiveStateEngine") } catch(ex){ print(ex) }
	try{dropStreamEngine("aggr1min") } catch(ex){ print(ex) }
	try{dropStreamTable(`snapshotStream) } catch(ex){ print(ex) }
	try{dropStreamTable(`outputTable) } catch(ex){ print(ex) }
	undef all
}
cleanEnvironment()
go

//load modules
use mytt

//define stream table
name = `tradetime`SecurityID`high`low`open`close`vol
type = `TIMESTAMP`SYMBOL`DOUBLE`DOUBLE`DOUBLE`DOUBLE`INT
share streamTable(100:0, name, type) as snapshotStream
name = `SecurityID`tradetime`K`D`J`DIF`DEA`MACD`UPPER`MID`LOWER`ROC`MAROC
type = `SYMBOL`TIMESTAMP`DOUBLE`DOUBLE`DOUBLE`DOUBLE`DOUBLE`DOUBLE`DOUBLE`DOUBLE`DOUBLE`DOUBLE`DOUBLE
share streamTable(1000000:0, name, type) as outputTable

//register stream computing engine
reactiveStateMetrics=<[
    tradetime,
    mytt::KDJ(close, high, low, N=9, M1=3, M2=3) as `K`D`J,
    mytt::MACD(close, SHORT_=12, LONG_=26, M=9) as `DIF`DEA`MACD,
    mytt::KTN(close, high, low, N=20, M=10) as `UPPER`MID`LOWER,
    mytt::ROC(close, N=12, M=6) as `ROC`MAROC
]>

createReactiveStateEngine("myttReactiveStateEngine", metrics=reactiveStateMetrics, dummyTable=snapshotStream, outputTable=outputTable, keyColumn=`SecurityID, keepOrder=true)

createTimeSeriesEngine(name="aggr1min", windowSize=60000, step=60000, metrics=<[first(open),max(high),min(low),last(close),sum(vol)]>, dummyTable=snapshotStream, outputTable=getStreamEngine("myttReactiveStateEngine"), timeColumn=`tradetime, useWindowStartTime=true, keyColumn=`SecurityID)

subscribeTable(tableName="snapshotStream", actionName="aggr1min", offset=-1, handler=getStreamEngine("aggr1min"), msgAsTable=true, batchSize=2000, throttle=1, hash=0, reconnect=true)

7. DolphinDB mytt 指标列表

7.1 核心工具函数

函数语法解释
RDRN(N, D = 3)四舍五入取 3 位小数
RETRET(S, N = 1)返回序列 倒数第 N 个值,默认返回最后一个
ABSABS(S)返回序列或数值 S 的绝对值
LNLN(S)求序列 S 底是 e 的自然对数
POWPOW(S, N)求序列 S 的 N 次方
SQRTSQRT(S)求序列 S 的平方根
MAXMAX(S1, S2)配对比较两个序列,给出比较以后大的序列
MINMIN(S1, S2)配对比较两个序列,给出比较以后小的序列
IFIF(S, A, B)序列布尔判断,if S == True return A else B
REFREF(S, N = 1)对序列整体下移动 N 个单位,返回平移后的序列,会产生 NAN
DIFFDIFF(S, N = 1)序列 S 的前一个值减后一个值,序列头部会产生 NAN
STDSTD(S, N)求序列 S 的滚动 N 日标准差,返回滚动标准差序列
SUMSUM(S, N)对序列 S 求滚动 N 日总和
CONSTCOUNT(S)返回序列 S 最后一个值组成常量序列
HHVHHV(S, N)求序列 S 的滚动 N 日最大值,返回滚动最大值序列
LLVLLV(S, N)求序列 S 的滚动 N 日最小值,返回滚动最小值序列
HHVBARSHHVBARS(S, N)求序列 S 的滚动 N 期内最高值到当前的天数, 返回距离天数序列
LLVBARSLLVBARS(S, N)求序列 S 的滚动 N 期内最低值到当前的天数, 返回距离天数序列
MAMA(S, N)求序列 S 的 N 日简单移动平均值,返回移动平均序列
EMAEMA(S, N)求序列 S 的指数移动平均,为了精度,S>4*N,EMA 至少需要 120 周期 alpha = 2/(span+1)
SMASMA(S, N, M = 1)中国式的 SMA,至少需要 120 周期才精确 (雪球 180 周期),alpha = 1/(1+N)
WMAWMA(S, N)求 S 序列 S 的 N 日加权移动平均,Yn = (1*X1+2*X2+3*X3+...+n*Xn)/(1+2+3+...+n)
DMADMA(S, A)求 S 的动态移动平均,A 作平滑因子,必须 0 < A < 1
AVEDEVAVEDEV(S, N)求序列 S 的滚动平均绝对偏差
SLOPESLOPE(S, N)求序列 S 的滚动 N 周期内的线性回归模型的斜率
FORCASTFORCAST(S, N)求序列 S 的滚动 N 周期内的线性回归模型的预测值
LAST_LAST_(S, A, B)BOOL 型判断,从前 A 日到前 B 日一直满足 BOOL 条件,要求 A > B & A > 0 & B >= 0

7.2 应用层函数 (通过核心工具函数实现)

函数语法解释
COUNTCOUNT(S, N)序列 S 是 BOOL 型,求最滚动 N 天内满足 BOOL 为 True 的天数
EVERYEVERY(S, N)序列 S 是 BOOL 型,求最滚动 N 天内 全部 满足 BOOL 为 True 的天数
EXISTEXIST(S, N)序列 S 是 BOOL 型,判断最滚动 N 天内 是否存在 满足 BOOL 为 True
BARSLASTBARSLAST(S)序列 S 是 BOOL 型,统计上一次条件成立到当前的周期
BARSLASTCOUNTBARSLASTCOUNT(S)序列 S 是 BOOL 型,统计连续满足条件的周期数
BARSSINCENBARSSINCEN(S, N)序列 S 是 BOOL 型,统计滚动周期 N 内第一次满足条件到当前的周期数
CROSSCROSS(S1, S2)判断两个序列是否交叉的函数,判断向上金叉穿越 CROSS(MA(C,5),MA(C,10)) ,判断向下死叉穿越 CROSS(MA(C,10),MA(C,5))
LONGCROSSLONGCROSS(S1, S2, N)判断两个序列是否在个持一定周期后再交叉的函数,判断两个序列是否再个持 N 周期后再交叉,N = 1 时等同于 CROSS(S1, S2)
VALUEWHENVALUEWHEN(S, X)解决当 S 条件成立时, 取 X 的当前值, 否则取 S 的上个成立时对应的 X 值
BETWEENBETWEEN(S, A, B)判断 S 序列是否介于 A 和 B 之间的函数,当 S 处于 A 和 B 之间时为真,包括 A<S<B 或 A>S>B

7.3 技术指标函数 (全部通过核心工具和应用函数实现)

函数语法解释
MACDMACD(CLOSE, SHORT = 12, LONG = 26, M = 9)平滑异同平均线
KDJKDJ(CLOSE, HIGH, LOW, N = 9, M1 = 3, M2 = 3)KDJ 指标
RSIRSI(CLOSE, N = 24)RSI 指标, 和通达信小数点 2 位相同
WRWR(CLOSE, HIGH, LOW, N = 10, N1 = 6)W&R 威廉指标
BIASBIAS(CLOSE, L1 = 6, L2 = 12, L3 = 24)BIAS 乖离率
BOLLBOLL(CLOSE, N = 20, P = 2)BOLL 指标,布林带
PSYPSY(CLOSE, N = 12, M = 6)PSY 指标,心理线
CCICCI(CLOSE, HIGH, LOW, N = 14)CCI 指标,顺势线
ATRATR(CLOSE, HIGH, LOW, N = 20)真实波动 N 日平均值
BBIBBI(CLOSE, M1 = 3, M2 = 6, M3 = 12, M4 = 20)BBI 多空指标
DMIDMI(CLOSE, HIGH, LOW, M1 = 14, M2 = 6)DMI 动向指标
TAQTAQ(HIGH, LOW, N)唐安奇通道 (海龟) 交易指标
KTNKTN(CLOSE, HIGH, LOW, N = 20, M = 10)肯特纳交易通道
TRIXTRIX(CLOSE, M1 = 12, M2 = 20)三重指数平滑平均线
VRVR(CLOSE, VOL, M1 = 26)VR 容量比率
EMVEMV(HIGH, LOW, VOL, N = 14, M = 9)EMV 简易波动指标
DPODPO(CLOSE, M1 = 20, M2 = 10, M3 = 6)区间震荡线
BRARBRAR(OPEN, CLOSE, HIGH, LOW, M1 = 26)BRAR-ARBR 情绪指标
DFMADFMA(CLOSE, N1 = 10, N2 = 50, M = 10)DFMA 平行线差指标
MTMMTM(CLOSE, N = 12, M = 6)MTM 动量指标
MASSMASS(HIGH, LOW, N1 = 9, N2 = 25, M = 6)梅斯线
ROCROC(CLOSE, N = 12, M = 6)变动率指标
EXPMAEXPMA(CLOSE, N1 = 12, N2 = 50)指数平均数指标
OBVOBV(CLOSE, VOL)能量潮指标
MFIMFI(CLOSE, HIGH, LOW, VOL, N = 14)MFI 资金流量
ASIASI(OPEN, CLOSE, HIGH, LOW, M1 = 26, M2 = 10)振动升降指标

8. 路线图(Road Map)

  • 优化当前无法在响应式状态引擎中使用的指标函数,包括 RET, CONST, BARSLAST, BARSLASTCOUNT,预计在 DolphinDB V1.30.19 和 DolphinDB V2.00.7 支持上述函数的增量计算和在响应式状态引擎中的使用。
  • 长期保持对 Python MyTT 包的同步更新。

附件

计算性能测试环境

  • CPU 类型:Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz 3.60 GHz
  • 逻辑 CPU 总数:8
  • 内存:32GB
  • OS:Windows 10

 

展开阅读全文
  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接