作者:CoinEx全球大使X
设计一个简单的交易风控系统
Octopus Trading是一个专注于数字货币交易的团队,在入行两年多的时间里,我们逐渐打磨交易系统,经历了好几轮市场的剧烈波动。成熟的风控系统是所有交易团队的标配,在这里我们分享一些我们的经验,或许能对一些新入行的朋友产生一些启发。
交易类型
在设计风控系统之前,首先要明白的是,你做的是什么类型的交易,你最大的风险在哪里?举个例子来说,如果你做的是套利,那你基本上不会持有一个敞口,所以当行情波动剧烈的时候,你只要保证你计算的持仓是正确的前提下,把订单撤掉,等风浪过去就可以了。反之,如果你做的是一个要持仓的策略,那在剧烈波动的时候,你要做的是不惜一切代价让你的策略的订单执行掉,错过了时机就是损失或者少赚。
不同的交易类型的风控策略肯定是不相同的,本文之后的系统设计均以套利策略为前提,简述如何构建一个可以给套利策略使用的风控系统。
敞口
套利策略的核心是,要尽量保持自己的敞口是0,即所有的交易都是对冲的。所以正确计算自己的持仓是关键的一步。一般我们都可以使用api从交易所获取实时的持仓,但是在一些特殊的情况下,交易所给你的持仓可能是错误的,或者延迟的。这样就会产生一个问题,在错误计算了持仓的情况下,程序错误地进行了对冲,而放飞的敞口随着价格变化,累积出了更多的亏损(当然有时候也可以是利润,但对风控系统来说,我们只考虑坏的情况)
所以,在计算持仓的时候,要考虑以下两种情况:
接口异常
- 这里的异常可能有多种情况,比如交易所维护升级,也有可能是api调用过于频繁被封。其表现形式是你无法通过接口获取到最新的持仓(余额)。在这种情况下,应该停止下单并且撤销现有挂单,停止交易一段时间,等待异常解除后继续。
- 另外一个需要小心的情况是,如果获取到一个"异常"的持仓,要不要采纳:比如交易所突然告诉你,你的持仓变0了,在没有成交的情况下,通常来说这是不可能发生的,这种情况下,丢掉错误的报文,告警,人工查看问题。
持仓延迟
- 持仓的变化是由订单引发的,当交易所的清算发生故障的时候,会发生明明订单已经成交,但是仓位没有变化的问题。订单的成交信息交易所是会有推送的,在这里,我们只需要在本地重新计算一下应该有多少持仓,如果和交易所返回的数据差距比较大,就应该停机检查。
此外,策略应该有逻辑在发现敞口时及时补回。通常的做法是定时补回一些,慢慢补,避免持仓计算出问题后,来回补单造成损失。按经验来说,一般每15-30秒补回一单即可。
挂单追踪
不管是什么类型的策略,都必须追踪自己的下单,直到交易所告知订单成交/取消。以下列出一些常见的异常情况,看看你的策略会在哪些情况下"中招"?
行情故障,你读到的行情比实际的延迟5秒以上,对着该行情的下单都不会成交。这些未成交的订单累积起来,是否会形成一个单边的敞口?
下单被拒,比如交易所升级了下单的精度参数,导致你在某个交易对上的下单请求全部都是被拒绝,这种情况下是否会导致该币种上的对冲不能正确执行?
下单超时,你下的挂单都因为超时没有收到回复,但实际上订单都下成功了,只是你没有收到订单ID,过一段时间后你会意外地发现有一些订单成交了,或者你的持仓发生了意外的改变。
ws推送延迟,你使用ws推送来订阅订单的更新状态,但是ws的推送断开/延迟了,你更新不到订单的状态,一些订单成交了你无法得知,应该如何容错?
一般来说,我们的策略是控制策略的总下单量,例如我们定义一个下单的通道,在每个通道上,我们只允许存在一个订单,如果前一个订单没有正确地清算,新的订单也不会继续发出,这样,我们策略持有的风险总量,就是我们在每个通道上能够下单的总金额,这样可以防止在异常情况下反复下单的最坏情况发生。
另外,一般来说下单的金额越高,承担的风险也就越高,所以在系统有部分订单追踪出现问题的时候,减少下单金额或者限制策略在大额订单通道上下单,都会有助于控制风险。
故障容错
如果你的策略运行在一些不太稳定的交易所上时,当交易所故障,或者维护时,策略能否优雅地容错,就成为了风控系统的重中之重。我们有过好几次回撤都是和交易所故障有关,可以说是花重金买来的经验教训也不为过。
有关故障容错,建议使用"白名单"法来处理,即,任何未知的错误,默认都会引发熔断,然后上线一段时间,把一些已知的,并不严重的异常归类进白名单,允许其偶尔发生但不引发熔断,从而提高系统运转效率。
这里列举了一些常见的异常(或者说是错误):
订单被拒绝
下单超时
长时间收不到订单/账号推送
长时间收不到行情推送
IP Ban
持仓错误(例如你发起了子母账号的划转,一边扣了钱但另一边没加)
应对以上异常的核心原则是重试、熔断、告警。例如订单因为余额不足被拒绝,这个情况偶尔发生,相对来说比较正常,可以放过。但是如果一分钟内连续出现多次,就应当告警并熔断,人工检查是否有变更。在一些很难有人值守的时刻,比如凌晨四五点,使用熔断机制避免连续大额的亏损。
熔断
熔断机制的设计简单来说就是一个计数器外加一个延时装置,即一个错误第一次发生的时候,熔断系统1秒,第n次发生的时候熔断n*n秒,期间如果恢复正常,则清空计数器。
一般来说,熔断只需要针对下单进行即可,其他需要熔断的组件例如websocket连接的行情或者订单推送。
熔断机制的作用是当异常发生时,避免因重复重试导致的大量损失,或者引发交易所的IP Ban。
稍稍高级一些的熔断策略包括一个慢启动的机制,例如在熔断结束时只允许一些小额的订单操作,直到成功率超过阈值后再允许下更大金额的订单。
日志
日志是风控系统失效后,事后复盘的重要一环,日志能帮助你检视系统在风险事件发生的时刻,你的系统是如何被异常事件绕开了风控。
如果把交易所和你的策略想成一个调用链上下游的两个系统的话,日志系统所需要记录的基本由以下几个部分组成:行情推送、订单指令、订单推送。行情推送和订单推送都是交易所提供的信息,需要记录原始的报文以及客户端收到推送的时间戳,方便定位问题。订单指令是策略发出的信号,同样地也需要记录时间戳。通过以上三个日志的对比,我们就可以还原一个订单完整的决策和生命周期。
把每一个事件的日志,和相关的订单信息都打印到一行里,可以使我们方便的从日志中提取出有效的信息。使用内部的订单ID作为串联,通过该ID可以找到订单生命周期中的所有日志,这些和互联网企业广泛使用的日志系统设计其实并无多大差别。
日志系统的关键部分在于存储和检索,建议使用ELK或者TICK全家桶管理日志。简单的风控系统只需要保留七天的日志就可以了,一个问题如果超过七天都没有人去检查的话,那应该也不是什么大问题。
故障复盘
最后一个重中之重就是故障的复盘了,当异常发生时,通常来说伴随着亏损。通过日志系统,我们要还原故障发生时系统收到的信号,并且检查其中是否有不合理的逻辑。比如:交易所的行情发生了一些延时,而策略未能及时熔断,导致下单滑点增加,引发亏损。那么我们就要检查出问题当时的行情,看看有没有什么线索能识别出行情已经发生了延迟。例如,我已经下了订单,我预期它会在订单簿上拿走一部分的流动性,但推送过来的行情却没有这部分的信息,于是我便知道这个订单还没能及时进入撮合引擎,在此通道上应该进行熔断。
有些时候,交易所给到的信息可能无法让你准确地区分出异常情况。这个时候,你需要整理好日志,和交易所的技术支持做一下沟通,让他们明白客户端发生的异常情况,也许他们可以新增一些辅助信息,帮助你在下一次定位到此类问题。比如行情延时的问题,现在交易所推送时基本都会带上撮合的时间戳,如果策略发现行情的时间戳大幅落后时,可及时熔断避免风险。
小结
以上就是一个简易的交易风控系统了。我们的系统便是在以上基础上,根据一次次的故障和风险事件,加上各种补丁拼凑而成。当然,除了交易执行风险以外,还有类似行情插针、资产划转失效等其他系统外的风险,这些都需要构建在交易系统的外围,通过控制信号来指示交易系统停止交易,撤销订单,进行风险规避。另外,如果条件允许的话,写一个App,在手机上订阅风控事件,控制交易开关,可以让你更进一步地沉迷在数字货币的交易世界里。欢迎入坑。