There are three ways to make a living in this business: Be first, be smarter, or cheat.
Now, I don’t cheat.
And although I like to think, we’ve some pretty smart people in this building.
It sure is a hell of a lot easier to just be first.
——《Margin Call》
2019年10月15日更新:增加交易时间对simnow常规服务器、7*24服务器进行无逻辑滚动市价报单测试的参考结果。
之所以越来越多的交易者选择自主开发交易程序,是因为对交易环节的控制能力在交易过程中越来越重要。虽然有很多交易者在量化投资领域无私的分享研究成果,但是关于交易延时的系统化的内容仍然很少。借AlgoPlus项目的机会,我对延时问题进行了深入探究。给大家提供参考的同时,也希望能有更多的朋友参与研究、讨论与分享。
延时种类
1、网络延时
信息通过网络在客户端、期货公司前置、交易所主机之间传输的时间。
期货公司交易前置到交易所主机的延时是由期货公司系统决定。同一个期货公司,交易者可申请接入高速行情、高速席位。不同期货公司的技术、硬件设备差异,可对比之后选择。
交易者客户端到期货公司交易前置的延时是由网络环境决定。互联网环境,交易者需要择优选择宽带服务商。如果要进一步降低网络延时,可以将程序托管到期货公司机房的内网环境。
2、交易请求与回调延时
从产生交易请求(买卖撤查)信号开始,经过逐层传递,到最终调用CTP官方API将信息发出的时间。以及,从CTP官方API接收到回报、通知开始,经过逐层传递,到最终在策略中接收到信息的时间。
这种延时是由交易系统设计决定的,一般来说编译型语言比解释型语言执行效率更高。
3、策略内延时
策略被计时器、行情通知、订单状态等引擎驱动一次的时间。
主要由逻辑算法决定。交易者需要综合分析算法瓶颈,将高耗时操作转换为低耗时操作。
4、行情分发延时
行情进程将数据共享给策略进程的时间。
主要由使用的在多进程间共享数据的技术有关。尤其涉及多合约、多策略、多账户时,进程数量越多。
除上述因素外,硬件对延时也有至关重要的影响。我在硬件方面经验有限,所以不做这方面讨论。希望有经验的朋友可以补充。
time.perf_counter()
time.perf_counter()返回计时器的精准时间(系统的运行时间),包含整个系统的睡眠时间,单位是秒。使用范例:
1 2 3 4 5 |
from time import perf_counter as timer start_time = timer() # 待测试的代码 anchor_time = timer() print(anchor_time - start_time) # 时间差 |
网络延时
AlgoPlus封装了一个check_service方法,成功链接服务器时返回True。
调用check_service之前记录一个时间,获得返回值之后再记录一个时间,这个时间差就是客户端与期货公司交易前置之间的网络延迟。我们进行100次连接测试,将数据写入csv文件,以便随后分析。
1 2 3 4 5 6 7 8 9 10 11 12 |
ikk = 0 while ikk < 100: ikk += 1 start_time = timer() if check_service(ip, port): anchor_time = timer() timer_dict["ID"] = ikk timer_dict["StartTime"] = start_time timer_dict["AnchorTime"] = anchor_time timer_dict["DeltaTime"] = anchor_time - start_time csv_writer.writerow(timer_dict) time.sleep(1) |
simnow7*24服务器测试结果:
行情服务器 | 交易服务器 |
---|---|
![]() |
![]() |
对期货公司实时行情通知的测试,在以后的文章中再跟大家分享。
交易请求与回调延时
我们在策略调用请求方法之前,记录一个时间,在底层最后一步调用CTP官方请求方法返回后,再记录一个时间。需要注意的是,CTP的方法都是异步执行的,也就是说,调用CTP官方请求方法返回时,并不代表CTP请求已执行完成,后续执行过程是CTP官方接口完成的。
以AlgoPlus为例,在策略请求函数前记录开始时间:
1 2 |
self.start_time = timer() self.buy_open(test_exchange_id, test_instrument_id, last_md.BidPrice1, test_vol, self.order_ref) # 排队价买开仓报单 |
在调用CTP官方请求方法返回后记录结束时间,并将相关数据保存至csv文件中:
1 2 3 4 5 6 7 8 9 10 11 12 |
# 录入报单请求 def ReqOrderInsert(self, pInputOrder): super(TraderEngine, self).ReqOrderInsert(pInputOrder) self.anchor_time = timer() self.timer_dict["OrderRef"] = pInputOrder.OrderRef self.timer_dict["FunctionName"] = "ReqOrderInsert" self.timer_dict["OrderStatus"] = b"" self.timer_dict["StartTime"] = self.start_time self.timer_dict["AnchorTime"] = self.anchor_time self.timer_dict["DeltaTime"] = self.anchor_time - self.start_time self.csv_writer.writerow(self.timer_dict) self.csv_file.flush() |
测试结果:AlgoPlus从策略中调用buy_open方法开始计时,到CTP官方API的ReqOrderInsert方法返回,平均延时40.3us(1s=1000000us)。
另外,在周末对Simnow7*24小时服务器进行无逻辑滚动市价报单(当收到成交通知时立即以市价录入新报单)测试,1秒内平均可完成105笔成交。也就是说,计算网络延迟,平均只需9.5ms即可完成一笔交易。以下为快期交易界面截图,第一列为ID,7344-7238=106。
考虑到周末服务器负载比较低,测试数据偏好。所以,为了避免引起误解。我们在交易时间,又分别对Simnow常规服务器和7*24服务器进行了测试。
交易时间,常规服务器无逻辑滚动市价报单测试秒内平均完成50笔交易(12766-12715=51):
交易时间,7*24服务器无逻辑滚动市价报单测试秒内平均完成70笔成交(9807-9739=68):
总结:交易时间延时数据比非交易时间显著增加,而且波动也较大。分析其原因:常规服务器交易时间用户量最多,负载也最高,7*24服务器交易时间次之,7*24服务器非交易时间基本没有负载。这也是为什么高频交易一般使用次席的原因。另外,ctpmini比常规ctp更快。
策略内延时
由于不同策略逻辑复杂程度不同,所以没有统一的标准。未来使用到技术指标的时,我们在有针对性的测评。
行情分发延时
要在多进程间计算时间差,需要使用其他计时器函数,我们下节再详细讲解。
由于延时测试时一个特别复杂的事情,虽然我已经尽了最大努力追求文中相关数据的参考意义,但仍难免有失偏颇,欢迎大家参考我的测试代码自行测试、参与讨论。源代码地址:
https://gitee.com/AlgoPlus/AlgoPlus/tree/master/AlgoPlus入门手册/11性能分析
安装AlgoPlus之后,运行相关代码即可得到延时结果。
评论前必须登录!
注册