设计交易引擎

一个完整的交易引擎包括资产系统、订单系统、撮合引擎和清算系统。

资产系统不仅记录了每个用户的所有资产,而且还要根据业务随时冻结和解冻用户资产。例如,下买单时,根据买入价格和买入数量,计算需要冻结的USD,然后对用户的可用USD进行冻结。

订单系统跟踪所有用户的所有订单。

撮合引擎是交易引擎中最重要的一个组件,它根据价格优先、时间优先的原则,对买卖订单进行匹配,匹配成功则成交,匹配不成功则放入订单簿等待后续成交。

清算系统则是处理来自撮合引擎的撮合结果。

                ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
                   ┌─────────┐    ┌─────────┐
Order Request ──┼─▶│  Order  │───▶│  Match  │ │
                   └─────────┘    └─────────┘
                │       │              │      │
                        │              │
                │       ▼              ▼      │
                   ┌─────────┐    ┌─────────┐
                │  │  Asset  │◀───│Clearing │ │
                   └─────────┘    └─────────┘
                └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

最后,把上述几个组件组合起来,我们就得到了一个完善的交易引擎。

我们观察交易引擎的输入,它是一系列确定的订单序列,而交易引擎的输出则是成交信息。与此同时,交易引擎本身是一个确定性的状态机,它的内部状态包括订单集、资产表和订单簿。每当一个新的订单请求被输入后,状态机即更新状态,然后输出成交信息。

注意到交易引擎在任何一个时刻的状态都是确定的,在一个确定的状态下,继续给定一个确定的订单请求,下一个状态也是确定的,即:

交易引擎当前状态是Sn,则下一个输入On+1会将其状态更新为Sn+1

因此,对于一组给定的输入订单集合[O1, O2, O3, ...],交易引擎每次内部状态的更新和输出都是完全确定的,与时间无关。

我们换句话说,就是给定一组订单输入的集合,让一个具有初始状态的交易引擎去执行,获得的结果集为[R1, R2, R3, ...],把同样的一组订单输入集合让另一个具有初始状态的交易引擎去执行,获得的结果集完全相同。

因此,要实现交易引擎的集群,可以同时运行多个交易引擎的实例,然后对每个实例输入相同的订单请求序列,就会得到完全相同的一组输出:

                   ┌──────┐
                ┌─▶│Engine│──▶ R1, R2, R3...
                │  └──────┘
O1, O2, O3... ──┤
                │  ┌──────┐
                └─▶│Engine│──▶ R1, R2, R3...
                   └──────┘

可见,交易引擎是一个事件驱动的状态机。

实现交易引擎有多种方式,例如,把资产、订单等放入数据库,基于数据库事务来保证交易完整性,这种方式的缺点就是速度非常慢,TPS很低。

也可以把全部组件放在内存中,这样能轻松实现一个高性能的交易引擎,但内存的易失性会导致宕机重启后丢失交易信息,因此,基于内存的交易引擎必须要解决数据的持久化问题。

在Warp Exchange项目中,我们将实现一个完全基于内存的交易引擎。