Conceção de uma estratégia de cobertura spot de criptomoedas (1)

Autora:Lydia., Criado: 2022-08-16 10:30:56, Atualizado: 2023-09-19 21:46:16

img

Conceção de uma estratégia de cobertura spot de criptomoedas (1)

As estratégias de hedging são estratégias de prática muito boas para iniciantes no design de estratégias.

Projetar algumas funções e parâmetros de interface de estratégia de acordo com os requisitos da estratégia

Em primeiro lugar, é claro que a estratégia a ser projetada é uma estratégia de hedging spot de criptomoeda. Nós projetamos a estratégia de hedge mais simples. Nós vendemos na bolsa com o preço mais alto apenas entre as duas bolsas spot, e compramos na bolsa com o preço mais baixo para tirar a diferença. Quando as bolsas com preços mais altos são todas moedas denominadas (porque as moedas com preços mais altos são vendidas), e as bolsas com preços mais baixos são todas moedas (as moedas com preços mais baixos são compradas), não pode ser hedged. Neste momento, só podemos esperar a reversão do preço para hedge.

Quando a cobertura é feita, o preço e a quantidade da ordem são limitados pela bolsa, e também há um limite na quantidade mínima de ordem. Além do limite mínimo, a estratégia de cobertura também precisa considerar o volume máximo de ordem de uma só vez. Se o volume de ordem for muito grande, não haverá volume de ordem suficiente. Também é necessário considerar como converter a taxa de câmbio se as duas moedas denominadas em bolsa forem diferentes. Quando a cobertura, a taxa de manipulação e o deslizamento do tomador de ordem são todos os custos de transação, não enquanto houver uma diferença de preço pode ser coberta. Portanto, a diferença de preço de cobertura também tem um valor de gatilho. Se for menor que uma certa diferença de preço, a cobertura perderá.

Com base nestas considerações, a estratégia deve ser concebida com vários parâmetros:

  • Diferença de cobertura:hedgeDiffPrice, quando a diferença exceder este valor, a operação de cobertura é desencadeada.
  • Valor mínimo da cobertura:minHedgeAmount, o montante mínimo de ordem (moedas) que pode ser coberto.
  • Valor máximo da cobertura:maxHedgeAmount, o montante máximo da ordem (moedas) para uma cobertura.
  • Precisão de preço de A:pricePrecisionA, a precisão do preço da ordem (número de casas decimais) colocada pela Bolsa A.
  • Precisão da quantidade de A:amountPrecisionA, a precisão do montante da ordem efectuada pela Bolsa A (número de casas decimais).
  • Precisão de preço de B:pricePrecisionB, a precisão do preço da ordem (número de casas decimais) colocada pela Bolsa B.
  • Precisão da quantidade de B:amountPrecisionB, a precisão do montante da ordem efectuada pela Bolsa B (número de casas decimais).
  • Taxa de câmbio A:rateA, a conversão da taxa de câmbio do primeiro objecto de câmbio adicionado, o valor por defeito é 1, não convertida.
  • Taxa de câmbio B:rateB, a conversão da taxa de câmbio do segundo objecto de câmbio adicionado, por defeito é 1, não convertida.

A estratégia de hedging precisa manter o número de moedas nas duas contas inalterado (ou seja, não manter posições em nenhuma direção e manter a neutralidade), por isso precisa haver uma lógica de equilíbrio na estratégia para sempre detectar o equilíbrio.

  • atualizaçãoAccs
    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 
    }
    

Após a colocação da ordem, se não houver uma ordem concluída, precisamos cancelá-la a tempo, e a ordem não pode ser mantida pendente.

  • cancelar tudo
    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)
                }
            }
        })
    }
    

Ao equilibrar o número de moedas, precisamos de encontrar o preço acumulado para um certo número de moedas em um certo nível de dados, então precisamos de uma função para lidar com isso.

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

Em seguida, precisamos projetar e escrever a operação específica de ordem de cobertura, que precisa ser projetada para colocar ordens simultâneas:

  • coberta
    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, vamos completar o projeto da função de equilíbrio, que é um pouco complicado.

  • manter o equilíbrio
    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 
        }
    }
    

Depois de conceber estas funções de acordo com os requisitos da estratégia, comece a conceber a função principal da estratégia.

Conceção da principal função da estratégia

Na plataforma FMZ, a estratégia é executada a partir domainNo início do período de transiçãomainfunção, temos que fazer algum trabalho de inicialização da estratégia.

  • Nome do objeto de troca Como muitas operações na estratégia têm que usar os objetos de troca, como obter cotações de mercado, colocar ordens e assim por diante.

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

    Isto torna mais fácil escrever código mais tarde.

  • Taxa de câmbio, conceção relacionada com a precisão

      // 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)
    

    Se os parâmetros do câmbiorateA, rateBsão definidas em 1 (o padrão é 1), ou seja,rateA != 1ourateB != 1não será acionado, pelo que a conversão da taxa de câmbio não será definida.

  • Reinicie todos os dados

    img

    Às vezes é necessário para excluir todos os registros e limpar os dados gravados quando a estratégia inicia.isReset, e projetar o código de reinicialização na parte de inicialização da estratégia, por exemplo:

      if (isReset) {   // When isReset is true, reset the data
          _G(null)
          LogReset(1)
          LogProfitReset()
          LogVacuum()
          Log("reset all data", "#FF0000")
      }
    
  • Restaurar os dados iniciais da conta, atualizar os dados da conta corrente Para avaliar o saldo, a estratégia deve registar continuamente os activos da conta inicial para comparação com os activos da conta corrente.nowAccsé usado para registrar os dados da conta corrente, usando a função que acabamos de projetarupdateAccspara obter os dados da conta da troca atual.initAccsÉ utilizado para registar o estado inicial da conta (número de moedas, número de moedas denominadas, etc. nas bolsas A e B).initAccs, utilizar o_G()função para restaurar primeiro (a função _G irá gravar dados de forma persistente, e pode retornar os dados gravados novamente, ver a documentação da API para mais detalhes: [link](https://www.fmz.com/api#_gk-v)), se a consulta não funcionar, utilizar as informações da conta corrente para atribuir o valor e_Gfunção para registar.

    Por exemplo, o seguinte código:

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

Lógica de negociação, circuito principal na função principal

O código no loop principal é o processo de cada rodada de execução da lógica da estratégia, que é executado repetidamente para formar o loop principal da estratégia.

  • Obter dados de mercado e julgar a validade dos dados de mercado

          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 
          }
    

    Aqui podemos ver que a função concorrenteexchange.Goda plataforma FMZ é usado para criar objetos simultâneosdepthARoutine, depthBRoutineque chamam aGetDepth()Quando estes dois objetos simultâneos são criados, oGetDepth()interface é chamada imediatamente, e ambas as solicitações de dados de profundidade são enviadas para a troca. Então chama owait()Método dedepthARoutine, depthBRoutineobjetos para obter os dados de profundidade.
    Após a obtenção dos dados de profundidade, é necessário verificar os dados de profundidade para determinar a sua validade.continueA instrução é acionada para re-executar o loop principal.

  • Utilize ospread valueParâmetro ouspread ratioParâ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
          }
    

    Em termos de parâmetros, fizemos um projeto: os parâmetros do FMZ podem serexibiçãoouesconder-sebaseado em um parâmetro, para que possamos fazer um parâmetro para decidir se usarprice spread, ouspread ratio.

    img

    Um parâmetrodiffAsPercentageAs outras duas configurações de parâmetros para mostrar ou ocultar com base neste parâmetro são:hedgeDiffPrice@!diffAsPercentage, que é exibido quandodiffAsPercentageé falso.hedgeDiffPercentage@diffAsPercentage, que é exibido quandodiffAsPercentageÉ verdade. Após este projeto, verificámos adiffAsPercentageO valor da diferença de preço é o valor da diferença de preço, que é a condição de desencadeamento da cobertura baseada no rácio da diferença de preço.diffAsPercentageSe o parâmetro for verificado, a cobertura é desencadeada pela diferença de preço.

  • Determinação das condições de desencadeamento da 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 
              }            
          }
    

    As condições de desencadeamento da cobertura são as seguintes:

    1. O preço de cobertura deve ser calculado de acordo com os parâmetros estabelecidos no artigo 4.o, n.o 3, do Regulamento (UE) n.o 600/2014.
    2. O montante que pode ser coberto no mercado deve cumprir o montante mínimo de cobertura estabelecido nos parâmetros.
    3. Os activos na troca da operação de venda são suficientes para vender, e os activos na troca da operação de compra são suficientes para comprar. Quando estas condições são atendidas, executar a função de cobertura para colocar uma ordem de cobertura.isTradeSe a cobertura for desencadeada, a variável é definida comotrueE redefinir a variável globallastKeepBalanceTSpara 0 (o lastKeepBalanceTS é utilizado para marcar a marca de tempo da última operação de equilíbrio, colocando-a em 0 a operação de equilíbrio será acionada imediatamente), e depois cancelar todas as ordens pendentes.
  • Operação de equilíbrio

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

    Pode-se ver que a função de equilíbrio é executada periodicamente, maslastKeepBalanceTSSe a operação de cobertura for reiniciada e o lucro for redefinido para 0, a operação de compensação será iniciada imediatamente e o lucro será calculado após uma compensação bem sucedida.

  • Informações da 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)
    

    A barra de estado não é particularmente complexa em design. Ela exibe a hora atual, a diferença de preço de Exchange A para Exchange B e a diferença de preço de Exchange B para Exchange A. E exibe o diferencial de meta de cobertura atual, os dados de ativos da conta de exchange A e da conta de exchange B.

Tratamento de pares de negociação de moedas denominadas diferentes

Em termos de parâmetros, desenhámos o parâmetro do valor da taxa de conversão, e também desenhámos a conversão da taxa de câmbio na operação inicial domainA estratégia tem por objectivo a melhoria da qualidade de vida dos trabalhadores.SetRateA função de conversão de taxa de câmbio deve ser executada primeiro. Porque esta função afeta dois aspectos:

  • Conversão de preços em todos os dados de mercado, dados de ordens e dados de posição.
  • A conversão da moeda denominada nos activos da conta. Por exemplo, o par de negociação atual éBTC_USDT, a unidade de preço éUSDT, e a moeda denominada disponível nos activos da conta é igualmenteUSDTSe eu quiser converter o valor em CNY, definirexchange.SetRate(6.8)no código para converter os dados obtidos por todas as funçõesexchangeobjecto de troca para CNY. Para converter para qual moeda denominada, passar emA taxa de câmbio da moeda denominada corrente para a moeda denominada de destinopara oSetRate function.

Estratégia completa:O valor da posição em risco deve ser calculado de acordo com o método de classificação da posição em risco.


Relacionados

Mais.