【核心技术】功夫交易系统架构篇之一 「非如此不可」

董可人/CEO

功夫 是一款为速度而生的量化交易执行系统,它能帮助对速度有极致要求的交易者大幅降低系统内延迟,内建的内存数据库会自动存储纳秒级时间精度的交易相关全部数据,赋予策略师在盘后进行高精度数据研究的能力。功夫的架构设计,绝非一朝一夕的灵感突发,而是浸淫多年的心得体会,是经年累月的实践打磨,更是实盘拼杀验证出的唯一选择。今后我会在本专栏发布系列文章详细解释功夫的设计思路。本篇作为开篇,首先要从整体上介绍功夫的设计原则和运行机制,希望读者可以在头脑中形成系统运行的整体图景,也希望你在读完全篇后,可以同意这个观点:功夫的设计和实现没有第二选择,是一种「非如此不可」的方案。

通信机制

功夫使用共享内存作为核心通信机制。在通信延迟这个指标上,共享内存远远领先于其他一切诸如消息队列、网络协议之类的方法。在功夫内部,通信功能的基础设施实际上由内存数据存储模块易筋经兼任,易筋经使用内存映射文件(Memory Mapped File – mmap )作为共享内存的创建方式。内存映射文件 mmap 的引入使得系统可以兼顾进程间通信及数据实时存储,这在高频交易的世界早已是公开的秘密和事实上的技术标准,任何一个初步了解高频架构的工程师都会采用这个技术实现系统底层,但现实中却从未有任何一个开源项目针对这一点做出标准化封装,使得无数高频团队内部的工程师不得不重复造轮子。功夫需要实现这样一个标准工具,免去重复开发的苦恼,这是「非如此不可」之其一。

存储模型

使用内存映射文件之后,在解决通信问题的同时,我们事实上还同时得到了实时存储的能力,因为 mmap 的运行原理是,操作系统会在后台 kernel 进程中保证磁盘上的映射文件内容和内存映射区域同步,使得你对该内存的任何写入操作,都会异步的持久化到磁盘上。这完美的解决了其他存储模型(文件、数据库等)写入耗时的问题,我们再也不用担心存储功能会影响系统处理速度。之前有人评论,认为 SSD 写入已经很快,写磁盘等于写内存,这显然是对延迟没有深入研究的想当然,请看下图:

对SSD的操作仍然比内存更耗时,并且写入 SSD 意味着一次额外的复制操作,而写入 mmap 映射的内存区域则是 Zero-Copy,孰优孰劣一目了然。

单纯使用 mmap,每次映射的文件是固定大小,这对于需要连续存储的应用来说是不可接受的,写入内容无法超过映射文件的大小。因此,必须有一个更上层的存储模型完成底层的存储文件拼接工作,通过自动管理链接映射文件,使得上层应用得到一个可以连续无上限写入的内存区域。这便是易筋经的主要职能。易筋经采用流式存储,每个流定义为 Journal,每个 Journal 内部由一系列内存映射文件组成,每个文件定义为 Page。按照这个机制,在读写进行到文件边界的时候,系统内会自动进行换页,对于追求速度的应用来说,换页所产生的巨大耗时是不可接受的,为了解决这个问题,易筋经内设后台进程进行换页预处理,使得应用层在需要换页时可以瞬时完成。

要兼顾通信和极速异步存储,必须使用 mmap 技术,使用 mmap 技术则必须有一套代码完成内存管理和换页操作才能提供无限长存储,因此易筋经的设计和实现成为必然选择,这是「非如此不可」之其二。

线程模型

交易程序必然是多线程架构,需要不同的线程同时处理接收行情、策略运算、柜台交互等任务。然而在这个世界上,如果有什么人天生无法理解多线程概念,我一定会说是我那些可爱的 Quant 朋友们。Quant 是一群数字天才,他们精于数学模型,可以在无尽的数字海洋中寻找到哪怕最微弱的信号,但是要让他们理解一台机器的内部运行机制和各种内部状态的同步异步问题,那可真的要了命。的确,曾经世界上有那么一种神奇的职位叫做 Quant Developer,专门负责把数学模型翻译成 C++ 代码,但是大浪红尘,君不见在这个24小时速成深度学习的年代,无数未经专业计算机培训的年轻人已经来到这个迅速数字化的世界寻求机会,让他们支付高昂的学习成本理解多线程已经是一个不可能的任务。

针对于此,功夫特别对策略实现部分采用了单进程单线程模型。使得策略开发者可以不再担心回调函数中的代码是否会引发线程安全问题。同时,不同的策略运行在不同的进程中,极大增强了系统稳定性,单一策略的崩溃不会影响系统的其他部分,极端情况下,即使你的策略程序全部异常崩溃,系统仍然会为你保持行情和交易连接,落地存储所有行情数据。

在策略引擎咏春的代码中,大家可以看到行情处理(MD – Market Data)、策略计算(BL – Business Logic)、柜台交互(TD – Trade)是分别独立的模块,每个模块都运行在自己独立的进程空间中。其中 MD 和 TD 由于是建立在诸如 CTP 之类的交易柜台 API 基础上,天然是一种多线程结构,我们会在系统内完成多线程处理;而 MD – BL – TD 的交互,则是通过前述基于易筋经共享内存通信的方式实现。这使得 BL 部分可以完全隔离在单独的单进程单线程中进行处理,从而实现单线程策略模型,这是「非如此不可」之其三。

确定性计算

对于一个复杂的计算机系统,因为内部多进程/线程处理的运行机制,不可避免的会产生一个极其复杂的内部状态机,这个状态机的状态迁移在系统运行期间往往具有很高的不确定性,由于变量众多,任何一个变量的变化都可能影响全局,这使得系统开发进入后期后,错误定位和排查工作变得异常艰难。由于采用了共享内存进行通信,进程隔离的设计机制,功夫进一步采用了完全确定性的处理方式。所有内部处理,都是基于从共享内存中的单线程数据输入,到向共享内存中的单线程数据输出这一链条进行,使得在同时拥有输入数据和处理代码这个先决条件下,可以完全重现处理过程和得到相同的处理结果。由于共享内存中的数据会经由 mmap 机制自动异步存储到磁盘,这个先决条件就变成了必然具备的条件,使得用户可以在系统运行结束后随时再次重现当时的运算处理场景,使 Debug 调试和修复都不依赖于实盘环境。要做到这一点,确定性计算的原则必须严格贯彻到系统的方方面面,这是「非如此不可」之其四。

数据标准

国内的量化交易行业方兴未艾,作为从业者,面对数十种风格迥异的交易柜台API,我常常感到十分头大和痛苦。很多时候,同样的交易策略,只是因为切换了交易柜台,就不得不进行重新开发,浪费大量的时间精力。这让我十分怀念在国外市场上大家都基于标准的 FIX 协议,以 TCP/UDP 协议的形式进行交互的友好环境。有感于此,我们尝试在长拳中抽象和规范化我们接触过的所有主流柜台数据格式。对于每一个希望快速移植策略程序的交易者,都一定需要这样的一个标准格式,这是「非如此不可」之其五。

============

综上,功夫的核心设计原则是对非常非常基础,但是非常非常重要的几个关键问题采取专门针对性措施。可以看到这些问题全部都和具体的交易逻辑无关,不论你使用什么类型的交易策略,这些问题都是必须要面对和解决的。你必须有这样一个坚如磐石的底层系统,上层应用才能得以生生不息,而非每次交易需求变化都需要对系统进行彻底重构,这是「非如此不可」之其五。

============

开源上线以来,有朋友表示并不是非常理解我们为什么要开源,有这样几种典型顾虑:

  1. 开源以后,无法形成有效的商业模式和收费方式;
  2. 竞争对手可能利用我们的开源代码进行二次开发;
  3. 既然技术有优势,自己利用优势做交易赚大钱更好;

关于这些问题,我们并非没有经过深思熟虑。做为一个在量化交易行业一线工作多年的“老兵”,我有幸见识了很多非常优秀的交易团队和各种罕见的科技方案,然而时过境迁,当年辉煌的精英团队隐退之后,那些神乎其技往往也从此埋没,江湖中只剩只言片语,往往进而形成吹牛不上税的浮夸风气。

工程师出身的我更希望交易这个世界可以出现更多公平、透明、规范的工具,对于每一个有志进入这个领域的人,可以不再关起门来自己造轮子,花费无数精力去解决一个又一个被前人解决了一次又一次只是没有传承的问题。当然我们也可以提供低价或免费但不开源的产品来解决这个问题,但是我始终相信没有形成有效交互的社区是不会长久的,在这个领域,我们需要一个有公信力、质量过硬、支持到位的产品,这个产品必须得到广泛的使用和贡献,基于此,开源是一个唯一的选择。如果我们有幸做到这一点,我相信一个有价值的产品并不难寻求商业上的成功。

我们并不奢望所有人都必须基于完整的功夫系统来实现全部交易功能,一款产品不可能面面俱到,但是功夫中的易筋经、咏春、长拳做为基础设施,应该具备独立使用的价值,我们希望你会愿意引入其中一些模块来加强你自己的系统。对于潜在的竞争对手,如果愿意基于功夫进行二次开发,我认为是我们的荣幸,我们也会开放正规的合作渠道,希望你也愿意自己的产品上有一个 Powered by 功夫的标记。

这是「非如此不可」之其六。