Estrategia de cobertura al contado de criptomonedas (1)

El autor:No lo sé., Creado: 2022-04-14 11:57:46, Actualizado: 2022-04-14 16:32:56

Estrategia de cobertura al contado de criptomonedas (1)

Para los principiantes en el diseño de estrategias, la estrategia de cobertura es muy buena para la práctica.

Funciones de diseño y parámetros por requerimiento de estrategia

En primer lugar, debemos asegurarnos de que la estrategia a diseñar sea una estrategia de cobertura spot de criptomonedas. Diseñamos la cobertura más simple. Solo vendemos en la plataforma con el precio más alto entre las dos plataformas spot, y compramos en la plataforma con el precio más bajo para ganar el spread de precio. Cuando la plataforma con el precio más alto está llena de símbolos de moneda de cotización (porque el precio es alto, todos los símbolos de moneda se venden), o cuando la plataforma con el precio más bajo está llena de símbolos de moneda (porque el precio es bajo, los símbolos de moneda se compran por todos los activos), no se puede cubrir. En este momento, solo puede esperar a que el precio se invierta para cubrir.

Para el precio de la orden y el monto durante la cobertura, hay límites de precisión en cada plataforma, y también hay un límite en el monto mínimo de la orden. Además del límite mínimo, la estrategia también necesita considerar el monto máximo de la orden para una cobertura. Si la cantidad de la orden es demasiado grande, el mercado no tendrá un volumen de orden suficiente para eso. También es necesario considerar cómo convertir el tipo de cambio si las dos plataformas tienen monedas de cotización diferentes. La tarifa de manejo durante la cobertura y el deslizamiento del tomador de la orden son todos los costos comerciales. La cobertura no siempre ocurre mientras haya una diferencia de precio. Por lo tanto, el diferencial de precio de cobertura también tiene un valor de disparador. Si es menor que un cierto diferencial de precio, la cobertura producirá una pérdida.

En base a ello, la estrategia debe diseñarse con varios parámetros:

  • Se trata de los siguientes tipos:hedgeDiffPriceCuando el diferencial exceda el valor, se activará una cobertura.
  • Valor mínimo de la cobertura:minHedgeAmount, el importe mínimo de la orden ( importe del símbolo) disponible para una cobertura.
  • Valor máximo de la cobertura:maxHedgeAmount, el importe máximo de la orden ( importe del símbolo) disponible para una cobertura.
  • Precisión del precio A:pricePrecisionA, la precisión del precio de la orden (números decimales) de la plataforma A.
  • Precisión del importe del pedido A:amountPrecisionA, la precisión del importe del pedido (cifras decimales) de la plataforma A.
  • Precisión del precio B:pricePrecisionB, la precisión del precio de la orden (números decimales) de la plataforma B.
  • Precisión del importe del pedido B:amountPrecisionB, la precisión del importe del pedido (cifras decimales) de la plataforma B.
  • Tipo de cambio A:rateA, el tipo de cambio de conversión del primer objeto de cambio añadido; el valor predeterminado es 1, lo que indica que no se debe convertir.
  • Tipo de cambio B:rateB, la conversión del tipo de cambio del segundo objeto de cambio añadido; el valor predeterminado es 1, lo que indica que no se debe convertir.

La estrategia de cobertura necesita mantener la cantidad del símbolo de moneda de las dos cuentas sin cambios (es decir, no mantener ninguna posición direccional y mantener la neutralidad), por lo que debe haber una lógica de equilibrio en la estrategia para detectar siempre el saldo.

  • Actualización de los Accs
    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 
    }
    

Después de colocar una orden, si no hay una orden ejecutada, debemos cancelarla a tiempo, y la orden no puede mantenerse pendiente.

  • Cancelar todo
    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)
                }
            }
        })
    }
    

Al equilibrar la cantidad de símbolos de moneda, necesitamos encontrar el precio con una cierta cantidad en un cierto fondo de datos, por lo que necesitamos una función como esta para manejarlo.

  • Obtener el DepthPrice
    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
    }
    

Luego tenemos que diseñar y escribir la operación específica de la orden de cobertura, que necesita ser diseñado para colocar órdenes simultáneamente:

  • el seto
    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()
    }
    

Finalmente, completemos el diseño de la función de equilibrio, que es un poco más complicada.

  • mantener el equilibrio
    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 
        }
    }
    

Estas funciones han sido diseñadas de acuerdo con los requisitos de la estrategia, y podemos comenzar a diseñar la función principal de la estrategia.

Estrategia Diseño de la función principal

En FMZ, la estrategia se ejecuta desde elmainEn el comienzo de lamainFunción, necesitamos hacer algunas inicialización de la estrategia.

  • Nombre del objeto de intercambio Para muchas operaciones en la estrategia de uso de objetos de intercambio, tales como obtener cotizaciones de mercado, la colocación de pedidos, y así sucesivamente, por lo que sería inconveniente utilizar un nombre más largo cada vez, mi pequeño truco es utilizar un nombre corto simple en su lugar, por ejemplo:

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

    Entonces, será más cómodo escribir el código más tarde.

  • Tipo de cambio y precisión

      // 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 uno de los parámetros del tipo de cambio, a saberrateAyrateB, se establece en 1 (el valor predeterminado es 1), es decir,rateA != 1o bienrateB != 1El tipo de cambio no puede ser convertido.

  • Reinicie todas las fechas

    img

    A veces, es necesario eliminar todos los registros y aspirar los registros de datos cuando se inicia la estrategia.isReset, y luego diseñar el código de restablecimiento en la parte de inicialización de la estrategia, por ejemplo:

      if (isReset) {   // when "isReset" is true, reset the data 
          _G(null)
          LogReset(1)
          LogProfitReset()
          LogVacuum()
          Log("Reset all data", "#FF0000")
      }
    
  • Recuperar los datos de la cuenta inicial y actualizar los datos de la cuenta actual Para juzgar el saldo, la estrategia debe registrar continuamente la condición del activo inicial de la cuenta para su comparación con la actual.nowAccsSe utiliza para registrar los datos de la cuenta corriente.updateAccsFunción que acabamos de diseñar para obtener los datos de la cuenta de la plataforma actual.initAccsSe utiliza para registrar el estado inicial de la cuenta (datos como el importe del símbolo de moneda tanto de A como de B, el importe de la moneda de cotización, etc.).initAccsEn primer lugar, utilice el_G()función para restaurar (la función _G registrará datos de forma persistente y puede devolver los datos registrados de nuevo; lea la documentación de la API para obtener detalles:Enlace).
    Si no puede consultar los datos, utilizar la información de la cuenta corriente para asignar y utilizar_G()función para registrar.

    Como el siguiente código:

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

Lógica de negociación, bucle principal en función principal

El código en el bucle principal es el proceso de cada ronda de ejecución de la lógica de la estrategia, y la ejecución repetitiva sin parar construye el bucle principal de la estrategia.

  • Obtener las cotizaciones del mercado y juzgar la validez

          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 
          }
    

    Aquí se puede ver que la función concurrenteexchange.Gode la plataforma FMZ se utiliza para crear objetos concurrentesdepthARoutineydepthBRoutineque llaman elGetDepth()Cuando estos dos objetos concurrentes se crean, elGetDepth()Interfaz se llama inmediatamente, y ambas solicitudes de los datos de profundidad se envían a la plataforma. Entonces, llama alwait()método del objetodepthARoutiney objetodepthBRoutinepara obtener los datos de profundidad. Una vez obtenidos los datos de profundidad, es necesario comprobar los datos de profundidad para juzgar su validez.continuese activa la instrucción para volver a ejecutar el bucle principal.

  • Utilizaciónprice spreado bienspread 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 términos de parámetros, hemos hecho tal diseño.muestrao bienescondersebasado en un parámetro, por lo que podemos hacer un parámetro para decidir si utilizarprice spread, ospread ratio.

    img

    El parámetrodiffAsPercentageLos otros dos parámetros, que se mostrarán o ocultarán en función del parámetro, se establecen como:hedgeDiffPrice@!diffAsPercentage; cuandodiffAsPercentagees falso, se mostrará.hedgeDiffPercentage@diffAsPercentage; cuandodiffAsPercentagees verdad, se mostrará.
    Después del diseño, hemos comprobado eldiffAsPercentageSi el valor de la cobertura es el valor de la cobertura, el valor de la cobertura es el valor de la cobertura.diffAsPercentageSi el parámetro no se comprueba, el diferencial de precios se utiliza como condición de activación de la cobertura.

  • El juez 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 
              }            
          }
    

    Hay varias condiciones de activación para la cobertura: 1.En primer lugar, cumplir con el diferencial de cobertura; sólo cuando el diferencial de mercado cumple con el parámetro de diferencial establecido, puede ser posible la cobertura.

    2.El importe de cobertura del mercado debe cumplir el importe mínimo de cobertura establecido en los parámetros.Debido a que el importe mínimo de la orden de las diferentes plataformas es diferente, se debe elegir el más pequeño de los dos.

    3.Los activos en la plataforma con la operación de venta son suficientes para vender, y los activos en la plataforma con la operación de compra son suficientes para comprar.Cuando se cumplan estas condiciones, ejecutar la función de cobertura para colocar órdenes por cobertura.Antes de la función principal, declaramos una variableisTradeEn este caso, si se activa la cobertura, la variable se establece entrue. Y restablecer la variable globallastKeepBalanceTSa 0 (lastKeepBalanceTS se utiliza para marcar la marca de tiempo de la última operación de balance, y si se establece a 0 se activará inmediatamente la operación de balance), y luego se cancelarán todas las órdenes pendientes.

  • Operación de equilibrio

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

    Se puede ver que la función de equilibrio se ejecuta periódicamente, pero si ellastKeepBalanceTSSi el valor de la operación de cobertura se restablece a 0, la operación de balance se activará inmediatamente.

  • Información de la barra de estado

          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 barra de estado no está diseñada para ser particularmente complicada. Muestra la hora actual, el diferencial de precios de la plataforma A a la plataforma B, así como el diferencial de precios de B a A; también muestra el diferencial de meta de cobertura actual, los datos de activos de la plataforma A y los datos de activos de la plataforma B.

Procesamiento de pares de operaciones para monedas de cotización diferentes

En cuanto a los parámetros, hemos diseñado el parámetro de conversión del valor del tipo de cambio, y también hemos diseñado la conversión del tipo de cambio en la operación inicial delmainEn la actualidad, la mayoría de los proyectos de investigación y desarrollo se centran en el desarrollo de nuevas tecnologías de la información.SetRateLa función de conversión de tipo de cambio debe ejecutarse primero.

Para la función afectará dos aspectos:

  • Conversión de precios en todos los datos de cotización de mercado, datos de pedidos y datos de posición.
  • Conversión de las monedas de cotización en activos de la cuenta.

Por ejemplo, el par de operaciones actual esBTC_USDT, la unidad de precio esUSDT, y la moneda de cotización disponible en los activos de la cuenta es tambiénUSDTSi quiero convertir el valor de los activos en CNY, establecerexchange.SetRate(6.8)en el código para convertir los datos obtenidos por todas las funciones bajo elexchangeobjetos, y luego convertirlos en CNY. Para convertir a qué moneda de cotización, importarel tipo de cambio de la moneda de cotización actual a la moneda de cotización objetivoEn elSetRate function.

Estrategia completa:Estrategia de cobertura al contado de diferentes monedas de cotización (Teaching)


Más.