En el artículo anterior, diseñamos una estrategia de monitoreo de propagación de contratos de múltiples especies juntos. En este artículo, continuaremos mejorando esta idea. Veamos si esta idea es factible, y la ejecutaremos con el bot de simulación OKEX V5 para verificar el diseño de la estrategia. Estos procesos también se requieren para ser experimentados en el proceso de operaciones programáticas y operaciones cuantitativas de criptomonedas. Espero que los principiantes puedan acumular una valiosa experiencia.
¡Alerta de spoiler, la estrategia está funcionando, y estoy un poco emocionado!
El diseño general de la estrategia se implementa de la manera más sencilla. Aunque los detalles no son demasiado exigentes, aún puedes aprender algunos consejos del código. El código de estrategia general es de menos de 400 líneas, por lo que no será aburrido de leer y entender. Por supuesto, esto es solo una demostración de prueba, se tarda un tiempo en probarla. Así que lo que quiero decir es: la estrategia actual solo tiene éxito en la apertura de posiciones, y varias situaciones como cerrar una posición necesitan ser probadas y verificadas. Los BUG en el diseño del programa son inevitables, por lo que las pruebas y DEBUG son muy importantes!
Volviendo al diseño de la estrategia, basado en el código del artículo anterior, se agrega la estrategia:
En la actualidad, el contrato perpetuo (a corto plazo) tiene una tasa de comisiones negativa, que solo puede esperar que el contrato perpetuo vea si puede aumentar las ganancias de la tasa.
Deja que la estrategia funcione por un tiempo ~
Después de realizar pruebas durante unos 3 días, la fluctuación de la propagación sigue siendo factible.
Aquí podemos ver las ganancias de algunas tasas de financiación.
Comparte el código fuente de la estrategia a continuación:
var arrNearContractType = strNearContractType.split(",")
var arrFarContractType = strFarContractType.split(",")
var nets = null
var initTotalEquity = null
var OPEN_PLUS = 1
var COVER_PLUS = 2
function createNet(begin, diff, initAvgPrice, diffUsagePercentage) {
if (diffUsagePercentage) {
diff = diff * initAvgPrice
}
var oneSideNums = 3
var up = []
var down = []
for (var i = 0 ; i < oneSideNums ; i++) {
var upObj = {
sell : false,
price : begin + diff / 2 + i * diff
}
up.push(upObj)
var j = (oneSideNums - 1) - i
var downObj = {
sell : false,
price : begin - diff / 2 - j * diff
}
if (downObj.price <= 0) { // Price cannot be less than or equal to 0
continue
}
down.push(downObj)
}
return down.concat(up)
}
function createCfg(symbol) {
var cfg = {
extension: {
layout: 'single',
height: 300,
col: 6
},
title: {
text: symbol
},
xAxis: {
type: 'datetime'
},
series: [{
name: 'plus',
data: []
}]
}
return cfg
}
function formatSymbol(originalSymbol) {
var arr = originalSymbol.split("-")
return [arr[0] + "_" + arr[1], arr[0], arr[1]]
}
function main() {
if (isSimulate) {
exchange.IO("simulate", true) // Switch to simulation environment
Log("Only OKEX V5 API is supported, switch to OKEX V5 simulation bot:")
} else {
exchange.IO("simulate", false) // Switch to real bot
Log("Only OKEX V5 API is supported, switch to OKEX V5 simulation bot:")
}
if (exchange.GetName() != "Futures_OKCoin") {
throw "Support OKEX futures"
}
// Initialization
if (isReset) {
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("Reset all data", "#FF0000")
}
// Initialization marker
var isFirst = true
// Profit print period
var preProfitPrintTS = 0
// Total equity
var totalEquity = 0
var posTbls = [] // Position table array
// Declare arrCfg
var arrCfg = []
_.each(arrNearContractType, function(ct) {
arrCfg.push(createCfg(formatSymbol(ct)[0]))
})
var objCharts = Chart(arrCfg)
objCharts.reset()
// Create object
var exName = exchange.GetName() + "_V5"
var nearConfigureFunc = $.getConfigureFunc()[exName]
var farConfigureFunc = $.getConfigureFunc()[exName]
var nearEx = $.createBaseEx(exchange, nearConfigureFunc)
var farEx = $.createBaseEx(exchange, farConfigureFunc)
// Pre-write the contract that require subscriptions
_.each(arrNearContractType, function(ct) {
nearEx.pushSubscribeSymbol(ct)
})
_.each(arrFarContractType, function(ct) {
farEx.pushSubscribeSymbol(ct)
})
while (true) {
var ts = new Date().getTime()
// Obtain market data
nearEx.goGetTickers()
farEx.goGetTickers()
var nearTickers = nearEx.getTickers()
var farTickers = farEx.getTickers()
if (!farTickers || !nearTickers) {
Sleep(2000)
continue
}
var tbl = {
type : "table",
title : "Long term-near term spread",
cols : ["Trading pair", "long term", "near term", "positive hedging", "negative hedging"],
rows : []
}
var subscribeFarTickers = []
var subscribeNearTickers = []
_.each(farTickers, function(farTicker) {
_.each(arrFarContractType, function(symbol) {
if (farTicker.originalSymbol == symbol) {
subscribeFarTickers.push(farTicker)
}
})
})
_.each(nearTickers, function(nearTicker) {
_.each(arrNearContractType, function(symbol) {
if (nearTicker.originalSymbol == symbol) {
subscribeNearTickers.push(nearTicker)
}
})
})
var pairs = []
_.each(subscribeFarTickers, function(farTicker) {
_.each(subscribeNearTickers, function(nearTicker) {
if (farTicker.symbol == nearTicker.symbol) {
var pair = {symbol: nearTicker.symbol, nearTicker: nearTicker, farTicker: farTicker, plusDiff: farTicker.bid1 - nearTicker.ask1, minusDiff: farTicker.ask1 - nearTicker.bid1}
pairs.push(pair)
tbl.rows.push([pair.symbol, farTicker.originalSymbol, nearTicker.originalSymbol, pair.plusDiff, pair.minusDiff])
for (var i = 0 ; i < arrCfg.length ; i++) {
if (arrCfg[i].title.text == pair.symbol) {
objCharts.add([i, [ts, pair.plusDiff]])
}
}
}
})
})
// Initialization
if (isFirst) {
isFirst = false
var recoveryNets = _G("nets")
var recoveryInitTotalEquity = _G("initTotalEquity")
if (!recoveryNets) {
// Check positions
_.each(subscribeFarTickers, function(farTicker) {
var pos = farEx.getFuPos(farTicker.originalSymbol, ts)
if (pos.length != 0) {
Log(farTicker.originalSymbol, pos)
throw "Initialized with a position"
}
})
_.each(subscribeNearTickers, function(nearTicker) {
var pos = nearEx.getFuPos(nearTicker.originalSymbol, ts)
if (pos.length != 0) {
Log(nearTicker.originalSymbol, pos)
throw "Initialized with a position"
}
})
// Construct nets
nets = []
_.each(pairs, function (pair) {
farEx.goGetAcc(pair.farTicker.originalSymbol, ts)
nearEx.goGetAcc(pair.nearTicker.originalSymbol, ts)
var obj = {
"symbol" : pair.symbol,
"farSymbol" : pair.farTicker.originalSymbol,
"nearSymbol" : pair.nearTicker.originalSymbol,
"initPrice" : (pair.nearTicker.ask1 + pair.farTicker.bid1) / 2,
"prePlus" : pair.farTicker.bid1 - pair.nearTicker.ask1,
"net" : createNet((pair.farTicker.bid1 - pair.nearTicker.ask1), diff, (pair.nearTicker.ask1 + pair.farTicker.bid1) / 2, true),
"initFarAcc" : farEx.getAcc(pair.farTicker.originalSymbol, ts),
"initNearAcc" : nearEx.getAcc(pair.nearTicker.originalSymbol, ts),
"farTicker" : pair.farTicker,
"nearTicker" : pair.nearTicker,
"farPos" : null,
"nearPos" : null,
}
nets.push(obj)
})
var currTotalEquity = getTotalEquity()
if (currTotalEquity) {
initTotalEquity = currTotalEquity
} else {
throw "Initialization to obtain total equity failed!"
}
} else {
// Recovery
nets = recoveryNets
initTotalEquity = recoveryInitTotalEquity
}
}
// Retrieve the grid and check if the trading is triggered
_.each(nets, function(obj) {
var currPlus = null
_.each(pairs, function(pair) {
if (pair.symbol == obj.symbol) {
currPlus = pair.plusDiff
obj.farTicker = pair.farTicker
obj.nearTicker = pair.nearTicker
}
})
if (!currPlus) {
Log("Not found", obj.symbol, " 's spread")
return
}
// Check grid, add dynamically
while (currPlus >= obj.net[obj.net.length - 1].price) {
obj.net.push({
sell : false,
price : obj.net[obj.net.length - 1].price + diff * obj.initPrice,
})
}
while (currPlus <= obj.net[0].price) {
var price = obj.net[0].price - diff * obj.initPrice
if (price <= 0) {
break
}
obj.net.unshift({
sell : false,
price : price,
})
}
// Search grid
for (var i = 0 ; i < obj.net.length - 1 ; i++) {
var p = obj.net[i]
var upP = obj.net[i + 1]
if (obj.prePlus <= p.price && currPlus > p.price && !p.sell) {
if (hedge(nearEx, farEx, obj.nearSymbol, obj.farSymbol, obj.nearTicker, obj.farTicker, hedgeAmount, OPEN_PLUS)) { // Positive hedging opening position
p.sell = true
}
} else if (obj.prePlus >= p.price && currPlus < p.price && upP.sell) {
if (hedge(nearEx, farEx, obj.nearSymbol, obj.farSymbol, obj.nearTicker, obj.farTicker, hedgeAmount, COVER_PLUS)) { // Positive hedging closing position
upP.sell = false
}
}
}
obj.prePlus = currPlus // Record the current spread as a cache, and use it to judge whether it's above the SMA or below the SMA next time
// Add other chart outputs
})
if (ts - preProfitPrintTS > 1000 * 60 * 5) { // Print every 5 minutes
var currTotalEquity = getTotalEquity()
if (currTotalEquity) {
totalEquity = currTotalEquity
LogProfit(totalEquity - initTotalEquity, "&") // Print dynamic equity profits
}
// Check positions
posTbls = [] // Reset, update
_.each(nets, function(obj) {
var currFarPos = farEx.getFuPos(obj.farSymbol)
var currNearPos = nearEx.getFuPos(obj.nearSymbol)
if (currFarPos && currNearPos) {
obj.farPos = currFarPos
obj.nearPos = currNearPos
}
var posTbl = {
"type" : "table",
"title" : obj.symbol,
"cols" : ["contract code", "amount", "price"],
"rows" : []
}
_.each(obj.farPos, function(pos) {
posTbl.rows.push([pos.symbol, pos.amount, pos.price])
})
_.each(obj.nearPos, function(pos) {
posTbl.rows.push([pos.symbol, pos.amount, pos.price])
})
posTbls.push(posTbl)
})
preProfitPrintTS = ts
}
// Show grid
var netTbls = []
_.each(nets, function(obj) {
var netTbl = {
"type" : "table",
"title" : obj.symbol,
"cols" : ["grid"],
"rows" : []
}
_.each(obj.net, function(p) {
var color = ""
if (p.sell) {
color = "#00FF00"
}
netTbl.rows.push([JSON.stringify(p) + color])
})
netTbl.rows.reverse()
netTbls.push(netTbl)
})
LogStatus(_D(), "total equity:", totalEquity, "initial total equity:", initTotalEquity, "floating profit and loss:", totalEquity - initTotalEquity,
"\n`" + JSON.stringify(tbl) + "`" + "\n`" + JSON.stringify(netTbls) + "`" + "\n`" + JSON.stringify(posTbls) + "`")
Sleep(interval)
}
}
function getTotalEquity() {
var totalEquity = null
var ret = exchange.IO("api", "GET", "/api/v5/account/balance", "ccy=USDT")
if (ret) {
try {
totalEquity = parseFloat(ret.data[0].details[0].eq)
} catch(e) {
Log("Failed to obtain the total equity of the account!")
return null
}
}
return totalEquity
}
function hedge(nearEx, farEx, nearSymbol, farSymbol, nearTicker, farTicker, amount, tradeType) {
var farDirection = null
var nearDirection = null
if (tradeType == OPEN_PLUS) {
farDirection = farEx.OPEN_SHORT
nearDirection = nearEx.OPEN_LONG
} else {
farDirection = farEx.COVER_SHORT
nearDirection = nearEx.COVER_LONG
}
var nearSymbolInfo = nearEx.getSymbolInfo(nearSymbol)
var farSymbolInfo = farEx.getSymbolInfo(farSymbol)
nearAmount = nearEx.calcAmount(nearSymbol, nearDirection, nearTicker.ask1, amount * nearSymbolInfo.multiplier)
farAmount = farEx.calcAmount(farSymbol, farDirection, farTicker.bid1, amount * farSymbolInfo.multiplier)
if (!nearAmount || !farAmount) {
Log(nearSymbol, farSymbol, "Order amount calculation error:", nearAmount, farAmount)
return
}
nearEx.goGetTrade(nearSymbol, nearDirection, nearTicker.ask1, nearAmount[0])
farEx.goGetTrade(farSymbol, farDirection, farTicker.bid1, farAmount[0])
var nearIdMsg = nearEx.getTrade()
var farIdMsg = farEx.getTrade()
return [nearIdMsg, farIdMsg]
}
function onexit() {
Log("Execute the tail function", "#FF0000")
_G("nets", nets)
_G("initTotalEquity", initTotalEquity)
Log("save the data:", _G("nets"), _G("initTotalEquity"))
}
Discurso público de la estrategia:https://www.fmz.com/strategy/288559
La estrategia utiliza una biblioteca de clases de plantilla escrita por mí mismo, que no es pública porque no es demasiado buena.
Si estás interesado, puedes usar un robot de simulación OKEX V5 para probar. Por cierto, esta estrategia no se puede probar