Analysis of Leek Harvester Strategy (1)

Analysis of Leek Harvester Strategy

Recently, the inventor quantified print moneythe robots discussed in the WeChat group , and the discussion was very hot, and a very old strategy has re-entered the vision of quants: the leek harvester .
print moneyThe trading principle of robots borrowed from the strategy of the leek harvester. I blamed myself for not understanding and understanding the strategy of the leek harvester. Therefore, I carefully read the original strategy again, and read the transplanted version of the inventor's quantification to transplant the OKCoin Leek Harvester .
Based on the transplanted version of the leek harvester strategy of the inventor's quantitative platform, the strategy is analyzed and the ideas of the strategy are explored. So that platform users can learn this strategy idea.
In this article, we analyze more from the level of strategic thinking, intention, etc., to minimize the boring content related to programming.

[Transplant OKCoin Leek 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

Generally, when you get a strategy for learning, when reading, first look at the overall program structure. The strategy code is not much, only less than 200 lines of code, which can be described as very streamlined, and the original strategy has a high degree of reduction, basically the same. The strategy code is main()executed from the function when it is running. The strategy code throughout the article, except main()for LeeksReaper()a function named , LeeksReaper()is well understood. This function can be understood as the constructor of the leek harvester strategy logic module (an object), simple That LeeksReaper()is to say, it is responsible for constructing a trading logic of a leek harvester.

Keywords:
Analysis of Leek Harvester Strategy (1)

Analysis of Leek Harvester Strategy (1)

  • The mainfirst line of the strategy function:, the
    var reaper = LeeksReaper()code declares a local variable reaper, and then calls the LeeksReaper() function to construct a strategy logic object and assign it to reaper.

  • The strategy mainfunction follows:
    while (true) {
      reaper.poll()
      Sleep(TickInterval)
    }

    Entering an whileendless loop, reaperthe processing function of the object is continuously executed poll(). The poll()function is the main logic of the trading strategy, and the entire strategy program starts to execute the trading logic continuously.
    As for Sleep(TickInterval)this line, it is well understood that it is to control the pause time after each execution of the overall transaction logic, and the purpose is to control the rotation frequency of the transaction logic.

Anatomy of the LeeksReaper()constructor

Look at how the LeeksReaper()function constructs a strategy logic object.

LeeksReaper()At the beginning of the function, an empty object is declared var self = {}. During the LeeksReaper()execution of the function , some methods and attributes will be gradually added to the empty object. Finally, the construction of the object is completed, and finally the object is returned (that is , the returned object at this step in the main()function). var reaper = LeeksReaper()Assigned to reaper).

selfAdd attributes to objects

Next self, I added a lot of attributes. I will describe each attribute below. I can quickly understand the purpose and intent of these attributes and variables. It is convenient to understand the strategy and avoid the cloud and mist when I see this bunch of code. .

    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          # 记录当前收益数值

selfAdd methods to objects

After adding these properties to self, start selfadding methods to the object so that this object can do some work 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 of this function is to obtain the latest market transaction data, and do some calculations and records based on the data, and provide it for use in the subsequent logic of the strategy.
I wrote line-by-line comments directly in the above code.
For _.reduceprobably confused students will no programming foundation, and simple speaking here, _.reduceis Underscore.js functions in this library, FMZJS strategy to support this library, so it is convenient for iterative calculation, Underscore.js data link

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
}

Just [1, 2, 3, 4]add up each number in the array . Going back to our strategy is to tradesadd up the volume value of each transaction record data in the array. Get a total of the latest transaction record transaction volume. self.vol = 0.7 * self.vol + 0.3 * _.reduce(...), Please allow me to ...replace that bunch of code. It is not difficult to see here that self.volthe calculation is also a weighted average. That is to say, the total transaction volume of the latest generation accounted for 30% of the weight, and the volume of the last weighted calculation accounted for 70%. This ratio is artificially set by the strategy author and may be related to observing market rules.
As for you, what should I do if the interface for obtaining the latest transaction data returns me repeated old data swelling, then the data I got are all wrong, is it still useful? Don’t worry, this issue has been considered in the strategy design, so the code has

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

This judgment. It can be judged based on the transaction ID in the transaction record, and the accumulation is triggered only when the ID is greater than the last recorded ID, or if the exchange interface does not provide an ID, that is trade.Id == 0, the timestamp in the transaction record is used for judgment, and self.lastTradeIdthe transaction record is stored at this time The timestamp, not the ID anymore.

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))
    }

Next, look at updateOrderBookthis function. From the meaning of the function name, you can see that this function is used to update the order book. However, it is more than just an update of the order book. The function starts to call the FMZ API function to GetDepth()obtain the current market order book data (sell one...sell n, buy one...buy n), and record the order book data in self.orderBookit. Next, it is judged that if the order book data is less than 3 levels of buy and sell orders, the invalid function is judged to return directly.

After that, two data calculations were performed:

  • The calculation of the bill
    of lading price also uses the weighted average calculation. For the calculation of the purchase order, the weight to buy one is 61.8% (0.618), and the weight of the sale one accounts for 38.2% (0.382) of the remaining weight
    . The same applies when calculating the price of the bill of lading sale , Giving a price right to sell is more important. As for why it is 0.618, it may be that the author prefers the golden ratio. As for the last addition and subtraction, the price (0.01) is to shift slightly to the center of the handicap.

  • The weighted average price of the
    first three levels of the order book on the updated time series is calculated on the weighted average of the buy and sell orders of the first three levels of the order book. The weight of the first level is 0.7, the weight of the second level is 0.2, and the weight of the third level is 0.1. Some students may say: "Eh, that's not right, there are 0.7, 0.2, 0.1 in the code."
    Let's look at the expansion of the calculation:
    (买一 + 卖一) * 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

    As you can see here, the final calculated price actually reflects the price position of the third gear in the current market.
    Then use the calculated price to update the self.pricesarray, kick out the oldest data (through shift()functions), and update the latest data (through push()functions, shift and push functions are all methods of JS language array objects. You can query JS data for details. ). Thus forming self.pricesan array is a data stream with a time series sequence.

Cough cough, drink saliva, let's analyze here first, see you next time~

Guess you like

Origin blog.51cto.com/13020291/2664274