Die Ressourcen sind geladen. Beförderung...

Strategieanalyse von LeeksReaper (1)

Schriftsteller:FMZ~Lydia, Erstellt: 2022-11-04 15:42:26, Aktualisiert: 2023-09-15 21:08:48

img

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:

img

· Die erste StrategieliniemainFunktion:var reaper = LeeksReaper(), der Code erklärt eine lokale Variablereaperund ruft dann die LeeksReaper() Funktion auf, um ein Strategie-Logikobjekt zu konstruieren, das einem Wert zuweistreaper.

Der nächste Schritt der StrategiemainFunktion:

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

Geben Sie einwhileendlose Schleife und halten die Ausführung der Verarbeitung Funktionpoll()derreaperGegenstand, diepoll()Die Funktion ist genau dort, wo die Hauptlogik der Handelsstrategie liegt und das gesamte Strategieprogramm beginnt, die Handelslogik immer wieder auszuführen. Was die Linie angeht:Sleep(TickInterval), ist leicht zu verstehen, ist es, die Pausezeit nach jeder Ausführung der gesamten Handelslogik zu steuern, mit dem Ziel, die Rotationsfrequenz der Handelslogik zu steuern.

AnalyseLeeksReaper()Konstrukteur

Schauen Sie sich an, wie dieLeeksReaper()Die Funktion konstruiert ein Strategie-Logikobjekt.

DieLeeksReaper()Die Funktion beginnt mit der Angabe eines leeren Objekts.var self = {}, und während der Ausführung derLeeksReaper()Funktion wird allmählich einige Methoden und Attribute zu diesem leeren Objekt hinzufügen, schließlich die Konstruktion dieses Objekts abzuschließen und es zurückzugeben (das heißt, der Schritt vonmain()Funktion innerhalb dervar reaper = LeeksReaper(), wird das zurückgegebene Objekt zugeordnetreaper).

Hinzufügen von Attributen zumselfGegenstand Als nächstes habe ich viele Attribute hinzugefügt.self. Ich werde jedes Attribut wie folgt beschreiben, was den Zweck und die Absicht dieser Attribute und Variablen schnell verstehen, das Verständnis von Strategien erleichtern und Verwirrung beim Ansehen des Codes vermeiden kann.

    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

Hinzufügen von Methoden zu Self-Objekten

Nach dem Hinzufügen dieser Attribute zu selbst, beginnen Sie Methoden zu demselfObjekt, so dass dieses Objekt einige Arbeit leisten und einige Funktionen haben kann.

Die erste Funktion fügte hinzu:

    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)

    }

Die FunktionupdateTradesDer Hauptzweck der Strategie ist es, die neuesten Markttransaktionsdaten zu erhalten, einige Berechnungen auf der Grundlage der Daten durchzuführen und sie für die spätere Nutzung in der Logik der Strategie zu erfassen. Die Zeile für Zeile Kommentare habe ich direkt in den Code oben geschrieben. Für_.reduce, kann jemand, der keine Grundkenntnisse im Programmieren hat, verwirrt sein. Lassen Sie uns kurz darüber sprechen,_.reduceist eine Funktion der Underscore.js-Bibliothek. Die FMZJS-Strategie unterstützt diese Bibliothek, so dass sie für iterative Berechnungen sehr praktisch ist.https://underscorejs.net/#reduce)

Die Bedeutung ist auch sehr einfach, zum Beispiel:

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
}

Das heißt, addieren Sie jede Zahl im Array[1, 2, 3, 4]Zurück zu unserer Strategie addieren wir die Handelsvolumenwerte der einzelnen Transaktionsdaten in dertradesErhalten Sie eine Gesamtsumme des letzten Transaktionsvolumensself.vol = 0.7 * self.vol + 0.3 * _.reduce (...), hier verwenden wir...Es ist nicht schwierig, die Berechnung vonself.volist auch ein gewichteter Durchschnitt, d. h. das neu generierte Handelsvolumen macht 30% des Gesamtvolumens aus und das letzte gewichtete Handelsvolumen 70% dieses Verhältnisses wurde vom Strategieaufsteller künstlich festgelegt und kann mit den Marktregeln zusammenhängen. Was Ihre Frage angeht, was wäre, wenn die Schnittstelle, um die neuesten Transaktionsdaten zu erhalten, auf die duplizierten alten Daten zurückkehrte, dann waren die Daten falsch und es wird nicht sinnvoll sein? Keine Sorge. Dieses Problem wurde im Strategie-Design berücksichtigt, so dass der Code:

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

Die Akkumulation wird nur ausgelöst, wenn die ID größer ist als die ID des letzten Datensatzes oder wenn die Austauschoberfläche keine ID bereitstellt, d. h.trade.Id == 0, verwenden Sie den Zeitstempel im Transaktionsdatensatz zu beurteilen.self.lastTradeIdSpeichert den Zeitstempel des Transaktionsprotokolls anstelle der ID.

Die zweite Funktion fügte hinzu:

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

Als nächstes schauen wir uns die Funktion an.updateOrderBook. Aus dem Namen der Funktion können wir sehen, dass sie zum Aktualisieren des Auftragsbuchs verwendet wird. Sie aktualisiert jedoch nicht nur das Auftragsbuch. Die Funktion ruft die FMZ API-Funktion auf.GetDepth()Um die Daten des aktuellen Auftragsbuchs des Marktes zu erhalten (einen verkaufen... n verkaufen, einen kaufen... n kaufen) und die Auftragsbuchdaten inself.orderBook. Als nächstes, entscheiden, ob die Bestellung und Verkaufsbestellung der Bestellbuchdaten weniger als 3, wenn ja, wird die ungültige Funktion direkt zurückgegeben.

Anschließend werden zwei Daten berechnet:

· Berechnung des Konnossementpreises Bei der Berechnung der Bestellung beträgt das Gewicht des Kaufpreises, der dem Transaktionspreis am nächsten liegt, 61,8% (0,618) und das Gewicht des Verkaufspreises, der dem Transaktionspreis am nächsten liegt, 38,2% (0,382). Bei der Berechnung des Frachtbriefes wird der Verkaufspreis, der dem Transaktionspreis am nächsten liegt, mit dem gleichen Gewicht bewertet. Wie für warum ist 0,618, kann es sein, dass der Autor das goldene Querschnittsverhältnis bevorzugt. Wie für den letzten Preis (0,01), ist es leicht zur Mitte der Öffnung zu verschieben.

· Aktualisierung des gewichteten Durchschnittspreises der ersten drei Stufen des Auftragsbuchs auf der Zeitreihe Für die ersten drei Stufen der Bestellpreise im Auftragsbuch wird der gewichtete Durchschnitt berechnet. Das Gewicht der ersten Stufe ist 0,7, das Gewicht der zweiten Stufe ist 0,2 und das Gewicht der dritten Stufe ist 0,1. Jemand mag sagen: Oh, nein, es gibt 0,7, 0,2, 0,1 im Code. Lassen Sie uns die Berechnung erweitern:

(Buy one+Sell one) * 0.35+(Buy two+Sell two) * 0.1+(Buy three+Sell three) * 0.05
->
(Buy one+sell one)/2 * 2 * 0.35+(Buy two+sell two)/2 * 2 * 0.1+(Buy three+sell three)/2 * 2 * 0.05
->
(Buy one+sell one)/2 * 0.7+(Buy two+sell two)/2 * 0.2+(Buy three+sell three)/2 * 0.1
->
Average price of the first level * 0.7+average price of the second level * 0.2+average price of the third level * 0.1

Wie wir hier sehen können, ist der endgültig berechnete Preis eigentlich eine Antwort auf die Preisposition der Mitte der dritten Öffnung auf dem aktuellen Markt. Verwenden Sie dann diesen berechneten Preis, um das Array zu aktualisierenself.prices, die eine der ältesten Daten (durch dieshift()Diese Daten werden in der Datenbank (z. B.push()Funktion, Verschiebung und Push-Funktionen sind Methoden des JS-Sprache-Array-Objektes, Sie können die JS-Daten für Details überprüfen).self.prices, das ein Datenstrom mit einer Zeitreihenfolge ist.

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


Verwandt

Mehr