As estratégias de hedging são estratégias de prática muito boas para iniciantes no design de estratégias.
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:
hedgeDiffPrice
, quando a diferença exceder este valor, a operação de cobertura é desencadeada.minHedgeAmount
, o montante mínimo de ordem (moedas) que pode ser coberto.maxHedgeAmount
, o montante máximo da ordem (moedas) para uma cobertura.pricePrecisionA
, a precisão do preço da ordem (número de casas decimais) colocada pela Bolsa A.amountPrecisionA
, a precisão do montante da ordem efectuada pela Bolsa A (número de casas decimais).pricePrecisionB
, a precisão do preço da ordem (número de casas decimais) colocada pela Bolsa B.amountPrecisionB
, a precisão do montante da ordem efectuada pela Bolsa B (número de casas decimais).rateA
, a conversão da taxa de câmbio do primeiro objecto de câmbio adicionado, o valor por defeito é 1, não convertida.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.
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.
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.
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:
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.
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.
Na plataforma FMZ, a estratégia é executada a partir domain
No início do período de transiçãomain
funçã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
, rateB
são definidas em 1 (o padrão é 1), ou seja,rateA != 1
ourateB != 1
não será acionado, pelo que a conversão da taxa de câmbio não será definida.
Reinicie todos os dados
À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 projetarupdateAccs
para 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_G
funçã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)
}
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.Go
da plataforma FMZ é usado para criar objetos simultâneosdepthARoutine
, depthBRoutine
que 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
, depthBRoutine
objetos 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.continue
A instrução é acionada para re-executar o loop principal.
Utilize ospread value
Parâmetro ouspread ratio
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
}
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
.
Um parâmetrodiffAsPercentage
As 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 adiffAsPercentage
O 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.diffAsPercentage
Se 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:
isTrade
Se a cobertura for desencadeada, a variável é definida comotrue
E redefinir a variável globallastKeepBalanceTS
para 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, maslastKeepBalanceTS
Se 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.
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 domain
A estratégia tem por objectivo a melhoria da qualidade de vida dos trabalhadores.SetRate
A função de conversão de taxa de câmbio deve ser executada primeiro.
Porque esta função afeta dois aspectos:
BTC_USDT
, a unidade de preço éUSDT
, e a moeda denominada disponível nos activos da conta é igualmenteUSDT
Se eu quiser converter o valor em CNY, definirexchange.SetRate(6.8)
no código para converter os dados obtidos por todas as funçõesexchange
objecto 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.