Diseño de una estrategia de cobertura al contado de criptomonedas (1)

El autor:- ¿ Por qué?, Creado: 2022-08-16 10:30:56, Actualizado: 2023-09-19 21:46:16

img

Diseño de una estrategia de cobertura al contado de criptomonedas (1)

Las estrategias de cobertura son muy buenas estrategias de práctica para principiantes en el diseño de estrategias.

Diseñar algunas funciones y parámetros de interfaz de estrategia de acuerdo con los requisitos de la estrategia

En primer lugar, está claro que la estrategia a diseñar es una estrategia de cobertura spot de criptomonedas. Diseñamos la estrategia de cobertura más simple. Vendemos en el intercambio con el precio más alto solo entre los dos intercambios spot, y compramos en el intercambio con el precio más bajo para tomar la diferencia. Cuando los intercambios con precios más altos son monedas denominadas (porque se venden las monedas con precios más altos), y los intercambios con precios más bajos son monedas (se compran las monedas con precios más bajos), no se puede cubrir. En este momento, solo podemos esperar a que la inversión de precios se cubra.

Cuando se hace cobertura, el precio y la cantidad del pedido están limitados por el intercambio, y también hay un límite en la cantidad mínima de pedido. Además del límite mínimo, la estrategia en la cobertura también necesita considerar el volumen máximo de pedido a la vez. Si el volumen de orden es demasiado grande, no habrá suficiente volumen de pedido. También es necesario considerar cómo convertir el tipo de cambio si las dos monedas denominadas en bolsa son diferentes. Cuando se hace cobertura, la tarifa de manejo y el deslizamiento del tomador de la orden son todos los costos de transacción, no mientras haya una diferencia de precio que pueda cubrirse. Por lo tanto, la diferencia de precio de cobertura también tiene un valor de disparo. Si es menor que una cierta diferencia de precio, la cobertura perderá.

Sobre la base de estas consideraciones, la estrategia debe diseñarse con varios parámetros:

  • Diferencia de cobertura:hedgeDiffPrice, cuando la diferencia exceda este valor, se activa la operación de cobertura.
  • Valor mínimo de la cobertura:minHedgeAmount, el importe mínimo de la orden (monedas) que puede cubrirse.
  • Valor máximo de la cobertura:maxHedgeAmount, el importe máximo de la orden (monedas) para una cobertura.
  • Precisión del precio de A:pricePrecisionA, la precisión del precio de la orden (número de decimales) colocada por el Intercambio A.
  • Precisión de la cantidad de A:amountPrecisionA, la precisión del importe de la orden efectuada por el Intercambio A (número de decimales).
  • Precisión del precio de B:pricePrecisionB, la precisión del precio de la orden (número de decimales) colocada por el Intercambio B.
  • Precisión de la cantidad de B:amountPrecisionB, la precisión del importe de la orden efectuada por el Intercambio B (número de decimales).
  • Tipo de cambio A:rateA, la conversión del tipo de cambio del primer objeto de cambio añadido, por defecto es 1, no convertida.
  • Tipo de cambio B:rateB, la conversión del tipo de cambio del segundo objeto de cambio añadido, por defecto es 1, no convertida.

La estrategia de cobertura necesita mantener el número de monedas en las dos cuentas sin cambios (es decir, no mantener posiciones en ninguna dirección y mantener la neutralidad), por lo que debe haber una lógica de equilibrio en la estrategia para detectar siempre el equilibrio.

  • 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 el pedido, si no hay un pedido completado, necesitamos cancelarlo a tiempo, y el pedido 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 el número de monedas, necesitamos encontrar el precio acumulado a un cierto número de monedas en un cierto dato de profundidad, por lo que necesitamos una función 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 concurrentes:

  • 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 ligeramente 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 the currency difference
        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("unable to balance")            
            } else {
                // balance order
                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("errors:", "initAccs.length:", initAccs.length, "nowAccs.length:", nowAccs.length, "depths.length:", depths.length)
            return true 
        } else {
            return true 
        }
    }
    

Después de diseñar estas funciones de acuerdo con los requisitos de la estrategia, comience a diseñar la función principal de la estrategia.

Diseño de la función principal de la estrategia

En la plataforma FMZ, la estrategia se ejecuta desde elmainEn el comienzo de lamainfunción, tenemos que hacer un poco de trabajo de inicialización de la estrategia.

  • Nombre del objeto de intercambio Debido a que muchas operaciones en la estrategia tienen que usar los objetos de intercambio, como obtener cotizaciones de mercado, realizar pedidos, etc. Por lo que sería engorroso usar un nombre largo cada vez, el consejo es usar un nombre simple en su lugar, por ejemplo:

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

    Esto hace que sea más fácil escribir código más tarde.

  • Tipo de cambio, diseño relacionado con la precisión

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

    Si los parámetros del tipo de cambiorateA, rateBse establecen en 1 (el valor predeterminado es 1), es decir,rateA != 1o bienrateB != 1no se activará, por lo que no se establecerá la conversión del tipo de cambio.

  • Reinicie todos los datos

    img

    A veces es necesario eliminar todos los registros y borrar los datos registrados cuando se inicia la estrategia.isReset, y 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")
      }
    
  • Restaurar los datos iniciales de la cuenta, actualizar los datos de la cuenta corriente Para juzgar el saldo, la estrategia debe registrar continuamente los activos de la cuenta inicial para su comparación con el activo corriente.nowAccsse utiliza para registrar los datos de la cuenta corriente, utilizando la función que acabamos de diseñarupdateAccspara obtener los datos de la cuenta del intercambio actual.initAccsSe utiliza para registrar el estado inicial de la cuenta (número de monedas, número de monedas denominadas, etc. en los intercambios A y B).initAccs, utilizar el_G()función para restaurar primero (la función _G registrará los datos de forma persistente, y puede devolver los datos registrados de nuevo, ver la documentación de la API para más detalles:www.fmz.com/api#_gk-v)), si la consulta no funciona, utilice la información de la cuenta corriente para asignar el valor y utilice el_Gfunció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, que se ejecuta una y otra vez para formar el bucle principal de la estrategia.

  • Obtener datos de mercado y juzgar su 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í podemos ver que la función concurrenteexchange.Gode la plataforma FMZ se utiliza para crear objetos concurrentesdepthARoutine, depthBRoutineque llaman elGetDepth()Cuando estos dos objetos concurrentes se crean, elGetDepth()La interfaz se llama inmediatamente, y ambas solicitudes de datos de profundidad se envían al intercambio. Entonces llama alwait()el método dedepthARoutine, depthBRoutineobjetos para obtener los datos de profundidad.
    Una vez obtenidos los datos de profundidad, es necesario comprobar los datos de profundidad para determinar su validez.continuese activa la instrucción para volver a ejecutar el bucle principal.

  • Utilice elspread valueParámetro o elspread ratio¿Qué parámetro?

          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 cuanto a los parámetros, hemos hecho un diseño: los parámetros de FMZ pueden sermuestrao bienescondersebasado en un parámetro, por lo que podemos hacer un parámetro para decidir si utilizarprice spread, ospread ratio.

    img

    Un parámetrodiffAsPercentageLas otras dos configuraciones de parámetros para mostrar o ocultar basadas en este parámetro son:hedgeDiffPrice@!diffAsPercentage, que se muestra cuandodiffAsPercentagees falso.hedgeDiffPercentage@diffAsPercentage, que se muestra cuandodiffAsPercentageEs cierto. Después de este diseño, verificamos eldiffAsPercentageLa diferencia de precio entre los precios de los activos y los precios de los activos es el parámetro que determina la condición de activación de la cobertura basada en la relación entre las diferencias de precios.diffAsPercentageel parámetro verificado, la cobertura se activa por la diferencia de precio.

  • Determinar las condiciones de activación de la cobertura

          if (depthA.Bids[0].Price - depthB.Asks[0].Price > targetDiffPrice && Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount) >= minHedgeAmount) {          // A -> B market conditions are met            
              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("trigger A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, price, amount, nowAccs[1].Balance / price, nowAccs[0].Stocks)  // Tips
                  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 conditions are met
              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("trigger B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, price, amount, nowAccs[0].Balance / price, nowAccs[1].Stocks)  // Tips
                  hedge(exA, exB, price, amount)
                  cancelAll()
                  lastKeepBalanceTS = 0
                  isTrade = true 
              }            
          }
    

    Las condiciones de activación de la cobertura son las siguientes:

    1. En caso de que el valor de la operación sea inferior al valor de la operación, el valor de la operación será igual al valor de la operación.
    2. El importe que puede cubrirse en el mercado debe cumplir el importe mínimo de cobertura establecido en los parámetros.
    3. Los activos en el intercambio de la operación de venta son suficientes para vender, y los activos en el intercambio de la operación de compra son suficientes para comprar. Cuando estas condiciones se cumplen, ejecutar la función de cobertura para colocar una orden de cobertura.isTradeEn este caso, si se activa la cobertura, la variable se establece entrue. Y restablecer la variable globallastKeepBalanceTSen 0 (lastKeepBalanceTS se utiliza para marcar la marca de tiempo de la última operación de balanceo, si se establece en 0 se activará inmediatamente la operación de balanceo), 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 en 0 después de que se inicie la operación de cobertura, la operación de balance se iniciará 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", 
              "current A, Stocks:", nowAccs[0].Stocks, "FrozenStocks:", nowAccs[0].FrozenStocks, "Balance:", nowAccs[0].Balance, "FrozenBalance", nowAccs[0].FrozenBalance, "\n", 
              "current B, Stocks:", nowAccs[1].Stocks, "FrozenStocks:", nowAccs[1].FrozenStocks, "Balance:", nowAccs[1].Balance, "FrozenBalance", nowAccs[1].FrozenBalance, "\n", 
              "initial A, Stocks:", initAccs[0].Stocks, "FrozenStocks:", initAccs[0].FrozenStocks, "Balance:", initAccs[0].Balance, "FrozenBalance", initAccs[0].FrozenBalance, "\n", 
              "initial B, Stocks:", initAccs[1].Stocks, "FrozenStocks:", initAccs[1].FrozenStocks, "Balance:", initAccs[1].Balance, "FrozenBalance", initAccs[1].FrozenBalance)
    

    La barra de estado no es particularmente compleja en su diseño. Muestra la hora actual, la diferencia de precio de Exchange A a Exchange B y la diferencia de precio de Exchange B a Exchange A. Y muestra el margen objetivo de cobertura actual, los datos de activos de la cuenta de Exchange A y la cuenta de Exchange B.

Tratamiento de pares de operaciones denominados en monedas diferentes

En cuanto a los parámetros, diseñamos el parámetro de valor del tipo de conversión, y también diseñamos la conversión del tipo de cambio en el funcionamiento 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. Porque esta función afecta a dos aspectos:

  • Conversión de precios en todos los datos de mercado, datos de pedidos y datos de posición.
  • La conversión de la moneda denominada en los activos de la cuenta. Por ejemplo, el par de operaciones actual esBTC_USDT, la unidad de precio esUSDT, y la moneda denominada disponible en los activos de la cuenta es tambiénUSDTSi quiero convertir el valor en CNY, establecerexchange.SetRate(6.8)en el código para convertir los datos obtenidos por todas las funciones bajo elexchangeobjeto de cambio a CNY. Para convertir a qué moneda denominada, pasar enel tipo de cambio de la moneda denominada actual a la moneda denominada de destinoEn elSetRate function.

Estrategia completa:Estrategia de cobertura al contado de diferentes monedas denominadas (Tutorial)


Relacionados

Más.