Analyse der Strategie des Gewinnsammlers (1)

Schriftsteller:- Ich bin ein Idiot., Erstellt: 2022-04-26 11:31:51, aktualisiert:

Analyse der Strategie des Gewinnsammlers (1)

Vor kurzem diskutierten Nutzer in der FMZ Quant WeChat-Gruppe über den Bot vonprint moneyDie Diskussion war sehr heiß, und eine sehr alte Strategie ist wieder in die Vision der Quanten eingetreten:Gewinn Erntegerät- Ich weiß. Das Prinzip des Bot-Handelsprint moneyIch habe mir die Schuld gegeben, dass ich die Strategie des Gewinnsammlers damals nicht zu gut verstanden habe.Übertragung von OKCoin Profit Harvester- Ich weiß. Wir analysieren diese Strategie und holen die Ideen der Strategie heraus, damit unsere Plattformbenutzer diese Strategie-Idee lernen können. In diesem Artikel analysieren wir mehr aus der Ebene des strategischen Denkens, der Absicht usw., um den langweiligen Inhalt im Zusammenhang mit der Programmierung zu minimieren.

[Port OKCoin Profit Harvester] Strategie Quellcode:

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

Überblick über die Strategie

Im Allgemeinen, wenn Sie eine Strategie zu lernen bekommen, beim Lesen, zuerst schauen Sie sich die Gesamtprogrammstruktur. Der Strategie-Code ist nicht lang, weniger als 200 Zeilen; es kann gesagt werden, sehr vereinfacht, und hat eine hohe Wiederherstellung für die ursprüngliche Version Strategie, die im Grunde das gleiche wie dieses ist.main()Der gesamte Strategie-Code, mit Ausnahme dermain(), hat nur eine Funktion namensLeeksReaper(). DieLeeksReaper()Diese Funktion kann als Konstruktor des Profit Harvester-Strategie-Logikmoduls (ein Objekt) verstanden werden.LeeksReaper()ist für die Konstruktion der Handelslogik eines Gewinnsammlers verantwortlich.

  • Die erste Zeile desmainFunktion in der Strategie:var reaper = LeeksReaper(); der Code erklärt eine lokale Variablereaper, und ruft dann die LeeksReaper() Funktion auf, um ein Strategie-Logikobjekt zu konstruieren und zuweist es anreaper.

  • Der folgende Teil dermainFunktion:

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

    Geben Sie einwhileDabei wird die Verarbeitungsfunktion derreaperGegenstandpoll(), und diepoll()Funktion ist die Hauptlogik der Strategie, also beginnt das gesamte Strategieprogramm, die Handelslogik kontinuierlich auszuführen.

    Was die Linie derSleep(TickInterval), ist leicht verständlich, der die Pausezeit nach jeder Ausführung der gesamten Handelslogik steuert und dessen Zweck es ist, die Rotationsfrequenz der Handelslogik zu steuern.

Analysieren Sie den KonstrukteurLeeksReaper()

Lassen Sie uns sehen, wie die FunktionLeeksReaper()Konstruiert ein Objekt in der Strategielogik.

DieLeeksReaper()Funktion beginnt und deklariert ein Nullobjekt,var self = {}. Während der Vollstreckung desLeeksReaper()Wenn das Objekt mit der Funktion null verknüpft ist, wird dieses Nullobjekt allmählich geändert, indem einige Methoden und Attribute hinzugefügt werden.var reaper = LeeksReaper()in dermain()Funktion, und das zurückgegebene Objekt wird zugewiesenreaper).

Attribut zum Objekt hinzufügenself

Als nächstes habe ich viele Attribute hinzugefügtselfSie können schnell den Zweck und die Absicht dieser Attribute und Variablen verstehen, was es einfacher macht, die Strategie zu verstehen und verwirrt zu bleiben, wenn Sie den Haufen Code sehen.

    self.numTick = 0         # it is used to record the number of times that the trading is not triggered when the poll function is called. When placing an order is triggered and the order logic is executed, reset self.numTick to 0
    self.lastTradeId = 0     # the trading record ID of the order that has been executed in the trading market; this variable records the current latest execution record ID in the market
    self.vol = 0             # after the weighted average calculation, the trading volume reference of the market at each inspection (the market data is obtained once per time of the loop, which can be understood as the inspection of the market once)
    self.askPrice = 0        # ask price of delivery order, which can be understood as the price of the pending sell order calculated by the strategy 
    self.bidPrice = 0        # bid price of delivery order
    self.orderBook = {Asks:[], Bids:[]}    # record the currently obtained order book data, that is, depth data (sell 1...sell n, buy 1...buy n)
    self.prices = []                       # an array that records the price in the time series after the weighted average calculation of the first three levels in the order book. Simply put, it is the weighted average price of the first three levels of the order book obtained by storing each time, and put them in an array as reference of the subsequent strategic trading signals. Therefore, the variable name is "prices", plural, which means a set of prices
    self.tradeOrderId = 0    # record the order ID after currently lading and ordering 
    self.p = 0.5             # position proportion; when the currency value is exactly half of the total asset value, the value of it is 0.5, which means the balanced state 
    self.account = null      # record the account asset data, which will be returned by the function GetAccount()
    self.preCalc = 0         # record the timestamp when calculating the return of the latest time, in milliseconds, which is used to control the frequency of triggering execution of the return calculation code
    self.preNet = 0          # record the current return value  

Methode zum Objekt hinzufügenself

Nachdem Sie diese Attribute zu self hinzugefügt haben, fangen Sie an, Methoden zumselfObjekt, so dass dieses Objekt einige Operationen ausführen und einige Funktionen haben kann.

Erste Funktion:

    self.updateTrades = function() {
        var trades = _C(exchange.GetTrades)  # call the encapsulated interface "GetTrades" of FMZ, to obtain the currently latest execution data in the market
        if (self.prices.length == 0) {       # when self.prices.length == 0, you need to fill values in the array of self.prices, which is only triggered when the strategy is started 
            while (trades.length == 0) {     # if there is no latest execution record in the market, the while loop will run infinitely, until there is new execution data; the, update the variable "trades"
                trades = trades.concat(_C(exchange.GetTrades))   # "concat" is a method of JS array type, which is used to match two arrays; here we use it to match the array "trades" and the array returned by "_C(exchange.GetTrades)" into one array 
            }
            for (var i = 0; i < 15; i++) {   # fill in values for "self.prices"; fill 15 latest execution prices 
                self.prices[i] = trades[trades.length - 1].Price
            }
        }
        self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {  # _.reduce function iteratively calculates the accumulated execution volume of the latest execution record 
            // 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 Funktion derupdateTradesDie wichtigste Aufgabe des Unternehmens ist es, die neuesten Ausführungsdaten auf dem Markt zu erhalten, einige Berechnungen durchzuführen und entsprechend aufzuzeichnen und die Ergebnisse für die spätere Strategie zu verwenden. Ich habe die Kommentare Zeile für Zeile direkt in den obigen Code geschrieben. Studenten, die keine Grundlagen für die Programmierung haben, werden verwirrt sein._.reduceHier ist eine kurze Einführung._.reduceist eine Funktion vonUnderscore.jsDaher ist es sehr praktisch, es zu verwenden, um iterative Berechnungen durchzuführen.Informationslink von Underscore.js

Sehr leicht zu verstehen, 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 is 10
}

Das ist die Summe jeder Zahl in den Arrays, nämlich[1, 2, 3, 4]Zurück zu unserer Strategie: Wir addieren den Volumenwert jeder Ausführungsdaten in dertradesDas Ergebnis ist die Summe des letzten Ausführungsvolumens.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...), erlauben Sie mir, dieses Stück Code durch...Es ist hier nicht schwer zu erkennen, daß die Berechnung vonself.volist auch ein gewichteter Durchschnitt, d. h. das neu generierte Gesamtvolumen der Ausführung beträgt 30% und das vorher berechnete Gesamtvolumen der Ausführung 70%. Dieses Verhältnis wird vom Strategieentwickler künstlich festgelegt, was möglicherweise mit der Beobachtung der Marktregeln zusammenhängt.

Was Ihre Frage angeht, was sollte ich tun, wenn die Schnittstelle zur Erfassung der neuesten Ausführungsdaten mir die doppelten alten Daten zurückgibt? Dann sind die erhaltenen Daten falsch, also gibt es einen Sinn zu verwenden? " Keine Sorge, die Strategie wurde mit diesem Gedanken konzipiert, so dass der Code den folgenden Inhalt hat:

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

Das Urteil kann auf der Grundlage der Ausführung ID in der Handelsausführung Rekord. Nur wenn die ID größer als die ID des letzten Mal ist, Akkumulation ausgelöst wird. Oder, wenn die Plattform-Schnittstelle keine ID, das heißt,trade.Id == 0, verwenden Sie den Zeitstempel in der Handelsausführung Aufzeichnung zu beurteilen.self.lastTradeIdist der Zeitstempel des Ausführungsdatensatzes, nicht die ID.

Zweite Funktion:

    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 dieupdateOrderBookFunktion. Aus der Bedeutung des Funktionsnamens lässt sich erkennen, dass die Funktion das Bestellbuch aktualisieren soll. Allerdings aktualisiert sie nicht nur das Bestellbuch. Die Funktion ruft zunächst die FMZ API-Funktion aufGetDepth()Um die Daten des aktuellen Auftragsbuchs des Marktes zu erhalten (verkaufen 1...verkaufen n, kaufen 1...kaufen n) und die Auftragsbuchdaten inin self.orderBookAnschließend wird beurteilt, ob das Niveau der Auftragsbuchdaten für Kauf- und Verkaufsbestellungen weniger als 3 Ebenen beträgt. Ist dies der Fall, wird es als ungültige Funktion beurteilt und wird direkt zurückgegeben.

Danach wurden zwei Datenberechnungen durchgeführt:

  • Berechnung des Lieferpreises Die Berechnung des Lieferpreises basiert ebenfalls auf der gewichteten Durchschnittsberechnung. Bei der Berechnung des Gebotspreises einer Kaufbestellung wird dem Kaufpreis 1 ein größeres Gewicht gegeben, nämlich 61.8% (0.618), und der Verkaufspreis 1 macht das verbleibende Gewicht von 38.2% (0.382) aus. Das gleiche gilt bei der Berechnung des Gebotspreises der Lieferbestellung, indem dem Verkaufspreis 1 mehr Gewicht gegeben wird. Warum es 0.618 ist, kann sein, dass der Entwickler das goldene Verhältnis bevorzugt. Was das kleine Teil des Preises (0.01) angeht, das am Ende hinzugefügt oder subtrahiert wird, ist es, um etwas weiter zum Marktzentrum zu kompensieren.

  • Aktualisierung des gewichteten Durchschnittspreises der ersten drei Ebenen des Auftragsbuchs auf der Zeitreihe Für die ersten drei Stufen der Bestellungen im Auftragsbuch wird ein gewichteter Durchschnitt berechnet. Das Gewicht der ersten Stufe beträgt 0,7, das Gewicht der zweiten Stufe 0,2 und das Gewicht der dritten Stufe 0,1. Einige Schüler sagen vielleicht: Oh, falsch, es gibt keinen 0,7, 0,2 oder 0,1 im Code!

    Schauen wir uns die erweiterte Berechnung an:

    (Buy 1 + Sell 1) * 0.35 + (Buy 2 + Sell 2) * 0.1 + (Buy 3 + Sell 3) * 0.05
    ->
    (Buy 1 + Sell 1) / 2 * 2 * 0.35 + (Buy 2 + Sell 2) / 2 * 2 * 0.1 + (Buy 3 + Sell 3) / 2 * 2 * 0.05
    ->
    (Buy 1 + Sell 1) / 2 * 0.7 + (Buy 2 + Sell 2) / 2 * 0.2 + (Buy 3 + Sell 3) / 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
    

    Es wird hier festgestellt, dass der endgültig berechnete Preis tatsächlich die mittlere Preisposition der dritten Stufen auf dem aktuellen Markt widerspiegelt. Verwenden Sie dann diesen berechneten Preis, um dieself.pricesDie Daten werden in einem Array aufgeteilt.shift()Die Daten werden in derpush()Funktion; shift und push Funktionen sind alle Methoden von JS-Array-Objekten, Sie können nach JS-bezogenen Materialien für weitere Details suchen).self.pricesist ein Datenfluss in Zeitreihenfolge.

    Lasst uns die Analyse hier beenden, und wir sehen uns beim nächsten Mal!


Mehr