La stratégie de couverture au comptant des crypto-monnaies (1)

Auteur:Je suis désolée., Créé à: 2022-04-14 11:57:46, mis à jour à: 2022-04-14 16:32:56

La stratégie de couverture au comptant des crypto-monnaies (1)

Pour les débutants dans la conception de stratégies, la stratégie de couverture est très bonne pour la pratique.

Fonctions de conception et paramètres selon les exigences stratégiques

Tout d'abord, nous devons nous assurer que la stratégie à concevoir est une stratégie de couverture au comptant de crypto-monnaie. Nous concevons la couverture la plus simple. Nous vendons uniquement sur la plate-forme avec le prix le plus élevé entre les deux plateformes au comptant, et achetons sur la plate-forme avec le prix le plus bas pour gagner l'écart de prix. Lorsque la plate-forme avec le prix le plus élevé est rempli de symboles de devises de devises (parce que le prix est élevé, tous les symboles de devises sont vendus), ou lorsque la plate-forme avec le prix le plus bas est rempli de symboles de devises (parce que le prix est bas, les symboles de devises sont achetés par tous les actifs), il ne peut pas être couvert. À ce moment-là, vous ne pouvez attendre que le prix à inverser pour couvrir.

Pour le prix de l'ordre et le montant pendant la couverture, il y a des limites de précision dans chaque plate-forme, et il y a aussi une limite sur le montant minimum de l'ordre. En plus de la limite minimale, la stratégie doit également considérer le montant maximal de l'ordre pour une couverture. Si le montant de l'ordre est trop grand, le marché n'aura pas un volume d'ordre suffisant pour cela. Il est également nécessaire de considérer comment convertir le taux de change si les deux plateformes ont des devises de devises différentes. Les frais de manutention pendant la couverture et le glissement du preneur d'ordre sont tous des coûts de négociation. La couverture ne se produit pas toujours tant qu'il y a une différence de prix. Par conséquent, l'écart de prix de couverture a également une valeur de déclenchement. S'il est inférieur à un certain écart de prix, la couverture fera une perte.

Sur cette base, la stratégie doit être conçue avec plusieurs paramètres:

  • Différence de couverture:hedgeDiffPrice; lorsque l'écart dépasse la valeur, une couverture est déclenchée.
  • Montant minimum de la couverture:minHedgeAmount, le montant minimum de l'ordre (montant du symbole) disponible pour une couverture.
  • Montant maximal de la couverture:maxHedgeAmount, le montant maximal de l'ordre (montant du symbole) disponible pour une couverture.
  • Précision du prix A:pricePrecisionA, la précision du prix de commande (chiffres décimaux) de la plateforme A.
  • Précision du montant de la commande A:amountPrecisionA, la précision du montant de la commande (chiffres décimaux) de la plateforme A.
  • Précision du prix B:pricePrecisionB, la précision du prix de commande (chiffres décimaux) de la plateforme B.
  • Précision du montant de la commande B:amountPrecisionB, la précision du montant de la commande (chiffres décimaux) de la plateforme B.
  • Taux de change A:rateA, le taux de change de conversion du premier objet de change ajouté; la valeur par défaut est 1, ce qui indique qu'il ne doit pas être converti.
  • Taux de change B:rateB, le taux de change de conversion du deuxième objet de change ajouté; la valeur par défaut est 1, ce qui indique qu'il ne doit pas être converti.

La stratégie de couverture doit maintenir le montant du symbole de monnaie des deux comptes inchangé (c'est-à-dire ne pas détenir de positions directionnelles et maintenir la neutralité), il doit donc y avoir une logique d'équilibre dans la stratégie pour toujours détecter le solde.

  • mise à jourAccs
    function updateAccs(arrEx) {
        var ret = []
        for (var i = 0 ; i < arrEx.length ; i++) {
            var acc = arrEx[i].GetAccount()
            if (!acc) {
                return null
            }
            ret.push(acc)
        }
        return ret 
    }
    

Après avoir placé un ordre, s'il n'y a pas d'ordre exécuté, nous devons l'annuler à temps, et l'ordre ne peut pas être maintenu en attente.

  • annuler tout
    function cancelAll() {
        _.each(exchanges, function(ex) {
            while (true) {
                var orders = _C(ex.GetOrders)
                if (orders.length == 0) {
                    break
                }
                for (var i = 0 ; i < orders.length ; i++) {
                    ex.CancelOrder(orders[i].Id, orders[i])
                    Sleep(500)
                }
            }
        })
    }
    

Lorsque l'équilibrage de la quantité de symboles de monnaie, nous devons trouver le prix avec une certaine quantité dans une certaine profondeur de données, donc nous avons besoin d'une fonction comme celle-ci pour le gérer.

  • Je vais vous donner le prix.
    function getDepthPrice(depth, side, amount) {
        var arr = depth[side]
        var sum = 0
        var price = null
        for (var i = 0 ; i < arr.length ; i++) {
            var ele = arr[i]
            sum += ele.Amount
            if (sum >= amount) {
                price = ele.Price
                break
            }
        }
        return price
    }
    

Ensuite, nous devons concevoir et écrire l'opération d'ordre de couverture spécifique, qui doit être conçue pour placer simultanément des ordres:

  • la haie
    function hedge(buyEx, sellEx, price, amount) {
        var buyRoutine = buyEx.Go("Buy", price, amount)
        var sellRoutine = sellEx.Go("Sell", price, amount)
        Sleep(500)
        buyRoutine.wait()
        sellRoutine.wait()
    }
    

Enfin, terminons la conception de la fonction d'équilibre, qui est un peu plus compliquée.

  • maintenir l'équilibre
    function keepBalance(initAccs, nowAccs, depths) {
        var initSumStocks = 0
        var nowSumStocks = 0 
        _.each(initAccs, function(acc) {
            initSumStocks += acc.Stocks + acc.FrozenStocks
        })
        _.each(nowAccs, function(acc) {
            nowSumStocks += acc.Stocks + acc.FrozenStocks
        })
      
        var diff = nowSumStocks - initSumStocks
        // calculate currency spread 
        if (Math.abs(diff) > minHedgeAmount && initAccs.length == nowAccs.length && nowAccs.length == depths.length) {
            var index = -1
            var available = []
            var side = diff > 0 ? "Bids" : "Asks"
            for (var i = 0 ; i < nowAccs.length ; i++) {
                var price = getDepthPrice(depths[i], side, Math.abs(diff))
                if (side == "Bids" && nowAccs[i].Stocks > Math.abs(diff)) {
                    available.push(i)
                } else if (price && nowAccs[i].Balance / price > Math.abs(diff)) {
                    available.push(i)
                }
            }
            for (var i = 0 ; i < available.length ; i++) {
                if (index == -1) {
                    index = available[i]
                } else {
                    var priceIndex = getDepthPrice(depths[index], side, Math.abs(diff))
                    var priceI = getDepthPrice(depths[available[i]], side, Math.abs(diff))
                    if (side == "Bids" && priceIndex && priceI && priceI > priceIndex) {
                        index = available[i]
                    } else if (priceIndex && priceI && priceI < priceIndex) {
                        index = available[i]
                    }
                }
            }
            if (index == -1) {
                Log("cannot balance")            
            } else {
                // balanced ordering 
                var price = getDepthPrice(depths[index], side, Math.abs(diff))
                if (price) {
                    var tradeFunc = side == "Bids" ? exchanges[index].Sell : exchanges[index].Buy
                    tradeFunc(price, Math.abs(diff))
                } else {
                    Log("invalid price", price)
                }
            }        
            return false
        } else if (!(initAccs.length == nowAccs.length && nowAccs.length == depths.length)) {
            Log("error:", "initAccs.length:", initAccs.length, "nowAccs.length:", nowAccs.length, "depths.length:", depths.length)
            return true 
        } else {
            return true 
        }
    }
    

Ces fonctions ont été conçues selon les exigences de la stratégie, et nous pouvons commencer à concevoir la fonction principale de la stratégie.

Stratégie Conception de la fonction principale

Sur FMZ, la stratégie est exécutée à partir dumainAu début de l'annéemainfonction, nous devons faire une certaine initialisation de la stratégie.

  • Nom de l'objet Exchange Pour beaucoup d'opérations dans la stratégie utilise des objets d'échange, tels que l'obtention de cotations de marché, la mise en place des commandes, et ainsi de suite, il serait donc gênant d'utiliser un nom plus long à chaque fois, mon petit tour est d'utiliser un nom court simple à la place, par exemple:

    var exA = exchanges[0]
    var exB = exchanges[1]
    

    Ensuite, il sera plus confortable d'écrire le code plus tard.

  • Taux de change et précision

      // settings of precision and exchange rate
      if (rateA != 1) {
          // set exchange rate A 
          exA.SetRate(rateA)
          Log("Platform A sets exchange rate:", rateA, "#FF0000")
      }
      if (rateB != 1) {
          // set exchange rate B
          exB.SetRate(rateB)
          Log("Platform B sets exchange rate:", rateB, "#FF0000")
      }
      exA.SetPrecision(pricePrecisionA, amountPrecisionA)
      exB.SetPrecision(pricePrecisionB, amountPrecisionB)
    

    Si l'un des paramètres du taux de change, à savoirrateAetrateB, est réglée sur 1 (la valeur par défaut est 1), c'est-à-direrateA != 1ourateB != 1les taux de change ne sont pas activés et ne peuvent être convertis.

  • Réinitialiser toute la date

    img

    Parfois, il est nécessaire de supprimer tous les journaux et vider les enregistrements de données lorsque la stratégie est lancée.isReset, puis concevoir le code de réinitialisation dans la partie d'initialisation de la stratégie, par exemple:

      if (isReset) {   // when "isReset" is true, reset the data 
          _G(null)
          LogReset(1)
          LogProfitReset()
          LogVacuum()
          Log("Reset all data", "#FF0000")
      }
    
  • Récupérer les données du compte initial et mettre à jour les données du compte courant Afin de juger du solde, la stratégie doit enregistrer en permanence l'état des actifs du compte initial en vue de la comparaison avec l'état actuel.nowAccsest utilisé pour enregistrer les données des comptes courants.updateAccsfonction que nous venons de concevoir pour obtenir les données de compte de la plateforme actuelle.initAccsest utilisé pour enregistrer l'état initial du compte (données telles que le montant du symbole de monnaie de A et de B, le montant de la monnaie de cotation, etc.).initAccs, utilisez d' abord le_G()fonction de restauration (la fonction _G enregistrera les données de manière persistante et peut renvoyer les données enregistrées à nouveau; consultez la documentation de l'API pour plus de détails:lien).
    Si vous ne pouvez pas interroger les données, utilisez les informations de compte courant pour attribuer et utiliser_G()fonction à enregistrer.

    Par exemple, le code suivant:

      var nowAccs = _C(updateAccs, exchanges)
      var initAccs = _G("initAccs")
      if (!initAccs) {
          initAccs = nowAccs
          _G("initAccs", initAccs)
      }
    

La logique de négociation, boucle principale dans la fonction principale

Le code dans la boucle principale est le processus de chaque cycle d'exécution de la logique de stratégie, et l'exécution répétée sans arrêt construit la boucle principale de stratégie.

  • Obtenez les offres du marché et jugez leur validité

          var ts = new Date().getTime()
          var depthARoutine = exA.Go("GetDepth")
          var depthBRoutine = exB.Go("GetDepth")
          var depthA = depthARoutine.wait()
          var depthB = depthBRoutine.wait()
          if (!depthA || !depthB || depthA.Asks.length == 0 || depthA.Bids.length == 0 || depthB.Asks.length == 0 || depthB.Bids.length == 0) {
              Sleep(500)
              continue 
          }
    

    Ici, vous pouvez voir que la fonction concurrenteexchange.Gode la plateforme FMZ est utilisé pour créer des objets simultanésdepthARoutineetdepthBRoutinequi appellent leGetDepth()Lorsque ces deux objets concomitants sont créés, leGetDepth()L'interface est appelée immédiatement, et les deux demandes de données de profondeur sont envoyées à la plateforme. Alors, appelez lewait()méthode de l'objetdepthARoutineet objetdepthBRoutinepour obtenir les données de profondeur. Une fois les données de profondeur obtenues, il est nécessaire de vérifier les données de profondeur pour en juger la validité.continuel'instruction est déclenchée pour réexécuter la boucle principale.

  • Utilisationprice spreadouspread ratio?

          var targetDiffPrice = hedgeDiffPrice
          if (diffAsPercentage) {
              targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
          }
    

    En termes de paramètres, nous avons fait une telle conception.le spectacleouse cacherbasé sur un paramètre, afin que nous puissions faire un paramètre pour décider d'utiliserprice spread, ouspread ratio.

    img

    Le paramètrediffAsPercentageLes deux autres paramètres, qui s'affichent ou se cachent en fonction du paramètre, sont définis comme suit:hedgeDiffPrice@!diffAsPercentage; lorsquediffAsPercentageest faux, il sera montré.hedgeDiffPercentage@diffAsPercentage; lorsquediffAsPercentageest vrai, il sera affiché.
    Après la conception, nous avons vérifié lesdiffAsPercentageLe taux d'intérêt de l'opérateur de couverture est le taux d'intérêt de l'opérateur de couverture.diffAsPercentageSi le paramètre n'est pas vérifié, l'écart de prix est utilisé comme condition de déclenchement de la couverture.

  • Le juge Hedge Trigger

          if (depthA.Bids[0].Price - depthB.Asks[0].Price > targetDiffPrice && Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount) >= minHedgeAmount) {          // A -> B market condition satisfied             
              var price = (depthA.Bids[0].Price + depthB.Asks[0].Price) / 2
              var amount = Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount)
              if (nowAccs[0].Stocks > minHedgeAmount && nowAccs[1].Balance / price > minHedgeAmount) {
                  amount = Math.min(amount, nowAccs[0].Stocks, nowAccs[1].Balance / price, maxHedgeAmount)
                  Log("triggerA->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, price, amount, nowAccs[1].Balance / price, nowAccs[0].Stocks)  // prompt message 
                  hedge(exB, exA, price, amount)
                  cancelAll()
                  lastKeepBalanceTS = 0
                  isTrade = true 
              }            
          } else if (depthB.Bids[0].Price - depthA.Asks[0].Price > targetDiffPrice && Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount) >= minHedgeAmount) {   // B -> A market condition satisfied 
              var price = (depthB.Bids[0].Price + depthA.Asks[0].Price) / 2
              var amount = Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount)
              if (nowAccs[1].Stocks > minHedgeAmount && nowAccs[0].Balance / price > minHedgeAmount) {
                  amount = Math.min(amount, nowAccs[1].Stocks, nowAccs[0].Balance / price, maxHedgeAmount)
                  Log("triggerB->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, price, amount, nowAccs[0].Balance / price, nowAccs[1].Stocks)  // prompt message
                  hedge(exA, exB, price, amount)
                  cancelAll()
                  lastKeepBalanceTS = 0
                  isTrade = true 
              }            
          }
    

    Il existe plusieurs conditions déclenchantes pour la couverture: 1.En premier lieu, respecter le spread de couverture; la couverture n'est possible que lorsque le spread du marché respecte le paramètre de spread fixé.

    2.Le montant de la couverture du marché doit correspondre au montant minimum de couverture fixé dans les paramètres.Parce que le montant minimum des ordres des différentes plateformes est différent, le plus petit des deux doit être choisi.

    3.Les actifs de la plateforme avec l'opération de vente sont suffisants pour vendre, et les actifs de la plateforme avec l'opération d'achat sont suffisants pour acheter. Lorsque ces conditions sont remplies, exécutez la fonction de couverture pour passer des ordres par couverture. Avant la fonction principale, nous déclarons une variableisTradeDans ce cas, si la couverture est déclenchée, la variable est réglée surtrue. Et réinitialisez la variable globalelastKeepBalanceTSà 0 (lastKeepBalanceTS est utilisé pour marquer l'horodatage de la dernière opération de balance, et le régler à 0 déclenchera immédiatement l'opération de balance), puis annuler tous les ordres en attente.

  • Opération de mise en équilibre

          if (ts - lastKeepBalanceTS > keepBalanceCyc * 1000) {
              nowAccs = _C(updateAccs, exchanges)
              var isBalance = keepBalance(initAccs, nowAccs, [depthA, depthB])
              cancelAll()
              if (isBalance) {
                  lastKeepBalanceTS = ts
                  if (isTrade) {
                      var nowBalance = _.reduce(nowAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
                      var initBalance = _.reduce(initAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
                      LogProfit(nowBalance - initBalance, nowBalance, initBalance, nowAccs)
                      isTrade = false 
                  }                
              }            
          }
    

    On peut voir que la fonction de balance est exécutée périodiquement, mais si lalastKeepBalanceTSSi la valeur de l'opération de couverture est réinitialisée à 0 après le déclenchement de l'opération de couverture, l'opération de solde est immédiatement déclenchée.

  • Informations de la barre d'état

          LogStatus(_D(), "A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, " B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, " targetDiffPrice:", targetDiffPrice, "\n", 
              "currentA,Stocks:", nowAccs[0].Stocks, "FrozenStocks:", nowAccs[0].FrozenStocks, "Balance:", nowAccs[0].Balance, "FrozenBalance", nowAccs[0].FrozenBalance, "\n", 
              "currentB,Stocks:", nowAccs[1].Stocks, "FrozenStocks:", nowAccs[1].FrozenStocks, "Balance:", nowAccs[1].Balance, "FrozenBalance", nowAccs[1].FrozenBalance, "\n", 
              "initialA,Stocks:", initAccs[0].Stocks, "FrozenStocks:", initAccs[0].FrozenStocks, "Balance:", initAccs[0].Balance, "FrozenBalance", initAccs[0].FrozenBalance, "\n", 
              "initialB,Stocks:", initAccs[1].Stocks, "FrozenStocks:", initAccs[1].FrozenStocks, "Balance:", initAccs[1].Balance, "FrozenBalance", initAccs[1].FrozenBalance)
    

    La barre d'état n'est pas conçue pour être particulièrement compliquée. Elle affiche l'heure actuelle, l'écart de prix de la plateforme A à la plateforme B ainsi que l'écart de prix de la plateforme B à A; elle affiche également l'écart cible de couverture actuel, les données d'actifs du compte de la plateforme A et les données d'actifs du compte de la plateforme B.

Traitement des paires de négociation pour des devises de cotation différentes

En ce qui concerne les paramètres, nous avons conçu le paramètre de conversion de la valeur du taux de change, et nous avons également conçu la conversion du taux de change dans le fonctionnement initial de lamainIl convient de noter que laSetRateLa fonction de conversion des taux de change doit être exécutée en premier.

Pour la fonction affectera deux aspects:

  • Conversion des prix dans toutes les données relatives aux cotations de marché, aux données relatives aux ordres et aux données relatives aux positions.
  • Conversion des monnaies de cotation en actifs du compte.

Par exemple, la paire de négociation actuelle estBTC_USDT, l' unité de prix estUSDT, et la devise de cotation disponible dans les actifs du compte est égalementUSDTSi je veux convertir la valeur des actifs en CNY, définirexchange.SetRate(6.8)dans le code pour convertir les données obtenues par toutes les fonctionsexchangeL'objectif est d'obtenir un taux de conversion en CNY. Pour convertir dans quelle devise de devises, importerle taux de change de la devise de cotation courante à la devise de cotation cibledans leSetRate function.

Stratégie complète:Stratégie de couverture au comptant de devises de cotation différentes (enseignement)


Plus de