Para los principiantes en el diseño de estrategias, la estrategia de cobertura es muy buena para la práctica.
En primer lugar, debemos asegurarnos de que la estrategia a diseñar sea una estrategia de cobertura spot de criptomonedas. Diseñamos la cobertura más simple. Solo vendemos en la plataforma con el precio más alto entre las dos plataformas spot, y compramos en la plataforma con el precio más bajo para ganar el spread de precio. Cuando la plataforma con el precio más alto está llena de símbolos de moneda de cotización (porque el precio es alto, todos los símbolos de moneda se venden), o cuando la plataforma con el precio más bajo está llena de símbolos de moneda (porque el precio es bajo, los símbolos de moneda se compran por todos los activos), no se puede cubrir. En este momento, solo puede esperar a que el precio se invierta para cubrir.
Para el precio de la orden y el monto durante la cobertura, hay límites de precisión en cada plataforma, y también hay un límite en el monto mínimo de la orden. Además del límite mínimo, la estrategia también necesita considerar el monto máximo de la orden para una cobertura. Si la cantidad de la orden es demasiado grande, el mercado no tendrá un volumen de orden suficiente para eso. También es necesario considerar cómo convertir el tipo de cambio si las dos plataformas tienen monedas de cotización diferentes. La tarifa de manejo durante la cobertura y el deslizamiento del tomador de la orden son todos los costos comerciales. La cobertura no siempre ocurre mientras haya una diferencia de precio. Por lo tanto, el diferencial de precio de cobertura también tiene un valor de disparador. Si es menor que un cierto diferencial de precio, la cobertura producirá una pérdida.
En base a ello, la estrategia debe diseñarse con varios parámetros:
hedgeDiffPrice
Cuando el diferencial exceda el valor, se activará una cobertura.minHedgeAmount
, el importe mínimo de la orden ( importe del símbolo) disponible para una cobertura.maxHedgeAmount
, el importe máximo de la orden ( importe del símbolo) disponible para una cobertura.pricePrecisionA
, la precisión del precio de la orden (números decimales) de la plataforma A.amountPrecisionA
, la precisión del importe del pedido (cifras decimales) de la plataforma A.pricePrecisionB
, la precisión del precio de la orden (números decimales) de la plataforma B.amountPrecisionB
, la precisión del importe del pedido (cifras decimales) de la plataforma B.rateA
, el tipo de cambio de conversión del primer objeto de cambio añadido; el valor predeterminado es 1, lo que indica que no se debe convertir.rateB
, la conversión del tipo de cambio del segundo objeto de cambio añadido; el valor predeterminado es 1, lo que indica que no se debe convertir.La estrategia de cobertura necesita mantener la cantidad del símbolo de moneda de las dos cuentas sin cambios (es decir, no mantener ninguna posición direccional y mantener la neutralidad), por lo que debe haber una lógica de equilibrio en la estrategia para detectar siempre el saldo.
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 una orden, si no hay una orden ejecutada, debemos cancelarla a tiempo, y la orden 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 la cantidad de símbolos de moneda, necesitamos encontrar el precio con una cierta cantidad en un cierto fondo de datos, por lo que necesitamos una función como esta 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 simultáneamente:
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 un poco más 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 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 funciones han sido diseñadas de acuerdo con los requisitos de la estrategia, y podemos comenzar a diseñar la función principal de la estrategia.
En FMZ, la estrategia se ejecuta desde elmain
En el comienzo de lamain
Función, necesitamos hacer algunas inicialización de la estrategia.
Nombre del objeto de intercambio Para muchas operaciones en la estrategia de uso de objetos de intercambio, tales como obtener cotizaciones de mercado, la colocación de pedidos, y así sucesivamente, por lo que sería inconveniente utilizar un nombre más largo cada vez, mi pequeño truco es utilizar un nombre corto simple en su lugar, por ejemplo:
var exA = exchanges[0]
var exB = exchanges[1]
Entonces, será más cómodo escribir el código más tarde.
Tipo de cambio y precisión
// 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)
Si uno de los parámetros del tipo de cambio, a saberrateA
yrateB
, se establece en 1 (el valor predeterminado es 1), es decir,rateA != 1
o bienrateB != 1
El tipo de cambio no puede ser convertido.
Reinicie todas las fechas
A veces, es necesario eliminar todos los registros y aspirar los registros de datos cuando se inicia la estrategia.isReset
, y luego 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")
}
Recuperar los datos de la cuenta inicial y actualizar los datos de la cuenta actual
Para juzgar el saldo, la estrategia debe registrar continuamente la condición del activo inicial de la cuenta para su comparación con la actual.nowAccs
Se utiliza para registrar los datos de la cuenta corriente.updateAccs
Función que acabamos de diseñar para obtener los datos de la cuenta de la plataforma actual.initAccs
Se utiliza para registrar el estado inicial de la cuenta (datos como el importe del símbolo de moneda tanto de A como de B, el importe de la moneda de cotización, etc.).initAccs
En primer lugar, utilice el_G()
función para restaurar (la función _G registrará datos de forma persistente y puede devolver los datos registrados de nuevo; lea la documentación de la API para obtener detalles:Enlace).
Si no puede consultar los datos, utilizar la información de la cuenta corriente para asignar y utilizar_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, y la ejecución repetitiva sin parar construye el bucle principal de la estrategia.
Obtener las cotizaciones del mercado y juzgar la 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í se puede ver que la función concurrenteexchange.Go
de la plataforma FMZ se utiliza para crear objetos concurrentesdepthARoutine
ydepthBRoutine
que llaman elGetDepth()
Cuando estos dos objetos concurrentes se crean, elGetDepth()
Interfaz se llama inmediatamente, y ambas solicitudes de los datos de profundidad se envían a la plataforma.
Entonces, llama alwait()
método del objetodepthARoutine
y objetodepthBRoutine
para obtener los datos de profundidad.
Una vez obtenidos los datos de profundidad, es necesario comprobar los datos de profundidad para juzgar su validez.continue
se activa la instrucción para volver a ejecutar el bucle principal.
Utilizaciónprice spread
o bienspread 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
}
En términos de parámetros, hemos hecho tal diseño.muestrao bienescondersebasado en un parámetro, por lo que podemos hacer un parámetro para decidir si utilizarprice spread
, ospread ratio
.
El parámetrodiffAsPercentage
Los otros dos parámetros, que se mostrarán o ocultarán en función del parámetro, se establecen como:hedgeDiffPrice@!diffAsPercentage
; cuandodiffAsPercentage
es falso, se mostrará.hedgeDiffPercentage@diffAsPercentage
; cuandodiffAsPercentage
es verdad, se mostrará.
Después del diseño, hemos comprobado eldiffAsPercentage
Si el valor de la cobertura es el valor de la cobertura, el valor de la cobertura es el valor de la cobertura.diffAsPercentage
Si el parámetro no se comprueba, el diferencial de precios se utiliza como condición de activación de la cobertura.
El juez 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
}
}
Hay varias condiciones de activación para la cobertura: 1.En primer lugar, cumplir con el diferencial de cobertura; sólo cuando el diferencial de mercado cumple con el parámetro de diferencial establecido, puede ser posible la cobertura.
2.El importe de cobertura del mercado debe cumplir el importe mínimo de cobertura establecido en los parámetros.Debido a que el importe mínimo de la orden de las diferentes plataformas es diferente, se debe elegir el más pequeño de los dos.
3.Los activos en la plataforma con la operación de venta son suficientes para vender, y los activos en la plataforma con la operación de compra son suficientes para comprar.Cuando se cumplan estas condiciones, ejecutar la función de cobertura para colocar órdenes por cobertura.Antes de la función principal, declaramos una variableisTrade
En este caso, si se activa la cobertura, la variable se establece entrue
. Y restablecer la variable globallastKeepBalanceTS
a 0 (lastKeepBalanceTS se utiliza para marcar la marca de tiempo de la última operación de balance, y si se establece a 0 se activará inmediatamente la operación de balance), 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 a 0, la operación de balance se activará 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",
"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)
La barra de estado no está diseñada para ser particularmente complicada. Muestra la hora actual, el diferencial de precios de la plataforma A a la plataforma B, así como el diferencial de precios de B a A; también muestra el diferencial de meta de cobertura actual, los datos de activos de la plataforma A y los datos de activos de la plataforma B.
En cuanto a los parámetros, hemos diseñado el parámetro de conversión del valor del tipo de cambio, y también hemos diseñado la conversión del tipo de cambio en la operación 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.
Para la función afectará dos aspectos:
Por ejemplo, el par de operaciones actual esBTC_USDT
, la unidad de precio esUSDT
, y la moneda de cotización disponible en los activos de la cuenta es tambiénUSDT
Si quiero convertir el valor de los activos en CNY, establecerexchange.SetRate(6.8)
en el código para convertir los datos obtenidos por todas las funciones bajo elexchange
objetos, y luego convertirlos en CNY.
Para convertir a qué moneda de cotización, importarel tipo de cambio de la moneda de cotización actual a la moneda de cotización objetivoEn elSetRate
function.
Estrategia completa:Estrategia de cobertura al contado de diferentes monedas de cotización (Teaching)