이전 기사에서 우리는 다종계약 스프레드 모니터링 전략을 함께 설계했습니다. 이 기사에서는 이 아이디어를 계속 개선할 것입니다. 이 아이디어가 실행 가능하는지 확인하고 전략 디자인을 확인하기 위해 OKEX V5 시뮬레이션 봇으로 실행해보겠습니다. 이 프로세스는 또한 암호화폐 프로그래밍 거래 및 양적 거래 과정에서 경험해야합니다. 초보자가 귀중한 경험을 축적 할 수 있기를 바랍니다.
스포일러 경보, 전략이 실행되고 있고, 저는 조금 흥분됩니다!
전략의 전체적인 디자인은 가장 간단한 방법으로 구현됩니다. 세부 사항이 너무 까다롭지 않지만 코드에서 몇 가지 팁을 배울 수 있습니다. 전체 전략 코드는 400 줄 미만이기 때문에 읽고 이해하는 것이 지루하지 않을 것입니다. 물론 이것은 테스트 데모입니다. 테스트하는 데 시간이 걸립니다. 그래서 내가 말하고 싶은 것은: 현재의 전략은 포지션을 열 때만 성공하며 포지션을 닫는 것과 같은 다양한 상황이 테스트되고 검증되어야합니다. 프로그램 설계에서 BUG는 피할 수 없습니다. 따라서 테스트와 DEBUG은 매우 중요합니다!
이전 문서의 코드를 바탕으로 전략 설계로 돌아가서, 전략을 추가합니다.
위는 추가 기능입니다. 디자인을 단순화하기 위해 전략은 단지 긍정적 인 헤지 (단기 장기 계약, 긴 단기 계약) 를 위해 설계되었습니다. 현재 영구 계약 (단기) 은 부정적인 수수료율을 가지고 있으며, 영구 계약이 비율 이윤을 증가시킬 수 있는지 여부를 확인하기 위해 오래 갈 수 있습니다.
전략은 잠시만 진행해
약 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) { // 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"))
}
전략의 연설:https://www.fmz.com/strategy/288559
이 전략은 내가 직접 작성한 템플릿 클래스 라이브러리를 사용하지만, 너무 좋지 않기 때문에 공개되지 않습니다. 위의 전략 소스 코드는 이 템플릿을 사용하지 않고도 수정할 수 있습니다.
관심이 있다면 OKEX V5 시뮬레이션 봇을 사용해 테스트할 수 있습니다. 이 전략은 검증할 수 없어