分享

技术揭秘 | 港交所OCG高性能交易协议赏析

 ahappyday 2020-11-24

交易所服务于交易业务的协议与服务于行情业务的协议有着诸多不同,开发者如果未曾有过具体的对接经验,可能会难以直接欣赏 OCG(Orion Central Gateway,港交所交易服务网关的名称)协议的精妙之处。本文旨在为详细探讨 OCG 协议作铺垫,先整体介绍一下交易所的交易业务及其所涉及到的协议。如您在交易业务协议方面已具备一定知识,可直接滑到末尾点赞分享。

交易所交易业务简介

所谓交易所,最重要的业务顾名思义就是交易了。此前的文章已经介绍过交易所的基本职能和它所提供的服务种类,这里便不再赘述,仅将讨论的重心聚焦于交易业务。

交易所的交易业务主要包括下列几种:

  1. 买卖撮合服务,也就是平常说的“交易”;

  2. 交易申报服务,即当发生了场外交易时,某些国家或地区会要求向指定机构申报特定种类的交易,通常交易所会承担一部分这样的职能;

  3. 询价服务,也就是给“做市”行为提供支持。

以我们最常见的买卖撮合服务为例,交易所需要将所有市场参与者的所有动向都实时反应在撮合系统中,而这每一笔动向都可能产生真金白银的交易,因此交易服务的可靠性极其重要。

同时交易指令能否被及时处理也会对市场参与者的收益产生巨大影响,因为市场参与者发送交易指令的依据就是当前的行情;指令发送与执行间的时间差越大,指令执行时的行情与发出指令时市场参与者看到的行情之间差距就越大,那么这条指令也就越有可能是不适合当前行情的“过时指令”,从而使客户蒙受损失。因此交易指令执行的时延也是一个非常重要的指标。

最后,取决于不同交易所面临的市场,交易参与者的数量可能非常庞大。而庞大的用户基础必然导致交易所需要同时处理的数据量非常庞大。这就引出了第三个指标——吞吐量。交易所必须具备足够的吞吐量来应付数量庞大的交易指令。

至此,高可靠性、低时延、高吞吐的需求构成了一个“不可能三角”,如果不进行妥协,将会使软硬件开发、运维成本提高到无法承受的地步,甚至不可能实现这样的系统。

交易服务的协议

在前文介绍的“不可能三角”中,最重要的就是高可靠性。因此几乎所有的交易所都选择了以 TCP 传输为基础的协议——它就是为可靠传输设计的。而在低延迟和高吞吐之间,不同交易所有不同的选择。例如在高频交易非常常见的市场中,低延迟的重要性高于高吞吐;反之,如果以普通交易为主的市场中(例如内陆市场),吞吐量则更为重要。

FIX 协议

尽管全球的交易所数量多达数百家,但是它们所承载的业务却大同小异。假设一家金融机构想要在多个市场参与交易,那么它的系统将会需要对接多家交易所。如果每家交易所的协议都非常不同,那对接的成本势必会非常高昂。

大家肯定也已经想到了,如果存在一个大部分交易所都遵守、同时可以满足大部分交易所业务的协议,那么交易所及市场参与者的工作量将会被大幅减轻。诞生于 1992 年的 FIX 正是这样一种协议。

关于 FIX 的历史本文不多赘述,它非常著名,以至于很多金融机构在招收 IT 工作人员时都会明确要求熟悉该协议。

FIX 协议如此流行,离不开它优秀的特性。FIX 采用的纯文本编码使得它具有很高的可读性;以键值对的方式存放信息使得扩展新消息时非常容易;它同时支持查询和订阅推送两种模式,还在消息中加入了序列号,使得软件可以在应用层进行消息序列的校验,从而进一步增强了可靠性

一条典型的 FIX 消息:8=FIX.4.2 | 9=176 | 35=8 | 49=PHLX | 56=PERS | 52=20071123-05:30:00.000 | 11=ATOMNOCCC9990900 | 20=3 | 150=E | 39=E | 55=MSFT | 167=CS | 54=1 | 38=15 | 40=2 | 44=15 | 58=PHLX EQUITY TESTING | 59=0 | 47=C | 32=0 | 31=0 | 151=15 | 14=0 | 6=0 | 10=128 |

其中的数字代表字段类型(在协议数据字典中定义),等号后面的内容为值。

FIX 协议在欧洲和北美交易所被广泛采纳(当然各自做了少量的定制),因此应付欧美市场的对接时,承接方的工作量较少。有多市场对接需求的公司几乎都会自研或者采用开源的 FIX 引擎,以及使其具备一定的低成本定制化能力(例如通过配置增删字段或消息),从而适应大部分交易所。然而亚太地区的交易所相对封闭,似乎对 FIX 协议并不十分感冒:例如韩国交易所、旧东京交易所、台湾交易所等都没有采取 FIX 协议(有一些交易所比如旧东京交易所采取了类似 FIX 的协议),也包括了本文尝试介绍的港交所。

交易服务协议大致分类

如前文所述,交易服务最看重的是可靠性,因此清一色地都采用了 TCP 的传输方式,这导致交易协议的种类并不如行情服务丰富。即便如此,我们也可以大致给交易协议分一下类。

  • 更新方式分:全量更新、增量更新;

  • 编码方式分:二进制、字符串;

  • 字段排列分:键值对、固定长度;

  • 字段分隔方式分:固定长度、动态长度。

我们来一一解说一下。

全量和增量更新的差异比较容易理解。全量就是每次都更新一个完整状态,而增量则只更新两次状态之间的差值。显然增量更新有利于提高吞吐量,因为它传递的冗余信息更少,所需带宽也更小。

编码方式也比较容易理解,二进制方式不考虑可读性,它可以利用一个字节的总共 256 种状态(0~255);而按字符串编码则只能利用其中的 128 个被 ASCII 编码定义的值。如果从编码密度来看,二进制更有优势。二进制还具有与内存存储方式相近的优点,因此在编解码时消耗更少的 CPU 时间。所以二进制编码较之字符串编码同时具有更高的编码密度和更低的时延,但是因为它不具备可读性,因此它的开发和调试更为困难。

字段排列方式的区分中,我们先看键值对方式。这种就是前面介绍的 FIX 协议所采用的方式,它在每个字段开始时会定义这个字段是什么(键),而紧接着是这个字段的内容(值)。使用这种编码方式时,接受方遇到一个字段后会先解析该字段的键,按照协议规定来理解这个键对应的值是什么类型以及是什么含义。而固定长度类型则是在协议中规定了从某个消息开始的第几位到第几位是什么字段,然后另一个区间是什么字段。打个比方,假设我们手上拿着一份名单在某个门口检查一队通过的人。键值对就像每个人都举着一个牌子说自己是谁,我们看到牌子后在名单里面查找那个人对应的信息就知道他/她是谁了;而固定长度编码则像名单中列好了这队人中第几个是谁,如果有人没有到,他的位置也必须空着,而且所有人必须按名单规定的顺序排队,否则我们就不能知道他们都是谁了。以键值对编码的协议通常会占用比较小的带宽(不需要为空值留位置),更改协议时也会比较灵活;而固定位置编码的优点是编解码速度非常快——因为它直接反映了各字段在内存中的位置,而它因为空字段而造成的空间浪费问题可以通过流式压缩算法来缓解。固定长度编码这种通过直接映射内存结构节约的编解码时间,又在为了节约带宽而引入的压缩算法中损耗了。这一加一减相对于键值对方式并没有明显的性能优势,反而丧失了字段排列、增减的灵活性。因此在教新的交易所系统中几乎看不到采用这种编码方式的协议了。

字段分隔方式与字段排列方式有一定的相关性。因为 TCP 协议是流式传输,数据之间没有天然的分隔,所以需要人为规定一种手段来区分一个消息中各个字段的位置。先看固定长度的分隔方式。它经常与固定长度的字段排列方式并存。协议定义大致如下所示:

这样的字段分隔方式在规定了消息位置的同时也规定了消息的长度。这也很好理解,因为下一个字段的位置 = 本字段的位置 + 本字段的长度。这样做的还有一个好处是可以不经过解析,快速定位到想要找到的字段。因为不像其他几种方式,固定长度排列和分隔的编码形式中每个字段所在的位置总是固定的,因此在收到“请找出第三个字段的值”时,并不需要将前面的字段都解析出来才知道第三个字段在哪里。动态长度字段是指每个字段的长度并不是固定的,而是可以根据需要而改变。

动态长度字段通常有两种编码方式,一种是以分隔符区分。这在字符串编码的协议中非常常见,例如前面说的 FIX 协议,就是以竖线来区分不同的消息(假如没有分隔符,FIX 消息“ 8=FIX.4.2 | 9=176 | 35=8 ”将会变成“ 8=FIX.4.29=17635=8 ”,解析时就会产生歧义)。另一种则是在每个字段的开始标明该字段占据的字节数,或者通过字段类型隐含了这个信息。例如要编码一段用户输入的字符串,则必须首先将字符串的长度写入消息流,再将字符串写入,这样解码的时候就可以先获得字符串的长度,再读入相应长度的字节来解析出被写入的字符串。而遇到有标准长度的消息例如整数、双精度等则可以由协议来规定这类字段的标准长度,就不需要再显式写入一个长度信息了。我们来看一个例子:

上述协议定义中只定义了两个字段,同时还需要假定字段 ID 本身占用 4 字节,以及规定不定长度字段的长度编码也占用 4 字节。下面来看一个具体的消息:

在该消息中,解码方在消息的开始一定会读取 4 字节的字段 ID,然后根据读取到的内容来判断字段类型,进而判断接下来应该读取多少以及如何解析。

最后因为内陆的交易所无一允许通过网络协议直接连接,开发者必须通过券商提供的柜台连接,因此本文并没有刻意进行分析。不过内陆交易所使用的 STEP 协议按上述分类是一种基于二进制编码、键值对、使用分隔符实现动态长度的协议;本质上是一种 FIX 协议的二进制编码(原本 FIX 使用字符串编码的)实现。

相比于此前提到的 OMD 行情协议,港交所在 OCG 交易协议上的创新虽然没有那么惊世骇俗,但也算是可圈可点。OCG 相比于亚太地区其他的任何交易所以及欧美地区的大多数交易所来说,都是相对比较先进的。OCG 交易协议的创新更多的是在编码层面上,而业务层面则几乎照搬了 FIX 协议。我们在本系列上篇已介绍过 FIX 协议,本篇以此为基础重点讨论港交所 OCG 系统的协议编码方式及给我们的启示

不可能三角的妥协

本系列上篇介绍的不可能三角:高准确性、低时延、高吞吐,如果不对任何一个角做出妥协,系统几乎是无法实现的。各大交易所多少会根据自己的业务在时延或吞吐上做出自己的妥协,但是没有任何一家会在准确性上妥协的

港交所身处号称亚洲金融中心的香港,面临着大量国际投资者的交易需求。而香港的政策并不排斥高频交易者,因此用这一类策略的市场参与者非常多;同时交易所的收入来源大部分来源于交易费用,因此这高频交易者通常不会受到交易所的主动打压,反而会受到照顾。对于高频交易来说,准确性仍然是第一位的,而时延和吞吐量之间如果做取舍的话,时延优先。因此港交所为了保证低时延,在协议的层面对高吞吐做了一定的妥协。而它妥协的方式也非常地巧妙。港交所对市场参与者发送的消息做了流量控制,对不同流量的市场参与者收取不同的费用。具体来说,如果要接入港交所,最低需要购买2条/秒的流量配额;如果流量不够,则可以通过加钱的方式额外购买每席位最多2000条/秒的配额。如果2000条仍然不够,可以再加钱购买更多席位,当然独立的席位就更贵了。

港交所通过这样的“妥协”使得自身系统的承载量和收入达到一个动态平衡,不可谓不精。这里仍然认为是妥协,是因为港交所无法在合理的成本下支持过高的吞吐量,只能将这部分成本转嫁给市场参与者。因为前面说过交易所最主要的收入来源是交易费,设置这些流量门槛也是无奈之举。

上图可见,来源于平台及基础设施的租用费用仅占收入的 5%。

港交所协议对低时延的优化

系列上篇讲解过协议的编码方式各有优劣,我们简单回顾一下几个对时延有较大的影响的因素。

首先是编码出来的消息大小,越小的消息传输速度越快。这是影响最大的因素,因为网络 IO 速度与 CPU 运算速度相比可以慢几个数量级。因此协议中首先需要优化的就是编码后的消息大小——承载同等量的信息,消息越小时延越低。这里常见的优化方式是采用动态长度来编码消息使得消息没有因可选字段没有设置而出现的“空洞”,以及采取压缩算法来处理消息。另外,与编码方式无关的且最显而易见的优化方式当然是采取增量更新以减少需要传输的信息量。

其次要考虑的是编解码速度。而事实上因为消息大小所造成的的影响非常大,编解码速度通常是被妥协的那个因素,一切都为了更小的消息而努力。

常见的交易所协议优劣

现在再来复习一下本系列上篇说过的一个可变长度消息是如何编码的:

这种编码方式的好处是非常灵活,它可以将 100 和 101 两个字段的编码顺序调换,也不会影响最后的解码结果。因为解码器是先读到字段 ID,再看是什么字段,解码完毕后拼成一个完整的消息输出给上层。假设我们能够接受字段顺序固定,是否有办法省去一些编码?答案是肯定的,那就是采用固定长度的编码方式。

这样我们就省去了编码字段 ID 的功夫,但是却造成了当某个字段缺失时还必须传输占位值的情况(假设不传上面的文章 ID 字段,而是直接将文章内容写在第 10 位上,那解码器则无从知道第 10 位究竟是文章 ID 还是文章内容,因为没有任何其他的信息来表示某个字段是否存在)。这种编码方式在信息大部分被填充的应用中可以很好地节约由于编码字段 ID 而带来的带宽损耗,但是在增量更新的协议中,消息的大部分字段都将不会被填写,这会造成巨大的带宽浪费

FAST 协议

在进入 OCG 协议之前,先介绍一下被众多国内外交易所采用的 FAST 协议。它的编码方式是将可变长度消息的字段 ID 位置变为单个字节的分隔符,并且规定了字段的编码顺序。如果某个字段不需要被填写,仅需要传一个分隔符。这样解码器就能“数”出某个字段位于第几位,其协议大致是这样:

这个协议以单个字节将原本可能需要两个甚至更多字节才能表示的字段 ID(因为一个字节只能表示 256 个 ID,而交易所协议字段是肯定超过这个数量的,因此会以两个甚至四个字节来表示),并仅用一个字节的代价就补足了空字段的缺陷,可谓非常精妙了。

天才的设计 —— 字段位图

从根本上看,OCG 协议不属于本系列上篇列举的任何一种类型。因此在对接时会发现之前为其他交易所协议写的编解码生成器无法通过简单扩展来适应这一种协议,而必须重新开发。关于编解码生成器可以期待后续的《技术揭秘》系列文章。

言归正传。我们先来复习一下“字节”是什么东西。字节是用来描述数据长度的一个单位。我们知道计算机使用二进制来表示数据,一个二进制码(一个比特,或“一位“)只能为 0 或 1,而一个字节则由 8 个比特组成,这样一个字节能表示 2 的 8 次方,也就是 256 种状态。

OCG 协议的天才之处,在于它利用了一个字节有 8 个“开关”这一特点,在 FAST 协议之上更进一层。

基于 FAST 协议的关于字段所处位置定义,OCG 在每个消息开头都编码 2 到 8 个字节用来告诉解码器该消息有哪些字段存在——协议将其命名为 Presence Map,我们翻译为字段位图,就像一张地图一样靠比特的位置信息来表示某字段是否存在。

下面假设我们只用一个字节的字段位图来表示一个有 8 个字段的消息:

字段位置定义
字段位图

解码器在解码时首先遇到的是字段位图,读取一个字节之后,根据协议中位置的信息,便可以构建出整个消息的完整结构;之后再按照字段在协议中定义的长度信息一个个往后读,即可完全解码。如解码上述字段位图后可知,该消息是由第 1、4、7、8 四个字段组成的,因此只要按照整数、整数、浮点、整数来解码即可知道对应位置的数据应解析为什么字段。

对比 FAST 协议,OCG 在一个8字段的消息中以一个字节的代价,省去了8个占位符(即8字节,净省7字节)的空间;而相对于更灵活的键值对编码方式则省去了8个字段ID的编码空间(假设每个ID消耗两个字节,净省15字节)。

简评 OCG 的编码协议

不同于早先 OMD 协议基于超高线路质量、超高带宽的假设,OCG 的协议仅在编码的运算复杂度上有所提升(而且这种复杂度的提升相对于 OMD 协议需要在各个频道之间同步来说不值一提),便节约了很可观的编码长度。如果说 OMD 展示的是设计团队妥协的艺术,那么 OCG 展示的则是纯粹的美感。这样大胆而精妙的设计值得我们所有技术人学习。

OCG 协议的其他部分

其实到此为止本文也即将迎来尾声,但是完整的协议并不止有编码部分。但 OCG 的其他协议细节其实并不出彩,甚至还有点落伍。这是因为 OCG 背后的撮合引擎核心仍然是一套老旧的系统,而 OCG 仅是一个消息门户,负责引擎与外界的信息交流——因此它的协议内容和机制设计不可避免要受到引擎的限制。因此本着取其精华去其糟粕的原则,本文仅详细介绍编码方式,对于其业务不做更多介绍。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多