最近发明者量化微信群内讨论print money的机器人,讨论的非常火热,一个非常古老的策略又重新进入了宽客们的视野:韭菜收割机。print money的机器人交易原理借鉴了韭菜收割机策略,怪自己当时对于韭菜收割机策略没有看太明白,没有理解到位。
所以,又重新认真的看了一遍原版策略,并且看了一遍在发明者量化上的移植版本移植 OKCoin 韭菜收割机。就以发明者量化平台的移植版韭菜收割机策略,剖析该策略,挖掘该策略的思路。以便平台用户学习到这个策略思路。本篇我们更多从策略思路、意图等层面剖析,尽量减少编程相关的枯燥内容。
[移植 OKCoin 韭菜收割机]策略源码:
策略通篇概览
一般拿到一个策略学习,阅读时,首先通篇看一下整体的程序结构。该策略代码并不多,只有不到200行代码,可谓非常精简,并且对于原版的策略还原度很高,基本上是一样的。策略代码运行时从main()函数开始执行,通篇策略代码,除了main(),就是一个名为LeeksReaper()的函数了,LeeksReaper()函数也很好理解,该函数可以理解为韭菜收割机策略逻辑模块(一个对象)的构造函数,简单说LeeksReaper()就是负责构造一个韭菜收割机交易逻辑用的。
关键字:
策略main函数第一行:
var reaper = LeeksReaper(),代码声明了一个局部变量reaper,然后调用LeeksReaper()函数构造了一个策略逻辑对象,赋值给reaper。
策略main函数接下来:
进入一个while死循环,不停的执行reaper对象的处理函数poll(),poll()函数正是交易策略的主要逻辑所在,整个策略程序就开始不停的执行交易逻辑了。至于Sleep(TickInterval)这行很好理解,就是为了控制每次整体交易逻辑执行之后的暂停时间,目的是控制交易逻辑的轮转频率。
剖析LeeksReaper()构造函数
看看LeeksReaper()函数是如何构造一个策略逻辑对象的。LeeksReaper()函数开始,声明了一个空对象,var self = {},在LeeksReaper()函数执行的过程中会逐步对这个空对象增加一些方法,属性,最终完成这个对象的构造,最后返回这个对象(也就是main()函数里面var reaper = LeeksReaper()这一步,返回的对象赋值给了reaper)。
给self对象添加属性
接下来给self添加了很多属性,以下我对每个属性都加以描述,可以快速理解这些属性、变量的用途,意图,方便理解策略,避免看到这一堆代码时,被绕的云里雾里。
给self对象添加方法
给self增加了这些属性之后,开始给self对象添加方法,让这个对象可以做一些工作,具备一些功能。
第一个添加的函数:
updateTrades这个函数的作用是获取一次最新的市场成交数据,并且根据数据做一些计算并记录,提供给策略后续的逻辑中使用。逐行的注释我直接写在上面代码中。对于_.reduce可能没有编程基础的同学会困惑了,这里简单讲下,_.reduce是Underscore.js这个库的函数,FMZJS策略支持了这个库,所以用来迭代计算很方便,Underscore.js资料链接
意思也很简单,例如:
就是把数组[1, 2, 3, 4]中的每个数加起来。回到我们的策略中,就是把trades数组中的每个交易记录数据其中成交量数值累加起来。得出一个最新的成交记录交易量总计。self.vol = 0.7 * self.vol + 0.3 * _.reduce(…),请允许我用…代替那一堆代码。这里不难看出对于self.vol的计算也是加权平均。即最新产生的成交总成交量占权重30%,上一次的加权计算得出的成交量占70%。
这个比例是策略作者人为设定的,可能与观察市场规律有关。至于你问我,万一获取最近成交数据的接口给我返回了重复的旧数据肿么办,那我得出的数据都是错的,还有使用意义么?不用担心,策略设计时是考虑过此问题的,所以代码中便有了
这个判断。可以基于成交记录中成交ID判断,只有ID大于上次记录的ID时才触发累计,或者如果交易所接口不提供ID时,即trade.Id == 0,使用成交记录中的时间戳判断,此时self.lastTradeId储存的就是成交记录的时间戳,而不是ID了。
第二个添加的函数:
接下来看updateOrderBook这个函数,从函数名字面意思就能看出,这个函数作用是更新订单薄。然鹅,可不仅仅只是更新一下订单薄。函数开始调用FMZ的API函数GetDepth()获取当前市场订单薄数据(卖一…卖n,买一…买n),并且把订单薄数据记录在self.orderBook中。接下来判断如果订单薄数据买单、卖单少于3档,就判定无效函数直接返回。
之后,进行了两项数据的计算:
计算提单价格
计算提单价格同样是使用加权平均计算,对于计算买单时,给买一的权重大些为61.8%(0.618),卖一占剩余的权重38.2%(0.382),计算提单卖单价格时则同样,给与卖一价格权重大些。至于为什么是0.618,可能是作者比较喜欢黄金分割比例。至于最后加减的那一点点价格(0.01)是为了略微再向盘口正中央偏移一点。
更新时间序列上订单薄前三档加权平均价格
对于订单薄前三档买单、卖单价格做加权平均计算,第一档权重0.7,第二档权重0.2,第三档权重0.1。可能有的同学会说:“诶,不对呀,代码中木有0.7,0.2,0.1呀”,我们把计算展开看下:
到这里可以看到,最后计算出的价格实际上是反应当前市场中盘口三档中位的价格位置。然后用这个算出的价格,更新self.prices数组,踢出一个最旧的数据(通过shift()函数),更新进去一个最新的数据(通过push()函数,shift、push函数都是JS语言数组对象的方法,具体可以查询JS资料)。从而形成self.prices数组是一个有时间序列顺序的数据流。
咳咳,喝口水,先剖析到这里,我们下期再见~