
Im vorherigen Artikel haben wir eine Strategie zur Überwachung der Spreads verschiedener Kontrakte entwickelt. In diesem Artikel werden wir diese Idee weiter verbessern. Lassen Sie uns prüfen, ob diese Idee umsetzbar ist, und sie auf der OKEX V5-Simulationsplattform ausführen, um den Strategieentwurf zu überprüfen. Diese Prozesse müssen auch im Prozess des programmatischen Handels und des quantitativen Handels mit digitalen Währungen durchlaufen werden. Ich hoffe, dass Neulinge wertvolle Erfahrungen sammeln können.
Lassen Sie mich zunächst etwas verraten: Die Strategie funktioniert und ich bin ein bisschen aufgeregt!



Das Gesamtkonzept der Strategie ist auf einfachste Weise umgesetzt. Obwohl keine übermäßigen Anforderungen an die Details gestellt werden, kann man aus dem Code dennoch einige Tipps lernen. Der gesamte Strategiecode umfasst weniger als 400 Zeilen, sodass das Lesen und Verstehen nicht langweilig ist. Natürlich ist dies nur eine Test-DEMO und Sie müssen sie eine Weile ausführen, um sie zu testen. Was ich also sagen möchte, ist: Die aktuelle Strategie dient nur dem erfolgreichen Öffnen einer Position, und verschiedene Situationen wie das Schließen einer Position müssen tatsächlich getestet und überprüft werden. Fehler sind beim Programmdesign unvermeidlich, daher sind Testen und Debuggen sehr wichtig!
Zurück zum Strategieentwurf. Basierend auf dem Code im vorherigen Artikel wird die Strategie wie folgt ergänzt:
Das Obige sind die hinzugefügten Funktionen. Um das Design zu vereinfachen, ist die Strategie nur für positive Absicherungen (kurze Terminkontrakte, lange kurzfristige Kontrakte) ausgelegt. Derzeit weist der unbefristete Vertrag (kurzfristig) einen negativen Zinssatz auf. Daher ist es ein guter Zeitpunkt, auf den unbefristeten Vertrag zu setzen und zu sehen, ob die Zinseinnahmen erhöht werden können.
Lassen Sie die Strategie eine Weile laufen ~
Nach einem Test von etwa 3 Tagen sind die Preisunterschiede tatsächlich akzeptabel.



Man sieht, dass es einige Einnahmen aus der Finanzierungsrate gibt.

Lassen Sie uns den Quellcode der Strategie teilen:
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) { // 价格不能小于等于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) // 切换为模拟环境
Log("仅支持OKEX V5 API,切换为OKEX V5 模拟盘:")
} else {
exchange.IO("simulate", false) // 切换为实盘
Log("仅支持OKEX V5 API,切换为OKEX V5 实盘:")
}
if (exchange.GetName() != "Futures_OKCoin") {
throw "支持OKEX期货"
}
// 初始化
if (isReset) {
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
// 初始化标记
var isFirst = true
// 收益打印周期
var preProfitPrintTS = 0
// 总权益
var totalEquity = 0
var posTbls = [] // 持仓表格数组
// 声明arrCfg
var arrCfg = []
_.each(arrNearContractType, function(ct) {
arrCfg.push(createCfg(formatSymbol(ct)[0]))
})
var objCharts = Chart(arrCfg)
objCharts.reset()
// 创建对象
var exName = exchange.GetName() + "_V5"
var nearConfigureFunc = $.getConfigureFunc()[exName]
var farConfigureFunc = $.getConfigureFunc()[exName]
var nearEx = $.createBaseEx(exchange, nearConfigureFunc)
var farEx = $.createBaseEx(exchange, farConfigureFunc)
// 预先写入需要订阅的合约
_.each(arrNearContractType, function(ct) {
nearEx.pushSubscribeSymbol(ct)
})
_.each(arrFarContractType, function(ct) {
farEx.pushSubscribeSymbol(ct)
})
while (true) {
var ts = new Date().getTime()
// 获取行情数据
nearEx.goGetTickers()
farEx.goGetTickers()
var nearTickers = nearEx.getTickers()
var farTickers = farEx.getTickers()
if (!farTickers || !nearTickers) {
Sleep(2000)
continue
}
var tbl = {
type : "table",
title : "远期-近期差价",
cols : ["交易对", "远期", "近期", "正对冲", "反对冲"],
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]])
}
}
}
})
})
// 初始化
if (isFirst) {
isFirst = false
var recoveryNets = _G("nets")
var recoveryInitTotalEquity = _G("initTotalEquity")
if (!recoveryNets) {
// 检查持仓
_.each(subscribeFarTickers, function(farTicker) {
var pos = farEx.getFuPos(farTicker.originalSymbol, ts)
if (pos.length != 0) {
Log(farTicker.originalSymbol, pos)
throw "初始化时有持仓"
}
})
_.each(subscribeNearTickers, function(nearTicker) {
var pos = nearEx.getFuPos(nearTicker.originalSymbol, ts)
if (pos.length != 0) {
Log(nearTicker.originalSymbol, pos)
throw "初始化时有持仓"
}
})
// 构造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 "初始化获取总权益失败!"
}
} else {
// 恢复
nets = recoveryNets
initTotalEquity = recoveryInitTotalEquity
}
}
// 检索网格,检查是否触发交易
_.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("没有查询到", obj.symbol, "的差价")
return
}
// 检查网格,动态添加
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,
})
}
// 检索网格
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)) { // 正对冲开仓
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)) { // 正对冲平仓
upP.sell = false
}
}
}
obj.prePlus = currPlus // 记录本次差价,作为缓存,下次用于判断上穿下穿
// 增加其它表格输出
})
if (ts - preProfitPrintTS > 1000 * 60 * 5) { // 5分钟打印一次
var currTotalEquity = getTotalEquity()
if (currTotalEquity) {
totalEquity = currTotalEquity
LogProfit(totalEquity - initTotalEquity, "&") // 打印动态权益收益
}
// 检查持仓
posTbls = [] // 重置,更新
_.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" : ["合约代码", "数量", "价格"],
"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
}
// 显示网格
var netTbls = []
_.each(nets, function(obj) {
var netTbl = {
"type" : "table",
"title" : obj.symbol,
"cols" : ["网格"],
"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(), "总权益:", totalEquity, "初始总权益:", initTotalEquity, " 浮动盈亏:", 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("获取账户总权益失败!")
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, "下单量计算错误:", 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("执行扫尾函数", "#FF0000")
_G("nets", nets)
_G("initTotalEquity", initTotalEquity)
Log("保存数据:", _G("nets"), _G("initTotalEquity"))
}

Öffentliche Strategieansprache: https://www.fmz.com/strategy/288559
Die Strategie verwendet eine Vorlagenbibliothek, die ich selbst geschrieben habe. Da sie nicht gut geschrieben ist, werde ich sie nicht veröffentlichen. Der obige Strategie-Quellcode kann geändert werden, um diese Vorlage nicht zu verwenden.
Bei Interesse können Sie zum Testen eine OKEX V5-Simulationsdiskette einrichten. Oh! Diese Strategie kann übrigens nicht Backtesting unterzogen werden.