php+撮合引擎,撮合引擎开发:数据结构设计
交易委托账本
交易委托账本(OrderBook)是整个撮合引擎里最核心也是最复杂的数据结构,每个交易对都需要维护一份交易委托账本,账本里保存着指定交易对所有待撮合的委托单。每份账本都有两个队列,一个卖单队列和一个买单队列,两个队列都需要按照价格优先、时间优先的原则进行排序。
所谓价格优先、时间优先,即是说:卖单队列的委托单是按价格由低到高排序,买单队列则相反,按价格由高到低排序;相同价格的委托单,则是按下单时间的先后来排序。
如上图,每个小方格表示一个委托单,标 H 的是排在头部的委托单,N则是与 H 同价格但下单时间上排在 H 后面的委托单,S则是下一档位价格的第一个委托单。可以从图中明显看出,横向上,委托单是按时间排序的,竖向上,又是按价格排序的。
撮合的时候,都是先取出 H 委托单与新委托单进行匹配。如果新委托单是买单,则获取卖单队列的 H 单出来匹配;如果新委托单是卖单,则获取买单队列的 H 单。如果 H 单全部匹配成交了,那标识为 N 的委托单就变成了新的 H 单。如果第一排的全部委托单都匹配完了,那就 S 单会变成新的 H 单。
交易委托账本可支持一些操作方法,包括初始化、增加买卖委托单、移除买卖委托单、获取头部委托单等。交易委托账本的类图大概如下:
其中,getHead 和 popHead 方法的区别是:get 只读头部委托单但不会移除它,而 pop 会将头部委托单从队列中移除。
订单队列
买单队列和卖单队列可以设计为使用统一的订单队列类型,两者只有价格排序方向不同,那订单队列就可以用一个属性来表示排序方向。队列里的所有订单可以采用二维数组或二维链表来保存,考虑到主要操作是插入和删除,用链表比用数组效率更高。如果想让操作效率更高,那就需要使用更复杂的数据结构了,比如再结合跳表。目前版本为了简单,采用简单的二维链表即可。
使用二维链表的话,那链表中的每个元素保存的就是横向上按时间排序的订单链表,这些订单链表又组成了竖向上按价格排序的链表。
另外,还可以保存一个 Map,将价格作为 Key,将同价格的订单链表作为 Value,这样就能加快同价格订单的查询。
订单队列可支持的操作方法也很简单,包括初始化、新增订单、移除订单、获取头部订单等。其类图大概如下:
sortBy 指定价格排序的方向,**parentList **保存整个二维链表,第一维以价格排序,第二维以时间排序,elementMap 则是 Key 为价格、Value 为第二维订单链表的键值对。
委托单
委托单则是撮合引擎里最基本的数据结构了,其数据主要是从上游服务传输过来的,其类图大概如下:
action 声明对委托单要进行哪种操作,我们只需支持两种操作:下单(create)和撤单(cancel)。symbol 指定该委托单所属的交易对,orderId 是该委托单的唯一标识,side 指明是买入(buy)还是卖出(sell)。**type 表示交易类型,即限价交易(limit)或市价交易(market)**等,我们的 MVP 版本只支持限价交易。**amount **是购买数量,price 是购买价格,timestamp 则是订单时间。
**toJson() **和 fromJson() 方法是为了支持订单数据传输时的序列化和反序列化。
成交记录
撮合成交的委托单就会生成对应的成交记录,成交记录需要发布到 MQ 给下游服务消费。成交记录的数据结构如下图:
maker 指挂单,是本来挂在交易委托账本里的订单,而 taker 则是吃单,是指吃掉 maker 的订单。**makerId **和 takerId 就是挂单和吃单的订单 ID。takerSide 就是吃单的买卖方向,我们在行情软件里看到的成交记录会有不同颜色,就是由这个 takerSide 决定的。**amount **就是成交数量,price 指成交价格,**timestamp **是成交时间。
Redis缓存
我们需要用 Redis 缓存委托单数据和撮合中的交易对数据,主要有两个作用,一是可以对请求做去重处理,二是程序重启后可以恢复数据。
由于网络中断或延迟,或其他异常情况,上游服务有可能会重复发送相同请求给到撮合引擎,因此,程序是需要做去重处理的,有了数据缓存就可以解决去重问题了。另外,由于我们采用的是内存撮合,撮合时的数据都是直接保存在程序内存里的,一旦程序退出了,那所有数据也都消失了,重启后就需要从其他地方重新加载数据,采用Redis缓存就可以很快速地缓存数据和加载数据。
开启一个交易对引擎时需要将交易对缓存,关闭时则从缓存中删除,保证缓存的都是运行中的交易对,当重启时,就可以重新启动这些交易对的撮合引擎了。需要缓存的交易对数据包括两个:symbol 和 price,即标识和价格。关于价格,每次产生新的成交记录时,价格也需要同步更新,因此价格的更新会非常频繁。而标识基本无需更新,因此两者最好分开缓存。
所有交易对的 symbol 可以统一缓存到一个 set 里。我们可以将 key 值设置为 matching:symbols,用 Redis 的 sadd 和 srem 命令将不同的 symbol 缓存到该 key 值里或从 key 中删除。而 price 则可以保存为 string 类型,为不同交易对的价格设置不同的 key,key 值可以设置为 matching:price:{symbol},{symbol} 为具体交易对的 symbol 值。
每个委托单也需要缓存和更新,为了能够从缓存中最快地读取和更新委托单数据,最好为每个委托单都设置一个单独的 key,key 值可以设置为 matching:order:{symbol}:{orderId}:{action},而 value 值建议设置为 hash 类型,因为 hash 类型特别适合存储结构化的对象。
交易对和委托单数据都缓存了,就能够解决去重问题和程序重启后重新启动各交易对的撮合引擎了,但其实还有一个问题,撮合引擎里的交易委托账本如何恢复?该问题先留给大伙去思考,后续章节我再来讲解我的方案。
小结
撮合引擎里涉及到的数据结构其实并不多,最复杂的也只有交易委托账本,其设计还会直接关系到撮合的速度。Redis 缓存的设计也有些学问在里面,设计得不好也一样会影响整体的撮合性能。本小节完成了数据结构的设计,下一小节我们就开始深入到代码实现。
最后,请抽时间研究下遗留的思考题:撮合引擎里的交易委托账本如何恢复?
php+撮合引擎,撮合引擎开发:数据结构设计相关推荐
- 数据结构设计_撮合引擎开发:数据结构设计
价值超5万的撮合引擎:开篇 价值超5万的撮合引擎:MVP版本 交易委托账本 交易委托账本(OrderBook)是整个撮合引擎里最核心也是最复杂的数据结构,每个交易对都需要维护一份交易委托账本,账本里保 ...
- 华为快应用引擎架构及开发实践
目 录 1 快应用技术架构 1.1快应用介绍及其特点 1.2华为快应用引擎架构简介 1.2.1 应用开发(前端框架 + 组件& API 能力) 1.2.2 系统整合(应用管理,卡片-嵌入式SD ...
- SuperSQL:跨数据源、跨DC、跨执行引擎的高性能大数据SQL中间件
导语:SuperSQL是腾讯数据平台部自研的跨数据源.跨数据中心.跨执行引擎的统一大数据SQL分析平台/中间件,支持对接适配多类外部开源SQL执行引擎,如Spark.Hive等. 背景 SuperSQ ...
- java版开源工作流引擎ccflow从表数据数据源导入设置
为什么80%的码农都做不了架构师?>>> 关键字 驰骋工作流引擎 流程快速开发平台 workflow ccflow jflow .net开源工作流 从表数据导入设置 概要说明 ...
- 火山引擎云原生大数据在金融行业的实践
本文整理自火山引擎云原生计算研发工程师-张云尧 在 DataFun 智能金融峰会上的演讲.大数据架构向云原生演进是行业的重要趋势,火山引擎协助关键金融客户在大数据云原生方向进行了深度实践,形成了整体解 ...
- 大数据时代:大数据引擎或改变大数据竞争格局
对于传统企业而言,无需任何繁杂的技术手段,只需要接入百度大数据引擎,即可利用大数据去帮助现有业务进行升级和创新了.峰哥认为百度此举是在加大此次大数据台风的风力.换一种角度看,这也是百度为了快速丰富各行 ...
- 聚播微信群控云控引擎二次开发SDK服务端对接接口
聚播微信群控云控引擎二次开发SDK服务端对接接口 case HeartBeatReq: {// 客户端发送的心跳包heartBeatReqHandler.handleMsg(ctx, msgVo);b ...
- 火山引擎 DataLeap:「数据血缘」踩过哪些坑?来看看字节跳动内部进化史
动手点关注 干货不迷路 DataLeap 是火山引擎数智平台 VeDI 旗下的大数据研发治理套件产品,帮助用户快速完成数据集成.开发.运维.治理.资产.安全等全套数据中台建设,降低工作成本和数据维护成 ...
- Quick BI产品核心功能大图(四):Quick引擎加速--十亿数据亚秒级分析
简介: 随着数字化进程的深入,数据应用的价值被越来越多的企业所重视.基于数据进行决策分析是应用价值体现的重要场景,不同行业和体量的公司广泛依赖BI产品制作报表.仪表板和数据门户,以此进行决策分析. 在 ...
最新文章
- java jar包 和 war包 区别
- C++操作Redis的简单例子
- Python绘图 二维、三维
- 服务器上的VGA切换原理,VGA切换器使用方法和常见问题说明
- 概述---《TCP/IP协议》卷一
- OpenGL Multi-Indirect Draw小行星的实例
- shell中的小括号与大括号
- [原创] 指针操作程序答案 — 谭浩强C语言习题答案
- tomcat.exe java home,tomcat.exe启动和startup.bat启动的不同
- Pocket英语语法---三、英语动词的特点是什么
- Java OpenCV之Mat类的概述、常用构造方法、常用函数
- Mybatis自定义分布式二级缓存实现与遇到的一些问题解决方案!
- Code-NFine:NFine权限控制
- Ubuntu 18.04 LTS版本 GoldenDict安装与配置
- android 功能防抖,Android RxJava 实战系列:功能防抖
- 股票量化对冲策略的黄金时期要来了?
- deep learning编程作业总结1---喵咪识别
- python多个文件夹合并成一个文件夹
- RISC_V(0) 指令集架构
- APP——功耗测试(耗电测试)——基础知识
热门文章
- CP-ABE和KP-ABE
- 读《测试构架师修炼之道》-Chapter3 测试构架师应该做的事
- 动态规划之扔鸡蛋(或手机)问题
- [047量化交易]python获取股票 量比 换手率 市盈率-动态 市净率 总市值 流通市值
- 女程序员和男程序员有区别吗?
- 微信蓝牙设备连接不上原因总结(5)
- 怎样实现ZBrush中Magnify膨胀笔刷的应用
- python战反爬虫:爬取猫眼电影数据 (二)(Requests, BeautifulSoup, MySQLdb,re等库)
- Linux(SLES)挂载NTFS移动硬盘实践
- linux 中etc全拼,英语中“等等”缩写成为etc.吗?要加一点吗?全拼是...