How to dissect cabbage harvesters (1)

Author: The Little Dream, Created: 2020-11-12 22:11:32, Updated: 2023-09-26 21:04:43

img

How to analyze cabbage harvesters

Recent discussions in the inventors' quantitative WeChat communityprint moneyIn a very heated discussion, a very old tactic has re-entered the eyes of the broad-minded:The cabbage harvesterprint moneyThe robot's trading principle was based on the strategy of the cabbage harvester, and he blamed himself for not being clear about the cabbage harvester strategy at the time. So, he re-examined the original strategy and looked at the transplanted version quantified by the inventor.Transplanting OKCoin to the cabbage harvesterI'm not going to lie. The idea is to use the inventor to quantify the strategy of the platform's ported version of the celery harvester, analyze the strategy, and mine the idea for the strategy; so that the platform users can learn the strategy idea. In this article, we analyze more from a strategic perspective, intentions and so on, trying to minimize the boring content related to programming.

[Transport OKCoin to cabbage harvester] Strategy source code:

function LeeksReaper() {
    var self = {}
    self.numTick = 0
    self.lastTradeId = 0
    self.vol = 0
    self.askPrice = 0
    self.bidPrice = 0
    self.orderBook = {Asks:[], Bids:[]}
    self.prices = []
    self.tradeOrderId = 0
    self.p = 0.5
    self.account = null
    self.preCalc = 0
    self.preNet = 0

    self.updateTrades = function() {
        var trades = _C(exchange.GetTrades)
        if (self.prices.length == 0) {
            while (trades.length == 0) {
                trades = trades.concat(_C(exchange.GetTrades))
            }
            for (var i = 0; i < 15; i++) {
                self.prices[i] = trades[trades.length - 1].Price
            }
        }
        self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {
            // Huobi not support trade.Id
            if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
                self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId)
                mem += trade.Amount
            }
            return mem
        }, 0)

    }
    self.updateOrderBook = function() {
        var orderBook = _C(exchange.GetDepth)
        self.orderBook = orderBook
        if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) {
            return
        }
        self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01
        self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01
        self.prices.shift()
        self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 +
            (orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 +
            (orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05))
    }
    self.balanceAccount = function() {
        var account = exchange.GetAccount()
        if (!account) {
            return
        }
        self.account = account
        var now = new Date().getTime()
        if (self.orderBook.Bids.length > 0 && now - self.preCalc > (CalcNetInterval * 1000)) {
            self.preCalc = now
            var net = _N(account.Balance + account.FrozenBalance + self.orderBook.Bids[0].Price * (account.Stocks + account.FrozenStocks))
            if (net != self.preNet) {
                self.preNet = net
                LogProfit(net)
            }
        }
        self.btc = account.Stocks
        self.cny = account.Balance
        self.p = self.btc * self.prices[self.prices.length-1] / (self.btc * self.prices[self.prices.length-1] + self.cny)
        var balanced = false
        
        if (self.p < 0.48) {
            Log("开始平衡", self.p)
            self.cny -= 300
            if (self.orderBook.Bids.length >0) {
                exchange.Buy(self.orderBook.Bids[0].Price + 0.00, 0.01)
                exchange.Buy(self.orderBook.Bids[0].Price + 0.01, 0.01)
                exchange.Buy(self.orderBook.Bids[0].Price + 0.02, 0.01)
            }
        } else if (self.p > 0.52) {
            Log("开始平衡", self.p)
            self.btc -= 0.03
            if (self.orderBook.Asks.length >0) {
                exchange.Sell(self.orderBook.Asks[0].Price - 0.00, 0.01)
                exchange.Sell(self.orderBook.Asks[0].Price - 0.01, 0.01)
                exchange.Sell(self.orderBook.Asks[0].Price - 0.02, 0.01)
            }
        }
        Sleep(BalanceTimeout)
        var orders = exchange.GetOrders()
        if (orders) {
            for (var i = 0; i < orders.length; i++) {
                if (orders[i].Id != self.tradeOrderId) {
                    exchange.CancelOrder(orders[i].Id)
                }
            }
        }
    }

    self.poll = function() {
        self.numTick++
        self.updateTrades()
        self.updateOrderBook()
        self.balanceAccount()
        
        var burstPrice = self.prices[self.prices.length-1] * BurstThresholdPct
        var bull = false
        var bear = false
        var tradeAmount = 0
        if (self.account) {
            LogStatus(self.account, 'Tick:', self.numTick, ', lastPrice:', self.prices[self.prices.length-1], ', burstPrice: ', burstPrice)
        }
        
        if (self.numTick > 2 && (
            self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -1)) > burstPrice ||
            self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -2)) > burstPrice && self.prices[self.prices.length-1] > self.prices[self.prices.length-2]
            )) {
            bull = true
            tradeAmount = self.cny / self.bidPrice * 0.99
        } else if (self.numTick > 2 && (
            self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -1)) < -burstPrice ||
            self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -2)) < -burstPrice && self.prices[self.prices.length-1] < self.prices[self.prices.length-2]
            )) {
            bear = true
            tradeAmount = self.btc
        }
        if (self.vol < BurstThresholdVol) {
            tradeAmount *= self.vol / BurstThresholdVol
        }
        
        if (self.numTick < 5) {
            tradeAmount *= 0.8
        }
        
        if (self.numTick < 10) {
            tradeAmount *= 0.8
        }
        
        if ((!bull && !bear) || tradeAmount < MinStock) {
            return
        }
        var tradePrice = bull ? self.bidPrice : self.askPrice
        while (tradeAmount >= MinStock) {
            var orderId = bull ? exchange.Buy(self.bidPrice, tradeAmount) : exchange.Sell(self.askPrice, tradeAmount)
            Sleep(200)
            if (orderId) {
                self.tradeOrderId = orderId
                var order = null
                while (true) {
                    order = exchange.GetOrder(orderId)
                    if (order) {
                        if (order.Status == ORDER_STATE_PENDING) {
                            exchange.CancelOrder(orderId)
                            Sleep(200)
                        } else {
                            break
                        }
                    }
                }
                self.tradeOrderId = 0
                tradeAmount -= order.DealAmount
                tradeAmount *= 0.9
                if (order.Status == ORDER_STATE_CANCELED) {
                    self.updateOrderBook()
                    while (bull && self.bidPrice - tradePrice > 0.1) {
                        tradeAmount *= 0.99
                        tradePrice += 0.1
                    }
                    while (bear && self.askPrice - tradePrice < -0.1) {
                        tradeAmount *= 0.99
                        tradePrice -= 0.1
                    }
                }
            }
        }
        self.numTick = 0
    }
    return self
}

function main() {
    var reaper = LeeksReaper()
    while (true) {
        reaper.poll()
        Sleep(TickInterval)
    }
}

Strategy overview

In general, when learning a policy, the first thing to do is to read the entire program structure. The policy code is not much, only less than 200 lines of code, can be said to be very streamlined, and for the original version of the policy rendering is very high, basically the same.main()The function starts executing, the whole policy code, exceptmain()It's calledLeeksReaper()So the function is going to beLeeksReaper()The function is also well understood, which can be understood as a constructor function of a cabbage harvester strategy logic module (an object), simply put.LeeksReaper()It is responsible for constructing the trading logic of a cabbage harvester.

Key words:img img

  • The strategymainThe first line of the function:var reaper = LeeksReaper()The code declares a local variable.reaper, and then invoke the LeeksReaper () function to construct a policy logical object that assigns a value to thereaper

  • The strategymainThe function goes like this:

    while (true) {
        reaper.poll()
        Sleep(TickInterval)
    }
    

    Enter onewhileThe cycle of death, continuous executionreaperObject processing functionspoll()poll()The function is the main logic of the trading strategy, and the entire strategic process begins with the continuous execution of the trading logic. As forSleep(TickInterval)This line is well understood to control the pause time after each execution of the overall transaction logic, with the aim of controlling the rotation frequency of the transaction logic.

The dissectionLeeksReaper()Construction of functions

Look at this.LeeksReaper()How does a function construct a strategic logical object?

LeeksReaper()The function starts by declaring an empty object.var self = {}, inLeeksReaper()In the process of executing the function, it will gradually add some methods, attributes to this empty object, finally completing the construction of this object, and finally returning this object (i.e.main()In the function.var reaper = LeeksReaper()This step assigns a value to the returned object.reaper)。

Give it to meselfObject added attributes

Next, give it toselfA lot of attributes have been added, and I'll describe each of them below, so you can quickly understand these attributes, the purpose of the variables, the intent, the easy-to-understand strategy, and avoid seeing this stack of code in a cloud of clouds.

    self.numTick = 0         # 用来记录poll函数调用时未触发交易的次数,当触发下单并且下单逻辑执行完时,self.numTick重置为0
    self.lastTradeId = 0     # 交易市场已经成交的订单交易记录ID,这个变量记录市场当前最新的成交记录ID
    self.vol = 0             # 通过加权平均计算之后的市场每次考察时成交量参考(每次循环获取一次市场行情数据,可以理解为考察了行情一次)
    self.askPrice = 0        # 卖单提单价格,可以理解为策略通过计算后将要挂卖单的价格
    self.bidPrice = 0        # 买单提单价格
    self.orderBook = {Asks:[], Bids:[]}    # 记录当前获取的订单薄数据,即深度数据(卖一...卖n,买一...买n)
    self.prices = []                       # 一个数组,记录订单薄中前三档加权平均计算之后的时间序列上的价格,简单说就是每次储存计算得到的订单薄前三档加权平均价格,放在一个数组中,用于后续策略交易信号参考,所以该变量名是prices,复数形式,表示一组价格
    self.tradeOrderId = 0    # 记录当前提单下单后的订单ID
    self.p = 0.5             # 仓位比重,币的价值正好占总资产价值的一半时,该值为0.5,即平衡状态
    self.account = null      # 记录账户资产数据,由GetAccount()函数返回数据
    self.preCalc = 0         # 记录最近一次计算收益时的时间戳,单位毫秒,用于控制收益计算部分代码触发执行的频率
    self.preNet = 0          # 记录当前收益数值

Give it to meselfMethod of adding objects

And then you add these attributes to the self and you start to give it.selfThe object adds methods to allow the object to do some tasks and have some functions.

The first function added:

    self.updateTrades = function() {
        var trades = _C(exchange.GetTrades)  # 调用FMZ封装的接口GetTrades,获取当前最新的市场成交数据
        if (self.prices.length == 0) {       # 当self.prices.length == 0时,需要给self.prices数组填充数值,只有策略启动运行时才会触发
            while (trades.length == 0) {     # 如果近期市场上没有更新的成交记录,这个while循环会一直执行,直到有最新成交数据,更新trades变量
                trades = trades.concat(_C(exchange.GetTrades))   # concat 是JS数组类型的一个方法,用来拼接两个数组,这里就是把“trades”数组和“_C(exchange.GetTrades)”返回的数组数据拼接成一个数组
            }
            for (var i = 0; i < 15; i++) {   # 给self.prices填充数据,填充15个最新成交价格
                self.prices[i] = trades[trades.length - 1].Price
            }
        }
        self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {  # _.reduce 函数迭代计算,累计最新成交记录的成交量
            // Huobi not support trade.Id
            if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
                self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId)
                mem += trade.Amount
            }
            return mem
        }, 0)

    }

updateTradesThe function's role is to obtain the most recent market transaction data, and perform some calculations and records based on the data, which are provided to the strategy for use in subsequent logic. I wrote the footnotes directly in the code above. For the_.reduceI'm going to tell you a little bit about the programming process, which may be confusing to students who don't have a background in programming._.reduceYes, it is.Underscore.jsThe functions in this library, the FMZJS policy supports this library, so it is very convenient to use it for iterative computation.Underscore.js资料链接

The meaning is also very simple, for example:

function main () {
   var arr = [1, 2, 3, 4]
   var sum = _.reduce(arr, function(ret, ele){
       ret += ele
       
       return ret
   }, 0)

   Log("sum:", sum)    # sum 等于 10
}

So let's say we have an array.[1, 2, 3, 4]So we're going to add up each of these numbers.tradesThe sum of the transaction logs of each transaction in the array; the sum of the transaction logs of each transaction in the array; and the sum of the transaction logs of each transaction in the array.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)Please allow me to use...Instead of a bunch of code.self.volThe calculation is also a weighted average; i.e. the most recent transaction is weighted at 30% of the total transaction and the last one at 70%; this ratio is set by the strategy author and may be related to the observation of market laws. And if you ask me, what if the interface that gets the most recent transaction data returns me duplicate old data, and the data that I get is all wrong, and it's still usable?

if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
    ...
}

This judgement can be based on the transaction ID judgement in the transaction record, triggering the accumulation only when the ID is larger than the last recorded ID, or if the exchange interface does not provide the ID.trade.Id == 0In this case, the time stamp used in the transaction records is used to determine the time.self.lastTradeIdThe timestamp of the transaction record is stored, not the ID.

The second function added:

    self.updateOrderBook = function() {
        var orderBook = _C(exchange.GetDepth)
        self.orderBook = orderBook
        if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) {
            return
        }
        self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01
        self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01
        self.prices.shift()
        self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 +
            (orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 +
            (orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05))
    }

Let's see.updateOrderBookThis function, as can be seen from the literal meaning of the function name, is the function to update the order thin.GetDepth()Obtain the current market order thinning data (sell one...sell n, buy one...buy n) and record the order thinning data in theself.orderBookIn this case, if the order is thin, the order is less than 3 orders, and the function is invalid.

The data was then calculated using two data points:

  • Calculate the price of the order The invoice price is also calculated using a weighted average, with a buy-to-let ratio of 61.8% ((0.618) and a sell-to-let ratio of 38.2% ((0.382)). The same applies when calculating the bid and ask price, giving and selling one price right is more significant. The reason why it is 0.618, probably because the author prefers the gold split ratio. The last point minus the one point price ((0.01) is to slightly deviate a little from the center of the discount.

  • Updated time series order thin first three rows plus weighted average price For the first three tiers of orders, the price of the order is calculated as a weighted average, the first tier weighs 0.7, the second tier weighs 0.2, and the third tier weighs 0.1. Let's do the math:

    (买一 + 卖一) * 0.35 + (买二 + 卖二) * 0.1 + (买三 + 卖三) * 0.05
    ->
    (买一 + 卖一) / 2 * 2 * 0.35 + (买二 + 卖二) / 2 * 2 * 0.1 + (买三 + 卖三) / 2 * 2 * 0.05
    ->
    (买一 + 卖一) / 2 * 0.7 + (买二 + 卖二) / 2 * 0.2 + (买三 + 卖三) / 2 * 0.1
    ->
    第一档平均的价格 * 0.7 + 第二档平均的价格 * 0.2 + 第三档平均的价格 * 0.1
    

    It can be seen that the price calculated in the end is actually the price position of the three intermediate tiers in the market against the current market. And then you use this calculated price to update.self.pricesArray, pull out one of the oldest data (((byshift()Function), updated to an updated data ((bypush()Functions, shift, and push are methods of array objects in the JS language, which can be queried in JS data.)self.pricesAn array is a data stream with a time sequence.

Cough, cough, drink water, come here and dissect, see you next time.


More

The slowly growing meHello, I would like to ask you.self.prices 15 historical transaction prices first filled in, then fill in the first three weighted average prices.

snlnppI would like to pay tribute to the dream.

m0606Unfortunately, many market makers have reduced the buy-sell price to just one tick, making it pointless to try to insert buy-sell-middle in the strategy.

mummyThanks, I wrote a version of python and ran it on a shiny coin, it's a reaper of paperwork.

bwxiaokIt's great, without the dream interpretation I can't really understand it completely, thanks for the patience and explanation!

EddieGold divided by 0.618 0.382 is used in Fibonacci. The dream of a cow

LEEEEEEEEOIt's a lot of cattle, that's all.

evan1987Thanks for sharing, I'm so happy to hear that you're doing well.

makebitThe dream cow p

shiyimjjcnI'm not going to say that I'm not. I'm not sure what you mean by that, but it seems very complicated.

Nine sunsThe dream is complete, cow lot!

The Little DreamYes, I did.

The Little DreamThe main thing is to learn ideas and gain insight into these high-frequency trading opportunities.

The Little DreamIn this article, you can read the analysis of the principles of the strategy written by Grasshopper, high frequency strategies need some support.

The Little DreamThank you for your support. If you like it, help share it, haha.

The Little DreamThank you for your support!

The Little DreamThank you for your support!

The Little DreamThank you for your support!

The Little DreamWhen I went to school, I remembered this golden ratio very clearly, saying that the rectangle with the long and wide ratio is the most beautiful.

The Little DreamThank you for your support.

The Little DreamIn fact, it's not complicated, this commentary is rather sloppy, each line is described in the easiest way possible.