Para iniciantes no projeto de estratégia, a estratégia de hedge é muito boa para prática.
Em primeiro lugar, precisamos garantir que a estratégia a ser projetada seja uma estratégia de hedge spot de criptomoeda. Projetamos a hedge mais simples. Nós apenas vendemos na plataforma com o preço mais alto entre as duas plataformas spot e compramos na plataforma com o preço mais baixo para ganhar o spread de preço. Quando a plataforma com o preço mais alto está cheia de símbolos de moeda de cotação (porque o preço é alto, todos os símbolos de moeda são vendidos), ou quando a plataforma com o preço mais baixo está cheia de símbolos de moeda (porque o preço é baixo, símbolos de moeda são comprados por todos os ativos), ela não pode ser hedged. Neste momento, você só pode esperar que o preço se inverta para hedge.
Para o preço da ordem e o valor durante a cobertura, existem limites de precisão em todas as plataformas, e também há um limite no valor mínimo da ordem. Além do limite mínimo, a estratégia também precisa considerar o valor máximo da ordem para uma cobertura. Se o valor da ordem for muito grande, o mercado não terá um volume de ordem suficiente para isso. Também é necessário considerar como converter a taxa de câmbio se as duas plataformas tiverem moedas de cotação diferentes. A taxa de manipulação durante a cobertura e o deslizamento do tomador da ordem são todos custos de negociação. A cobertura nem sempre acontece enquanto há uma diferença de preço. Portanto, o spread do preço da cobertura também tem um valor de gatilho. Se for menor que um certo spread de preço, a cobertura fará um prejuízo.
Com base nisso, a estratégia deve ser concebida com vários parâmetros:
hedgeDiffPrice
Se o diferencial exceder o valor, será activada uma cobertura.minHedgeAmount
, o montante mínimo de ordem (valor do símbolo) disponível para uma cobertura.maxHedgeAmount
, o montante máximo da ordem (valor do símbolo) disponível para uma cobertura.pricePrecisionA
, a precisão do preço da ordem (números decimais) da plataforma A.amountPrecisionA
, a precisão do montante da ordem (números decimais) da plataforma A.pricePrecisionB
, a precisão do preço da ordem (números decimais) da plataforma B.amountPrecisionB
, a precisão do montante da ordem (números decimais) da plataforma B.rateA
, a conversão da taxa de câmbio do primeiro objecto de câmbio adicionado; o valor por defeito é 1, indicando não conversão.rateB
, a conversão da taxa de câmbio do segundo objecto de câmbio adicionado; o valor por defeito é 1, indicando não conversão.A estratégia de hedge precisa manter o valor do símbolo de moeda das duas contas inalterado (ou seja, não manter posições direcionais e manter neutro), 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 de uma ordem, se não houver uma ordem executada, 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 a quantidade de símbolos de moeda, precisamos de encontrar o preço com uma certa quantidade em um certo nível de dados, então precisamos de uma função como esta 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 de ordem de cobertura específica, que precisa ser projetada para colocar ordens simultaneamente:
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 mais 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 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 funções foram concebidas de acordo com os requisitos da estratégia, e podemos começar a conceber a função principal da estratégia.
No FMZ, a estratégia é executada a partir domain
No início do período de transiçãomain
Função, precisamos fazer alguma inicialização da estratégia.
Nome do objeto de troca Para muitas operações na estratégia usar objetos de troca, tais como obter cotações de mercado, a colocação de ordens, e assim por diante, por isso seria inconveniente usar um nome mais longo a cada vez, meu pequeno truque é usar um simples nome curto em vez disso, por exemplo:
var exA = exchanges[0]
var exB = exchanges[1]
Então, será mais confortável escrever o código mais tarde.
Taxa de câmbio e precisão
// 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)
Se um dos parâmetros da taxa de câmbio, a saber:rateA
erateB
, é definido como 1 (o padrão é 1), ou seja,rateA != 1
ourateB != 1
A taxa de câmbio não pode ser convertida.
Reinicie todas as datas
Às vezes, é necessário excluir todos os registros e aspirar os registros de dados quando a estratégia é iniciada.isReset
, e, em seguida, 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")
}
Recuperar os dados da conta inicial e atualizar os dados da conta corrente
Para avaliar o saldo, a estratégia deve registar continuamente a condição do activo inicial da conta para comparação com a corrente.nowAccs
O sistema de registo das contas correntes é utilizado para registar os dados das contas correntes.updateAccs
função que acabamos de projetar para obter os dados da conta da plataforma atual.initAccs
É utilizado para registar o estado inicial da conta (dados como o valor do símbolo de moeda de A e B, o valor da moeda de cotação, etc.).initAccs
, primeiro use o_G()
função para restaurar (a função _G gravará dados de forma persistente e pode retornar os dados gravados novamente; leia a documentação da API para mais detalhes:ligação).
Se você não pode consultar os dados, use as informações da conta corrente para atribuir e usar_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 de lógica de estratégia, e a execução repetitiva sem parar constrói o loop principal de estratégia.
Obtenha as cotações do mercado e julgue a 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 que a função concorrenteexchange.Go
da plataforma FMZ é usado para criar objetos simultâneosdepthARoutine
edepthBRoutine
que chamam aGetDepth()
Quando estes dois objetos simultâneos são criados, oGetDepth()
interface é chamada imediatamente, e ambas as solicitações para os dados de profundidade são enviados para a plataforma.
Então, chama owait()
Método do objetodepthARoutine
e objetodepthBRoutine
para obter os dados de profundidade.
Após a obtenção dos dados de profundidade, é necessário verificar os dados de profundidade para avaliar a sua validade.continue
A instrução é acionada para re-executar o loop principal.
Utilizaçãoprice spread
ouspread 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
}
Em termos de parâmetros, nós fizemos tal projeto.exibiçãoouesconder-sebaseado em um parâmetro, para que possamos fazer um parâmetro para decidir se usarprice spread
, ouspread ratio
.
O parâmetrodiffAsPercentage
Os outros dois parâmetros, que serão exibidos ou ocultados com base no parâmetro, são definidos como:hedgeDiffPrice@!diffAsPercentage
; quandodiffAsPercentage
é falso, será mostrado.hedgeDiffPercentage@diffAsPercentage
; quandodiffAsPercentage
Se for verdade, será exibido.
Após o desenho, verificámos adiffAsPercentage
O valor de cobertura é o valor da taxa de variação de risco.diffAsPercentage
Se o parâmetro não for verificado, o spread de preço é utilizado como condição de activação da cobertura.
Juiz 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
}
}
Existem várias condições de desencadeamento para a cobertura: 1.Em primeiro lugar, satisfazer o diferencial de cobertura; só quando o diferencial de mercado satisfazer o parâmetro de diferencial definido, é possível a cobertura.
2.O montante de cobertura do mercado deve corresponder ao montante mínimo de cobertura estabelecido nos parâmetros.Dado que o montante mínimo de ordem das diferentes plataformas é diferente, deve ser escolhido o menor dos dois.
3.Os ativos na plataforma com a operação de venda são suficientes para vender, e os ativos na plataforma com a operação de compra são suficientes para comprar.isTrade
Aqui, se a cobertura é acionada, 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 saldo, e a sua definição para 0 desencadeará imediatamente a operação de saldo), e depois cancelar todas as ordens pendentes.
Operação de balanço
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 balanço é executada periodicamente, mas se a função de balanço é executadalastKeepBalanceTS
Se a operação de cobertura for reiniciada, a operação de equilíbrio será iniciada imediatamente e o rendimento será calculado após o êxito da operação.
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",
"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)
A barra de estado não é projetada para ser particularmente complicada. Ela exibe a hora atual, o spread de preço da plataforma A para a plataforma B, bem como o spread de preço de B para A; também exibe o spread de meta de cobertura atual, os dados do ativo da plataforma A e os dados do ativo da plataforma B.
Em termos de parâmetros, concebemos o parâmetro de conversão do valor da taxa de câmbio, e também concebemos a conversão da taxa de câmbio na operação inicial do sistema de câmbio.main
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.
Para a função afectará dois aspectos:
Por exemplo, o par de negociação atual éBTC_USDT
, a unidade de preço éUSDT
, e a moeda de cotação disponível nos activos da conta é igualmenteUSDT
Se eu quiser converter o valor dos ativos em CNY, definirexchange.SetRate(6.8)
no código para converter os dados obtidos por todas as funçõesexchange
objecto, e depois converter em CNY.
Para converter para qual moeda de cotação, importarA taxa de câmbio da moeda de cotação corrente para a moeda de cotação-alvopara oSetRate
function.
Estratégia completa:Estratégia de cobertura spot de moeda de cotação diferente (Teaching)