資源の読み込みに... 荷物...

リークスリーパー戦略分析 (1)

作者: リン・ハーンFMZ~リディア作成日:2022年11月14日 15:42:26 更新日:2025年1月13日 20:27:55

LeeksReaper Strategy Analysis(1)

リークスリーパー戦略分析 (1)

最近,熱い議論が起こっています.print moneyFMZ Quantの微信グループで ロボットです ロボット取引の原則print moneyそこで,私は元の戦略を注意深く再読し,移植されたOKCoinの移植版をFMZ Quantで見ました. FMZ Quantプラットフォームをベースにした移植した刈りの戦略を分析し,戦略のアイデアを探求します. この記事では,プログラミングに関連する退屈なコンテンツを最小限に抑えるために戦略のアイデアと意図の側面からさらに分析します.

[OKCoin LeeksReaperを移植する]戦略のソースコード:

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("Start balance", 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("Start balance", 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)
    }
}

戦略の概要

一般的に,勉強する戦略を手に入れたとき,まず全体的なプログラム構造を見てください. 戦略コードは非常に長くない,コードの200行未満で,それは非常に簡潔で,元の戦略はほとんど同じで,非常に復元されています. 戦略コードはmain()戦略コードのすべて,を除いてmain(), は関数である.LeeksReaper().....LeeksReaper()簡単に言うと,この関数は,leeksreaper戦略論理モジュール (オブジェクト) のコンストラクターとして理解できます.LeeksReaper()を収穫する商取引の論理を構築する責任があります.

キーワード:

LeeksReaper Strategy Analysis(1)

· 戦略の第一線main機能:


The next step of strategy ```main``` function:

{ true } { true } { true } { true } { true } 収穫者 投票者 睡眠 (ティック間隔) { \ pos (192,220) }

Enter a ```while``` endless loop and keep executing the processing function ```poll()``` of the ```reaper``` object, the ```poll()``` function is exactly where the main logic of the trading strategy lies and the whole strategy program starts executing the trading logic over and over again.
As for the line ```Sleep(TickInterval)```, it is easy to understood, it is to control the pause time after each execution of the overall trading logic, with the purpose of controlling the rotation frequency of the trading logic.

### Analyse ```LeeksReaper()``` constructor
Look at how the ```LeeksReaper()``` function constructs a strategy logic object.

The ```LeeksReaper()``` function starts by declaring an empty object, ```var self = {}```, and during the execution of the ```LeeksReaper()``` function will gradually add some methods and attributes to this empty object, finally completing the construction of this object and returning it (that is, the step of ```main()``` function inside the ```var reaper = LeeksReaper()```, the returned object is assigned to ```reaper```).

Add attributes to the ```self``` object
Next, I added a lot of attributes to ```self```. I will describe each attribute as follows, which can understand the purpose and intention of these attributes and variables quickly, facilitate the understanding of strategies, and avoid being confused when seeing the code.
self.numTick = 0         # It is used to record the number of transactions not triggered when the poll function is called. When the order is triggered and the order logic is executed, self.numTick is reset to 0
self.lastTradeId = 0     # The transaction record ID of the order that has been transacted in the transaction market. This variable records the current transaction record ID of the market
self.vol = 0             # Reference to the trading volume of each market inspection after weighted average calculation (market data is obtained once per loop, which can be interpreted as a time of market inspection)
self.askPrice = 0        # The bill of lading price of the sales order can be understood as the price of the listing order after the strategy is calculated
self.bidPrice = 0        # Purchase order bill of lading price
self.orderBook = {Asks:[], Bids:[]}    # Record the currently obtained order book data, that is, depth data (sell one... sell n, buy one... buy n)
self.prices = []                       # An array that records the prices on the time series after the calculation of the first three weighted averages in the order book, which means that each time the first three weighted averages of the order book are stored, they are placed in an array and used as a reference for subsequent strategy trading signals, so the variable name is prices, in plural form, indicating a set of prices
self.tradeOrderId = 0    # Record the order ID after the current bill of lading is placed
self.p = 0.5             # Position proportion: when the value of currency accounts for exactly half of the total asset value, the value is 0.5, that is, the equilibrium state
self.account = null      # Record the account asset data, which is returned by the GetAccount() function
self.preCalc = 0         # Record the timestamp of the last time when the revenue was calculated, in milliseconds, to control the frequency of triggering the execution of the revenue calculation code
self.preNet = 0          # Record current return values
### Add methods to self objects
After adding these attributes to self, start adding methods to the ```self``` 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)  # Call the FMZ encapsulated interface GetTrades to obtain the latest market transaction data
    if (self.prices.length == 0) {       # When self.prices.length == 0, the self.prices array needs to be filled with numeric values, which will be triggered only when the strategy starts running
        while (trades.length == 0) {     # If there is no recent transaction record in the market, the while loop will keep executing until the latest transaction data is available and update the trades variable
            trades = trades.concat(_C(exchange.GetTrades))   # concat is a method of JS array type, which is used to concatenate two arrays, here is to concatenate the "trades" array and the array data returned by "_C(exchange.GetTrades)" into one array
        }
        for (var i = 0; i < 15; i++) {   # Fill in data to self.prices, and fill in 15 pieces of latest transaction prices
            self.prices[i] = trades[trades.length - 1].Price
        }
    }
    self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {  # _. Reduce function is used for iterative calculation to accumulate the amount of the latest transaction records
        // 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)

}
The function ```updateTrades``` is to get the latest market transaction data and do some calculations based on the data and record it for using in the subsequent logic of the strategy.
The line-by-line comments I wrote in the code above directly.
For ```_.reduce```, someone who have no programming basic learning may be confused. Let's talk about it briefly, ```_.reduce``` is a function of the Underscore.js library. The FMZJS strategy supports this library, so it is very convenient for iterative calculation. The Underscore.js data link (https://underscorejs.net/#reduce)

The meaning is also very simple, for exmaple:

main () { の関数 変数arr = [1,2,3,4] 減算する = 減算する = 減算する ret += エレ

   return ret

}, 0)

合計は10です. { \ pos (192,220) }

That is, add up each number in the array ```[1, 2, 3, 4]```. Back to our strategy, we add up the trading volume values of each transaction record data in the ```trades``` array. Get a total of the latest transaction volume ```self.vol = 0.7 * self.vol + 0.3 * _.reduce (...)```, here we use ```...``` to replace the code. It is not difficult to see the calculation of ```self.vol``` is also a weighted average. That is, the newly generated trading volume accounts for 30% of the  total, and the last weighted trading volume accounts for 70%. This ratio was set by the strategy author artificially and it may be related to the market rules.
As for your question, what if the interface to obtain the latest transaction data returned to the duplicate old data, then the data I got was wrong, and won't it be meaningful? Don't worry. This problem was considered in the strategy design, so the code has:

if ((trade.Id > self.lastTradeId) について (trade.Id == 0 && trade.Time > self.lastTradeId)) { ... ほら { \ pos (192,220) }

the judgement. It can be judged based on the transaction ID in the transaction record. Accumulation is triggered only when the ID is greater than the ID of the last record, or if the exchange interface does not provide an ID, that is, ```trade.Id == 0```, use the timestamp in the transaction record to judge. At this time, ```self.lastTradeId``` stores the timestamp of the transaction record instead of 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))
}
Next, let's look at the function ```updateOrderBook```. From the name of the function, we can see that it is used to update the order book. However, it doesn't update the order book only. The function starts to call the FMZ API function ```GetDepth()``` to obtain the current market order book data (sell one... sell n, buy one... buy n), and record the order book data in ```self.orderBook```. Next, judge if the purchase order and sales order of the order book data are less than 3, if so, the invalid function will be returned directly.

After that, two pieces of data are calculated:

· Calculate the bill of lading price
  The bill of lading price is also calculated by using the weighted average method. When calculating the purchase order, the weight given to the purchase price closest to the transaction price is 61.8% (0.618), and the weight given to the selling price closest to the transaction price is 38.2% (0.382)
  When calculating the bill of lading bill of sale price, the same weight is given to the selling price closest to the transaction price. As for why is 0.618, it may be that the author prefers the golden section ratio. As for the last price (0.01), it is to offset to the center of the opening slightly.

· Update the weighted average price of the first three level of the order book on the time series
  For the first three levels of purchase and sales order prices in the order book, the weighted average is calculated. 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. Someone may say, "Oh, no, there are 0.7, 0.2, 0.1 in the code."
  Let's expand the calculation:

(1つ買う+1つ売る) *0.35+(2つ買う+2つ売る) *0.1+(3つ買う+3つ売る) *0.05 -> 2 * 2 * 0.35+(2 * 2 * 0.1+(3 * 3 を買って売る) 2 * 2 * 0.05 -> (1つ買って1つ売る)/2*0.7+(2つ買って2つ売る)/2*0.2+(3つ買って3つ売る)/2*0.1 -> 第1レベル平均価格 * 0.7+第2レベル平均価格 * 0.2+第3レベル平均価格 * 0.1 As we can see here, the final calculated price is actually a response to the price position of the middle of the third opening in the current market. Then use this calculated price to update the arrayself.prices, kicking out one of the oldest data (through theシフトfunction) and updating one of the newest data into it(through the押すfunction, shift and push functions are methods of the JS language array object, you can check the JS data for details). Thus forming the arrayself.prices`は,時間列順序のデータストリームです.

休憩して 次の号で会おう~


関連コンテンツ

もっと見る