В предыдущей статье мы разработали стратегию мониторинга распространения контрактов с несколькими символами вместе. В этой статье мы продолжим улучшать эту идею. Давайте посмотрим, является ли идея осуществимой, и запустим ее с OKEX V5 симулируемым ботом для проверки дизайна стратегии. Эти процессы также требуются для опыта в процессе программированной торговли криптовалютами и количественной торговли. Я надеюсь, что вы можете накопить ценный опыт из этого.
Спойлер: стратегия работает, что немного захватывает.
Общий дизайн стратегии реализуется по самой простой идее. Хотя нет строгих требований к детальной обработке, вы все равно можете узнать некоторые хитрости из кода. Вся стратегия составляет менее 400 строк, поэтому ее чтение не слишком скучно. Конечно, это только демонстрационная версия для тестирования, и нам нужно запустить ее некоторое время, чтобы увидеть результат. Я хочу сказать: нынешняя стратегия успешна только при открытии позиций, и есть различные ситуации, такие как закрытие позиций, которые действительно должны быть протестированы и обнаружены. Ошибки в дизайне программы неизбежны, поэтому тестирование и отладка очень важны!
Возвращаясь к разработке стратегии, основанной на коде в предыдущей статье, я добавил:
В настоящее время вечный контракт (короткосрочный) имеет отрицательную ставку финансирования; просто сделайте в вечном контракте, чтобы увидеть, можно ли увеличить доходность ставки финансирования.
Позвольте стратегии работать некоторое время.
Его тестировали около 3 дней, и колебания распространения на самом деле нормальные.
Часть доходности от ставки финансирования можно увидеть на следующей картинке.
Исходный код стратегии разделен следующим образом:
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) { // the 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 the simulated environment
Log("Only support OKEX V5 API, and switch to OKEX V5 simulated bot:")
} else {
exchange.IO("simulate", false) // switch to the bot
Log("Only support OKEX V5 API, and switch to OKEX V5 bot:")
}
if (exchange.GetName() != "Futures_OKCoin") {
throw "support OKEX Futures"
}
// initialize
if (isReset) {
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("reset all data", "#FF0000")
}
// initialize the mark
var isFirst = true
// the profit prints the period
var preProfitPrintTS = 0
// the total equity
var totalEquity = 0
var posTbls = [] // the array of position table
// declare arrCfg
var arrCfg = []
_.each(arrNearContractType, function(ct) {
arrCfg.push(createCfg(formatSymbol(ct)[0]))
})
var objCharts = Chart(arrCfg)
objCharts.reset()
// create objects
var exName = exchange.GetName() + "_V5"
var nearConfigureFunc = $.getConfigureFunc()[exName]
var farConfigureFunc = $.getConfigureFunc()[exName]
var nearEx = $.createBaseEx(exchange, nearConfigureFunc)
var farEx = $.createBaseEx(exchange, farConfigureFunc)
// write the contracts to be subscribed in advance
_.each(arrNearContractType, function(ct) {
nearEx.pushSubscribeSymbol(ct)
})
_.each(arrFarContractType, function(ct) {
farEx.pushSubscribeSymbol(ct)
})
while (true) {
var ts = new Date().getTime()
// obtain the market quotes
nearEx.goGetTickers()
farEx.goGetTickers()
var nearTickers = nearEx.getTickers()
var farTickers = farEx.getTickers()
if (!farTickers || !nearTickers) {
Sleep(2000)
continue
}
var tbl = {
type : "table",
title : "long-short term spread",
cols : ["trading pair", "long term", "shaort term", "positive hedge", "negative hedge"],
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]])
}
}
}
})
})
// initialize
if (isFirst) {
isFirst = false
var recoveryNets = _G("nets")
var recoveryInitTotalEquity = _G("initTotalEquity")
if (!recoveryNets) {
// detect positions
_.each(subscribeFarTickers, function(farTicker) {
var pos = farEx.getFuPos(farTicker.originalSymbol, ts)
if (pos.length != 0) {
Log(farTicker.originalSymbol, pos)
throw "There are positions during the initialization"
}
})
_.each(subscribeNearTickers, function(nearTicker) {
var pos = nearEx.getFuPos(nearTicker.originalSymbol, ts)
if (pos.length != 0) {
Log(nearTicker.originalSymbol, pos)
throw "There are positions during the initialization"
}
})
// 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 "Fail to obtain the total equity by initialization!"
}
} else {
// recover
nets = recoveryNets
initTotalEquity = recoveryInitTotalEquity
}
}
// query the grid, to detect whether a trade 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 detected", obj.symbol, "spread")
return
}
// examine the grid; dynamically add
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,
})
}
// detect 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 hedge, open positions
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 hedge, close positions
upP.sell = false
}
}
}
obj.prePlus = currPlus // record the spread of the time, as cache, which will be used to judge upcross or downcross for the next time
// add other tables to export
})
if (ts - preProfitPrintTS > 1000 * 60 * 5) { // print every 5 minutes
var currTotalEquity = getTotalEquity()
if (currTotalEquity) {
totalEquity = currTotalEquity
LogProfit(totalEquity - initTotalEquity, "&") // print the dynamic profit of equity
}
// detect positions
posTbls = [] // reset and 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
}
// display the 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 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("Fail 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, "Wrong calculation of the order amount:", 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 onexit function", "#FF0000")
_G("nets", nets)
_G("initTotalEquity", initTotalEquity)
Log("Save data:", _G("nets"), _G("initTotalEquity"))
}
Адрес стратегии:https://www.fmz.com/strategy/288559
В стратегии использовался один из шаблонов, разработанных мной; шаблон недостаточно хорош, чтобы показать его здесь, поэтому вы можете использовать другой шаблон, немного изменив исходный код стратегии.
Если вы заинтересованы, вы можете запустить стратегию в моделированном боте OKEX V5. О, да, стратегию нельзя проверить!