avatar of 发明者量化-小小梦 发明者量化-小小梦
focar em Mensagem privada
4
focar em
1271
Seguidores

Desenho de estratégia de cobertura à vista de moeda digital (1)

Criado em: 2021-07-19 17:38:24, atualizado em: 2023-09-20 10:35:16
comments   1
hits   3142

Desenho de estratégia de cobertura à vista de moeda digital (1)

Desenho de estratégia de cobertura à vista de moeda digital (1)

Para iniciantes em design de estratégia, a estratégia de hedge é uma ótima estratégia de treinamento. Este artigo implementa uma estratégia de hedge à vista de moeda digital simples, mas real, na esperança de que iniciantes possam adquirir alguma experiência em design.

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

Primeiro de tudo, fica claro que a estratégia a ser desenhada é uma estratégia de hedge spot de moeda digital. Nós projetamos a estratégia de hedge mais simples, que é vender na bolsa com preço mais alto entre duas bolsas spot e comprar na bolsa com preço mais baixo para ter lucro. Pegue a diferença. Quando todas as bolsas com preços mais altos são denominadas em moedas (porque todas as moedas com preços mais altos são vendidas) e todas as bolsas com preços mais baixos são denominadas em moedas (todas as moedas com preços mais baixos são compradas), é impossível fazer hedge. Neste momento, você só pode esperar o preço reverter e fazer hedge.

Ao fazer hedge, a bolsa tem restrições de precisão quanto ao preço e à quantidade do pedido, e também há um limite mínimo de quantidade do pedido. Além do limite mínimo, a estratégia também deve considerar o volume máximo de ordens para hedge de uma só vez. Se o volume de ordens for muito grande, não haverá ordens suficientes no mercado. Você também precisa considerar como converter usando a taxa de câmbio se as duas bolsas tiverem moedas denominadas diferentes. Ao fazer hedge, taxas de manuseio e slippage em ordens são ambos custos de transação. Não é possível fazer hedge enquanto houver uma diferença de preço. Portanto, também há um valor de gatilho para fazer hedge da diferença de preço. Quando a diferença de preço é menor que um certo nível, o hedge resultará em perda.

Com base nessas considerações, a estratégia precisa projetar vários parâmetros:

  • Cobertura da diferença:hedgeDiffPrice, quando a diferença de preço ultrapassa esse valor, a operação de hedge é acionada.
  • Valor mínimo de cobertura:minHedgeAmount, a quantidade mínima do pedido (em moedas) que pode ser protegida.
  • Valor máximo de cobertura:maxHedgeAmount, a quantidade máxima de pedido (número de moedas) para um hedge.
  • Precisão do preço:pricePrecisionA, a precisão do preço da ordem (número de casas decimais) da bolsa A.
  • Precisão da quantidade do pedido:amountPrecisionA, a precisão da quantidade do pedido (número de casas decimais) da troca A.
  • Precisão do preço:pricePrecisionB, a precisão do preço da ordem (número de casas decimais) da bolsa B.
  • Precisão da quantidade do pedido:amountPrecisionB, a precisão da quantidade do pedido (número de casas decimais) da bolsa B.
  • Uma taxa de câmbio:rateA, a conversão da taxa de câmbio do primeiro objeto de câmbio adicionado, o valor padrão é 1, sem conversão.
  • Taxa de câmbio do câmbio B:rateB, a conversão da taxa de câmbio do segundo objeto de câmbio adicionado, o valor padrão é 1 e nenhuma conversão é realizada.

A estratégia de hedge precisa manter o número de moedas nas duas contas inalterado (ou seja, não manter nenhuma posição direcional e manter a neutralidade), então é preciso haver uma lógica de equilíbrio na estratégia para sempre verificar o saldo. Ao verificar o saldo, é inevitável obter dados de ativos de duas bolsas. Precisamos escrever uma função para usá-lo.

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

Se um pedido não for executado após ser feito, precisamos cancelá-lo a tempo e não deixá-lo pendente. Essa operação precisa ser tratada tanto no módulo de saldo quanto na lógica de hedge, portanto, uma função completa de retirada de ordens precisa ser projetada.

  • cancelAll
    
    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 balancear o número de moedas, precisamos encontrar o preço de um certo número de moedas acumuladas em uma certa profundidade 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
    }
    

Então precisamos projetar e escrever a operação de ordem de hedge específica, que precisa ser projetada para ser uma ordem simultânea:

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

Por fim, vamos concluir o projeto da função de equilíbrio, que é um pouco complicado.

  • keepBalance

    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
      // 计算币差
      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("无法平衡")            
          } else {
              // 平衡下单
              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("价格无效", price)
              }
          }        
          return false
      } else if (!(initAccs.length == nowAccs.length && nowAccs.length == depths.length)) {
          Log("错误:", "initAccs.length:", initAccs.length, "nowAccs.length:", nowAccs.length, "depths.length:", depths.length)
          return true 
      } else {
          return true 
      }
    }
    

Agora que essas funções foram projetadas de acordo com os requisitos da estratégia, podemos começar a projetar a função principal da estratégia.

Estratégia principal função design

Na FMZ a estratégia émainA função começa a ser executada. existirmainNo início da função, precisamos fazer alguma inicialização de estratégia.

  • Nome do objeto de troca Porque muitas operações na estratégia exigem o uso de objetos de troca, como obtenção de informações de mercado, colocação de ordens, etc. Então seria problemático usar um nome longo toda vez. O truque é usar um nome simples, por exemplo:
  var exA = exchanges[0]
  var exB = exchanges[1]

Isso torna muito confortável escrever código mais tarde.

  • Design relacionado à taxa de câmbio e precisão
    // 精度,汇率设置
    if (rateA != 1) {
        // 设置汇率A
        exA.SetRate(rateA)
        Log("交易所A设置汇率:", rateA, "#FF0000")
    }
    if (rateB != 1) {
        // 设置汇率B
        exB.SetRate(rateB)
        Log("交易所B设置汇率:", rateB, "#FF0000")
    }
    exA.SetPrecision(pricePrecisionA, amountPrecisionA)
    exB.SetPrecision(pricePrecisionB, amountPrecisionB)

Se o parâmetro da taxa de câmbiorateArateBAlguns são definidos como 1 (o padrão é 1), ou sejarateA != 1ourateB != 1Não será acionado, portanto nenhuma conversão de taxa de câmbio será definida.

  • Redefinir todos os dados

Desenho de estratégia de cobertura à vista de moeda digital (1)

Às vezes, quando uma política é iniciada, é necessário excluir todos os logs e limpar os dados registrados. Você pode projetar um parâmetro de interface de estratégiaisReset, e então projete o código de reinicialização na seção de inicialização da estratégia, por exemplo:

    if (isReset) {   // 当isReset为真时重置数据
        _G(null)
        LogReset(1)
        LogProfitReset()
        LogVacuum()
        Log("重置所有数据", "#FF0000")
    }
  • Restaurar dados iniciais da conta e atualizar dados atuais da conta Para determinar o saldo, a estratégia precisa registrar continuamente os ativos da conta inicial para comparação com a situação atual.nowAccsEsta variável é usada para registrar os dados da conta corrente, usando a função que acabamos de projetar.updateAccsObtenha os dados da conta da bolsa atual.initAccsUsado para registrar o status inicial da conta (dados como o número de moedas e o número de moedas denominadas da Bolsa A e da Bolsa B). parainitAccsPrimeiro uso_G()Recuperação de função (_A função G registrará dados persistentemente e pode retornar os dados registrados novamente. Para detalhes, veja a documentação da API:Link), se a consulta falhar, use as informações da conta atual para atribuir um valor e usar_GRegistro de função.

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, loop 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. A execução recíproca contínua constitui o loop principal da estratégia. Vamos dar uma olhada no fluxo de cada execução do programa no loop principal.

  • Obter dados de mercado e determinar sua validade
        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 você pode ver as funções simultâneas da plataforma FMZ.exchange.Go, criou a chamadaGetDepth()Objeto de simultaneidade da interfacedepthARoutinedepthBRoutine. Quando esses dois objetos simultâneos forem criados, chameGetDepth()A interface também ocorreu imediatamente, e duas solicitações de obtenção de dados de profundidade foram enviadas à bolsa. Então liguedepthARoutinedepthBRoutineObjetowait()Método para obter dados de profundidade.
Após obter os dados de profundidade, é necessário verificá-los para determinar sua validade. Execução de gatilho para anomalias de dadoscontinueA instrução reexecuta o loop principal.

  • usar价差值Parâmetros ou差价比例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
        }

Fizemos esse projeto em termos de parâmetros. Os parâmetros do FMZ podem ser baseados em um determinado parâmetromostrarouesconder, para que possamos criar um parâmetro para decidir se devemos usar价格差,ainda差价比例

Desenho de estratégia de cobertura à vista de moeda digital (1)

Um parâmetro foi adicionado aos parâmetros da interface de estratégiadiffAsPercentage. Dois outros parâmetros que são mostrados ou ocultados com base neste parâmetro são definidos como: hedgeDiffPrice@!diffAsPercentage,quandodiffAsPercentageFalso exibe este parâmetro. hedgeDiffPercentage@diffAsPercentage,quandodiffAsPercentageVerdadeiro para exibir este parâmetro. Após este projeto, verificamosdiffAsPercentageOs parâmetros são baseados na taxa de diferença de preço como condição de ativação do hedge. DesmarcardiffAsPercentageO parâmetro é usar a diferença de preço como condição de gatilho de hedge.

  • Determinar as condições de ativação 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 盘口条件满足            
            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("触发A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, price, amount, nowAccs[1].Balance / price, nowAccs[0].Stocks)  // 提示信息
                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 盘口条件满足
            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("触发B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, price, amount, nowAccs[0].Balance / price, nowAccs[1].Stocks)  // 提示信息
                hedge(exA, exB, price, amount)
                cancelAll()
                lastKeepBalanceTS = 0
                isTrade = true 
            }            
        }

Existem várias condições de ativação de hedge: 1. Primeiro, satisfaça a diferença de preço de hedge. O hedge só pode ser executado quando a diferença de preço do mercado atende aos parâmetros de diferença de preço definidos. 2. O volume de hedge no mercado deve atender ao volume mínimo de hedge definido nos parâmetros. Como diferentes exchanges podem ter volumes mínimos de ordens diferentes, o menor dos dois deve ser tomado. 3. Há ativos suficientes na bolsa para operações de venda para vender, e há ativos suficientes na bolsa para operações de compra para comprar. Quando essas condições são atendidas, a função de hedge é executada para colocar uma ordem de hedge. Antes da função principal declaramos uma variável antecipadamenteisTradeUsado para marcar se o hedge ocorre. Se o hedge for acionado, esta variável é definida comotrue. E redefinir as variáveis ​​globaislastKeepBalanceTSDefina lastKeepBalanceTS como 0 (lastKeepBalanceTS é usado para marcar o registro de data e hora da operação de balanceamento mais recente. Definir como 0 acionará a operação de balanceamento imediatamente) e, em seguida, cancele todos os pedidos pendentes.

  • Um ato 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 
                }                
            }            
        }

Você pode ver que a função de equilíbrio é executada regularmente, mas se a operação de hedge for acionada,lastKeepBalanceTSSe redefinido para 0, a operação de balanceamento será acionada imediatamente. Após o balanço ser bem-sucedido, o lucro será calculado.

  • Informações da barra de status
        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", 
            "当前A,Stocks:", nowAccs[0].Stocks, "FrozenStocks:", nowAccs[0].FrozenStocks, "Balance:", nowAccs[0].Balance, "FrozenBalance", nowAccs[0].FrozenBalance, "\n", 
            "当前B,Stocks:", nowAccs[1].Stocks, "FrozenStocks:", nowAccs[1].FrozenStocks, "Balance:", nowAccs[1].Balance, "FrozenBalance", nowAccs[1].FrozenBalance, "\n", 
            "初始A,Stocks:", initAccs[0].Stocks, "FrozenStocks:", initAccs[0].FrozenStocks, "Balance:", initAccs[0].Balance, "FrozenBalance", initAccs[0].FrozenBalance, "\n", 
            "初始B,Stocks:", initAccs[1].Stocks, "FrozenStocks:", initAccs[1].FrozenStocks, "Balance:", initAccs[1].Balance, "FrozenBalance", initAccs[1].FrozenBalance)

A barra de status não é particularmente complexa em design. Ela exibe o horário atual, a diferença de preço da bolsa A para a bolsa B e a diferença de preço da bolsa B para a bolsa A. Exibe o spread alvo de hedge atual. Exibe os dados de ativos da conta de câmbio A e da conta de câmbio B.

Processamento de pares de negociação com diferentes moedas denominadas

Em termos de parâmetros, projetamos o parâmetro de valor da taxa de conversão no início da estratégiamainTambém projetamos a conversão da taxa de câmbio para a operação inicial da função. Deve-se notar queSetRateA função de conversão da taxa de câmbio precisa ser executada primeiro. Porque esta função afeta dois níveis:

  • Conversão de preços em todos os dados de mercado, dados de pedidos e dados de posição.
  • Conversão de moeda denominada em ativos de conta. Por exemplo, o par de negociação atual éBTC_USDT, as unidades de preço sãoUSDTA moeda disponível nos ativos da conta éUSDT. Se eu quiser convertê-lo para CNY, defina-o no códigoexchange.SetRate(6.8)ApenasexchangeOs dados obtidos por todas as funções sob este objeto de troca são convertidos em CNY. Por que a moeda é usada para conversão?SetRatePassagem de funçãoA taxa de câmbio da moeda atual para a moeda alvo

A estratégia completa:Estratégias de hedge spot para diferentes moedas (tutorial)