Las estrategias de cobertura son muy buenas estrategias de práctica para principiantes en el diseño de estrategias.
En primer lugar, está claro que la estrategia a diseñar es una estrategia de cobertura spot de criptomonedas. Diseñamos la estrategia de cobertura más simple. Vendemos en el intercambio con el precio más alto solo entre los dos intercambios spot, y compramos en el intercambio con el precio más bajo para tomar la diferencia. Cuando los intercambios con precios más altos son monedas denominadas (porque se venden las monedas con precios más altos), y los intercambios con precios más bajos son monedas (se compran las monedas con precios más bajos), no se puede cubrir. En este momento, solo podemos esperar a que la inversión de precios se cubra.
Cuando se hace cobertura, el precio y la cantidad del pedido están limitados por el intercambio, y también hay un límite en la cantidad mínima de pedido. Además del límite mínimo, la estrategia en la cobertura también necesita considerar el volumen máximo de pedido a la vez. Si el volumen de orden es demasiado grande, no habrá suficiente volumen de pedido. También es necesario considerar cómo convertir el tipo de cambio si las dos monedas denominadas en bolsa son diferentes. Cuando se hace cobertura, la tarifa de manejo y el deslizamiento del tomador de la orden son todos los costos de transacción, no mientras haya una diferencia de precio que pueda cubrirse. Por lo tanto, la diferencia de precio de cobertura también tiene un valor de disparo. Si es menor que una cierta diferencia de precio, la cobertura perderá.
Sobre la base de estas consideraciones, la estrategia debe diseñarse con varios parámetros:
hedgeDiffPrice
, cuando la diferencia exceda este valor, se activa la operación de cobertura.minHedgeAmount
, el importe mínimo de la orden (monedas) que puede cubrirse.maxHedgeAmount
, el importe máximo de la orden (monedas) para una cobertura.pricePrecisionA
, la precisión del precio de la orden (número de decimales) colocada por el Intercambio A.amountPrecisionA
, la precisión del importe de la orden efectuada por el Intercambio A (número de decimales).pricePrecisionB
, la precisión del precio de la orden (número de decimales) colocada por el Intercambio B.amountPrecisionB
, la precisión del importe de la orden efectuada por el Intercambio B (número de decimales).rateA
, la conversión del tipo de cambio del primer objeto de cambio añadido, por defecto es 1, no convertida.rateB
, la conversión del tipo de cambio del segundo objeto de cambio añadido, por defecto es 1, no convertida.La estrategia de cobertura necesita mantener el número de monedas en las dos cuentas sin cambios (es decir, no mantener posiciones en ninguna dirección y mantener la neutralidad), por lo que debe haber una lógica de equilibrio en la estrategia para detectar siempre el equilibrio.
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
}
Después de colocar el pedido, si no hay un pedido completado, necesitamos cancelarlo a tiempo, y el pedido no puede mantenerse pendiente.
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)
}
}
})
}
Al equilibrar el número de monedas, necesitamos encontrar el precio acumulado a un cierto número de monedas en un cierto dato de profundidad, por lo que necesitamos una función para manejarlo.
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
}
Luego tenemos que diseñar y escribir la operación específica de la orden de cobertura, que necesita ser diseñado para colocar órdenes concurrentes:
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, completemos el diseño de la función de equilibrio, que es ligeramente complicada.
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
}
}
Después de diseñar estas funciones de acuerdo con los requisitos de la estrategia, comience a diseñar la función principal de la estrategia.
En la plataforma FMZ, la estrategia se ejecuta desde elmain
En el comienzo de lamain
función, tenemos que hacer un poco de trabajo de inicialización de la estrategia.
Nombre del objeto de intercambio Debido a que muchas operaciones en la estrategia tienen que usar los objetos de intercambio, como obtener cotizaciones de mercado, realizar pedidos, etc. Por lo que sería engorroso usar un nombre largo cada vez, el consejo es usar un nombre simple en su lugar, por ejemplo:
var exA = exchanges[0]
var exB = exchanges[1]
Esto hace que sea más fácil escribir código más tarde.
Tipo de cambio, diseño relacionado con la precisión
// 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)
Si los parámetros del tipo de cambiorateA
, rateB
se establecen en 1 (el valor predeterminado es 1), es decir,rateA != 1
o bienrateB != 1
no se activará, por lo que no se establecerá la conversión del tipo de cambio.
Reinicie todos los datos
A veces es necesario eliminar todos los registros y borrar los datos registrados cuando se inicia la estrategia.isReset
, y diseñar el código de restablecimiento en la parte de inicialización de la estrategia, por ejemplo:
if (isReset) { // When isReset is true, reset the data
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("reset all data", "#FF0000")
}
Restaurar los datos iniciales de la cuenta, actualizar los datos de la cuenta corriente
Para juzgar el saldo, la estrategia debe registrar continuamente los activos de la cuenta inicial para su comparación con el activo corriente.nowAccs
se utiliza para registrar los datos de la cuenta corriente, utilizando la función que acabamos de diseñarupdateAccs
para obtener los datos de la cuenta del intercambio actual.initAccs
Se utiliza para registrar el estado inicial de la cuenta (número de monedas, número de monedas denominadas, etc. en los intercambios A y B).initAccs
, utilizar el_G()
función para restaurar primero (la función _G registrará los datos de forma persistente, y puede devolver los datos registrados de nuevo, ver la documentación de la API para más detalles:www.fmz.com/api#_gk-v)), si la consulta no funciona, utilice la información de la cuenta corriente para asignar el valor y utilice el_G
función para registrar.
Como el siguiente código:
var nowAccs = _C(updateAccs, exchanges)
var initAccs = _G("initAccs")
if (!initAccs) {
initAccs = nowAccs
_G("initAccs", initAccs)
}
El código en el bucle principal es el proceso de cada ronda de ejecución de la lógica de la estrategia, que se ejecuta una y otra vez para formar el bucle principal de la estrategia.
Obtener datos de mercado y juzgar su validez
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
}
Aquí podemos ver que la función concurrenteexchange.Go
de la plataforma FMZ se utiliza para crear objetos concurrentesdepthARoutine
, depthBRoutine
que llaman elGetDepth()
Cuando estos dos objetos concurrentes se crean, elGetDepth()
La interfaz se llama inmediatamente, y ambas solicitudes de datos de profundidad se envían al intercambio.
Entonces llama alwait()
el método dedepthARoutine
, depthBRoutine
objetos para obtener los datos de profundidad.
Una vez obtenidos los datos de profundidad, es necesario comprobar los datos de profundidad para determinar su validez.continue
se activa la instrucción para volver a ejecutar el bucle principal.
Utilice elspread value
Parámetro o elspread ratio
¿Qué 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
}
En cuanto a los parámetros, hemos hecho un diseño: los parámetros de FMZ pueden sermuestrao bienescondersebasado en un parámetro, por lo que podemos hacer un parámetro para decidir si utilizarprice spread
, ospread ratio
.
Un parámetrodiffAsPercentage
Las otras dos configuraciones de parámetros para mostrar o ocultar basadas en este parámetro son:hedgeDiffPrice@!diffAsPercentage
, que se muestra cuandodiffAsPercentage
es falso.hedgeDiffPercentage@diffAsPercentage
, que se muestra cuandodiffAsPercentage
Es cierto.
Después de este diseño, verificamos eldiffAsPercentage
La diferencia de precio entre los precios de los activos y los precios de los activos es el parámetro que determina la condición de activación de la cobertura basada en la relación entre las diferencias de precios.diffAsPercentage
el parámetro verificado, la cobertura se activa por la diferencia de precio.
Determinar las condiciones de activación de la 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
}
}
Las condiciones de activación de la cobertura son las siguientes:
isTrade
En este caso, si se activa la cobertura, la variable se establece entrue
. Y restablecer la variable globallastKeepBalanceTS
en 0 (lastKeepBalanceTS se utiliza para marcar la marca de tiempo de la última operación de balanceo, si se establece en 0 se activará inmediatamente la operación de balanceo), y luego se cancelarán todas las órdenes pendientes.Operación de equilibrio
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
}
}
}
Se puede ver que la función de equilibrio se ejecuta periódicamente, pero si ellastKeepBalanceTS
Si el valor de la operación de cobertura se restablece en 0 después de que se inicie la operación de cobertura, la operación de balance se iniciará inmediatamente.
Información de la 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)
La barra de estado no es particularmente compleja en su diseño. Muestra la hora actual, la diferencia de precio de Exchange A a Exchange B y la diferencia de precio de Exchange B a Exchange A. Y muestra el margen objetivo de cobertura actual, los datos de activos de la cuenta de Exchange A y la cuenta de Exchange B.
En cuanto a los parámetros, diseñamos el parámetro de valor del tipo de conversión, y también diseñamos la conversión del tipo de cambio en el funcionamiento inicial delmain
En la actualidad, la mayoría de los proyectos de investigación y desarrollo se centran en el desarrollo de nuevas tecnologías de la información.SetRate
La función de conversión de tipo de cambio debe ejecutarse primero.
Porque esta función afecta a dos aspectos:
BTC_USDT
, la unidad de precio esUSDT
, y la moneda denominada disponible en los activos de la cuenta es tambiénUSDT
Si quiero convertir el valor en CNY, establecerexchange.SetRate(6.8)
en el código para convertir los datos obtenidos por todas las funciones bajo elexchange
objeto de cambio a CNY.
Para convertir a qué moneda denominada, pasar enel tipo de cambio de la moneda denominada actual a la moneda denominada de destinoEn elSetRate
function.Estrategia completa:Estrategia de cobertura al contado de diferentes monedas denominadas (Tutorial)