高频交易系统 E02

Yes, count and count on every tick!

Gateway

网关系统的职责是负责对外联系,一般分为行情数据(Market Data)和订单执行(Order Execution)两个部分。交易所一般会提供机房租赁,衍生品交易公司会采用co-location策略把交易主机放在租的机房里,确保和交易所的Matching Engine距离最近。所谓Matching Engine就是交易所自己用来匹配订单(order)的中央机器(其实有很多个机器)。
就行情数据而言,绝大多数交易所是用UDP组播的方式下发数据的。收到组播数据后,需要把订单报价更新的信息尽快处理并上发。就程序逻辑优化而言,第一是要尽最大可能减少内存拷贝(memory copy)。第二是要钻研(并且实验!)交易所给的数据格式,避免去读取不需要的字段。其次某些交易所的市场数据可能还是通过ASCII字符下放的,这边就要尽量优化string to int的延迟。

Market Data

对于某些交易Gateway实现,也会包含price book/order book的更新。行情数据可以细分为不同的频道(channel),一些会播放price book update, 另一些则是order book update. 后者往往包含更多的交易信息,这些交易信息与trade update合并关联之后,就能给pricing module提供实时更新。对于price book/ order book,写代码的小哥往往会对他们各自抽象各自的数据结构,这些数据结构需要保证book update的响应尽量快。在book得到更新后,上层系统(strategy, pricing module)对它的需求是不同的,有些需要full depth of book,有些可能只需要几个book level或是只要TOB(top of book),针对这些不同的需求,系统也会给出不同的优化。也有系统把book update的具体操作另外归类为几个特殊事件直接传给系统上层、以便做出最快响应。这些事件可能包括巨大的价格变动(此时很可能需要撤单)。

另一部分个重要部分是重启、恢复机制。由于UDP传输的不稳定性(尽管已经是co-location了),交易所一般提供通信恢复的专门channel播放恢复数据。在网络中断或者系统故障后,需要通过数据恢复机制重建各种交易工具(instrument)的最新信息。

Order Execution

再来讲讲发单的模块。系统给发单模块的指令已经是经过各种决策后的,一般不需要再进行过滤。一般而言,这部分是通过TCP传输的。为了尽快发单,一般采用数据结构预填充的方法,即把交易信息用数据结构表示,里面固定的字段预先填好。比如C++实现时,Order Execution模块会把各种发单请求写成类成员,在OE class构造时预先构造好。对于OE而言,一般情况下并发并不是主要考虑的因素,因为大部分交易所是允许单个消息包含多个订单请求的(bulk orders or bulk delete orders), 往往把交易请求拆单并发发出的总体耗时比用单个请求发出的更不划算。但这部分仍然需要通过做实验观察得出结论,对于部分的交易所,将一个大单拆成几小份可能有一半左右能抢到,但是如果做成一个大单可能真的是Fill(10%) or kill(90%)了。对于一些需要并发才能优化的交易所,也许会需要使用无锁队列(如MPMC Queue)进行优化。

socket连接?有人可能会问socket怎么搞。其实这种时候用整个系统用busy loop, blocking的方式或许比费劲心事async来得更有效。当然对整个trading engine而言,它应当是多线程、适当异步的,或许Proactor模式更适合一些。所谓多线程,在通信不是很繁忙的desk上,整个gateway系统可以是一个线程,在比较繁忙的desk上,把market data和order execution拆成两个线程做是一种解法。

关于优化,再说一句

不只是应用层的软件,在内核层也需要对性能进行优化。一个简单方法是购买专用的网络硬件,然后利用驱动(等)把网络传输的操作从内核态操作转移到用户态进行。学过操作系统的同学应该知道,在内核态和用户态中间反复切换是有成本的。

此外,一般用于高频交易的主机往往要求CPU主频够快,对核心数要求并不特别高。为了主频够快,一些公司甚至会购买定制优化的超频主机来使用,通过将线程直接分别绑定在CPU core上,然后isolate cpu,再考虑NUMA node,继而用realtime scheduler实现各种黑优化。

发表评论

电子邮件地址不会被公开。 必填项已用*标注