【问题标题】:rewrite a kdb script in DolphinDB database在 DolphinDB 数据库中重写 kdb 脚本
【发布时间】:2019-12-23 01:01:29
【问题描述】:

我正在尝试在 DolphinDB 中重写一个 kdb 脚本。

让我先解释一下我需要做什么。如果信号高于阈值 T1,我们在证券中建立多头头寸。我们不想在信号跌破 T1 时立即平仓,所以我们给它一个缓冲:只有当信号跌破低于 T1 的 T10 时,我们才会平仓。

另一方面,如果信号低于阈值 T2,我们建立空头头寸。只有当信号高于 T20>T2 时,我们才会平仓。

T1>T10>T20>T2。

基本上我需要以下向量:

 - if signal>T1, return 1. Subsequent elements are 1 until when signal<T10; 
 - if signal<T2, return -1. Subsequent elements are -1 until when signal>T20;
 - 0 otherwise

上述任务的 kdb 脚本是:

0h^fills(-).(0N 1h)[(signal>T1;signal<T2)]^'(0N 0h)[(signal<T10;signal>T20)]

有人如何在 DolphinDB 中重写它吗?

【问题讨论】:

    标签: kdb dolphindb


    【解决方案1】:

    我在 DolphinDB 0.97.4 版本中进行了直译

    eachPost(-, loop(nullFill, [iif(signal<T10, 0h, 00h), iif(signal>T20, 0h, 00h)], [iif(signal>T1, 1h, 00h), iif(signal<T2, 1h, 00h)]))[0].ffill().nullFill(0h)
    

    iif(cond, trueResult, falseResult) 是一个逐元素条件函数。 00h 表示短类型的空值。 nullFill(X, Y) 将 X 中的空值替换为 Y 中的相应值。ffill(X) 将 X 中的空值替换为前面的值。 loopeachPost 都是两个高阶函数。

    DolphinDB 中的测试用例

    T1= 60
    T10 = 50
    T20 = 30
    T2 = 20
    signal = 10 20 70 59 42 49 19 25 26  35
    eachPost(-, loop(nullFill, [iif(signal<T10, 0h, 00h), iif(signal>T20, 0h, 00h)], [iif(signal>T1, 1h, 00h), iif(signal<T2, 1h, 00h)]))[0].ffill().nullFill(0h)
    
    -1 -1 1 1 0 0 -1 -1 -1 0
    

    KDB+中的测试用例

    T1:60
    T10:50
    T20:30
    T2:20
    signal:10 20 70 59 42 49 19 25 26  35
    0h^fills(-).(0N 1h)[(signal>T1;signal<T2)]^'(0N 0h)[(signal<T10;signal>T20)]
    
    -1 -1 1 1 0 0 -1 -1 -1 0
    

    我还做了一个快速的性能比较。我生成了 1000 万个随机信号,并分别在 DolphinDB 和 KDB+ 中运行了上述表达式。 KDB+ 耗时 800 毫秒,而 DolphinDB 仅耗时 480 毫秒。下面是性能测试代码。

    //DolphinDB
    T1= 60
    T10 = 50
    T20 = 30
    T2 = 20
    signal = 1 + rand(99.0, 10000000)
    timer eachPost(-, loop(nullFill, [iif(signal<T10, 0h, 00h), iif(signal>T20, 0h, 00h)], [iif(signal>T1, 1h, 00h), iif(signal<T2, 1h, 00h)]))[0].ffill().nullFill(0h)
    
    //KDB+
    T1:60
    T10:50
    T20:30
    T2:20
    signal: 1.0 + 10000000 ? 99.0
    \t  0h^fills(-).(0N 1h)[(signal>T1;signal<T2)]^'(0N 0h)[(signal<T10;signal>T20)]
    

    【讨论】:

      【解决方案2】:

      我优化了 DolphinDB 中的测试代码,如下所示:

      t1= 60
      t10 = 50
      t20 = 30
      t2 = 20
      signal = rand(100.0, 10000000)
      timer direction = (iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal <t2, 1h, iif(signal > t20, 0h, 00h))).ffill().nullFill(0h)
      

      只用了 330 毫秒。


      更新: 平行版

      DolphinDB 提供函数pcall 来并行执行。

      def foo(signal){
       t1= 60
       t10 = 50
       t20 = 30
       t2 = 20
       return iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal <t2, 1h, iif(signal > t20, 0h, 00h))
      }
      signal = rand(100.0, 250000000)
      
      //with single threads
      timer foo(signal).ffill().nullFill(0h)
      
      //with multiple threads
      timer pcall(foo,signal).ffill().nullFill(0h)
      

      单线程计算耗时 6,412 毫秒(2.5 亿),两个线程耗时 2,938 毫秒,四个线程仅耗时 2,086 毫秒。

      【讨论】:

        【解决方案3】:

        2019 年 9 月 11 日更新

        注意更新版本

        WITH 
            rand()/4294967295*100 AS s,
            60 AS t1, 
            50 AS t10, 
            30 AS t20, 
            20 AS t2, 
            if(s < t10, 0, if(s > t1, 1, NULL)) as signal1,
            if(s > t20, 0, if(s < t2, 1, NULL)) as signal2
        SELECT
            arrayFill(x -> (x != -2), groupArray(toInt8(ifNull(signal1 - signal2, -2)))) as k
        FROM numbers_mt(10000000);
        

        根据 Summer.H 的反馈进行了更新。在我的系统(Core i7-7820X)上,每个时间最好运行 5 次。两者之间的时间差异非常小。

        1000 万个信号

        随机生成+计算

        • 1 个线程 - DolphinDB 337 毫秒,ClickHouse 291 毫秒
        • 2 个线程(1 个核心)- DolphinDB 226 毫秒,ClickHouse 189 毫秒

        仅计算

        • 1 个线程 - DolphinDB 302 毫秒,ClickHouse 233 毫秒
        • 2 个线程(1 个核心)- DolphinDB 179 毫秒,ClickHouse 165 毫秒

        2.5 亿信号

        随机生成+计算

        • 1 个线程 - DolphinDB 7.901s、ClickHouse 7.103s
        • 2 个线程(1 个核心)- DolphinDB 4.786s、ClickHouse 4.297s
        • 4 线程(2 核)- ClickHouse 2.965s

        仅计算

        • 1 个线程 - DolphinDB 7.106s、ClickHouse 5.564s
        • 2 个线程(1 个核心)- DolphinDB 3.966s、ClickHouse 3.668s
        • 4 线程(2 核)- ClickHouse 2.573s

        原创

        请注意,这并不能回答与 DolphinDB 相关的具体问题 - 但这里也有一个使用 ClickHouse 的版本。

        WITH 
            60 AS t1, 
            50 AS t10, 
            30 AS t20, 
            20 AS t2, 
            ([if(s < t10, 0, NULL), if(s > t20, 0, NULL)], [if(s > t1, 1, NULL), if(s < t2, 1, NULL)]) AS signal
        SELECT arrayFill(x -> (x != -2), groupArray(ifNull(coalesce((signal.1)[1], (signal.2)[1]) - coalesce((signal.1)[2], (signal.2)[2]), -2))) AS k
        FROM 
        (
            SELECT arrayJoin([10, 20, 70, 59, 42, 49, 19, 25, 26, 35]) AS s
        )
        FORMAT TSV
        
        [-1,-1,1,1,0,0,-1,-1,-1,0]
        

        对 1000 万个随机样本进行基准测试。 Summer.H 在我的系统上最快的 DolphinDB 答案,最好用完 5 个:

        DolphinDB (4 threads)
        
        ./dolphindb
        DolphinDB Systems 0.99.0 64 bit Copyright (c) 2011~2019 DolphinDB, Inc. Licensed to Trial User. Expires on 2019.12.31 (Build:2019.10.25)
        
        >timer t1= 60
        timer t10 = 50
        timer t20 = 30
        timer t2 = 20
        timer signal = rand(100.0, 10000000)
        timer direction = (iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal <t2, 1h, iif(signal > t20, 0h, 00h))).ffill().nullFill(0h)
        ;>>>>>>
        Time elapsed: 0.01 ms
        Time elapsed: 0.001 ms
        Time elapsed: 0.001 ms
        Time elapsed: 0.001 ms
        Time elapsed: 72.675 ms
        Time elapsed: 305.442 ms
        
        Total time: 378 ms
        

        ClickHouse(限制 4 个线程,如 DolphinDB),最好用完 5 个:

        CREATE TEMPORARY TABLE dtest2 AS
        WITH
            rand()%100 + rand()/4294967295 AS s,
            60 AS t1,
            50 AS t10,
            30 AS t20,
            20 AS t2,
            ([if(s < t10, 0, NULL), if(s > t20, 0, NULL)], [if(s > t1, 1, NULL), if(s < t2, 1, NULL)]) AS signal
        SELECT arrayFill(x -> (x != -2), groupArray(ifNull(coalesce((signal.1)[1], (signal.2)[1]) - coalesce((signal.1)[2], (signal.2)[2]), -2))) AS k
        FROM numbers_mt(10000000)
        
        Ok.
        
        0 rows in set. Elapsed: 0.300 sec. Processed 10.00 million rows, 80.00 MB (33.37 million rows/s., 266.94 MB/s.)
        
        Total time 300 ms
        

        ClickHouse,默认配置/无线程限制,最好用完5个:

        CREATE TEMPORARY TABLE dtest2 AS
        WITH
            rand()%100 + rand()/4294967295 AS s,
            60 AS t1,
            50 AS t10,
            30 AS t20,
            20 AS t2,
            ([if(s < t10, 0, NULL), if(s > t20, 0, NULL)], [if(s > t1, 1, NULL), if(s < t2, 1, NULL)]) AS signal
        SELECT arrayFill(x -> (x != -2), groupArray(ifNull(coalesce((signal.1)[1], (signal.2)[1]) - coalesce((signal.1)[2], (signal.2)[2]), -2))) AS k
        FROM numbers_mt(10000000)
        
        Ok.
        
        0 rows in set. Elapsed: 0.191 sec. Processed 10.00 million rows, 80.00 MB (52.22 million rows/s., 417.74 MB/s.)
        

        每个人的最终时间:

        kdb (800ms), DolphinDB (480ms, 378ms, 330ms?), ClickHouse (191ms)

        我在这里进一步用 2.5 亿个随机信号进行了基准测试 - DolphinDB 耗时 9214.99 毫秒(1420 毫秒随机信号生成 + 7794.24 毫秒计算)。 ClickHouse 总共花费了 4272 毫秒用于随机生成和计算。

        受到我的 DolphinDB 许可证的限制,但 ClickHouse 在 17.4 秒(内存表)或 20.1 秒(到磁盘)内管理了 10 亿个信号。

        【讨论】:

        • 有趣的结果! DolphinDB 和 kdb+ 实际上都在单线程中运行脚本,尽管 DolphinDB 社区版提供了最多 2 个线程(不是 4 个)。 DolphinDB 数据库提供函数pcall 来并行执行。在 DolphinDB 中,单线程计算耗时 6,412 ms(2.5 亿),两个线程耗时 2,938 ms,四个线程仅耗时 2,086 ms。最新的代码在我更新的答案中。也许你可以试试。
        • 对 - 我认为早期的社区版本有 4 个线程?使用您更新的代码在我的系统上使用 250m 计数器重新运行基准测试。仅用于计算,1个线程,DolphinDB为7.802s,ClickHouse为5.704s。有趣的事情发生在两个线程上。将语句(随机生成和计算)合二为一——DolphinDB 是 5.441s,ClickHouse 是 4.609s。仅用于计算 DolphinDB 为 3978.46 毫秒,ClickHouse 为 3.747 秒,但是在单独的语句中 ClickHouse 中的随机数据生成比 DolphinDB 长约 300 毫秒。
        • 1000 万个信号,4 个线程,clickhouse 耗时 300 毫秒(在原始答案中),而 1 个线程,耗时 291 毫秒(在更新后的答案中)。 1个线程比clickhouse中的4个线程快吗?
        • 嗯,是的 - 我更新了查询,但没有将其放入已编辑的答案中。现在已经更新了。新查询比我提供的原始查询要快。
        【解决方案4】:

        DolphinDB 1.01 引入了一项新功能 JIT。只需在函数定义前添加一个符号@jit,就可以实现很大的性能提升。此外,可以使用for循环来解决上述问题,比矢量化解决方案容易得多。

        @jit
        def calculate_with_jit(signal, n, t1, t10, t20, t2) {
          cur = 0
          idx = 0
          output = array(INT, n, n)
          for (s in signal) {
            if(s > t1) {           // (t1, inf)
              cur = 1
            } else if(s >= t10) {  // [t10, t1]
              if(cur == -1) cur = 0
            } else if(s > t20) {   // [t20, t10)
              cur = 0
            } else if(s >= t2) {   // [t2, t20]
              if(cur == 1) cur = 0
            } else {               // (-inf, t2)
              cur = -1
            }
            output[idx] = cur
            idx += 1
          }
          return output
        }
        

        在我的机器上,jit 版本只需要 170ms 处理 1000 万长的信号,而矢量化版本需要 410ms

        更多详情请参考 jit 教程 (https://github.com/dolphindb/Tutorials_EN/blob/master/jit.md)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-11-05
          • 1970-01-01
          • 2019-11-29
          • 2019-11-09
          • 2021-03-22
          • 2019-09-05
          • 2022-01-03
          • 1970-01-01
          相关资源
          最近更新 更多