Die Ressourcen sind geladen. Beförderung...

Strategieanalyse von LeeksReaper (1)

Schriftsteller:FMZ~Lydia, Erstellt: 2022-11-04 15:42:26, Aktualisiert: 2025-01-13 20:27:55

LeeksReaper Strategy Analysis(1)

Strategieanalyse von LeeksReaper (1)

In letzter Zeit gibt es eine heiße Diskussion über dieprint moneyEine sehr alte Strategie ist wieder in die Augen der Quants gekommen: LeeksReaper. Das Roboterhandelsprinzip derprint moneyAlso habe ich die ursprüngliche Strategie nochmals sorgfältig durchgelesen und mir die transplantierte Version des transplantierten OKCoin-Poees auf dem FMZ Quant angesehen. Die Strategie des auf der FMZ Quant Plattform basierenden transplantierten Laucherners wird analysiert, um die Idee der Strategie zu erforschen. In diesem Artikel werden wir mehr aus den Aspekten der Strategieidee und Absicht analysieren, um den langweiligen Inhalt im Zusammenhang mit der Programmierung zu minimieren.

Der Quellcode der [Transplantation von OKCoin LeeksReaper] Strategie:

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

Übersicht über die Strategie

Im Allgemeinen, wenn Sie eine Strategie zu studieren haben, sollten Sie sich zunächst die Gesamtprogrammstruktur ansehen. Der Strategiecode ist nicht sehr lang, mit weniger als 200 Zeilen Code, es ist sehr prägnant, und die ursprüngliche Strategie ist stark restauriert, fast identisch.main()Der gesamte Strategiecode, mit Ausnahmemain(), ist eine Funktion namensLeeksReaper(). DieLeeksReaper()Die Funktion ist sehr leicht zu verstehen, sie kann als Konstruktor des Logikmoduls (ein Objekt) verstanden werden.LeeksReaper()ist für den Aufbau einer Logik für den Handel mit Porenernten verantwortlich.

Schlüsselwörter:

LeeksReaper Strategy Analysis(1)

· Die erste StrategieliniemainFunktion:


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

während (wahr) { Reaper.Poll (siehe unten) Schlaf ((TickIntervall) - Ich weiß.

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:

Funktion Haupt () { Das Ergebnis der Analyse ist das Ergebnis der Analyse. Verminderung der Funktion Ret += ele

   return ret

}, 0)

Log ((sum:, Summe) # Summe = 10 - Ich weiß.

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:

wenn ((Trade.Id > self.lastTradeId) gesetzt wird (trade.Id == 0 && trade.Time > self.lastTradeId)) Ich... - Ich weiß.

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:

(Einen kaufen + einen verkaufen) * 0,35+(Zwei kaufen + zwei verkaufen) * 0,1+(Drei kaufen + drei verkaufen) * 0,05 - Nein. (Einen kaufen + einen verkaufen) / 2 * 2 * 0,35 + ((Zwei kaufen + zwei verkaufen) / 2 * 2 * 0,1 + ((Drei kaufen + drei verkaufen) / 2 * 2 * 0,05 - Nein. (Einen kaufen + einen verkaufen) / 2 * 0,7 + ((Zwei kaufen + zwei verkaufen) / 2 * 0,2 + ((Drei kaufen + drei verkaufen) / 2 * 0,1 - Nein. Durchschnittlicher Preis der ersten Stufe * 0,7+durchschnittlicher Preis der zweiten Stufe * 0,2+durchschnittlicher Preis der dritten Stufe * 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 theSchichtfunction) and updating one of the newest data into it(through theSchieben.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`, der ein Datenstrom mit einer Zeitreihenfolge ist.

Also lassen Sie uns hier eine Pause machen, und wir sehen uns in der nächsten Ausgabe ~


Inhalte dazu

Weitere Informationen