Trong bài viết trước, chúng tôi đã thiết kế một chiến lược giám sát phân tán hợp đồng đa ký hiệu cùng nhau. Trong bài viết này, chúng tôi sẽ tiếp tục cải thiện ý tưởng này. Hãy xem ý tưởng có khả thi hay không, và chạy nó với OKEX V5 mô phỏng bot để xác minh thiết kế chiến lược. Các quy trình này cũng cần phải được trải nghiệm trong quá trình giao dịch tiền điện tử được lập trình và giao dịch định lượng. Tôi hy vọng rằng bạn có thể tích lũy kinh nghiệm có giá trị từ đó.
Spoiler: chiến lược đã được chạy, mà là một chút thú vị.
Các chiến lược được thiết kế theo một ý tưởng đơn giản nhất. Mặc dù không có yêu cầu nghiêm ngặt về xử lý chi tiết, bạn vẫn có thể học một số thủ thuật từ mã. Toàn bộ chiến lược ít hơn 400 dòng, vì vậy nó không quá nhàm chán để đọc. Tất nhiên, đây chỉ là một bản demo để thử nghiệm, và chúng ta cần chạy nó trong một thời gian để xem kết quả. Điều tôi muốn nói là: chiến lược hiện tại chỉ thành công trong việc mở các vị trí, và có nhiều tình huống khác nhau, chẳng hạn như đóng các vị trí, thực sự được thử nghiệm và phát hiện. Các lỗi trong thiết kế chương trình là không thể tránh khỏi, vì vậy kiểm tra và gỡ lỗi rất quan trọng!
Trở lại với thiết kế chiến lược, dựa trên mã trong bài viết trước, tôi đã thêm:
Những điều trên là các chức năng được thêm vào. Để đơn giản, chiến lược chỉ thiết kế bảo hiểm tích cực (làm ngắn cho hợp đồng dài hạn; làm dài cho hợp đồng ngắn hạn). Hiện tại, hợp đồng vĩnh cửu (kết hợp ngắn hạn) có tỷ lệ tài trợ âm; chỉ cần làm trong hợp đồng vĩnh cửu, để xem liệu lợi nhuận của tỷ lệ tài trợ có thể được tăng.
Hãy để chiến lược hoạt động trong một thời gian.
Nó đã được thử nghiệm trong khoảng 3 ngày, và biến động lây lan thực sự là tốt.
Một phần lợi nhuận từ tỷ lệ tài trợ có thể được nhìn thấy trong hình dưới đây.
Mã nguồn chiến lược được chia sẻ như sau:
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"))
}
Địa chỉ chiến lược:https://www.fmz.com/strategy/288559
Chiến lược sử dụng một trong những mẫu được thiết kế bởi tôi; mẫu không đủ tốt để được hiển thị ở đây, vì vậy bạn có thể sử dụng một mẫu khác bằng cách sửa đổi mã nguồn chiến lược một chút.
Nếu bạn quan tâm, bạn có thể chạy chiến lược trong OKEX V5 robot mô phỏng. Oh, đúng, chiến lược không thể được kiểm tra lại!