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

业务场景

我司于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变得更优秀,变得更好,为金融方向落地使用提供经验借鉴,也是一个比较好的尝试!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350