高频交易是一个很隐蔽的领域,我找了一圈资料,几乎没有深入谈及具体策略的,偶然在medium上发现了一篇搭建高频交易系统的文章,发布于2022年8月,讲的非常详细,这里分享给各位。
在进入文章之前,再简单聊几句高频交易的基本情况。高频交易一般来讲分两种:做市和套利。做市商就相当于一个串串二道贩子,把东西从甲买过来再转手给乙,为市场提供流动性并从中赚取差价,但是单笔交易的利润很薄,所以做市商要靠规模和堆积交易次数赚钱。套利就是在多品种上寻找定价偏差,比如统计套利、延时套利等等。
我们来看几种高频策略的例子。
做市(market maker):
从买卖价差中赚钱。举个例子,当前有一只股票的订单薄长这样:
做市商会把单子挂在买卖价差里,做市商分别挂出bid 4.8和ask 5.1的单子,这样就占据最优的买价和卖价,下图是做市商挂单后的订单薄:
这时候有市价卖单进来,首先和做市商买一价成交,股价维持不变的情况下,市价买单进来,做市商又能以卖一价把仓位平掉。这样一倒腾做市商就吃到了5.1-4.8=0.3的利润。利润非常微薄,好在这样的高频做市策略稳定,薄利多销也能积攒丰厚的利润。
配对交易(pairs trading):
统计套利的一种。两种相关资产的长期价差服从某个均值,当价差扩大或缩小进场,做多一种资产,做空另一种资产,预期价差会均值回归。公众号有几篇关于配对交易的文章,有兴趣可以去看看哦。
延时套利(latency albitrage):
顾名思义,就是利用时间的延迟赚钱。比如你想买A公司的股票,此时此刻的买一价是10,卖一价是11,于是你打算挂一个中间价位10.5的买单。
在你挂上买单之前的一瞬间,价格变了,买一价和卖一价变成了9.5和10,但是你有网络延迟,所以看到的还是10和11的买一和卖一价。
延时套利策略瞄准了这样的时间缝隙,他们的延迟比你低,所以比你先看到价格变化:买一价变成了9.5,同时也看到了你挂的10.5的买单,此刻就产生了套利空间,他们以9.5的价格买入股票,再转手以10.5的价格卖给你,这1块钱就是他们的利润。
现在我们进入正文,看看怎么搭建一个低延迟的高频交易系统。
这篇文章会把重点放在高频系统软件设计和构造上。一套高频系统的建立,除了本文讲的内容外,还有很多其他的要素:网络通信、路由器、专用服务器、FPGA、系统调整(比如kernel-bypassing)等等。
高频系统的核心就是在尽可能短的时间内处理大量数据。在我搭建高频系统的过程中遇到了2个主要问题:
要保证系统能处理多种策略,无论我添加多少策略,系统能一贯表现良好。
从多个平台(venue)重建订单薄。以外汇市场为例,外汇市场有很多平台和ECNS,他们用的API和连接标准都不同,高频系统要从这些不同的信息来源中获取数据,然后把数据整合到我们自己构建的内部订单薄中。
这篇文章会把重点放在高频系统软件设计和构造上。一套高频系统的建立,除了本文讲的内容外,还有很多其他的要素:网络通信、路由器、专用服务器、FPGA、系统调整(比如kernel-bypassing)等等。
高频系统的核心就是在尽可能短的时间内处理大量数据。在我搭建高频系统的过程中遇到了2个主要问题:
- 要保证系统能处理多种策略,无论我添加多少策略,系统能一贯表现良好。
- 从多个平台(venue)重建订单薄。以外汇市场为例,外汇市场有很多平台和ECNS,他们用的API和连接标准都不同,高频系统要从这些不同的信息来源中获取数据,然后把数据整合到我们自己构建的内部订单薄中。
1、获取数据和重构订单薄
任何交易系统首先都要对数据进行处理。没有准确的数据,就不可能有准确的交易信号。因此,我们的做法是从不同的交易所获取数据。
每个平台每一秒都会产生巨量的数据,下图是我们处理数据的流程:
在以前,系统和平台的连接是通过FIX协议实现的(FIX protocol,金融信息交换协议),如今越来越多的平台提供了更好更快的连接协议,比如FAST, ITCH/OUTCH这类二进制协议,也是我常用的协议。
和平台的连接建立好后,下一步就是把平台时时刻刻发来的数据填充到我的内部订单薄里,这一步就是重建订单薄,过程如下图:
由于每个平台发来的数据都会有一个独一无二的ID标识,所以不会混淆不同来源的订单薄数据。另外记住每秒会有成千上万的数据进来,我们要选用增删改查快的数据结构来保存订单薄数据。
在系统初始化阶段,我们先建立两个数组:bid array和ask array,比方说我们要构建一个10档深度的订单薄,先在bid和ask数组预分配10个元素,后面会一直重复利用这些元素,对它们进行移动/替代操作,这种预先分配元素的方式称作"pre-allocate"。另外一种是动态分配"dynamic allocate",会有增加/删除元素的操作。
我写了两段代码对比预分配(pre-allocate)和动态分配(dynamic allocation)的区别。
动态分配:
//动态分配
void AddRemoveDynamically(int type, BookPrice& b)
{
if (type == 1)
{
totAddsDyn++;
//增加元素
aDynamic.push_back(b);
}
else if (type == 2)
{
totDelsDyn++;
aDynamic.erase(std::remove_if(aDynamic.begin(), aDynamic.end(), [&](BookPrice const & p) {return p.price == b.price;}),aDynamic.end());
}
std::sort(aDynamic.begin(), aDynamic.end(), [&](BookPrice& ltd, BookPrice& rtd){ return ltd.price < rtd.price; });
}
预分配:
// 预分配
void AddRemovePre(int type, BookPrice& b)
{
if (type == 1)
{
totAddsFix++;
// 一直重写第一个item
aPreallocated[0] = b;
}
else if (type == 2)
{
totDelsFix++;
auto it= find_if(aPreallocated.begin(), aPreallocated.end(), [&](BookPrice const & p) {return p.price == b.price;});
if (it != aPreallocated.end())
{
it->price=0;
it->size=0;
}
}
std::sort(aPreallocated.begin(), aPreallocated.end(), [&](BookPrice& ltd, BookPrice& rtd){ return ltd.price < rtd.price; });
}
在100000次迭代的样本中计算预分配/动态分配的的CPU周期,我得到以下结果:
两者有60%的区别!动态分配花了2522s,预分配只花了1575s。
2、订单管理系统
订单管理系统(Order management system: OMS)负责发送、取消和替换订单,以及访问有关已执行订单的信息。
OMS系统必须以非常高效和经济的方式发送订单,要考虑到下面的因素:
3、软件架构模式:
这一步要把所有的零件组装在一起了。最基本的架构如下图:
保证整个系统的“并发”最为关键,也就是每个策略能够同时访问内存中的数据。
Observer Design Pattern是一种常见的模式,数据和策略交互的具体流程是:
subject是订单薄,observer是各种策略。订单薄一旦变化,就会“通知”各个策略。
订单薄的数据一更新,我们的策略就会消化新的信息,符合开仓条件便发出信号,这是一个线性执行的过程:1号策略用几十毫秒处理完数据,接下来是2号策略...3号策略...,一直到最后一个策略。
这种执行方式非常笨拙缓慢,当最后一个策略执行的时候,价格行情已经过去好大一会儿了。
那么用多线程呢?将会更糟糕,内存开销会非常大。(作者在原文没有做详细解释)
用C++实现Observer Design Pattern代码如下:
关于软件架构模式到这里就结束了,后续作者如有更新,我会继续更新到公众号上。作者是一位高频交易工程师,附上原文连接:
https://wire.insiderfinance.io/how-do-i-design-high-frequency-trading-systems-and-its-architecture-part-2-3-4c618574a8ef
所有文章会被更新到我的公众號:
跟puppy玩量化
有些不方便发出来的东西也会发在gzh上
快来关注我吧!
另有一个量化小群,欢迎有一定量化基础的玩家加入。有兴趣的后台留言哦 |
|