交易系统
2025-02-17
整体架构
服务整体的系统架构大致如上图所示,用户通过app或者网页端(还包括第三方openapi提供的restful api)的请求接口访问交易所, 通过网关层,根据具体的请求访问参数,路由转发到相应的业务接入层模块后,再根据具体业务请求场景,转发至具体对应的业务服务。
针对k线行情数据、交易事件数据(订单成交、撤销、条件单触发等事件类型),通过websocket服务提供的消息订阅、推送,来进行对外接入。
下面将针对不同业务层的功能进行展示详述,并重点阐述之前自身负责的核心业务模块。
前端接入层
用于对接不同类型的用户,主要为用户提供了android、ios、web端3种不同的交易系统应用。核心服务了一百多个国家的用户,具体不同交易品类的数据信息可以参考cmc相关资料信息。
网关层
网关层会根据具体不同的接口请求,路由转发至不同的业务接入层,根据下游服务的不同,也会进行对应协议的转换。 在这个过程中,也会通过一些流控、安全策略(比如根据IP / 网段、用户ID进行访问限频 或 黑白名单策略),来避免一些恶意的流量攻击 或者 是过量的请求导致下游服务过载。另外,网关层也会通过一些CDN加速策略,来就近接入服务的流量,降低服务整体的延时情况。
业务接入层
负责对请求进行参数校验,并根据内部协议的具体数据结构进行相关数据参数的转换,并根据下游服务配置的路由信息(包含一些服务对应的主备节点)和容量信息,进行具体路由的选择,或者流量的控制(熔断、限流)。针对一些相对静态或者只需要准实时的资源信息,通过访问数据缓存系统(如redis 或者 一些DB、存储系统)直接获取。
在线系统层
包含了核心的柜台、价格、撮合、websocket推送、流动性制造等服务。
柜台服务: 负责用户信息的记录和管理,包含每个用户具体的仓位、资产等信息。
价格服务: 负责实时k线行情信息记录和管理,包括各类型k线数据(1分钟、5分钟、15分钟、1小时、1天...)的计算存储,以及不同币对最新价的合并推送。
撮合服务: 负责根据具体的买卖盘交易(包含用户和"机器人"交易),进行交易的撮合和深度、摆盘、逐笔成交等信息的生成(orderbook的生成)。
websocket推送: 负责交易信息和行情信息的异步推送更新(比如成交/撤销等交易信息、资产信息、实时k线、orderbook信息的实时更新等)
流动性制造: 用于创造市场流动性,确保币对交易市场的买卖订单深度(厚度),以此来降低流动性不足的币对(通常是新币、小币)的交易滑点、降低交易成本。
基础服务层
由于在线服务的服务之间涉及到一些数据传递。
其中对于事件驱动、存在时序性关系的场景(比如用户具体的资产/仓位变更信息的推送、最新价的获取/推送展示、实时k线 / 摆盘 / 成交统计等截面或增量数据信息的推送展示等),使用了MQ(kafka)作为消息传递的媒介。
而对于一些需要保证事务和持久化存储的场景(更多的是一些具体的订单交易场景)则使用了MySQL来进行核心数据的存储。(对于一些需要用到缓存的场景(如历史k线), 则是使用redis进行存储优化)。
日志监控的作用,则是为了便于服务的问题定位告警(包含流水日志、抽样埋点日志、trace日志(链路追踪)等)、性能优化(包含调用延时、cpu/内存资源消耗等)、业务分析(包含核心业务指标监控,如平均交易/仓位额度、大单用户量、用户留存等)等。
定时任务则是用于调度一些离线(或者准实时)的任务场景,以满足线上服务实时性要求不太高的数据场景(比如历史k线的生成、历史数据的清理(如历史k线)、冷热数据的分离(分库分表)等)。
价格服务
价格服务提供了核心k线(实时k线、历史k线)和最新价(标记价格、现货指数价格)的功能。
实时k线:如上图所示,在线服务通过消费撮合写入MQ的逐笔成交数据(包含具体数据生成的时间撮),在线实时计算出每个币对对应的实时k线数据, 并将生成的数据写入MQ中,提供给柜台服务获取最新成交价格、websocket服务推送订阅端实时k线数据。
历史k线:定时任务每分钟开始的时候,通过自动消费最新1分钟的逐笔成交数据,结合数据库中存储的历史k线数据,来计算生成不同类型的历史k线数据,并重新写入数据库中。
最新价:按币对的维度,根据撮合服务推送的逐笔成交数据,定时(300毫秒)合并数据后,生成包含最新价的"截面"数据(open、close、high、low价格)。
websocket
负责为订阅的用户提供"公有数据"和"私有数据"的推送。
公有数据: 主要包括实时k线、最新价及24小时成交数据、摆盘数据。这些信息和具体的用户无关,通过一个通用服务计算生成后,广播推送给各个订阅的用户。
私有数据: 主要包含了用户具体的仓位和委托信息、资产、资金流水等用户较为个性化的信息,通过柜台服务感知用户发生了具体数据变更(如仓位变更、资产变更等)后,通过将消息通知写入MQ通知websocket服务后,主动触发了数据的更新推送。
算法订单
由于每日交易中,或多或少的会存在一些大额订单的交易,这些订单的交易,如果直接下单交易到市场之中,由于对手盘往往不容易存在同等交易量的大额订单,所以,容易造成盘口的冲击,或造成较大的交易滑点。因此,需要将大额订单分拆为多个小额子订单,逐步下单交易至市场,以此来减小市场的冲击。基于此目标,评判算法订单的拆单执行效果的核心指标主要包括:
1、市场偏离度
通过计算出算法订单拆单后,下单的平均成交价格和交易期间市场平均成交价格的差值比例,可以大致评估出算法拆单执行效果的优劣程度。2、平均成交率
用于评估拆单算法执行的过程中,订单实际的成交比例,一定程度反应了拆单的执行效率(拆单至下单撮合的延时过高,可能降低交易订单的成交率)和拆单效果。3、最大回撤率
最大回撤率通过计算比较每次成交价格和峰值成交价格的差异比例,来反映算法在不利市场条件下的风险程度。
服务核心评估指标的确定,有利于系统的长期迭代建设(比如引入ABTest系统,通过比较不同拆单算法之间的核心指标差异,评估出不同场景下拆单效果的好坏差异),也有助于我们思考,如何能够更好的构建好的拆单算法,其实核心需要关注:
1、交易意图的隐藏
交易意图的隐藏是十分重要的,假设我们对手盘有位交易者能够发现你正在通过某种交易算法,来进行大单的拆单交易,那么,他一定会设计一个对抗的算法,来通过尽可能低廉的价格,完成对手盘的交易行为, 这也会极大的冲击我们订单在市场中的平均成交价格。2、交易时机的选择
算法拆单的本质,其实就是通过延长交易的时长,来降低每次交易的数量,减小对市场的冲击。但是,这里的交易时长拉的过长,价格可能会逐步偏离起始的价格,导致成交的概率逐步下降。因此,如何去平衡这里的矛盾,选择合适的时机进行下单,也是算法拆单设计,需要核心考虑的地方。3、交易参数的设定
在每次执行拆单的过程中,如何选择具体下单交易的价格和数量,也会对最终的交易成本、成交率造成一定的影响,进行影响整体的交易质量。
服务架构如上图所示,我们以TWAP拆单算法为例,简单论述,如何进行拆单策略的执行:
- [1] 拆单服务每间隔一定的时间周期(由TWAP算法参数决定,由用户设定),消费判断MQ中的最新成交价,是否触达了对手盘一档的交易价格。如果价格未触达,则退出流程。
- [2] 根据币对的最小交易量,随机生成一个整数后(乘以最小交易数量,不大于最大需要进行拆单交易的币对数), 计算生成需要进行下单交易的数量,结合对手盘一档的交易价格,生成一个ioc(不成交立即撤销)订单,下单交易至柜台服务。
- [3-6] 柜台服务根据算法订单服务下单的币对价格和数量,通过访问撮合服务后,将最终订单的成交/撤销信息,再返回给拆单服务。
- [7] 拆单服务将相关订单信息进行记录,并更新父订单信息后,存储记录至存储服务中,方便进行下一轮拆单操作的执行。