一、实盘引擎是做什么的?
实盘引擎是加载并执行用户策略的一组后端服务,它实时地读取市场数据,捕捉到数据的变动后调用用户在策略中编写的回调逻辑,产生交易信号并通过交易接口下达交易指令至交易所,自动化地完成整个投资决策过程。下面这张图的红色模块就是一个实盘引擎在整个量化策略基础架构中所处的位置。
我们可以形象地做个比喻,假如整个自动化的投资过程是一场音乐剧的话,用户自定义的策略就是乐谱,交易接口就是各个单独的乐器演奏家,而实盘引擎则是整个音乐剧的指挥家。
一个好的实盘引擎,需要满足以下几个条件:
(1)、简单到傻瓜化的用户策略编程接口。也就是说,用户不需要了解底层的数据、交易、账户、时间、日志、边界异常处理等细节,只需要基于实盘引擎提供的简易接口实现自己的投资逻辑即可。所有的复杂的事情,交给实盘引擎去做吧。
(2)、快速的执行效率。假如用户策略是1分钟级别的策略(也就是说每1分钟要执行一次策略逻辑,进行下单),而实盘引擎执行完一个用户回调逻辑需要超过1分钟时间,那显然,这个实盘引擎会慢慢滞后于实盘,从而产生一些错误的执行结果的。当然,用户的策略回调逻辑也需要尽可能简单(复杂的计算还是不要放在这里吧,用户可以预定义好一些中间计算结果,节省回调执行时间),理论上1分钟的策略,用户的回调逻辑执行时间最好不要超过30秒。
(3)、完备、精准、及时的市场数据。用户策略产生的信号都是基于市场数据的,如果实盘引擎暴露给用户的数据不是完备、精准且及时的话,这些交易信号有可能就是错误的。
二、数字货币的用户策略应该长成什么样子?
我们来梳理一下用户策略一般都是怎么运行的。
首先,用户策略一定是在一个大的循环中反复执行的,因为我们并不希望策略跑完一个简单的逻辑之后就退出工作了。
然后,我们来分析一下,在这个大的循环中,策略的逻辑大概是什么样子。一般来讲,策略逻辑一开始,需要获得最新的持仓信息以及最近的交易行情,有些策略甚至需要获得长时间的历史数据;然后,策略逻辑根据历史数据、当前行情和持仓详情等已知的信息,调用各种科学计算、统计分析、机器学习、模式识别等计算库,分析是否有交易信号产生;如果发现有信号产生,就调用策略引擎的交易API下达交易指令。策略引擎将用户的交易指令转化后下达至交易所产生交易委托,并获取订单委托的执行情况,及时地更新到用户的现有持仓列表中去。然后,用户策略就可以在下个循环开始之前,读取到最新的持仓和盈亏情况,并根据该情况,执行下个循环的策略逻辑。
关于如何触发下一个循环,一般有两种方式。第一种方式是由计时器(timer)驱动,计时器按照固定的时间轮询反复执行策略逻辑,直至达到某个退出条件后才退出;另一种方式是行情驱动(data driven),即循环由行情数据的更新来驱动。我推荐用行情驱动的方式来触发下一个循环,因为用户策略的信号都是源于新的数据更新,如果采用计时器驱动,在计时器休眠时间内,可能有大量行情数据被遗漏掉,从而错失很多交易机会。
基于以上分析,提炼出一套完整的用户策略所需的API接口如下:
-
API列表
-
上下文对象context
-
数据类context.data
-
帐号类context.account
-
帐号起始状态context.account_initial
-
时间类context.time
-
订单类context.order
-
日志输出类context.log
-
用户自定义变量context.user_data
-
上下文context对象是用户策略执行过程中用户策略与实盘引擎对接的唯一信息桥梁,包括系统参数读取、价格信息获取、订单操作、交易所帐号信息获取、系统时间计算、用户自定义变量保存、用户自定义日志输出等所有底层功能,都是从这个context对象中获取的。用户直接调用这些暴露出来的对象方法就可以,不用关心对象内部的实现细节。
功能性的函数和字段通常以context的二级引用方式进行调用,例如:
>>> context.data.get_price(count=1) # 获取当前策略品种的历史数据
>>> context.account.huobi_cny_cash # 获取帐号现金余额
数据类函数在context.data的命名空间下,我们在实盘引擎中提供以下数据类的API:
-
context.data.get_price 获取最近历史价格
-
context.data.get_current_price 获取行情最新成交价格
其他类型的API接口,可以直接参考我的实盘引擎源码,秒懂的呢。
基于以上API接口,一个完整的zipline style的比特币交易策略就出炉啦:
有没有似曾相识的感觉?
三、实盘引擎是怎么载入用户策略并生成交易订单的?
整个实盘引擎的设计中,上下文context是核心中的核心,是链接用户策略和实盘引擎的唯一通道。实际上,在用户策略中的参数context,本身就是一个实盘引擎对象。这点可以从BaseLiveStrategyEngine的构造函数中看到。
程序执行入口main.py中:
我们看到,实盘引擎BaseLiveStrategyEngine的构造函数中传入了用户策略SimpleMA,然后我们看看BaseLiveStrategyEngine的构造函数到底是怎么把引擎的API暴露给策略的用户的。
原来,下面这段代码,就实现了向用户策略中注入实盘引擎的对象:self.strat.initialize(self),那么,还有个问题,就是context下面的各级接口(系统参数读取、价格信息获取、订单操作、交易所帐号信息获取、系统时间计算、用户自定义变量保存、用户自定义日志输出等所有底层功能),我们是怎么动态添加到context对象上去的呢?答案在这:
原来在BaseLiveStrategy的构造函数中,我们动态地assign了一系列对象到context的log/data/order/account/time/account_initial的成员变量中去了,难怪context这么神通广大,各种底层功能无所不能! 有了这样的操作,传入用户策略中的context参数,就是具备强大底层功能的实盘引擎对象,这样用户在编写自己的策略时,才可以行云流水,随心所欲~~
四、怎么编写我自己的策略并执行它?
很简单,到userStrategy目录下,参考SimpleMA.py,新建一个策略文件(例如MyOwnStrategy.py),定义好策略的初始化回调函数initialize和每个行情数据周期的回调函数handle_data,就可以尝试着跑起来啦!(需要注意的是,目前本人还没有拿到实时的行情源,context.data.get_price拿到的数据是模拟数据,下周搞定行情源,就可以实盘开干啦,有没有一种马上要赚1个亿的感觉~~)
def initialize(context):
#你自己的策略初始化逻辑在这
def handle_data(context):
#你自己的行情回调函数,发现信号,进行调仓