这里主要讲的是,中低频交易策略的程序化实盘代码实现。

策略

一个量化交易策略的诞生,一般是经由对市场的观察了解,产生策略 idea,然后设计策略的具体细节,再回测多品种的数据,从而获取、验证具体参数数值的效果。如果效果可以,具有普适性,那么下一步就是上实盘小资金测试。样本外实盘可行,最终就可以加大资金并长期运行。

为了防杠精,说明一下,当然也有其他方法产生策略,比如数据挖掘,挖呀挖呀挖因子。还有机器学习、强化学习这类自动生成因子。不过,这类黑盒方法,在策略回撤期很难坚持运行,因为你不知道这些挖出来的因子,什么时候失效,为什么失效。而做量化交易,最重要的一点就是要坚持实盘,行情不好,你可以缩仓,但是不能停机。没有人能知道大行情什么时候爆发。错过了,你之前的回撤就是白瞎。

一个类比,策略的设计、回测,就相当于《孙子兵法》上说的“庙算”,“夫未战而庙算胜者,得算多也;未战而庙算不胜者,得算少也。多算胜,少算不胜,而况于无算乎!”

所以,交易策略在最初的设计上就得谋划好,计算周密,不然还没实盘其实就已经输了。这一步是最难的,后面的实盘代码虽然也不容易,但不过大部分只是繁琐而已,多花花时间精力,总能做好的。实盘的关键还是在风控。

交易策略,或者叫交易规则,就是让我们 “做正确的事”,具体的实盘落地,就是“正确地做事”。很多时候,知道正确的方向,远远要比走完路程要难。就如那个维修设备的段子,画一条线收费一千块。画线本身只值 1 块,但是知道在哪里画收费 999。

策略+实盘共同组成交易系统。关于交易系统的设计,可以参看之前这篇:完备交易系统的七要素。这里的实盘其实就是交易系统的后面 4 步“进出盈损”的具体操作。前面 3 步,在策略设计完成时,就基本已经确定了。

但是,就算策略不错,如果实盘执行不到位,那么效果也会大打折扣,甚至导致停掉本来还不错的潜力策略。研究出一个好策略是很不容易的事情,不要倒在执行这一关。好的想法,要最终稳妥落地才行。

问题

1. 突然的行情

这种是有 bar 内信号的策略才会遇到。bar 内就是没有采用某根 k 线的关仓价或者开仓价,而是盘中的价格来触发信号。

这里以几天前实盘遇到的一个问题为例。一个交易市场的微观例子。在币圈挺普遍的。

下图是前几天(2023.7.10 17:21) BNB 永续合约的 10 秒 k 线(就是一根 bar 是 10 秒内的交易数据聚合出 OHLCV 的意思)。

可以看到,那根大阳线振幅 3.53%,成交了大概 3 千万刀左右。10 秒内完成。在这次行情启动前,走势几乎毫无波澜。

再看下图是当时的 1 秒 k 线,可以得到更多的细节。

基本上 4 秒左右就完全涨到位了。第 1 秒涨了大概2 个多点。这些,基本都是那些高频策略(事件驱动,高频趋势,做市等)还有可能事先布置的算法单之类的博弈结果。

再来看 BNB 的 4 小时 k 线。最大那根阳线就是当时消息出来的时间段。振幅 4.05%。

对比一下这几个周期的 k 线就可以看出,这种消息驱动的波动(这次消息为币安新IEO,ARKM),一般都是瞬间完成的。4 小时的涨幅居然和 10 秒相差并不太多,而真正的启动,也就 4 秒左右。这种战场,是属于那些武装到牙齿的高频策略的。但是,它也可能会影响到中低频策略,造成更大的滑点。

这里不研究高频策略,对于中低频,说明什么问题呢?我想到的主要有两点:

1. 有的趋势爆发非常突然,而且是在很短时间内完成,所以不能随便暂停策略。在币圈的实盘要保证 24 小时在线,特别是有仓位的情况下,不能掉线。

2. 实盘的滑点有可能达到 2 个百分点级别。看上面的 1 秒 k 线,如果你的买入信号是以某个价格为基准,这个价格又正好在大 k 线的下部出来,那么不管怎样,你进场后的价格大概都可能是滑出 2~3 个百分点。最差能到 4 个百分点,那些在针尖成交的买单就是。不过,如果你是卖单,那么就会获取超出预期的利润。但是,根据墨菲法则,落下的面包总是涂着黄油的一面着地。长期来看,大概率是滑点更多。

不过,好的是,这种情况发生的次数有限。只要策略没问题,滑点大一点,只是盈利回吐而已,不影响长期正收益。就当多承受了一次震荡磨损。

缓解的一个办法,就是采用 websocket 获取最新价格,文档说 250 毫秒更新一次,但是有的时候其实更新更快,这样就有可能在第一时间反应了。websocket 的另外一个优点,是不占用交易所 Restful API 的频率限制。这个后面有详细提到。

2. 整点行情

这种是很多趋势策略会遇到,因为大都是采用开关仓价格来计算进出场的信号。那么整点一过,各自纷纷启动。

整点时刻,特别是 8 的倍数,因为无论你的策略是什么周期,这三个点应该都遇到公约数时间了,可能大家的自动化策略都会有动作,高中低频挤在一起产生各自的信号,互为对手盘。

另外,这个时候,交易所还需要结算交割。虽然永续合约不交割,但是要计算交割资金费,可能还有其他操作。在 0/8/16 这三个整点之后的开始 6 秒钟左右,币安的 websocket 是不推送 k 线信息的,它停了!就问你怎么办?现实情况,很多时候就是没有办法。

更严重的是,行情会共振,特别是下跌行情,恐慌的传染性更强烈。交易所的服务器更忙了,很多行情激烈的小币直接就宕机,下单是下不进去的,全是 1001 错误。等你忙不迭地下单成功了,可能针尖上成交的那些单子就是你的。

所以,滑点真的是避免不了的,都是命,接受它吧,过分优化代码也没太大用处。就如前面说的,就当多止损了一次。

不过,也有一些小技巧分享。

可以在整点前 n 秒提前计算信号跑路,那个时候还没开始拥挤,因为可能大多数自动化策略都是等到整点 k 线关仓价出来之后,才开始计算信号的。不过这样操作带来的问题就是,可能会产生错误信号,例如价格马上又回去了,本来不该有信号的,false positive 发生了。这时就看你的取舍了,要不要这么干。不过,可以再加一定阈值,超过了才触发,这样防止短时间价格回归造成的误发信号。滑点能减少一点是一点吧。

还有就是采用时间 offset 或者 shift k 线这类偏移信号的方法,不在整点产生信号,不跟别人卷到一起去,更好地避开拥挤。还有就是 TWAP 等等方式。

不过,这些都需要一些技巧,会加大实盘代码的难度。

实盘

初级的中低频 CTA 策略,一般来说实盘代码非常简单。一个品种只有一个策略,而且没有加减仓,没有交叉信息混淆在一起。那么本地代码可以不维护任何状态,因为交易所帮你记录了一切。是否有仓位,保证金多少,单子是否成交等等。因为是中低频,需要的时候可以随时查询交易所的账户信息,而且这个是最即时准确的。免去了本地使用数据库之类记录交易状态的麻烦。

这种简单策略其实也是可行的,但是问题就是可能回撤会大一点,不够分散,风险高。单品种可能一直没行情,震荡回撤不见回头,或者遇到极端行情,资金曲线上又会来一个大坑。严重点,扛不住就会被吓得停策略,轻一点,交易体验会差很多。注意,这些说的都是在合理仓位的条件下。不然仓位太轻,回撤是小了,但是收益也降低了,盈亏同源,机会错过也可惜;太重,就是错误,迟早要完蛋的。

不过,建议新手可以先搞一个这种策略实盘跑起来。实践才能快速提高自己的交易水平。但是注意一定要轻仓,然后杠杆要低一点,最好是不用杠杠,这样可以坚持更长时间。

如果要做得更专业一点,降低单点风险,资金曲线更平滑,实盘的时候更少担心(如前所述,合理仓位下,这点其实挺重要,因为更容易坚持运行策略,等到风来)一般都是多品种多策略多参数的模式,然后还有加减仓。不一定是在一个策略上的加减仓,也可以是多个子策略实现加减仓的效果。不过,这样的话,策略就会比较拥挤,如果还想尽量降低开平仓滑点,那么代码复杂度就会直线上升。

下面说说这种多品种多策略多参数模式带来的难题。

难点

1. API 频率限制

多策略的第一个难题就是 API 频率限制。品种多了,策略多了,为了获得即时的价格、仓位、下单等信息,就得不停去访问交易所。像前面讲的,频率低了,慢了,那么获得价格就可能不是最新的,滑点就可能大不少。

交易所的服务器资源有限,那么就产生了 API 的访问频率限制。币安的限制是每分钟 2400,然后还有 10 秒的限制,然后还有其他所谓机器学习探测恶意行为的模式,反正就是太频繁肯定不行,至于怎么不行,交易所说了算,这种你懂的,没有定式。

虽然说是 2400,但是我用并发随便测试了一下,如果是请求 k 线,一次就算是最短的 99 根(API 权重为 1)那么,一起请求 75 个币左右就会被 ban 几分钟。所以,不能轻易去挑战极限的。

违反了,就是收到 429, 418 错误,IP 被封禁几分钟到几天,这个时候,如果你有仓位就惨了(下单,特别是出清已有仓位不受限制,但是你没有价格信息了,除非还有 websocket)

这个问题的解决,就是使用 websocket 获取数据,这样交易所主动推过来,时间及时又不占用 API。可以减少一些滑点。带来的问题,就是你得再维护一套基于 websocket 的行情中心。实盘代码难度上升了,变复杂了。要 24 小时不掉线,断线了要及时自动重连等等。

2. 策略状态

前面说了,策略多了,复杂了,就不得不记录每个策略的状态,不然交易所的仓位也不知道是哪个策略开的,每个开的多少之类。而且后面要复盘的话,也得记录更多数据。

这个时候就得引入数据库了(当然,用文件记录也行,一个意思)。

引入了数据库,就带来数据库的那些问题。不过这和其他行业的软件开发类似,没有新意。特别是电商行业,因为都是资金余额,订单管理这些类似的东西,没有经验的可以看看这类书籍文章。

要注意的就是,很多步骤要达到类似数据库事务的原子化操作,就是一组操作要么就是全部成功,一旦某一步失败,就得什么都不干,回滚到最初状态。

然后,因为毕竟是真金白银的交易,对于下单信息的数据库操作,最好是采用 4 个隔离级别的最高级,串行化 Serializable,杜绝所有脏读、不可重复读、幻读的可能性。也就是不采用任何并发,多线程,甚至异步去读写数据库的关键信息,所有操作在一个线程内完成。实现操作的冥等。

不要轻易去挑战并发编程。这种系统出了问题大概都无从下手,属于软件开发里最难发现的 bug。能把这类活完成得干净利落的,都是 5 万+月薪级别的程序员。

我能想到的不多的采用多线程或者异步(多线程和异步,建议采用多线程,因为异步在 Python 里就跟传染病似的,一旦用了,一条调用链上都得用它才行)确实有益处的地方,就是发送钉钉告警信号之类。因为钉钉服务在中国,而币圈交易所服务器又都在海外,你的代码服务器应该布置在海外,更靠近交易所,所以发送钉钉,有时候要等好几秒才返回,有时候甚至阻塞十几秒都有。

最后

其实很多事情做不到也不要紧。只要有接受大一点滑点的觉悟,也就是可能吐出差不多 1/5 甚至更多的利润出来。重要的还是策略,收益正期望,能够不停机一直运行才是王道。

总之,实盘的第一要务是持续稳定执行策略的既定逻辑。滑点是交易的一部分,尽力减少就行了,只要不伤筋动骨。

一流策略,2、3 流实盘实现,问题不大。但是如果策略不过关,顶级实盘代码也无济于事,该亏也还是得亏。所以光是代码写得好,是做不好量化交易的。策略好,实盘代码也健壮,当然是最好的。不过,在时间有限的情况下,要把主要精力放在策略上。

To be continued