中泰证券股份有限公司科技研发部总经理 何波
量化交易在中国的蓬勃发展、人工智能在投资领域的应用、交易工具的丰富,使得程序化交易在证券市场越来越受欢迎。程序化交易能够更快地、更有纪律性地执行策略,从而减少冲击成本、降低情绪影响。规模较大的私募,由于具有交易策略多样性、交易市场广泛性、交易品种复杂性等特点,对程序化交易的需求显得尤为迫切。此外,程序化交易中的做市商策略、算法交易策略也对降低市场流动性风险起到积极作用。不过,历史上也曾发生过因为软件bug导致的亏损及个股闪崩等事件,这些风险事件大多是在程序化交易过程的关键点上没能做好检查和风险控制所致。如果能从系统构建开始就有意识的做好防范,使用正确的编程方式,就可以在很大程度上避免风险事件的发生。笔者希望本文能起到抛砖引玉的作用,吸引更多的程序化交易工程师分享经验,共同降低程序化交易的风险。
程序化交易系统的构建
程序化交易是把可量化的分析方法,用计算机编成交易策略进行自动下单交易,程序化交易是量化交易的一部分。根据证监会2015年10月9日公布《证券期货市场程序化交易管理办法(征求意见稿)》里的定义,程序化交易是指通过既定程序或特定软件,自动生成或执行交易指令的交易行为。在设计上一般包括行情网关、交易网关、策略器管理、风控及头寸管理器、监控客户端等若干环节。
1.行情网关
行情网关指的是连接不同的行情接口的适配层。针对股票类的行情网关有宏汇、国泰安、交易所原始行情接口、券商提供的接口等;针对期货的行情网关有CTP、飞马、飞创、飞鼠、盛立金融、艾克朗科等,还有各种境外行情。在行情网关的设计上,要充分考虑期货、期权、股票的兼容性,并且通过一致的行情结构体来统一,同时在行情上要保存好交易所的时间戳和本地时间戳。
行情网关的使用中,要注意以下4个风险点:过期的行情会导致错误的信号;行情断开或者重连会导致信号延迟或者错误;超过涨跌停价格可能产生错误的行情;多路行情源引发的行情重复问题。
要克服以上风险点,就需要在行情网关做好清洗及告警工作。需要注意的是,告警工作很重要,行情延迟可以通过声音报警。
2.交易网关
交易网关和行情网关类似,同样需要对接交易柜台系统,如针对股票类的金证KCXP、恒生UFX、顶点、XTP等柜台;期货类对接的柜台和行情网关类似。
交易网关也采用统一的订单结构,但是在订单层面需要考虑的内容比行情要多得多,如手数Lot、期货的乘数Mulitple、杠杆率Leverage、最小价格变动单位Mintick等。如果是交易跨境期货还需要考虑盎司等非标单位的换算,考虑针对不同品种设置不同的手续费等。这些也应该在设计之初就统一考虑。
在交易网关上,除了下单外,成交回报是另一项重要内容。成交回报信息是策略重要的驱动因素,需要根据成交回报对订单状态进行更新。在更新状态的时候要注意顺序问题,不要让前面的状态覆盖了后面的状态。当订单收到全成、全撤、部撤、废单、拒单等状态时,需要将订单设为IsDone状态。所有IsDone状态的订单,状态形式不能再做改变。
在交易网关接口处理上要注意成交回报的顺序。乱序的成交回报如果没处理好,会导致已终结的订单被中间状态覆盖,比如全成变成部成,会引发资金扣减错误和持仓错误等问题。
图1 行情网关图和交易网关图
3.策略管理器
策略管理器是程序化交易最核心的环节,设计的好坏,直接决定了程序化交易系统的策略扩展性。策略管理器在不同的团队有不同的设计方式,比较建议的设计方式是:一套平台,多种策略。目前常用的策略主要是趋势交易、Alpha策略、套利策略、做市商策略等。有同时买卖数百只股票,对并发有要求的策略,也有对低延迟有高要求的做市商策略。不管是哪种,都需要在策略管理器层面尽可能的统一。在实践中非常重要的理念,就是“用配对交易的理念来设计系统”。如果策略管理器从一开始就能支持配对交易,那么对其他策略的兼容性就会很好。
程序化交易的触发方式一般有两种:信号触发和定时触发。定时触发比较简单,这里重点谈谈信号触发。
信号触发的信号,一般指行情、事件和成交回报,所以在策略管理器的第一关就是消息派发。在消息派发上,主要有复杂事件处理引擎CEP、消息队列MQ、进程内RPC三种。
复杂事件处理引擎CEP。CEP又称复杂事件处理引擎,主流产品有Apama CEP、Sybase CEP等,在国外共同基金使用较多,而对冲基金用的较少。在程序化交易系统里使用CEP的少之又少,最根本的原因在于CEP过于复杂,过于复杂的系统会带来高延迟和更多的不确定性。
消息队列MQ。消息队列的代表是互联网体系的Kafka、RocketMQ等,一般称之为分布式消息中间件,大多用在互联网的高并发场景,在低延迟的程序化交易系统里用的很少。程序化交易多为单体应用,在一台服务器上跑完策略,更看重低延迟而不是分布式和高并发。
进程内RPC。在单台服务器上策略模块的通讯多采用进程间通讯,也有直接在单个进程内通过无锁队列来实现消息派发,不过在高频交易中的实践里,使用mmap做进程间的通讯基本已经达成一致。mmap也是进程间通讯最快的方式。
4.策略调度
首先在策略语言的支持上,倾向于同时支持Python及C++,也有部分团队采用Rust编写策略及交易系统,都是很不错的实践。
在策略的调度上,倾向于一个品种一个策略示例,通过一个策略组来调度和通讯,每个策略对应的品种在配置文件里进行配置。
5.风控及头寸管理器
风控是策略非常重要的环节,分为三个层次:一个是账户层面的风控,包括账户总体盈亏、挂撤比、最大下单数、安全垫等;一个是策略层面的风控,一个账户里可能运行多个策略,每个策略都有自己的风控指标;再就是品种层面的风控,每个品种都设置最大可用资金、最大下单数、未成交订单数等;
图2 订单基础信息图
6.风险指标
在风控环节一定要加上涌浪单检测来防止乌龙指。策略交易系统一般是行情和成交回报驱动策略,这也就决定了不太会有单品种频繁下单或者单品种在市场上暴露过多未成交订单。如果出现上述情况,往往是交易系统本身或者策略本身出现逻辑bug,这时应该做对应的拦截,防止对市场造成冲击。
图3 订单状态图
7.资金扣减
交易系统里一定要设置资金扣减,在资金扣减上要注意市价单的处理。对于市价单,要用涨停价格来扣减,然后需要根据成交回报来更新实际的资金占用,所以这里成交回报的顺序和鲁棒就显得尤其重要。如果收到全成后又收到部成,并且没有仔细处理的话,容易导致资金和持仓错误。有些工程师会不注意策略交易系统的资金控制,认为柜台会做校验不会透支,然而如果有配对交易,比如期现套利,股指端成交后,现货发现透支,将会导致风险敞口的发生。
8.监控客户端
在工作过程中遇到有些私募会因为自动化交易而忽视监控客户端的建设,然而自动化交易的客户端具有非常重要的意义,主要承担的职责有:交易信号监控、交易执行监控、交易风险监控、异常流程处置等。
9.各类信号监控
客户端一个很重要的职责是监控策略的执行情况,包括开平仓信号、订单执行情况、账户浮动盈亏、策略浮动盈亏、每个品种的浮动盈亏、整体风险指标、各种告警信号等,告警信号一般辅以声音。
10.异常流程处置
一般程序化交易系统能达到95%的自动化率,但是依然有5%的情况需要手动干预。程序化交易一般不使用市价单,而是会手动模拟IOC订单,也就是发出去限价单后,延迟几秒发出撤销单,在收到撤单回报后,用更高的限价单去追单,追单一定次数依然没买到,会报警提示手动处理,这里就需要交易员根据行情真实情况手动决定如何操作,所以手动单是非常重要的环节。在手动环节中有些私募会直接使用券商或者期货公司提供的手动交易软件来处理,但是往往策略交易系统并不知道手动交易单,在策略交易系统收到成交回报前会有透支风险,并且自有客户端的手动下单功能也是将订单下到策略交易系统里,统一处理。
客户端还需要处理半自动交易,比如在开仓信号没达到设定值的时候,手动开仓,这个时候也要执行一系列操作,除了信号点不同外,其余的和自动化交易完全相同,也要考虑单腿风险、自动追单等。
开发技巧
策略交易系统的开发模式与其他系统最大的区别在与对异常和不确定性的低容忍,“所有事情都要做到能够被解释”是一条很重要的纪律。在开发或者测试过程中发生的任何异常情况都要仔细分析背后可能存在的原因,往往某个隐藏的bug会导致生产上的亏损,所以不能接受任何“我这里是正常的”“重启试试”等逃避问题的行为。
1.防御式编程
防御式编程是提高软件质量的有益辅助手段,也是《代码大全》里的推荐方式。防御式编程的主要思想是:子程序不因传入错误数据而被破坏,哪怕是由其他子程序产生的错误数据。这种思想将可能出现的错误影响控制在有限的范围内。“假设你面对的是一个残酷的非法数据世界,你要足够鲁棒来保证你不受侵害”。可以通过接口类和内部类将数据隔离,在接口类上做数据的判断和检查,包括行情的判断、文件数据的判断、用户输入的判断等。
2.大量使用断言
编写代码时,我们总是会做出一些假设,断言就用于在代码中捕捉这些假设。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真,使用断言可以创建更稳定、品质更好且不易于出错的代码。断言只出现在Debug版本里,不会影响Release版本的性能。可以使用断言判断指针不为空、使用断言判断收到全成或部撤后不再收到成交回报、使用断言判断在成交回报回来查表时候一定能找到对应订单、使用断言判断行情的时间戳是顺序的等等,在每个逻辑关键点加上断言会极大程度上保障系统的健壮性,也能在重构过程中充满信心。
3.用单元测试用例来覆盖
单元测试也叫白盒测试,通过编写对应的测试代码来验证代码功能的正确性。单元测试是保障代码质量的重要手段,越早进行测试,未来修复bug的代价越少,但是单元测试也是“短期利益与长期利益冲突”的典型。长期来看有好处,短期却需要付出更多的劳动。单元测试有以下三点好处。
尽可能早的测试功能正确性。一个功能越早被测试,就能越早发现问题。当系统构建成功后发现bug,通过日志或者各种调试工具来修正错误的代价极其高昂,而早期通过单元测试代码覆盖,能尽可能的保障每个功能的健壮。
驱动代码编写规范,独立可测。当开始编写单元测试用例的时候,为了更好的测试功能类,会尽可能的把类写的独立可测,会尽可能的降低代码的耦合程度,也会更好的遵循单一职责原则(SRP),总而言之就是通过编写更多的单元测试来驱动代码写得更好。
单元测试是重构的重要保障。单元测试案例随着时间流逝会越积累越多,这也是重构的重要保障。当重构完并且通过所有单元测试,就会对产品的品质更有信心,也会更有信心去重构系统,而缺乏单元代码覆盖的系统重构起来困难重重,重构代价很高。
声明:本文来自金融电子化,版权归作者所有。文章内容仅代表作者独立观点,不代表安全内参立场,转载目的在于传递更多信息。如有侵权,请联系 anquanneican@163.com。