Para os iniciantes no design de estratégias, a estratégia de hedge é uma ótima estratégia de prática. Este artigo implementa uma estratégia de hedge de caixa de moeda digital simples, mas prática, com a esperança de que os iniciantes aprendam alguma experiência de design.
Para começar, é preciso esclarecer que a estratégia que estamos a planear é uma estratégia de hedge de moeda digital, que é a mais simples, onde só se vende uma moeda com um preço mais alto entre duas moedas, e a moeda mais baixa é comprada e ganha o diferencial. Quando a moeda mais alta é comprada e a moeda mais baixa é comprada, não se pode hedgear.
O preço, a quantidade e a precisão dos pedidos são limitados no momento do hedge, e há um limite de quantidade mínima de pedidos. Além do limite mínimo, a estratégia fora do limite mínimo também deve considerar o máximo de pedidos de um hedge, o que significa que não haverá quantidade suficiente de pedidos. Também é necessário considerar como converter os preços de câmbio se as duas moedas negociadas forem diferentes.
Com base nessas considerações, a estratégia precisa projetar vários parâmetros:
hedgeDiffPrice
Quando a diferença excede esse valor, a operação de hedge é desencadeada.minHedgeAmount
O número mínimo de moedas cobertas é o valor mínimo de moedas cobertas.maxHedgeAmount
O número máximo de moedas cobertas por um hedge.pricePrecisionA
A precisão do preço de compra da bolsa A (número de dígitos menores)amountPrecisionA
A precisão da quantidade de unidades (número de dígitos menores) no mercado A.pricePrecisionB
A exibição de preços é feita por uma plataforma de negociação chamada B.amountPrecisionB
A precisão da quantidade da bolsa B é menor que a quantidade da bolsa B.rateA
, a conversão de taxa de câmbio do primeiro objeto de troca adicionado, o 1 não é convertido por defeito.rateB
, a conversão de taxa de câmbio do segundo objeto de troca adicionado, o 1 não é convertido por defeito.A estratégia de hedge requer manter o número de moedas de ambas as contas inalterado (isto é, não manter posições direcionais, mantendo-se neutro), portanto, é necessário que a estratégia tenha uma lógica de equilíbrio para sempre detectar o equilíbrio. Ao detectar o equilíbrio, não é necessário obter dados de ativos de ambas as bolsas. Precisamos escrever uma função para usar.
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 a ordem não for executada, precisamos cancelar a ordem em tempo hábil e não podemos manter a ordem pendente. Esta operação precisa ser tratada tanto no módulo de equilíbrio quanto na lógica de hedge, então é necessário projetar uma função de cancelamento total de ordem.
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)
}
}
})
}
Quando estamos a equilibrar o número de moedas, precisamos de encontrar um preço que se acumule a um determinado número de moedas em algum tipo de dados de profundidade, por isso 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
}
E então nós precisamos de criar e escrever uma operação de sub-ordem para um hedge específico, que precisa ser projetado como uma sub-ordem em simultâneo:
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 terminar o design da função de equilíbrio, que é um pouco mais complexa.
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
}
}
As funções foram projetadas de acordo com as necessidades da política, e abaixo você pode começar a projetar as principais funções da política.
A estratégia no FMZ é de:main
A função é executada em.main
A parte inicial da função é onde nós vamos fazer algumas estratégias de inicialização.
Nome do objeto da troca Uma vez que muitas das operações da estratégia são usadas para objetos de câmbio, como obter transações, fazer pedidos, etc. Por isso, usar um nome mais longo a cada vez pode ser um problema.
var exA = exchanges[0]
var exB = exchanges[1]
A partir daí, é mais confortável escrever código para trás.
Desenhos relacionados a taxas 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 de câmbiorateA
、rateB
A definição de 1 (default é 1), ou seja,rateA != 1
OurateB != 1
A conversão de taxa de câmbio não é iniciada, portanto não é configurada.
Reinicie todos os dados
Às vezes, quando uma política é iniciada, é necessário excluir todos os registros, dados de registros vazios.isReset
A partir daí, a parte de código redesenhada que foi initializada na política é projetada, por exemplo:
if (isReset) { // 当isReset为真时重置数据
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
Restaurar dados de conta inicial, atualizar dados de conta atual
Para determinar o equilíbrio, a estratégia requer um registro contínuo da situação dos ativos da conta inicial em comparação com a atual.nowAccs
Esta variável é usada para registrar os dados da conta atual usando uma função que acabamos de criar.updateAccs
Para obter os dados da conta da bolsa atual.initAccs
Para registrar o estado inicial da conta (datos como número de moedas, quantidade de moedas de câmbio A e B);initAccs
Usar primeiro_G()
Função de recuperação ((_G função que registra dados permanentemente e pode retornar os dados registrados, veja a documentação API:LinksSe você não consegue fazer a consulta, atribui e usa as informações da conta atual._G
A função é registrada.
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 ciclo principal é o processo executado a cada rodada da lógica da estratégia, e a execução repetida e contínua constitui o ciclo principal da estratégia. Vejamos abaixo o processo executado a cada vez pelo programa no ciclo principal.
Obter dados do mercado para avaliar a eficácia dos dados do 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 você pode ver uma função paralela usando a plataforma FMZ.exchange.Go
A partir daí, o Google criou um chamado.GetDepth()
Objetos em simultâneo da interfacedepthARoutine
、depthBRoutine
│ Quando os dois objetos são criados em simultâneo, é chamadoGetDepth()
A interface também ocorreu de imediato, quando as duas solicitações para obter dados de profundidade foram enviadas para a troca.
E depois chamou.depthARoutine
、depthBRoutine
Objetowait()
Como obter dados em profundidade?
Após a obtenção de dados de profundidade, é necessário examinar os dados de profundidade para determinar sua eficácia.continue
A frase re-executou o ciclo principal.
Utilização价差值
Os parâmetros são差价比例
Parâmetros?
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 esse design. Os parâmetros do FMZ podem ser baseados em algum parâmetro.DisponívelOuEscondidoEntão podemos fazer um parâmetro para decidir se usar.价格差
Ou差价比例
。
Adicionar um parâmetro no parâmetro da interface da estratégiadiffAsPercentage
Os outros dois parâmetros para exibição ou ocultação baseados neste parâmetro são:hedgeDiffPrice@!diffAsPercentage
QuandodiffAsPercentage
Para mostrar este parâmetro.hedgeDiffPercentage@diffAsPercentage
QuandodiffAsPercentage
Por que mostrar o parâmetro como verdadeiro?
Depois de ter feito esse projeto, escolhemos:diffAsPercentage
Parâmetros, ou seja, proporção de diferença de preços como condição de ação de hedge; não selecionadodiffAsPercentage
O parâmetro é a diferença de preço como condição para o desencadeamento do hedge.
Determinação de condições de desencadeamento do 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
}
}
A partir daí, a empresa começou a vender seus produtos para a China.
1, o diferencial de hedge é satisfeito primeiro e só pode ser hedgeado quando o diferencial de transação atende aos parâmetros de diferencial definidos.
2, o volume de hedge disponível deve satisfazer o mínimo de hedge definido no parâmetro, pois o limite mínimo de unidades que diferentes exchanges podem limitar é diferente, portanto, o menor dos dois deve ser escolhido.
3. Os ativos da bolsa que vendeu a operação são suficientes para vender e os ativos da bolsa que comprou a operação são suficientes para comprar.
Quando essas condições são atendidas, executar a função de hedge é executado. Antes da função principal, declaramos uma variável antecipadamente.isTrade
Para marcar se houve ou não um hedge, aqui, se o hedge for desencadeado, a variável é definida comotrue
E reinicie as variáveis globais.lastKeepBalanceTS
Para 0 ((lastKeepBalanceTS é usado para marcar a barra de tempo da operação de balanço mais recente, que é definida como 0 para iniciar a operação de balanço imediatamente) e desativar todas as listas 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
}
}
}
A função de equilíbrio pode ser executada regularmente, mas se a operação de hedge for desencadeada, a função de equilíbrio pode ser executada regularmente, mas a função de equilíbrio não pode ser executada regularmente.lastKeepBalanceTS
A operação de equilíbrio é imediatamente acionada se for reiniciada para 0; o lucro é calculado após o sucesso do equilíbrio.
Informações de 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 é de design especialmente complicado, mostra o tempo atual, mostra o diferencial de A para B e de B para A. Mostra o diferencial atual do alvo de hedge. Mostra os dados de ativos da conta de A e os dados de ativos da conta de B.
No parâmetro, nós projetamos o parâmetro de conversão de valores de câmbio, que é o início da estratégia.main
A parte inicial da operação da função que nós também projetamos é a conversão de taxas.SetRate
A função de conversão de câmbio precisa ser executada primeiro.
A função é uma função que se aplica a dois níveis:
BTC_USDT
Os preços são:USDT
A moeda disponível para a conta é também a moeda.USDT
Se eu quiser converter em CNY, configure no código:exchange.SetRate(6.8)
E então,exchange
Todos os dados obtidos por funções sob este objeto de troca são convertidos em CNY.
Em troca do preço da moeda.SetRate
Função de entradaTaxa de câmbio da moeda atual para a moeda alvo。A estratégia completa:Estratégias de hedge de curto prazo para diferentes moedas
Espinhaço-de-cabeça-largo _ Espada Santa da UcrâniaMuito bem.