浅谈撮合引擎设计
撮合引擎简介
撮合引擎是所有撮合交易系统的核心组件,不管是股票交易系统——包括现货交易、期货交易、期权交易等,还是数字货币交易系统——包括币币交易、合约交易、杠杆交易等,以及各种不同的贵金属交易系统、大宗商品交易系统等,虽然各种不同交易系统的交易标的不同,但只要都是采用撮合交易模式,都离不开撮合引擎。
撮合引擎是可以具有通用性的,一套具有通用性的撮合引擎实现理论上可以应用到任何撮合交易系统中,而无需做任何代码上的调整。即是说,同一套撮合引擎实现,既可以应用在股票交易系统,也可以应用在数字货币交易系统,可以用于现货交易,也可以用于合约交易等。
撮合引擎的发展
国内的很多数字货币交易所的撮合引擎,就是从数据库撮合迭代为内存撮合。
以前,基于数据库的撮合技术,TPS 一般只有10笔/秒。而现在基本都是采用内存撮合技术,TPS 很容易就能达到1000笔/秒,如果使用独占的高性能服务器,1万笔/秒甚至更高的 TPS 都不难达到。
LMAX 公司(LMAX是伦敦的一家外汇交易所)开源的 Disruptor,以及LMAX 架构,它的一个核心概念就是把数据放内存里,由此实现了每秒处理600万订单的超高效率。
LMAX架构巧妙在于,它并不认为多核CPU和多线程能解决问题,因为锁是一种很复杂的东西,而且并不见得效率高,它把锁去掉,降低整个系统的复杂度,并且用无锁队列 RingBuffer 来处理生产者消费者问题。
当然也并非所有的交易所都用LMAX架构。
币安
属于全内存撮合引擎LMAX架构,订单处理速度能达到 140 万单/秒。内存快照使用mmap(memory map file)的技术保证断电不会丢失数据,并且重写了很多 Java 的底层类库,去掉同步 lock,去掉异常的检查,定制化成撮合引擎专用的类库,进行了大量的 JVM 调优。
中小型交易所
内存撮合与数据库撮合结合的方式。
小型交易所
数据库撮合,只能支持 1000-2000 个用户。
业务
交易流程
- 系统开放某个交易对的交易功能。
- 用户提交该交易对的的买卖申报,即委托单。
- 系统验证委托单是否有效,包括交易标的是否处于可交易的状态、订单的价格和数量是否符合要求等。
- 确定该委托单的**挂单(Maker)费率和吃单(Taker)**费率。
- 检查用户的资产账户情况,包括账户状态是否交易受限,是否有足够资金用于下单等。
- 将详细的委托单数据持久化到数据库,并冻结用户账户中相应数量的资金。
- 将委托单进行撮合处理,即在交易**委托账本(OrderBook)**中寻找能与该委托单匹配成交的订单,匹配的结果可能是:全部成交、部分成交或无匹配。全部成交或部分成交时,可能在交易委托账本中存在一个或多个匹配的订单,即会产生一条或多条成交记录。当无匹配或部分成交时,委托单的部分数据包括剩余未成交的数量会暂时保存到交易委托账本中,等待与后续的委托单匹配撮合。
- 将撮合产生的成交记录持久化到数据库,并根据历史成交记录生成市场行情数据,如K线数据、今日涨跌幅等。
- 更新数据库中所有成交订单的委托单数据,以及更新订单用户的资产账户余额。
- 更新的订单数据、市场数据等推送给到前台。
整个交易流程中涉及到多个服务,包括用户服务、账户服务、订单服务、撮合服务、市场数据服务等。其中,只有第7步是撮合引擎处理的。从单一职责原则来说,撮合引擎就应该只做一件事,那就是负责撮合订单。撮合之前的委托单持久化、冻结资金等,以及撮合之后生成K线数据等,都不应该属于撮合引擎的职责。
竞价方式
撮合竞价方式一般有两种,一是集合竞价,二是连续竞价。股票交易系统一般会在不同交易时间段采用不同的竞价方式,比如在开盘或收盘时采用集合竞价,从而产生开盘价或收盘价,其余时间采用连续竞价。而大多数字货币交易系统则没有集合竞价,只有连续竞价,开盘价一般是在开始交易之前就设定好的。
集合竞价: 是指对一段时间内接收的买卖委托单一次性集中撮合的竞价方式
连续竞价: 是指对买卖委托单逐笔连续撮合的竞价方式。用户的挂单,只要满足成交条件,就能即时成交
交易所常用指令
Limit Order(限价单):限价委托必须以指定价格或者更好的价格成交,如果不能立即成交,则进入盘口等待成交,直到完全成交或者收到撤销指令或者收盘。
Market Order(市价单):市价委托立即以当前最佳价格成交。
Immediate-Or-Cancel (IOC): IOC委托必须立即以限价或者更好的价格成交,如果不能完全成交,未成交的部分立即取消。
Fill-Or-Kill (FOK):FOK委托必须立即以限价或者更好的价格完全成交,如果不能完全成交,则整个委托立即取消。
Cancel(取消订单):撤销指定的委托
开发
简易架构设计

亲和锁: linux在3.x以后增加了亲和锁的特性,可以将线程(或进程)绑定到给定的某个核心上,让它独享一核心,比如队列的忙等策略中、或者netty的eventLoop,将这个忙等的线程绑定到一个cpu核上,可以确保该进程的最大执行速度,实现低延迟,消除操作系统进行调度过程导致线程迁移所造成的抖动影响,还可以避免由于缓存失效而导致的性能开销.
撮数据结构设计
交易委托账本
交易委托账本(OrderBook)是整个撮合引擎里最核心也是最复杂的数据结构,每个交易对都需要维护一份交易委托账本,账本里保存着指定交易对所有待撮合的委托单。每份账本都有两个队列,一个卖单队列和一个买单队列,两个队列都需要按照价格优先、时间优先的原则进行排序。

限价委托单

其它委托单
TODO
关键代码实现
1.创建一个ringbuffer
/**
* 创建一个RingBuffer
* eventFactory: 事件工厂
* threadFactory: 我们执行者(消费者)的线程该怎么创建
* waitStrategy : 等待策略: 当我们ringBuffer 没有数据时,我们怎么等待
*/
@Bean
public RingBuffer<OrderEvent> ringBuffer(
EventFactory<OrderEvent> eventFactory,
ThreadFactory threadFactory,
WaitStrategy waitStrategy,
EventHandler<OrderEvent>[] eventHandlers
) {
/**
* 构建disruptor
*/
Disruptor<OrderEvent> disruptor = null;
ProducerType producerType = ProducerType.SINGLE;
if (disruptorProperties.isMultiProducer()) {
producerType = ProducerType.MULTI;
}
disruptor = new Disruptor<OrderEvent>(eventFactory, disruptorProperties.getRingBufferSize(), threadFactory, producerType, waitStrategy);
disruptor.setDefaultExceptionHandler(new DisruptorHandlerException());
// 设置消费者---我们的每个消费者代表我们的一个交易对,有多少个交易对,我们就有多少个eventHandlers ,事件来了后,多个eventHandlers 是并发执行的
disruptor.handleEventsWith(eventHandlers);
RingBuffer<OrderEvent> ringBuffer = disruptor.getRingBuffer();
disruptor.start(

文章介绍了撮合引擎在交易系统中的核心作用,从早期的数据库撮合到现在的内存撮合技术,如LMAX的Disruptor架构。内容涵盖了撮合引擎的通用性、交易所的撮合方式、交易流程、竞价类型以及关键代码实现,如RingBuffer的创建和订单撮合逻辑。此外,文章还提及了中小型交易所的撮合策略和高可用性问题的思考。
最低0.47元/天 解锁文章
548






