Les ressources ont été chargées... Je charge...

Commerce quantitatif de crypto-monnaie pour les débutants - vous rapprocher de la crypto-monnaie quantitative (6)

Auteur:FMZ~Lydia, Créé: 2022-08-05 17:13:26, Mis à jour: 2023-09-21 21:02:17

img

Dans l'article précédent, nous avons fait une stratégie de grille simple ensemble. Dans cet article, nous avons mis à niveau et élargi cette stratégie en une stratégie de grille de points multi-espèces, et laissons cette stratégie être testée en pratique. Le but n'est pas de trouver un " saint graal ", mais de discuter de divers problèmes et solutions lors de la conception de stratégies. Cet article expliquera une partie de mon expérience dans la conception de cette stratégie. Le contenu de cet article est légèrement compliqué et nécessite une certaine base en programmation.

Réflexion de conception basée sur les besoins stratégiques

Cet article, comme le précédent, traite encore de la conception basée sur le FMZ Quant (FMZ.COM).

  • Multifonctions Pour être franc, je pense que cette stratégie de réseau ne peut pas seulementBTC_USDT, mais aussiLTC_USDT/EOS_USDT/DOGE_USDT/ETC_USDT/ETH_USDTQuoi qu'il en soit, les paires de négociation au comptant et les variétés qui veulent courir sont toutes négociées sur la grille en même temps.

    Ça fait du bien de capturer le marché volatil de plusieurs espèces. L'exigence semble très simple, et le problème vient lors de la conception.

      1. La première étape est d'obtenir les cotations de marché de plusieurs variétés. C'est le premier problème à résoudre. Après avoir consulté la documentation API des bourses, j'ai constaté que la plupart des bourses fournissent des interfaces de marché agrégées. OK, utilisez l'interface de marché agrégée pour obtenir des données.
      1. Le deuxième problème rencontré est les actifs du compte. Parce que c'est une stratégie multi-espèces, il est nécessaire de considérer la gestion de chaque actif de paire de négociation séparément. Et nous devons obtenir des données pour tous les actifs à la fois, et les enregistrer. Pourquoi avons-nous besoin d'obtenir les données des actifs du compte? Pourquoi devons-nous séparer les enregistrements de chaque paire? Parce que vous devez juger des actifs disponibles lors de la passation d'une commande. Et vous avez besoin de calculer les profits, est-il également nécessaire d'enregistrer un compte initial des données d'actifs d'abord, puis obtenir les données d'actifs de compte courant et de les comparer avec la première pour calculer le bénéfice et la perte? Heureusement, l'interface de compte d'actifs de l'échange renvoie généralement toutes les données d'actifs de devises, nous n'avons qu'à les obtenir une fois, puis traiter les données.
      1. Conception de paramètres de stratégie. La conception de paramètres de multi-espèce est assez différente de la conception de paramètres de single-variété, bien que la logique de trading de chaque variété de multi-variété soit la même, il est possible que les paramètres pendant le trading soient différents. Par exemple, dans la stratégie de grille, vous pouvez vouloir échanger 0,01 BTC à chaque fois lorsque vous faites la paire de trading BTC_USDT, mais il est évidemment inapproprié d'utiliser ce paramètre (trading 0,01 pièces) lorsque vous faites DOGE_USDT. Bien sûr, vous pouvez également traiter le montant USDT. Mais il y aura toujours des problèmes. Et si vous voulez échanger 1000U pour BTC_USDT et 10U pour DOGE_USDT? La demande ne peut jamais être satisfaite. Il peut y avoir quelqu'un qui réfléchira à ce problème et demandera ensuite: Je peux définir plusieurs ensembles de paramètres pour contrôler les paramètres de différentes paires de trading à faire séparément. Cela n'est toujours pas assez flexible pour répondre aux besoins, combien d'ensembles de paramètres sont bons à définir? Trois ensembles de paramètres sont définis, et si je veux faire 4 variétés? Dois-je modifier la stratégie et augmenter les paramètres?.. Par conséquent, lors de la conception des paramètres de la stratégie multi-espèces, il est nécessaire de tenir pleinement compte des besoins de ces paramètres différenciés. Par exemple:
      ETHUSDT:100:0.002|LTCUSDT:20:0.1
      

      Parmi eux, gaman divise les données de chaque espèce, ce qui signifie queETHUSDT:100:0.002contrôle la paire de négociation ETH_USDT, etLTCUSDT:20:0.1La valeur de l'échange est calculée en fonction de la valeur de l'échange.ETHUSDT:100:0.002, où ETHUSDT indique quelle paire de trading vous voulez faire, 100 est l'espacement de la grille, 0,002 est le nombre de pièces ETH négociées dans chaque grille, et le : est de diviser ces données (bien sûr, ces règles de paramètres sont faites par le concepteur de stratégie, vous pouvez concevoir n'importe quoi en fonction de vos besoins). Ces chaînes contiennent les informations de paramètres de chaque espèce que vous voulez faire. Parser ces chaînes dans la stratégie, et attribuer des valeurs aux variables de la stratégie pour contrôler la logique de trading de chaque espèce. Comment l'analyser? Utilisez toujours l'exemple ci-dessus.

      function main() {
          var net = []  // The recorded grid parameters, use the data when running to the grid trading logic
          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 volume
              net.push({symbol : symbol, diff : diff, amount : amount})
          })
          Log("Grid parameter data:", net)
      }
      

      img

      En regardant cela, les paramètres sont analysés. Bien sûr, vous pouvez également utiliser des chaînes JSON directement, ce qui est plus simple.

      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, use the data when running to the grid trading logic        
          _.each(net, function(pair) {
              Log("Trading pairs:", pair.symbol, pair)
          })
      }
      

      img

      1. Durabilité des données Il y a aussi une grande différence entre les stratégies qui peuvent être appliquées dans la pratique et les stratégies tutorielles. Les stratégies tutorielles de l'article précédent ne sont qu'un test préliminaire de la logique et de la conception de la stratégie, et il y a plus de problèmes à considérer en ce qui concerne le monde réel. Dans le bot réel, il est possible de démarrer et d'arrêter le vrai trading. À ce moment-là, toutes les données pendant l'opération du bot réel seront perdues. Alors, comment faire pour que le bot réel redémarre pour continuer à fonctionner dans l'état précédent après son arrêt? Ici, il est nécessaire d'enregistrer les données clés de manière persistante lorsque le vrai bot est en cours d'exécution, de sorte que les données puissent être lues et continuer à fonctionner lorsqu'il est redémarré. Vous pouvez utiliser le_G()fonction sur la plateforme de négociation quantitative FMZ, ou utiliser la fonction d'exploitation de base de donnéesDBExec(), et vous pouvez consulter la documentation FMZ API pour plus de détails.

      Par exemple, nous concevons une fonction de balayage de queue et utilisons le_G()fonction de sauvegarde des données de réseau.

      var net = null 
      function main() {  // Strategy main functions
          // Read the stored net first
          net = _G("net")
          
          // ...
      }
      
      function onExit() {
          _G("net", net)
          Log("Perform tail-sweeping processing and save data", "#FF0000")
      }
      
      function onexit() {    // The exit sweep function defined by the platform system, triggered the execution when the real bot is clicked to stop
          onExit()
      }
      
      function onerror() {   // The abnormal exit function defined by the platform system, triggered the execution when the program is abnormal
          onExit()
      }
      
      1. Limites telles que la précision de la quantité de commande, la précision du prix de commande, la quantité minimale de commande et le montant minimum de la commande, etc.

      Le système de backtesting n'impose pas de restrictions aussi strictes sur le montant de l'ordre et l'exactitude de l'ordre, mais chaque échange peut avoir des normes strictes pour le prix et le montant de l'ordre lors de la passation d'un ordre dans le vrai bot, et ces restrictions ne sont pas les mêmes dans les différents échanges.

      Pour les cas multi-espèces, cette exigence est plus compliquée. Pour une stratégie d'une seule espèce, vous pouvez concevoir un paramètre pour spécifier des informations telles que l'exactitude, mais lors de la conception d'une stratégie multi-espèces, il est évident que l'écriture de ces informations dans les paramètres rendra les paramètres très gonflés.

      À ce stade, vous devez vérifier la documentation API de l'échange pour voir s'il existe des informations d'interface liées aux paires de trading dans la documentation de l'échange. Si elles existent, vous pouvez concevoir une interface d'accès automatique dans la stratégie pour obtenir des informations telles que l'exactitude, et la configurer dans les informations de paire de trading impliquées dans le trading (en bref, l'exactitude ou quelque chose est obtenue de l'échange automatiquement, puis adaptée aux variables liées aux paramètres de la stratégie).

      1. Adaptation aux différents échanges Pourquoi mettre cette question à la fin? Parce que les solutions à ces problèmes dont nous avons parlé ci-dessus apporteront le dernier problème, parce que notre stratégie prévoit d'utiliser l'interface de marché agrégée, l'accès à l'exactitude de la paire de négociation de change et d'autres données adaptatives, l'accès aux informations de compte pour traiter avec chaque paire de négociation séparément, ces solutions peuvent varier considérablement d'un échange à l'autre. Il existe des différences dans les appels d'interface et les mécanismes. Pour les échanges au comptant, la différence est plus petite si la stratégie de grille est étendue à la version à terme. Les différences dans le mécanisme de divers échanges sont encore plus grandes. Une solution consiste à concevoir une bibliothèque de classes de modèles FMZ. Écrivez la conception dans la bibliothèque de classes pour implémenter ces différences. Réduisez le couplage entre la stratégie elle-même et l'échange. L'inconvénient de cela est que vous devez écrire une bibliothèque de classes de modèle et l'implémenter spécifiquement pour chaque différence d'échange dans ce modèle.

Concevoir une bibliothèque de classes de modèle

Sur la base de l'analyse ci-dessus, une bibliothèque de classes de modèle est conçue pour réduire le couplage entre la stratégie et le mécanisme d'échange et l'interface.

Nous pouvons concevoir cette bibliothèque de classes de modèle comme ceci (une partie du code est omise):

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()
    
    // Interfaces to be implemented
    self.interfaceGetTickers = null   // Create a function to asynchronously obtain a thread of aggregated market data
    self.interfaceGetAcc = null       // Create a function that asynchronously obtains account data thread
    self.interfaceGetPos = null       // Get a position
    self.interfaceTrade = null        // Create concurrent orders
    self.waitTickers = null           // Waiting for concurrent market data 
    self.waitAcc = null               // Waiting for account concurrent data
    self.waitTrade = null             // Waiting for order concurrent data
    self.calcAmount = null            // Calculate the order volume based on data such as trading pair accuracy
    self.init = null                  // Initialization work, obtaining data such as accuracy
    
    // Execute the configuration function to configure the object
    funcConfigure(self)

    // Check whether the interfaces agreed by configList are implemented
    _.each(configList, function(funcName) {
        if (!self[funcName]) {
            throw "interface" + funcName + "unimplemented"
        }
    })
    
    return self
}

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

Dans le modèle, il est écrit pour des échanges spécifiques, prenons par exemple le bot simulé de FMZ WexApp:

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 trading pair information
        var symbolInfo = self.getSymbolInfo(symbol)
        if (!symbol) {
            throw symbol + ", the trading pair information cannot be checked"
        }
        var tradeAmount = null 
        var equalAmount = null  // Number of coins recorded
        if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
            tradeAmount = _N(amount * price, parseFloat(symbolInfo.pricePrecision))
            // Check the minimum trading volume
            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))
            // Check the minimum trading volume
            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() {   // Functions that deal with conditions such as accuracy automatically
        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
            })
        })        
    }
}

L'utilisation de ce modèle dans une stratégie est simple:

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 get tickers
    ex.goGetTickers()
    var tickers = ex.getTickers()
    Log("tickers:", tickers)
    
    // Test to obtain account information
    ex.goGetAcc(symbol, ts)
    
    _.each(arrTestSymbol, function(symbol) {        
        _.each(tickers, function(ticker) {
            if (symbol == ticker.originalSymbol) {
                // print ticker data
                Log(symbol, ticker)
            }
        })

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

Stratégie réel bot

Il est très simple de concevoir et d'écrire une stratégie basée sur le modèle ci-dessus.

img

img

Il perd de l'argent actuellement.T_T, le code source ne sera pas publié pour le moment.

Voici quelques codes d'enregistrement, si vous êtes intéressé, vous pouvez utiliser la wexApp pour essayer:

Buy address: https://www.fmz.com/m/s/284507
Registration code: 
adc7a2e0a2cfde542e3ace405d216731
f5db29d05f57266165ce92dc18fd0a30
1735dca92794943ddaf277828ee04c27
0281ea107935015491cda2b372a0997d
1d0d8ef1ea0ea1415eeee40404ed09cc

Juste après 200 U, quand j'ai commencé à courir, j'ai rencontré un grand marché unilatéral, mais je me suis rétabli lentement. La stabilité n'est pas mauvaise, elle n'a pas été modifiée depuis le 27 mai, et le réseau des contrats à terme n'a pas osé tenter temporairement.


Relationnée

Plus de