Analyse de la stratégie de LeeksReaper (1)

Auteur:Je ne sais pas., Créé à: 2022-11-04 15:42:26, Mis à jour à: 2023-09-15 21:08:48

img

Analyse de la stratégie de LeeksReaper (1)

Récemment, il y a eu un débat houleux sur laprint moneyUne stratégie très ancienne est revenue aux yeux des Quants: LeeksReaper. Le principe du robot de négociation deprint moneyJe me reproche de ne pas avoir lu trop dans la stratégie de la récolte de poireaux à ce moment-là et de ne pas l'avoir bien comprise. La stratégie de la récolte de poireaux transplantés basée sur la plateforme FMZ Quant est analysée pour explorer l'idée de la stratégie afin que les utilisateurs de la plateforme puissent apprendre l'idée de cette stratégie. Dans cet article, nous analyserons davantage les aspects de l'idée de stratégie et de l'intention pour minimiser le contenu ennuyeux lié à la programmation.

Le code source de la stratégie [Transplantation 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)
    }
}

Résumé de la stratégie

Généralement, lorsque vous avez une stratégie à étudier, vous devez d'abord jeter un coup d'œil à la structure globale du programme. Le code de stratégie n'est pas très long, avec moins de 200 lignes de code, il est très concis, et la stratégie originale est très restaurée, presque la même.main()L'ensemble du code de stratégie, à l'exception de:main(), est une fonction nomméeLeeksReaper()LeLeeksReaper()La fonction est très facile à comprendre, elle peut être comprise comme le constructeur du module logique de la stratégie leeksreaper (un objet).LeeksReaper()Il est responsable de la construction d'une logique de négociation des poires.

Les mots clés:

img

· La première ligne de la stratégiemainfonction:var reaper = LeeksReaper(), le code déclare une variable localereaperet appelle ensuite la fonction LeeksReaper() pour construire un objet logique de stratégie qui attribue une valeur àreaper.

La prochaine étape de la stratégiemainfonction:

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

Entrez unewhileboucle sans fin et continuer à exécuter la fonction de traitementpoll()de l'annéereaperl'objet, lepoll()la fonction est exactement où se trouve la logique principale de la stratégie de trading et l'ensemble du programme de stratégie commence à exécuter la logique de trading encore et encore. Pour ce qui est de la ligneSleep(TickInterval), il est facile à comprendre, il est de contrôler le temps de pause après chaque exécution de la logique globale de négociation, dans le but de contrôler la fréquence de rotation de la logique de négociation.

AnalyseLeeksReaper()constructeur

Regardez comment leLeeksReaper()La fonction construit un objet logique de stratégie.

LeLeeksReaper()la fonction démarre par la déclaration d'un objet vide,var self = {}, et lors de l'exécution de laLeeksReaper()fonction ajoutera progressivement des méthodes et des attributs à cet objet vide, terminant finalement la construction de cet objet et le renvoyant (c'est-à-dire l'étape demain()fonction à l'intérieur de lavar reaper = LeeksReaper(), l'objet retourné est attribué àreaper).

Ajouter des attributs auselfobjet Ensuite, j'ai ajouté beaucoup d'attributs àself. Je vais décrire chaque attribut comme suit, qui peut comprendre le but et l'intention de ces attributs et variables rapidement, faciliter la compréhension des stratégies, et éviter d'être confus en voyant le 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

Ajouter des méthodes aux objets self

Après avoir ajouté ces attributs à soi, commencer à ajouter des méthodes à laselfobjet de sorte que cet objet peut faire un certain travail et avoir certaines fonctions.

La première fonction ajoutait:

    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)

    }

La fonctionupdateTradesIl s'agit d'obtenir les données les plus récentes sur les transactions de marché et de faire des calculs basés sur les données et de les enregistrer pour les utiliser dans la logique ultérieure de la stratégie. Les commentaires ligne par ligne que j'ai écrits dans le code ci-dessus directement. Pour_.reduce, quelqu'un qui n'a pas d'apprentissage de base de la programmation peut être confus._.reduceest une fonction de la bibliothèque Underscore.js. La stratégie FMZJS prend en charge cette bibliothèque, il est donc très pratique pour le calcul itératif.https://underscorejs.net/#reduce)

La signification est également très simple, par exemple:

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
}

C'est-à-dire, additionner chaque nombre dans le tableau[1, 2, 3, 4]Pour revenir à notre stratégie, nous additionnons les valeurs du volume de négociation de chaque transaction enregistrée dans letradesObtenez un total du dernier volume de transactionself.vol = 0.7 * self.vol + 0.3 * _.reduce (...), ici nous utilisons...Il n'est pas difficile de voir le calcul deself.volest également une moyenne pondérée, c'est-à-dire que le volume de négociation nouvellement généré représente 30% du total et que le dernier volume de négociation pondéré représente 70%. Pour ce qui est de votre question, si l'interface pour obtenir les dernières données de transaction retourne aux anciennes données en double, alors les données que j'ai obtenues sont erronées, et cela n'aura pas de sens?

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

L'accumulation n'est déclenchée que lorsque l'identifiant est supérieur à l'identifiant du dernier enregistrement, ou si l'interface d'échange ne fournit pas d'identifiant, c'est-à-diretrade.Id == 0, utilisez l'horodatage dans l'enregistrement de transaction pour juger.self.lastTradeIdstocke l'horodatage de l'enregistrement de transaction au lieu de l'ID.

La deuxième fonction ajoutait:

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

Ensuite, regardons la fonctionupdateOrderBook. D'après le nom de la fonction, nous pouvons voir qu'elle est utilisée pour mettre à jour le carnet de commandes. Cependant, elle ne met pas à jour le carnet de commandes seulement. La fonction commence à appeler la fonction FMZ API.GetDepth()pour obtenir les données actuelles du carnet de commandes du marché (vendre un... vendre n, acheter un... acheter n), et enregistrer les données du carnet de commandes dansself.orderBook. Ensuite, jugez si l'ordre d'achat et l'ordre de vente des données du carnet de commandes sont inférieurs à 3, si c'est le cas, la fonction invalide sera retournée directement.

Ensuite, deux données sont calculées:

· Calcul du prix du connaissement Le prix du connaissement est également calculé en utilisant la méthode de la moyenne pondérée. Lors du calcul de l'ordre d'achat, la pondération donnée au prix d'achat le plus proche du prix de transaction est de 61,8% (0,618) et la pondération donnée au prix de vente le plus proche du prix de transaction est de 38,2% (0,382) Lors du calcul du prix de la facture, le même poids est donné au prix de vente le plus proche du prix de la transaction.

· Mise à jour du prix moyen pondéré des trois premiers niveaux du carnet de commandes sur les séries chronologiques Pour les trois premiers niveaux de prix des ordres d'achat et de vente dans le carnet de commandes, la moyenne pondérée est calculée. Le poids du premier niveau est de 0,7, le poids du deuxième niveau est de 0,2 et le poids du troisième niveau est de 0,1. Quelqu'un peut dire: Oh, non, il y a 0,7, 0,2, 0,1 dans le code. Élargissons le calcul:

(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

Comme on peut le voir ici, le prix calculé final est en réalité une réponse à la position de prix du milieu de la troisième ouverture sur le marché actuel. Ensuite, utilisez ce prix calculé pour mettre à jour le tableau.self.prices, en éliminant l'une des données les plus anciennes (à travers leshift()La fonction de l'établissement est de mettre à jour l'une des données les plus récentes (par l'intermédiaire de l'établissement).push()fonction, shift et push sont des méthodes de l'objet de matrice de langage JS, vous pouvez vérifier les données JS pour plus de détails).self.prices, qui est un flux de données avec un ordre de séries chronologiques.

Alors, on se repose ici, et on se voit dans le prochain numéro ~


Relationnée

Plus de