
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.
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:
hedgeDiffPrice, quando a diferença de preço ultrapassa esse valor, a operação de hedge é acionada.minHedgeAmount, a quantidade mínima do pedido (em moedas) que pode ser protegida.maxHedgeAmount, a quantidade máxima de pedido (número de moedas) para um hedge.pricePrecisionA, a precisão do preço da ordem (número de casas decimais) da bolsa A.amountPrecisionA, a precisão da quantidade do pedido (número de casas decimais) da troca A.pricePrecisionB, a precisão do preço da ordem (número de casas decimais) da bolsa B.amountPrecisionB, a precisão da quantidade do pedido (número de casas decimais) da bolsa B.rateA, a conversão da taxa de câmbio do primeiro objeto de câmbio adicionado, o valor padrão é 1, sem conversão.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.
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.
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.
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:
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.
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.
var exA = exchanges[0]
var exB = exchanges[1]
Isso torna muito confortável escrever código mais tarde.
// 精度,汇率设置
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âmbiorateA、rateBAlguns 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.

À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")
}
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)
}
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.
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 interfacedepthARoutine、depthBRoutine. 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 liguedepthARoutine、depthBRoutineObjetowait()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.
价差值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差价比例。

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.
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.
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.
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.
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:
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)