【核心技术】高精回测之二因子工厂模式下的研究,回测与调试

wei-jianan/核心开发

有向无环图与Actor模型

先不考虑另类数据或者基本面数据,也别管所谓各路机构路演时候讲的什么基本们量价因子三七开之类的万年cliche。一个量化策略干的事儿,抽象来看还是接受/响应信息,与下撤单。从软件工程的角度看,就要做到接口足够的简单,与可复用。无论国内外的交易所,有无柜台,通常可见的并且绝对有价值的信息,就那么几种类型,Tick,切面,逐笔委托与成交,增量深度等等。因此响应这些数据结构的接口必不可少,这些自不必多说。

但存在一些其他的情况: 

  1. 基本面数据,其数据结构有可能非常多样,甚至有可能不是那么结构化,需要许多琐细的处理。
  2. 逐笔数据固然信息量最大,但是对不少统计出身的,纯粹的量化研究员来说1 )理解困难。2)处理逐笔数据的码力可能不足3)逐笔数据不是等间距的时间序列,需要采样,否则很难使用常规的统计学工具 4) 技术管理需求。研究代码与实盘代码的对齐问题。

在量化机构工作的人想必没少见过这样的代码。 low, high, open, close, volume  = pd.read_csv(“low.csv”), pd.read_csv(“high.csv”)…..  然后就是一通apply,rolling制造出一堆因子。 至于这些.csv 从何处来,有到何处去。八仙过海,各显神通吧。

这些ochlv也就是常说的K线Bar数据,最广为使用,当然也有对应的volume bar数据等等。任何程序员我想都不会希望自己的同事们,各路量化研究员/交易员/策略师,为了采样出常规的的ochlv,自己按照自己的理解, 在Strategy::on_行情数据(data)下 写写一堆千奇百怪的代码吧。最优的实现当然是流式算法, 复杂度O(1),但有可能出现各种其他情况,最糟糕的情况为了计算当前的ochlv,制造出 O(n!) 的复杂度,这并非骇人听闻,而是亲眼所见。

因此两种方案:

  1. 提供一个统一的BarGenerator库,大家各自在Strategy::on_行情数据(data)下使用,保证了合成数数据的统一性。
  2. 提出一个兼有行情与策略进程的一个抽象—算子,该进程即可以订阅行情,同时可以发布合成数据,同时还可以订阅其他算子。

方案1每个进程根据行情维护一组合成数据,看似浪费,实际上并没有那么紧要,绝对是一个可行的方案。 具体的我们后文阐释。我们先讲方案2。

因子工厂模式有其必要性,1)随着竞争激烈,单一因子的有效性降低到无法打平交易成本,因此需要更多的因子更强的预测 2) 整个交易流程随着aum的增加,复杂度也相应增加,必然产生挖掘,组合,执行等等分工,专人专事才能提高效率,降低上手成本 3) 知识产权保护,不多讲了。

直接举例了,如图,尽可能的简化后,operator1 复杂逐笔数据的高频因子,有可能内部要做很多重建orderbook的操作,因此直接订阅行情; operator2对逐比数据采样后,将类似ochlv之类的合成数据发布,提供给operator3,可以计算各种均线,波动率,rsi之类的或复杂或简单的指标,归一化成因子,作为合成数据发布。strategy1 组合operator1,operator3 的因子,获得最终的仓位信号,下单。

当然,随着复杂度的提升,我们可以将所有的算子分门别类。总体上来说可以归类为:

  • 自定义数据算子–> 自定义行情数据;
  • 因子算子–>因子; 
  • 因子组合/预测算子—> 目标仓位/开平信号。 最终策略根据开平信号或者目标持仓完成算法执行(algo )的任务。

如果按照上文提出的算子来实现,每个算子对象,各自接受上游的消息,计算,发出消息供下游使用。实盘怎么做都可以,通信开个socket,zmp,redis,nnmsg广播就完了,下游算子收到算收到,收不到算球。通常问题不大,可靠性不低,这些中间件起码安全生产5个9。至于延迟嘛,都因子工厂了机器学习模型预测了,别那么在意延迟啦,总体满意。 问题是回测怎么办?回测需要多个团队分开运行,下游模型团队,依赖上游因子团队的因子,因子团队又依赖更上游的自定义数据。此外分工很重要的一点是,各个步骤环节,应当有独立评估工作绩效的能力。

于是又回到了,研究过程当中,因子研究员,各自对着原始数据,维护着长达20行的SQL,胆战心惊的解析出一个csv。每天对着pandas的文档冥思苦想,薅尽了头发。模型研究员,对着一个巨大的表,一行又一行不明所以得数,一遍又一遍的调参,翻遍了sklearn和xgboost文档,提交任务,等待夏普率的落地。1和2之间埋藏了多少激动又不安的等待。 

接下来,大家招呼来自己的dev兄弟,坐在一起结对编程,一点一点把pandas代码转写成c++。无数多来自人的争吵 和编译器的聒噪。最终策略上线了,奇怪?亏钱了! dev说researcher你这是过拟合,researcher说一定是dev代码没写好…… 本可不必如此。

我们还是希望 1) 回测和实盘用一套代码 2) 各个算子,模型,组合优化研究院可以分别工作互不打扰。3) 模型依赖因子,因子的变动对模型的影响立竿见影,因此要版本管理要简便。由此引出了 4)最终的因子加策略,得做到代码一旦更改,回测可以不依赖之前复杂的数据管理,一条全新的pnl线就可以画出来。具体点,如果某个因子研究员新出了,或者变更了已有的某个因子算子。那么一方面,可以按照传统的研究方式,将该因子和其他因子做一下共线性|相关性分析。但更直接的方式,无疑是直接从因子,组合,执行从头来一遍,获得一条全新的回测pnl线更有说服力。

并行计算理论当中有一个经典的Actor也支持前文所述状态机抽象。简单介绍下。

Actor 的核心思想是 独立维护隔离状态,并基于消息传递实现异步通信。

一个 actor 定义为一个计算单元。每个 Actor 包含了存储、通信、计算等能力,彼此之间并发的运行。

每个 actor 持有一个邮箱(mailbox),本质上是一个队列,用于存储消息。

每个 actor 可以发送消息至任何 actor。

每个 actor 可以通过处理消息来更新内部状态,

看起来Actor可以实现状态机的抽象,满足我们整个系统实盘回放回测的需求。

在分布式系统中,Actor模型通常会遇到现实的约束—计算资源的有限性。Actor毕竟是一个抽象的计算单元,是理论上无限的,而物理实体往往是有限的。 这就涉及到计算的调度。而由于Actor之间往往存在消息的依赖关系。一个Actor的mailbox当中如果不存在内容,当然可以挂起,将物理的计算资源让出。万幸的是在量化这个场景, 把算子Actor视作图(此图是组合数学里的图)中的节点,上下游的通信视作表达依赖关系的有向边。我们可以清晰的构造出一个有向”无环”图。接下来,根据依赖关系,对该图做拓扑排序(由某个集合上的一个偏序得到该集合上的一个全序)。按照排序,在一个物理计算资源上顺序执行每一个Actor,得出的结果和并行执行Actor得到的结果理论上就是一致的。amazing!

还是举个例子,实盘中 market data, operator1, operator2, operator3, startegy1, strategy2, trade engine都是独立进程,并行运行的actor。回测时, market data已有,trade engine是撮合机,两者不考虑。 那么想要做到和实盘一致的运行结果,应该怎么调度呢? 最简单的方式是,顺序执行 operator1-> operator2 -> operator3 -> startegy1 -> strategy2,该序与 operator2-> operator1 -> operator3 -> startegy2 -> strategy1 等价。实现简单,且计算资源只需要1份即可。这个功能我们称之为—图回测。

留一个思考题,如果因子工厂的因子算子实在太多,从头顺序运行一遍过于缓慢。 方法1 自然是做好版本管理和因子数据缓存。但总归还是希望能够重头全量运行一次更为安心,那应当如何充分并行的快速完成图回测呢?

于是因子工厂模式下的回测我们搞定了。哦 差点忘了。 因子研究工作,当中重要的一环是评估因子本身的质量。既然辛辛苦苦在算子内以实盘回测统一的api完成了因子计算的代码实现,那怎么评估呢?这个我会放在另一章讲。