TDengine在股市实时行情计算的应用

业务场景

我司于2019年自研发了一套股市信号回测平台,使用到了TDengine1.6版本。在今年TDengine发布了2.0版本之后,我司正好也要开发股市策略交易平台,就实装上了TDengine2.0。该平台沿袭了回测平台模块化、去代码的策略构建方式,以及分布式的实时计算,针对股市的时效性要求和策略的复杂性实现做了大量的工作和优化,时效性是主要采用TDengine2.0来作为tick数据的存储,在查询中自动聚合实时的K线用于计算,速度上还是很快的。该平台目前已经上线使用。

平台的整体架构就不多做介绍,简单的介绍一下策略信号计算这块的架构,后面的交易层面并未用到TDengine2.0。
系统架构图

image.png

可以看到,整体的架构比较清晰。底层有MongoDB和TDengine共同提供数据支持,MongoDB存储历史股票数据,TDengine来提供实时行情Tick数据。Master服务器根据平台配置的任务参数,把计算任务写入Redis中间件的队列,Slave服务器组从队列中取出任务,根据历史数据和实时行情数据,进行实时计算,最后把计算结果写入elasticsearch,计算出来的信号供后面的交易模块使用。

为什么选择TDengine作为实时行情数据库

我们选择用TDengine来作为实时行情的数据库有如下几个原因:
1.写入快。经过数据库比较和使用体验,TDengine的写入速度远超其他数据库,这都依赖于TDengine的列式存储设计。
2.时间序列设计。金融数据是时间序列数据,对时间要求强一致,这一点与TDengine的设计初衷是完全一致的,由于其时间序列的设计,数据的写入非常方便,如果由于网络抖动,后面的时间写进去了,前面的时间数据再写入也能被接收,支持乱序写入,这就保证了时间序列的强一致性。
3.聚合方便。TDengine的聚合函数目前已经比较丰富,用其中的聚合函数,我们能直接聚合出K线。
4.读出快。TDengine多核写入,多核读出,对于查询请求,也是以map/reduce类似的形式去查询,效率高。
5.免费,而且是集群版免费。毋庸置疑,这是巨大的优势,其他的金融数据库,一个节点需要15W甚至30W,而TDengine做到了集群免费,这一点不得不要respect一下涛思数据的陶总,这种勇气和见识非一般人能达到。
基于以上几点,TDengine是我们目前最好的选择。

设计

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

  1. 对Tick数据建库。库下一个Tick超级表,以超级表做模板建子表,一只股票一张子表。共有4022个子表。每只股票的历史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     |

实践

  • 数据写入
    写入数据在0.01秒左右,写完4000多张表。对于这个速度,我们已经比较满意。相较于MongoDB,做了索引的表,写入要在0.5秒左右,性能差距巨大。
  • 数据查询
    由于业务特殊性,要求一次性查出并聚合指定的若干只股票在当前时间下的K线数据。为此,在深入研究了超级表查询语法规则下,我们设计了如下的查询语句:
    第一种是对超级表下的子表的全查询:
    select first(current),MAX(current),MIN(current), last(current), MAX(volume), MAX(money) from tick.tick where ts >= \'2020-10-28 09:30:00\' and ts <= now INTERVAL(30m) FILL(null) group by code_jq
    这个语句会找到每一张子表(股票)今天早上开盘到当前时刻的所有数据,然后聚合出开、高、低、收、量、金额,输出记录。
    image.png

第二种是对超级表下的部分子表的查询:
select first(current),MAX(current),MIN(current), last(current), MAX(volume), MAX(money) from tick.tick where (code_jq = '000001.XSHE' or code_jq = '600304.XSHG' ...) and (ts >= \'2020-10-28 09:30:00\' and ts <= now) INTERVAL(30m) FILL(null) group by code_jq
这个语句是对第一种语句的变种,通过对TAG的过滤查询,可以做到对部分子表的查询。

image.png

TDengine应用情况

在最初的设想中,每个Slave节点都要多核高并发的去请求TDengine数据库,请求不同的数据。目前,我们拥有6个slave节点,每个节点16核32线程。所以在满负荷下,同时会有将近200个进程同时向数据库请求数据。这极大考验TDengine的高并发能力。可惜的是,在单节点测试中,也就是32进程高并发下,进程偶尔会发生异常,进入僵死。僵死时没有报错信息,通过日志定位,我们定位到某一个进程中TDengine的连接异常,不报错也不退出。历时几天,在涛思数据技术工程师的协助下,我们找到了问题所在,但发现不可解,并且经过沟通,目前涛思也没有足够的资源来解决这个问题,最后只能认清TDengine不能多进程并发请求的现实,放弃这个方案。这个方案一次性请求所有tick数据的效率最高,时间为1.33秒。


多进程获取全部.png

在认识到多进程方案不行之后,我们改为多线程获取全部数据。发现开了20多个线程同时请求时,仍旧会出现线程僵死的情况,无奈之下还是放弃。多线程方案一次性请求所有tick数据的效率次之,时间为2.66秒。


多线程获取全部.png

无奈之下,我们只能改用单线程方案。这下终于没问题了,一次性请求所有tick数据的效率最差,时间为3.77秒。
单线程获取全部.png

平心而论,最后的方案是令人不满意的,时间上面相差了2秒,如果对于金融的高频策略来说,这几乎是不可忍受的。但是由于我们主要不是高频策略,因此还勉强能用。目前,该方案已正式上线使用,已正常运行10多天,偶尔会突然无法建立连接,但通过异常处理机制重新创建连接也能处理好。

结语

这次使用TDengine2.0,第一个感受就是巨变,与1.0版本已经截然不同,很多参数都已经改变,据了解,使用了新的架构。第二个感受就是涛思团队的专业和负责任的态度,我们虽然不是一个付费用户,但涛思团队的苏晓慰技术工程师尽心尽力的协助我们上线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