【核心技术】高精回测之一逐笔回测的原理

wei-jianan/核心开发

状态机的抽象

首先介绍状态机这个抽象。抽象之为抽象就在于,其可以映射到诸多的具体的领域问题中。熟悉了这些工具就相当于把握了诸多领域问题的通解。把具体的领域问题形式化以后,找到相应的抽象工具,就可以直接利用现成的,学术界多年积累的成果。

我们描述这样一个具体的场景。从面向对象的角度看,通常一个策略实例,在软件层面的设计是观察者模式。 策略订阅了行情与交易,同时能够接收到各种类型的行情数据与 交易和成交回报数据。这种接收+响应的对应的设计采用的是异步/回调风格的api ,如on_tick 、on_entrust,、on_order,、on_trade 等等的回调函数。不论是用虚函数还是CRTP的方式,总归是模板模式 (当然,量化还存在一种主动型/同步风格的API,之后再聊)。 运行时的假设是每一个回调函数都会在相应的新数据收到时被调用。 被调用时,接收到的各种类型的数据被视为 输入或者事件, 发生的各种计算改变的是策略实例内部的诸多状态。 由此就形成了一个输入/事件序列与状态序列。

一个确定性的,无终止的,状态机由构成。其中:

表示输入/事件集合;

表示非空的状态集合;

表示初始状态;

表示状态转移函数:  在这个问题中是确定性(deterministic)的,  但如果是非确定性的也可以用来表达。

我们定义一个功能—回放。 具体的场景是,量化策略收了一天的行情,下了一天的单,也收了一天的订单成交回报。 这些作为时间/输入的 订单回报与行情数据,在实盘过程中通通落盘,成为输入/事件序列。 希望能够通过回放一整个输入/事件序列来观察,调试整个策略的内部状态。尤其是极其看重低延迟的高频类策略,实盘时为了追求极限可以不打log,那么程序在预期外的行为只能通过盘后回放来完成。

在这个功能中。将所有行情和订单回报数据定义为,策略内部状态定义为, 策略初始化时的状态定义为。  那么收行情,算因子,生成目标仓位,下单,接受订单回报,维护仓位管理订单等等所有操作写成的代码,通通可以就可以归于状态转移函数 (通常策略的实现是确定性的)。 于是我们完成了回放。这个问题的形式化,可以接使用分布式系统领域大名鼎鼎的复制状态机(Replicated State Machine)— 当初始状态给定,为确定性的函数, 已知输入/事序列, 可以推出状态序列。 

一个恼人的回放功能我们就算理论上解决了。在一个量化系统/平台/框架/库中,如果能够做到实盘数据全部落盘,并可以顺序重新读取,那么盘后回放功能就可以实现。从此,量化就不再因为实盘记录日志影响延迟,,少打了日志,盘后盯着预期外的程序发疯薅完了头发不知如何是好的尴尬了。 哪个时间点出了问题去哪里,吼不吼呀?当然,日志是个极端的例子,真的打了日志未见得对延迟的影响就有那么紧要,但能够把调试器挂进策略中,盘后调试,直接定位到不符合预期的代码位置,总归是有一定吸引力的,不是吗?

以上是回放这个场景下我们对状态,以及输入/事件的定义。 那么高精逐笔回测场景呢? 回测的目的是,让策略在历史中运行一遍,观察收益情况。那么策略的订单回报数据来自于策略根据历史行情的下撤单情况,因此订单回报数据并非一成不变的,而是需要一个虚拟的交易所,根据策略的下单撤单情况在回测运行时,生成订单回报与成交回报。

形式化如下策略的下单来自逐笔行情数据定义为, 策略内部状态 + 虚拟交易所的撮合机状态定义为, 状态转移包含了,通常是确定性的策略实现,以及有可能是非确定性的自定义虚拟交易所撮合机的实现。必然会有人好奇虚拟撮合机内部的状态转移如何实现,都有哪些问题要解决,这个我们后文再说。

好了,形式化清晰了,抽象也清楚了,该写代码了。优秀的开发者,仰望星空(抬头看路),脚踏实地(低头拉车)。