美文网首页
TDengine在股市信号回测平台的应用

TDengine在股市信号回测平台的应用

作者: Sdoopy | 来源:发表于2020-05-27 21:24 被阅读0次

    业务场景

    我司于2019年自研发了一套股市信号回测平台,该平台集数据采集/清洗,因子组合,信号回测等功能于一身,实现了模块化、去代码的策略构建方式,分布式计算等市面上回测平台所没有的功能,数据精度达到Tick级,针对算力要求极高的Tick级数据信号回测进行了深入优化,极大地减少了策略回测时间,为我司的策略研发提供底层支持。

    该回测平台的底层数据库原来使用的是MongoDB数据库。该数据库具有查询,写入灵活的特点,因而被选用做底层数据库,然而在实际使用中发现,由于平台的分布式计算架构设计,对数据库的查询和写入性能要求很高,要求能够支撑高并发的读写请求,然而面对我600GB的Tick数据,即使采用了索引,查询优化等技术,MongoDB也难以负担多台服务器对数据的并发请求,对一个全市场的时间截面数据请求的响应时间有时可以长达几分钟,导致分布式计算的设计优点无法完整的体现出来。因此,我开始调研市面上的数据库,期望能找到能支撑高并发的读写请求,并且切换成本较低的数据库。在对比了dolphin、TDengine,并且与两位公司的负责人沟通之后,我们选择了TDengine,就是看中了TDenginge的时序数据库,以及一个设备一张表(对应一只股票一个表)这些与金融数据特性契合的特点。
    系统架构图

    系统架构图
    在我们的应用场景中,有一个master节点管理分布式计算任务的创建和分配,在master节点下的每一个slave节点在被分配到任务后,需要从底层数据库获取预置数据(3800只股票的历史数据),并且模拟真实情况,按时间推移去从30多亿条Tick数据中,获取Tick时间截面下的全市场(3800只股票)数据,计算“当前Tick时刻”下的股票信号。每个slave节点采用多进程的方式运行计算任务,即一核一任务,力求压榨出服务器的极限计算性能。目前,我们拥有一个master节点,6个slave节点,每个slave节点24核48线程,所以在任务运行的情况下,最高会同时有将近250+的进程同时向数据库请求数据。master节点的内网带宽占用有时甚至会达到900Mb/s。在这样的高并发请求下,原来的MongoDB不堪重负,经常查询堵塞。
    分布式计算示意图
    分布式计算示意图

    设计

    在深入了解了TDengine的特性以及使用原则后,我们设计了如下的数据模型:

    1. 对Tick数据建库。库下一个Tick超级表,以超级表做模板建子表,一只股票一张子表。共有3841个子表。每只股票的历史Tick都统一以时间戳为主键写入分别的一张表。每只股票以股票代码作为表名,设有6个标签:股票聚宽代码、拼音缩写、名称、上市日期、退市日期、类型。
    2. 超级表共有26个字段。包含了每一个Tick的市场信息,当前价,最高价,最低价,成交量,成交金额,买五卖五的挂单情况和均价线。
    • 超级表结构
                                 Field                              |      Type      |  Length   |  Note  |
    =======================================================================================================
    ts                                                              |TIMESTAMP       |          8|        |
    current                                                         |FLOAT           |          4|        |
    high                                                            |FLOAT           |          4|        |
    low                                                             |FLOAT           |          4|        |
    volume                                                          |FLOAT           |          4|        |
    money                                                           |FLOAT           |          4|        |
    a1_p                                                            |FLOAT           |          4|        |
    a1_v                                                            |FLOAT           |          4|        |
    a2_p                                                            |FLOAT           |          4|        |
    a2_v                                                            |FLOAT           |          4|        |
    a3_p                                                            |FLOAT           |          4|        |
    a3_v                                                            |FLOAT           |          4|        |
    a4_p                                                            |FLOAT           |          4|        |
    a4_v                                                            |FLOAT           |          4|        |
    a5_p                                                            |FLOAT           |          4|        |
    a5_v                                                            |FLOAT           |          4|        |
    b1_p                                                            |FLOAT           |          4|        |
    b1_v                                                            |FLOAT           |          4|        |
    b2_p                                                            |FLOAT           |          4|        |
    b2_v                                                            |FLOAT           |          4|        |
    b3_p                                                            |FLOAT           |          4|        |
    b3_v                                                            |FLOAT           |          4|        |
    b4_p                                                            |FLOAT           |          4|        |
    b4_v                                                            |FLOAT           |          4|        |
    b5_p                                                            |FLOAT           |          4|        |
    b5_v                                                            |FLOAT           |          4|        |
    average                                                         |FLOAT           |          4|        |
    code_jq                                                         |BINARY          |         11|tag     |
    abbr                                                            |BINARY          |          5|tag     |
    name                                                            |NCHAR           |          4|tag     |
    start_date                                                      |BINARY          |         10|tag     |
    end_date                                                        |BINARY          |         10|tag     |
    type                                                            |BINARY          |          5|tag     |
    
    

    实践

    • 数据写入
      每张表的数据量在50W-200万条之间。总共35亿条+。看似数量不算特别多,但是由于一条记录本身字段就比较多,写入的时候,速度只能达到5W条/s。然而这样的速度,就已经让我们惊为天人了,确实是快。如果字段少点,速度应该能快上不少,涛思数据的工程师跟我说一般都30W条/s。
    • 数据查询
      由于业务特殊性,要求一次性查出指定的若干只股票在同一个时间截面下(如 2019-05-10 09:45:03)的截面数据。为此,在深入研究了超级表查询语法规则下,我们设计了如下的查询语句:
      第一种是对超级表下的子表的全查询:
      select last(*) from tick.tick where ts >= \'2019-05-10 09:00:00\' and ts <= \'2019-05-10 09:45:03\' group by code_jq order by code_jq desc
      这个语句会找到每一张子表(股票)最接近2019-05-10 09:45:03这个时刻的一条数据,然后集合起来,输出3841条记录。
      第二种是对超级表下的部分子表的查询:
      select last(ts,current,[...]) from tick.tick where (code_jq = '000001.XSHE' or code_jq = '600304.XSHG' ...) and ts >= \'2019-05-10 09:00:00\' and ts <= \'2019-05-10 09:45:03\' group by code_jq order by code_jq desc
      这个语句是对第一种语句的变种,通过对TAG的过滤查询,可以做到对部分子表的查询。Last函数里面的字段可以随意配置。
      数据花了2天的时间写入了数据库。结果在查询测试上碰到了问题。在真实环境中,我们做一次第一种的全查询,查询时间达到了30-45s,这对我们来说是不能忍受的,通过跟涛思数据的李总沟通业务需求,李总帮助我们优化了数据库结构,优化的细节详情可见TDengine实战中的第9条细节。优化了之后,我们的数据库结构变成了一个vnode62张表,一个表分配1M的内存,这样来极致的利用服务器的性能,首次查询时间降低到了12s左右的地步,还能接受。
    
                  name              |     created time     |  ntables  |  vgroups  |replica| days  |  keep1,keep2,keep(D)   |  tables   |   rows    | cache(b)  |      ablocks       |tblocks| ctime(s)  | clog | comp |time precision|  status  |
    ==============================================================================================================================================================================================================================================
    log                             | 20-03-11 17:16:41.598|          4|          1|      1|     10|30,30,30                |         32|       1024|       2048|             2.00000|     32|       3600|     1|     2|us            |ready     |
    tick                            | 20-03-11 17:20:07.895|       3835|         62|      1|     10|3650,3650,3650          |         62|       4096|    1048576|             8.00000|     50|       3600|     1|     2|ms            |ready     |
    
    • 开发环境测试
      因为业务的原因,我们在python中,由一个父进程创建了若干个一级子进程,再由一级子进程创建二级子进程,二级子进程才真正创建taos连接,向server获取数据。从结构上来说,相对复杂,对TDengeine的性能要求较高。
      首先,单进程查询测试过没有问题后,我们开始上多进程并发查询。2.0版本对多进程高并发做了架构上的优化,使得1.6版本出现的并发问题被解决了。响应并发的速度非常快,不过一旦请求数过多,服务器处理不过来,请求就会排队等待。因为回测平台对实时的要求没有那么高,这些也不是什么问题。经过压力测试后,TDengine 2.0正式投入使用。在TDengine 2.0 的新功能中,集群是一个非常大的亮点,一般集群都是作为企业付费版的功能存在,taos却选择开源,实在是不得不佩服,由于精力问题,还没有去研究集群的使用方式,后面如果用上,再来更新这方面的使用经验,相信并发的响应速度能更上一个台阶。
      在与涛思数据的工程师们的配合中,我真切的感受到了他们的负责任的态度和全力以赴的工作状态,双方的配合默契又舒适,实在是能做事能打胜仗的队伍。在这期间,涛思又获得了红杉资本的千万美元融资,让人不得不叹服涛思的实力和红杉的眼光,在这里也恭喜涛思,相信你们肯定能做出震惊世界的物联网级数据库!

    使用TDengine的体会

    TDenginge的列式存储设计是如此高效率写入和查询的核心设计,写入速度确实令人叹为观止,而查询速度相比写入速度并没有那么让人惊艳,在我们的业务场景下,老的mongoDB经过查询优化,也能达到和TDengine类似的查询速度,查询功能我认为还有待拓展,特别是超级表查询,如Tag查询等还需要支持更多的查询方式来满足不同的业务需求。TDenging是时序数据库,所有有时间关联性的业务场景用TDengine准没错,金融方向这块,我们应该算是比较尝鲜的几家之一,这次使用能帮助TDengine变得更优秀,变得更好,为金融方向落地使用提供经验借鉴,也是一个比较好的尝试!

    相关文章

      网友评论

          本文标题:TDengine在股市信号回测平台的应用

          本文链接:https://www.haomeiwen.com/subject/dajuahtx.html