Anfänger, schauen Sie es sich an Ich bringe Sie zum Quantitative Trading mit Kryptowährungen (6)

Schriftsteller:- Ich bin ein Idiot., Erstellt: 2022-04-21 18:13:03, Aktualisiert: 2022-04-22 12:00:05

Anfänger, schauen Sie es sich an Ich bringe Sie zum Quantitative Trading mit Kryptowährungen (6)

In dem letzten Artikel haben wir gemeinsam eine einfache Gitterstrategie erstellt. In diesem Artikel haben wir diese Strategie in eine Multi-Symbol-Spot-Gitterstrategie aktualisiert und erweitert und diese Strategie in der Praxis testen lassen. Der Zweck ist es nicht, einen "heiligen Gral" zu finden, sondern verschiedene Probleme und Lösungen im Prozess der Strategieentwicklung zu diskutieren. Dieser Artikel wird einige meiner Erfahrungen bei der Gestaltung der Strategie erklären. Der Inhalt dieses Artikels ist etwas kompliziert und erfordert eine gewisse Grundlagenbildung in der Programmierung.

Das Denken über Design basierend auf Strategieanforderungen

In diesem Artikel, wie im vorherigen, diskutieren wir über das Design auf Basis der FMZ Quant Trading Platform (FMZ.COM).

  • Mehrfaches Symbol Ich möchte Ihnen sagen, daß ich die Grid-StrategieBTC_USDT, aber auchLTC_USDT/EOS_USDT/DOGE_USDT/ETC_USDT/ETH_USDTWie auch immer, für die Spot-Handel Paare, betreiben Sie die Gitter Handel aller Symbole, die Sie gleichzeitig handeln möchten.

    Ja, es fühlt sich gut an, die vibrierenden Marktkurse von mehreren Symbolen einzufangen.
    Obwohl die Anforderung einfach klingt, wird sie schwierig, wenn man anfängt zu entwerfen.

      1. Zuerst, die Marktquote von mehreren Symbolen zu erhalten. Es ist das erste Problem zu lösen. Nach dem Lesen der Plattform-API-Dokumentation, fand ich, dass Plattformen in der Regel die aggregierten Schnittstellen zur Verfügung stellen. Okay, wir verwenden die aggregierte Marktdaten-Schnittstelle, um die Daten zu erhalten.
      1. Das zweite Problem sind die Kontovermögen. Weil wir Multi-Symbol-Strategien ausführen wollen, sollten wir in Betracht ziehen, die Vermögenswerte jedes Handelspares separat zu verwalten und alle Vermögensdaten zu erhalten und sie einmal aufzuzeichnen. Warum sollten wir die Kontovermögensdaten erhalten? Und warum auch jedes Handelspärchen separat aufzeichnen?

    Da Sie bei der Auftragserteilung die verfügbaren Vermögenswerte beurteilen müssen, ist es nicht notwendig, die Daten vor der Beurteilung zu erhalten? Wird die erste Aktiva-Bilanz erst erfasst und dann die Aktiva-Bilanz der Leistungsbilanz ermittelt und der Gewinn und Verlust durch Vergleich mit der ersten Berechnung berechnet? Glücklicherweise gibt die Asset-Account-Schnittstelle einer Plattform normalerweise alle Währungs-Asset-Daten zurück, also müssen wir sie nur einmal erhalten und dann die Daten verarbeiten.

      1. Strategieparameter-Design. Das Parameter-Design einer Multi-Symbol-Strategie unterscheidet sich deutlich vom Parameter-Design einer Single-Symbol-Strategie, denn selbst die Handelslogik jedes Symbols in der Multi-Symbol-Strategie ist die gleiche, es ist möglich, dass die Parameter jedes Symbols während des Handels unterschiedlich sind. Zum Beispiel möchten Sie in der Gitterstrategie möglicherweise jedes Mal 0,01 BTC handeln, wenn Sie BTC_USDT-Handelspaar machen, aber es ist offensichtlich unangemessen, diesen Parameter (Handel mit 0,01 Währung) zu verwenden, wenn Sie DOGE_USDT machen. Natürlich können Sie auch nach der Menge von USDT verarbeiten. Aber es wird immer noch Probleme geben. Was ist, wenn Sie 1000U mit BTC_USDT und 10U mit DOGE_USDT handeln wollen? Die Nachfrage kann nie erfüllt werden. Wahrscheinlich könnten einige Studenten über die Frage nachdenken und vorschlagen, dass Ich mehr Gruppen von Parametern festlegen und die Parameter der verschiedenen zu handelnden Paare separat steuern kann. Das kann immer noch nicht flexibel den Bedarf erfüllen, für wie viele Gruppen von Parametern sollte festgelegt werden? Wir setzen 3 Gruppen; was ist, wenn wir wollen, um 4 Symbole zu betreiben? müssen wir die Strategie zu ändern und Parameter zu erhöhen. Daher sollten Sie bei der Gestaltung von Multi-Symbol-Strategieparametern die Notwendigkeit der Differenzierung berücksichtigen. Eine Lösung besteht darin, die Parameter in gemeinsame Zeichenfolgen oder JSON-Zeichnungen zu gestalten.
        Zum Beispiel:
      ETHUSDT:100:0.002|LTCUSDT:20:0.1
      

      Die Angabe der Daten für jedes Symbol wird mit verwendet, um anzuzeigen, dassETHUSDT:100:0.002das Handelspaar ETH_USDT steuert undLTCUSDT:20:0.1Der in der Mitte spielt eine Rolle der Segmentierung. InETHUSDT:100:0.002, ETHUSDT repräsentiert das Handelspaar, das Sie betreiben möchten; 100 ist der Rasterabstand; 0,002 ist der gehandelte ETH-Betrag jedes Rasters; : wird verwendet, um die oben genannten Daten zu spalten (sicherlich werden die Regeln der Parameter vom Strategieentwickler festgelegt; Sie können entwerfen, was Sie wollen, basierend auf Ihren Bedürfnissen).
      Diese Zeichenfolgen enthalten bereits die Parameterinformationen jedes Symbols, das Sie bedienen müssen. Sie können die Zeichenfolgen analysieren und den Variablen in der Strategie Werte zuweisen, um die Handelslogik jedes Symbols zu steuern. Wie analysieren? Lassen Sie uns das oben erwähnte Beispiel verwenden.

      function main() {
          var net = []  // the recorded grid parameters; when specifically running the grid trading logic, use the data from here 
          var params = "ETHUSDT:100:0.002|LTCUSDT:20:0.1"
          var arrPair = params.split("|")
          _.each(arrPair, function(pair) {
              var arr = pair.split(":")
              var symbol = arr[0]              // trading pair name 
              var diff = parseFloat(arr[1])    // grid spacing 
              var amount = parseFloat(arr[2])  // grid order amount 
              net.push({symbol : symbol, diff : diff, amount : amount})
          })
          Log("Grid parameter data:", net)
      }
      

      img

      Siehst du, wir haben die Parameter analysiert. Sicher, du kannst direkt JSON-Strings verwenden, was einfacher ist.

      function main() {        
          var params = '[{"symbol":"ETHUSDT","diff":100,"amount":0.002},{"symbol":"LTCUSDT","diff":20,"amount":0.1}]'
          var net = JSON.parse(params)  // the recorded grid parameters; when specifically running the grid trading logic, use the data from here         
          _.each(net, function(pair) {
              Log("Trading pair:", pair.symbol, pair)
          })
      }
      

      img

      1. Datenbeständigkeit Es gibt einen großen Unterschied zwischen einer praktischen Strategie und einer Lehrstrategie. Die Lehrstrategie im letzten Artikel ist nur für den ersten Test der Strategie Logik und Deign. Es gibt mehr Probleme, um die man sich Sorgen machen muss, wenn man tatsächlich eine Strategie in einem Bot ausführt. Wenn man einen Bot ausführt, kann der Bot gestartet und gestoppt werden. Zu diesem Zeitpunkt werden alle Daten während des Laufens des Bots verloren gehen. Wie kann man den vorherigen Status bei dem Neustart des Bots fortsetzen, nachdem der Bot gestoppt wurde? Hier ist es notwendig, die Schlüsseldaten dauerhaft zu speichern, wenn der Bot ausgeführt wird, so dass die Daten gelesen werden können und der Bot weiterhin ausgeführt wird, wenn der Bot neu gestartet wird. Sie können die_G()Funktion auf FMZ Quant, oder die Funktion Operation verwendenDBExec()in der Datenbank, und Sie können die FMZ API Dokumentation für Details abfragen.

      Zum Beispiel wollen wir eine Reinigungsfunktion mit der Funktion_G(), um die Netzdaten zu speichern.

      var net = null 
      function main() {  // strategy main function 
          // first read the stored net 
          net = _G("net")
          
          // ...
      }
      
      function onExit() {
          _G("net", net)
          Log("Execute the clean-up processing, and save the data", "#FF0000")
      }
      
      function onexit() {    // the onexit function defined by the platform system, which will be triggered when clicking the bot to stop 
          onExit()
      }
      
      function onerror() {   // the onerror function defined by the platform system, which will be triggered when the program exception occurs 
          onExit()
      }
      
      1. Grenzen für die Präzision des Auftragsbetrags, die Präzision des Auftragspreises, das Mindestbestellvolumen, den Mindestbestellbetrag usw.

      Das Backtest-System hat keine so strengen Grenzen für das Auftragsvolumen und die Auftragspräzision; aber in einem Bot hat jede Plattform strenge Standards für den Auftragspreis und das Auftragsvolumen, und verschiedene Handelspare haben unterschiedliche Grenzen. Daher testen Anfänger OKEX oft im Backtest-System. Sobald die Strategie auf einem Bot ausgeführt wurde, gibt es verschiedene Probleme, wenn ein Handel ausgelöst wird, und dann wird der Inhalt der Fehlermeldung nicht gelesen und verschiedene verrückte Phänomene erscheinen.

      Für Multi-Symbol-Fälle ist die Anforderung komplizierter. Für eine Single-Symbol-Strategie können Sie einen Parameter entwerfen, um Informationen wie Präzision anzugeben. Wenn Sie jedoch eine Multi-Symbol-Strategie entwerfen, ist es offensichtlich, dass das Schreiben der Informationen in einen Parameter den Parameter sehr mühsam macht.

      Wenn diese Schnittstellen vorhanden sind, können Sie eine automatische Zugriffsoberfläche in der Strategie entwerfen, um Informationen wie Präzision zu erhalten, und sie in die Handelspaarinformationen im Handel konfigurieren (kurz gesagt, die Präzision wird automatisch von der Plattform erhalten und dann an die Variable angepasst, die mit dem Strategieparameter zusammenhängt).

      1. Anpassung einer anderen Plattform Warum wird das Problem am Ende erwähnt? Die Bearbeitung aller oben genannten Probleme führt zu dem letzten Problem, denn wir planen, die aggregierte Marktschnittstelle in der Strategie einzusetzen, die Anpassung des Zugangs, die Anpassung der Da unsere Strategie die Verwendung der aggregierten Marktoberfläche vorsieht, werden Lösungen wie der Zugriff auf die Präzision des Handelspares der Plattform und andere Datenanpassungen sowie der Zugriff auf die Kontoinformationen, um jedes Handelspaar separat zu verarbeiten, etc. aufgrund verschiedener Plattformen große Unterschiede mit sich bringen. Für Spot-Plattformen ist der Unterschied vergleichsweise gering, wenn die Gitterstrategie auf die Futures-Version ausgedehnt wird. Die Unterschiede im Mechanismus jeder Plattform sind noch größer. Eine Lösung besteht darin, eine FMZ-Vorlagenbibliothek zu entwerfen; schreiben Sie das Design der Umsetzung der Differenzierung in der Bibliothek, um die Kopplung zwischen der Strategie selbst und der Plattform zu reduzieren. Der Nachteil davon ist, dass Sie eine Vorlagenbibliothek schreiben müssen, und in dieser Vorlage speziell die Differenzierung auf Basis jeder Plattform implementieren.

Entwerfen einer Vorlagenbibliothek

Auf der Grundlage der obigen Analyse entwerfen wir eine Template-Bibliothek, um die Verbindung zwischen Strategie, Plattformmechanismus und Schnittstelle zu reduzieren.
Wir können die Vorlagebibliothek so gestalten (ein Teil des Codes wird weggelassen):

function createBaseEx(e, funcConfigure) {
    var self = {}
    self.e = e 
    
    self.funcConfigure = funcConfigure
    self.name = e.GetName()
    self.type = self.name.includes("Futures_") ? "Futures" : "Spot"
    self.label = e.GetLabel()
    
    // the interfaces that need to be implemented 
    self.interfaceGetTickers = null   // create a function that asynchronously obtains the aggregated market quote threads
    self.interfaceGetAcc = null       // create a function that asynchronously obtains the account data threads 
    self.interfaceGetPos = null       // obtain positions 
    self.interfaceTrade = null        // create concurrent orders 
    self.waitTickers = null           // wait for the concurrent market quote data  
    self.waitAcc = null               // wait for the account concurrent data 
    self.waitTrade = null             // wait for order concurrent data
    self.calcAmount = null            // calculate the order amount according to the trading pair precision and other data 
    self.init = null                  // initialization; obtain the precision and other data 
    
    // execute the configuration function, to configure objects 
    funcConfigure(self)

    // detect whether all the interfaces arranged by configList can be implemented 
    _.each(configList, function(funcName) {
        if (!self[funcName]) {
            throw "interface" + funcName + "not implemented"
        }
    })
    
    return self
}

$.createBaseEx = createBaseEx
$.getConfigureFunc = function(exName) {
    dicRegister = {
        "Futures_OKCoin" : funcConfigure_Futures_OKCoin,    //  the implementation of OKEX Futures 
        "Huobi" : funcConfigure_Huobi,
        "Futures_Binance" : funcConfigure_Futures_Binance,
        "Binance" : funcConfigure_Binance,
        "WexApp" : funcConfigure_WexApp,                    // the implementation of wexApp
    }
    return dicRegister
}

In der Vorlage implementieren Sie das Schreiben von Codes, die auf eine bestimmte Spielform abzielen; nehmen Sie den FMZ-Simulationsbot WexApp als Beispiel:

function funcConfigure_WexApp(self) {
    var formatSymbol = function(originalSymbol) {
        // BTC_USDT
        var arr = originalSymbol.split("_")
        var baseCurrency = arr[0]
        var quoteCurrency = arr[1]
        return [originalSymbol, baseCurrency, quoteCurrency]
    }

    self.interfaceGetTickers = function interfaceGetTickers() {
        self.routineGetTicker = HttpQuery_Go("https://api.wex.app/api/v1/public/tickers")
    }

    self.waitTickers = function waitTickers() {
        var ret = []
        var arr = JSON.parse(self.routineGetTicker.wait()).data
        _.each(arr, function(ele) {
            ret.push({
                bid1: parseFloat(ele.buy), 
                bid1Vol: parseFloat(-1),
                ask1: parseFloat(ele.sell), 
                ask1Vol: parseFloat(-1),
                symbol: formatSymbol(ele.market)[0],
                type: "Spot", 
                originalSymbol: ele.market
            })
        })
        return ret 
    }

    self.interfaceGetAcc = function interfaceGetAcc(symbol, updateTS) {
        if (self.updateAccsTS != updateTS) {
            self.routineGetAcc = self.e.Go("GetAccount")
        }
    }

    self.waitAcc = function waitAcc(symbol, updateTS) {
        var arr = formatSymbol(symbol)
        var ret = null 
        if (self.updateAccsTS != updateTS) {
            ret = self.routineGetAcc.wait().Info
            self.bufferGetAccRet = ret 
        } else {
            ret = self.bufferGetAccRet
        }
        if (!ret) {
            return null 
        }        
        var acc = {symbol: symbol, Stocks: 0, FrozenStocks: 0, Balance: 0, FrozenBalance: 0, originalInfo: ret}
        _.each(ret.exchange, function(ele) {
            if (ele.currency == arr[1]) {
                // baseCurrency
                acc.Stocks = parseFloat(ele.free)
                acc.FrozenStocks = parseFloat(ele.frozen)
            } else if (ele.currency == arr[2]) {
                // quoteCurrency
                acc.Balance = parseFloat(ele.free)
                acc.FrozenBalance = parseFloat(ele.frozen)
            }
        })
        return acc
    }

    self.interfaceGetPos = function interfaceGetPos(symbol, price, initSpAcc, nowSpAcc) {
        var symbolInfo = self.getSymbolInfo(symbol)
        var sumInitStocks = initSpAcc.Stocks + initSpAcc.FrozenStocks
        var sumNowStocks = nowSpAcc.Stocks + nowSpAcc.FrozenStocks
        var diffStocks = _N(sumNowStocks - sumInitStocks, symbolInfo.amountPrecision)
        if (Math.abs(diffStocks) < symbolInfo.min / price) {
            return []
        }
        return [{symbol: symbol, amount: diffStocks, price: null, originalInfo: {}}]
    }

    self.interfaceTrade = function interfaceTrade(symbol, type, price, amount) {
        var tradeType = ""
        if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
            tradeType = "bid"
        } else {
            tradeType = "ask"
        }
        var params = {
            "market": symbol,
            "side": tradeType,
            "amount": String(amount),
            "price" : String(-1),
            "type" : "market"
        }
        self.routineTrade = self.e.Go("IO", "api", "POST", "/api/v1/private/order", self.encodeParams(params))
    }

    self.waitTrade = function waitTrade() {
        return self.routineTrade.wait()
    }

    self.calcAmount = function calcAmount(symbol, type, price, amount) {
        // obtain the trading pair information 
        var symbolInfo = self.getSymbolInfo(symbol)
        if (!symbol) {
            throw symbol + ",trading pair information not found"
        }
        var tradeAmount = null 
        var equalAmount = null  // record the symbol amount  
        if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
            tradeAmount = _N(amount * price, parseFloat(symbolInfo.pricePrecision))
            // detect the minimum trading amount 
            if (tradeAmount < symbolInfo.min) {
                Log(self.name, " tradeAmount:", tradeAmount, "less than", symbolInfo.min)
                return false 
            }
            equalAmount = tradeAmount / price
        } else {
            tradeAmount = _N(amount, parseFloat(symbolInfo.amountPrecision))
            // detect the minimum trading amount 
            if (tradeAmount < symbolInfo.min / price) {
                Log(self.name, " tradeAmount:", tradeAmount, "less than", symbolInfo.min / price)
                return false 
            }
            equalAmount = tradeAmount
        }
        return [tradeAmount, equalAmount]
    }

    self.init = function init() {   // the function that automatically processes conditions like precision, etc.  
        var ret = JSON.parse(HttpQuery("https://api.wex.app/api/v1/public/markets"))
        _.each(ret.data, function(symbolInfo) {
            self.symbolsInfo.push({
                symbol: symbolInfo.pair,
                amountPrecision: parseFloat(symbolInfo.basePrecision),
                pricePrecision: parseFloat(symbolInfo.quotePrecision),
                multiplier: 1,
                min: parseFloat(symbolInfo.minQty),
                originalInfo: symbolInfo
            })
        })        
    }
}

Es ist sehr einfach, die Vorlage in der Strategie zu verwenden:

function main() {
    var fuExName = exchange.GetName()
    var fuConfigureFunc = $.getConfigureFunc()[fuExName]
    var ex = $.createBaseEx(exchange, fuConfigureFunc)

    var arrTestSymbol = ["LTC_USDT", "ETH_USDT", "EOS_USDT"]
    var ts = new Date().getTime()
    
    // test to obtain the market quotes 
    ex.goGetTickers()
    var tickers = ex.getTickers()
    Log("tickers:", tickers)
    
    // test to obtain the account information 
    ex.goGetAcc(symbol, ts)
    
    _.each(arrTestSymbol, function(symbol) {        
        _.each(tickers, function(ticker) {
            if (symbol == ticker.originalSymbol) {
                // print the market quote data 
                Log(symbol, ticker)
            }
        })

        // print asset data 
        var acc = ex.getAcc(symbol, ts)
        Log("acc:", acc.symbol, acc)
    })
}

Strategie Bot

Es ist sehr einfach, die Strategie basierend auf der obigen Vorlage zu entwerfen und zu schreiben. Die gesamte Strategie hat etwa über 300 Zeilen Code. Das implementiert eine Kryptowährungs-Spot-Multi-Symbol-Gitterstrategie.

img

img

Im Moment hat es Verluste.T_T, so dass der Quellcode nicht bereitgestellt wird.

Es gibt mehrere Registrierungscodes; wenn Sie interessiert sind, können Sie sie in wexApp ausprobieren:

Purchase Address: https://www.fmz.com/m/s/284507
Registration Code:
adc7a2e0a2cfde542e3ace405d216731
f5db29d05f57266165ce92dc18fd0a30
1735dca92794943ddaf277828ee04c27
0281ea107935015491cda2b372a0997d
1d0d8ef1ea0ea1415eeee40404ed09cc

Es gab nur über 200 USD, und als der Bot gerade gestartet wurde, stieß er auf einen großen einseitigen Markt. Er braucht Zeit, um den Verlust zu decken. Der größte Vorteil der Spot-Grid-Strategie ist: sich sicher schlafen! Die Stabilität der Strategie ist in Ordnung, und ich habe sie seit dem 27. Mai nicht mehr geändert.


Mehr