鉴于期现对冲策略对冲频率并不是特别高,实际上是可以手动操作的。但是如果纯手动去操作,切换各个交易所页面,观察价格,计算差价就很不方便,并且有时候还可能想多看几个品种,弄好几个显示器显示行情也是没太大必要。这样是不是用一个半自动的策略能实现手动操作的这个需求呢?最好还是多品种的,哦!对,最好还有一键开仓、平仓。哦!对,还要有仓位显示…
有了需求,马上动手!
写的比较啰嗦,代码600行不到。
function createManager(fuEx, spEx, symbolPairs, cmdHedgeAmount, fuMarginLevel, fuMarginReservedRatio) {
var self = {}
self.fuEx = fuEx
self.spEx = spEx
self.symbolPairs = symbolPairs
self.pairs = []
self.fuExTickers = null
self.spExTickers = null
self.tickerUpdateTS = 0
self.fuMarginLevel = fuMarginLevel
self.fuMarginReservedRatio = fuMarginReservedRatio
self.cmdHedgeAmount = cmdHedgeAmount
self.preUpdateAccTS = 0
self.accAndPosUpdateCount = 0
self.profit = []
self.allPairs = []
self.PLUS = 0
self.MINUS = 1
self.COVER_PLUS = 2
self.COVER_MINUS = 3
self.arrTradeTypeDesc = [\"正套\", \"反套\", \"平正套\", \"平反套\"]
self.updateTickers = function() {
self.fuEx.goGetTickers()
self.spEx.goGetTickers()
var fuExTickers = self.fuEx.getTickers()
var spExTickers = self.spEx.getTickers()
if (!fuExTickers || !spExTickers) {
return null
}
self.fuExTickers = fuExTickers
self.spExTickers = spExTickers
self.tickerUpdateTS = new Date().getTime()
return true
}
self.hedge = function(index, fuSymbol, spSymbol, tradeType, amount) {
var fe = self.fuEx
var se = self.spEx
var pair = self.pairs[index]
var timeStamp = new Date().getTime()
var fuDirection = null
var spDirection = null
var fuPrice = null
var spPrice = null
if (tradeType == self.PLUS) {
fuDirection = fe.OPEN_SHORT
spDirection = se.OPEN_LONG
fuPrice = pair.fuTicker.bid1
spPrice = pair.spTicker.ask1
} else if (tradeType == self.MINUS) {
fuDirection = fe.OPEN_LONG
spDirection = se.OPEN_SHORT
fuPrice = pair.fuTicker.ask1
spPrice = pair.spTicker.bid1
} else if (tradeType == self.COVER_PLUS) {
fuDirection = fe.COVER_SHORT
spDirection = se.COVER_LONG
fuPrice = pair.fuTicker.ask1
spPrice = pair.spTicker.bid1
} else if (tradeType == self.COVER_MINUS) {
fuDirection = fe.COVER_LONG
spDirection = se.COVER_SHORT
fuPrice = pair.fuTicker.bid1
spPrice = pair.spTicker.ask1
} else {
throw \"unknow tradeType!\"
}
fe.goGetAcc(fuSymbol, timeStamp)
se.goGetAcc(spSymbol, timeStamp)
var nowFuAcc = fe.getAcc(fuSymbol, timeStamp)
var nowSpAcc = se.getAcc(spSymbol, timeStamp)
if (!nowFuAcc || !nowSpAcc) {
Log(fuSymbol, spSymbol, \",获取账户数据失败\")
return
}
pair.nowFuAcc = nowFuAcc
pair.nowSpAcc = nowSpAcc
var nowFuPos = fe.getFuPos(fuSymbol, timeStamp)
var nowSpPos = se.getSpPos(spSymbol, spPrice, pair.initSpAcc, pair.nowSpAcc)
if (!nowFuPos || !nowSpPos) {
Log(fuSymbol, spSymbol, \",获取持仓数据失败\")
return
}
pair.nowFuPos = nowFuPos
pair.nowSpPos = nowSpPos
var fuAmount = amount
var spAmount = amount
if (tradeType == self.PLUS || tradeType == self.MINUS) {
if (nowFuAcc.Balance < (pair.initFuAcc.Balance + pair.initFuAcc.FrozenBalance) * self.fuMarginReservedRatio + (fuAmount * fuPrice / self.fuMarginLevel)) {
Log(pair.fuSymbol, \"保证金不足!\", \"本次计划使用\", (fuAmount * fuPrice / self.fuMarginLevel), \"当前可用:\", nowFuAcc.Balance,
\"计划预留:\", (pair.initFuAcc.Balance + pair.initFuAcc.FrozenBalance) * self.fuMarginReservedRatio)
return
}
if ((tradeType == self.PLUS && nowSpAcc.Balance < spAmount * spPrice)) {
Log(pair.spSymbol, \"资金不足!\", \"本次买入计划使用\", spAmount * spPrice, \"当前可用:\", nowSpAcc.Balance)
return
} else if (tradeType == self.MINUS && nowSpAcc.Stocks < spAmount) {
Log(pair.spSymbol, \"资金不足!\", \"本次卖出计划使用\", spAmount, \"当前可用:\", nowSpAcc.Stocks)
return
}
} else {
var fuLongPos = self.getLongPos(nowFuPos)
var fuShortPos = self.getShortPos(nowFuPos)
var spLongPos = self.getLongPos(nowSpPos)
var spShortPos = self.getShortPos(nowSpPos)
if ((tradeType == self.COVER_PLUS && !fuShortPos) || (tradeType == self.COVER_MINUS && !fuLongPos)) {
Log(fuSymbol, spSymbol, \",期货没有对应持仓!\")
return
} else if (tradeType == self.COVER_PLUS && Math.abs(fuShortPos.amount) < fuAmount) {
fuAmount = Math.abs(fuShortPos.amount)
} else if (tradeType == self.COVER_MINUS && Math.abs(fuLongPos.amount) < fuAmount) {
fuAmount = Math.abs(fuLongPos.amount)
}
if ((tradeType == self.COVER_PLUS && !spLongPos) || (tradeType == self.COVER_MINUS && !spShortPos)) {
Log(fuSymbol, spSymbol, \",现货没有对应持仓!\")
return
} else if (tradeType == self.COVER_PLUS && Math.min(Math.abs(spLongPos.amount), nowSpAcc.Stocks) < spAmount) {
spAmount = Math.min(Math.abs(spLongPos.amount), nowSpAcc.Stocks)
} else if (tradeType == self.COVER_MINUS && Math.min(Math.abs(spShortPos.amount), nowSpAcc.Balance / spPrice) < spAmount) {
spAmount = Math.min(Math.abs(spShortPos.amount), nowSpAcc.Balance / spPrice)
}
}
fuAmount = fe.calcAmount(fuSymbol, fuDirection, fuPrice, fuAmount)
spAmount = se.calcAmount(spSymbol, spDirection, spPrice, spAmount)
if (!fuAmount || !spAmount) {
Log(fuSymbol, spSymbol, \"下单量计算错误:\", fuAmount, spAmount)
return
} else {
fuAmount = fe.calcAmount(fuSymbol, fuDirection, fuPrice, fuAmount[1])
spAmount = se.calcAmount(spSymbol, spDirection, spPrice, Math.min(fuAmount[1], spAmount[1]))
if (!fuAmount || !spAmount) {
Log(fuSymbol, spSymbol, \"下单量计算错误:\", fuAmount, spAmount)
return
}
}
Log(\"合约代码:\", fuSymbol + \"/\" + spSymbol, \"方向:\", self.arrTradeTypeDesc[tradeType], \"差价:\", fuPrice - spPrice, \"期货数量:\", fuAmount, \"现货数量:\", spAmount, \"@\")
fe.goGetTrade(fuSymbol, fuDirection, fuPrice, fuAmount[0])
se.goGetTrade(spSymbol, spDirection, spPrice, spAmount[0])
var feIdMsg = fe.getTrade()
var seIdMsg = se.getTrade()
return [feIdMsg, seIdMsg]
}
self.process = function() {
var nowTS = new Date().getTime()
if(!self.updateTickers()) {
return
}
_.each(self.pairs, function(pair, index) {
var fuTicker = null
var spTicker = null
_.each(self.fuExTickers, function(ticker) {
if (ticker.originalSymbol == pair.fuSymbol) {
fuTicker = ticker
}
})
_.each(self.spExTickers, function(ticker) {
if (ticker.originalSymbol == pair.spSymbol) {
spTicker = ticker
}
})
if (fuTicker && spTicker) {
pair.canTrade = true
} else {
pair.canTrade = false
}
fuTicker = fuTicker ? fuTicker : {}
spTicker = spTicker ? spTicker : {}
pair.fuTicker = fuTicker
pair.spTicker = spTicker
pair.plusDiff = fuTicker.bid1 - spTicker.ask1
pair.minusDiff = fuTicker.ask1 - spTicker.bid1
if (pair.plusDiff && pair.minusDiff) {
pair.plusDiff = _N(pair.plusDiff, Math.max(self.fuEx.judgePrecision(fuTicker.bid1), self.spEx.judgePrecision(spTicker.ask1)))
pair.minusDiff = _N(pair.minusDiff, Math.max(self.fuEx.judgePrecision(fuTicker.ask1), self.spEx.judgePrecision(spTicker.bid1)))
}
if (nowTS - self.preUpdateAccTS > 1000 * 60 * 5) {
self.fuEx.goGetAcc(pair.fuSymbol, nowTS)
self.spEx.goGetAcc(pair.spSymbol, nowTS)
var fuAcc = self.fuEx.getAcc(pair.fuSymbol, nowTS)
var spAcc = self.spEx.getAcc(pair.spSymbol, nowTS)
if (fuAcc) {
pair.nowFuAcc = fuAcc
}
if (spAcc) {
pair.nowSpAcc = spAcc
}
var nowFuPos = self.fuEx.getFuPos(pair.fuSymbol, nowTS)
var nowSpPos = self.spEx.getSpPos(pair.spSymbol, (pair.spTicker.ask1 + pair.spTicker.bid1) / 2, pair.initSpAcc, pair.nowSpAcc)
if (nowFuPos && nowSpPos) {
pair.nowFuPos = nowFuPos
pair.nowSpPos = nowSpPos
self.keepBalance(pair)
} else {
Log(pair.fuSymbol, pair.spSymbol, \"组合仓位更新失败,nowFuPos:\", nowFuPos, \" nowSpPos:\", nowSpPos)
}
self.accAndPosUpdateCount++
}
})
if (nowTS - self.preUpdateAccTS > 1000 * 60 * 5) {
self.preUpdateAccTS = nowTS
self.profit = self.calcProfit()
LogProfit(self.profit[0], \"期货:\", self.profit[1], \"现货:\", self.profit[2], \"&\") // 打印总收益曲线,使用&字符不打印收益日志
}
var cmd = GetCommand()
if(cmd) {
Log(\"交互命令:\", cmd)
var arr = cmd.split(\":\")
if(arr[0] == \"plus\") {
var pair = self.pairs[parseFloat(arr[1])]
self.hedge(parseFloat(arr[1]), pair.fuSymbol, pair.spSymbol, self.PLUS, self.cmdHedgeAmount)
} else if (arr[0] == \"cover_plus\") {
var pair = self.pairs[parseFloat(arr[1])]
self.hedge(parseFloat(arr[1]), pair.fuSymbol, pair.spSymbol, self.COVER_PLUS, self.cmdHedgeAmount)
}
}
LogStatus(\"当前时间:\", _D(), \" 数据更新时间:\", _D(self.tickerUpdateTS), \"持仓账户更新计数:\", self.accAndPosUpdateCount, \"\\n\", \"盈亏:\", self.profit[0], \" 期货盈亏:\", self.profit[1],
\" 现货盈亏:\", self.profit[2], \"\\n`\" + JSON.stringify(self.returnTbl()) + \"`\", \"\\n`\" + JSON.stringify(self.returnPosTbl()) + \"`\")
}
self.keepBalance = function (pair) {
var nowFuPos = pair.nowFuPos
var nowSpPos = pair.nowSpPos
var fuLongPos = self.getLongPos(nowFuPos)
var fuShortPos = self.getShortPos(nowFuPos)
var spLongPos = self.getLongPos(nowSpPos)
var spShortPos = self.getShortPos(nowSpPos)
if (fuLongPos || spShortPos) {
Log(\"不支持反套\")
}
if (fuShortPos || spLongPos) {
var fuHoldAmount = fuShortPos ? fuShortPos.amount : 0
var spHoldAmount = spLongPos ? spLongPos.amount : 0
var sum = fuHoldAmount + spHoldAmount
if (sum > 0) {
var spAmount = self.spEx.calcAmount(pair.spSymbol, self.spEx.COVER_LONG, pair.spTicker.bid1, Math.abs(sum), true)
if (spAmount) {
Log(pair.fuSymbol, pair.spSymbol, \"现货头寸多出\", Math.abs(sum), \"fuShortPos:\", fuShortPos, \"spLongPos:\", spLongPos)
self.spEx.goGetTrade(pair.spSymbol, self.spEx.COVER_LONG, pair.spTicker.bid1, spAmount[0])
var seIdMsg = self.spEx.getTrade()
}
} else if (sum < 0) {
var fuAmount = self.fuEx.calcAmount(pair.fuSymbol, self.fuEx.COVER_SHORT, pair.fuTicker.ask1, Math.abs(sum), true)
if (fuAmount) {
Log(pair.fuSymbol, pair.spSymbol, \"期货头寸多出\", Math.abs(sum), \"fuShortPos:\", fuShortPos, \"spLongPos:\", spLongPos)
self.fuEx.goGetTrade(pair.fuSymbol, self.fuEx.COVER_SHORT, pair.fuTicker.ask1, fuAmount[0])
var feIdMsg = self.fuEx.getTrade()
}
}
}
}
self.getLongPos = function (positions) {
return self.getPosByDirection(positions, PD_LONG)
}
self.getShortPos = function (positions) {
return self.getPosByDirection(positions, PD_SHORT)
}
self.getPosByDirection = function (positions, direction) {
var ret = null
if (positions.length > 2) {
Log(\"持仓错误,检测到三个持仓:\", JSON.stringify(positions))
return ret
}
_.each(positions, function(pos) {
if ((direction == PD_LONG && pos.amount > 0) || (direction == PD_SHORT && pos.amount < 0)) {
ret = pos
}
})
return ret
}
self.calcProfit = function() {
var arrInitFuAcc = []
var arrNowFuAcc = []
_.each(self.pairs, function(pair) {
arrInitFuAcc.push(pair.initFuAcc)
arrNowFuAcc.push(pair.nowFuAcc)
})
var fuProfit = self.fuEx.calcProfit(arrInitFuAcc, arrNowFuAcc)
var spProfit = 0
var deltaBalance = 0
_.each(self.pairs, function(pair) {
var nowSpAcc = pair.nowSpAcc
var initSpAcc = pair.initSpAcc
var stocksDiff = nowSpAcc.Stocks + nowSpAcc.FrozenStocks - (initSpAcc.Stocks + initSpAcc.FrozenStocks)
var price = stocksDiff > 0 ? pair.spTicker.bid1 : pair.spTicker.ask1
spProfit += stocksDiff * price
deltaBalance = nowSpAcc.Balance + nowSpAcc.FrozenBalance - (initSpAcc.Balance + initSpAcc.FrozenBalance)
})
spProfit += deltaBalance
return [fuProfit + spProfit, fuProfit, spProfit]
}
self.returnPosTbl = function() {
var posTbl = {
type : \"table\",
title : \"positions\",
cols : [\"索引\", \"期货\", \"期货杠杆\", \"数量\", \"现货\", \"数量\"],
rows : []
}
_.each(self.pairs, function(pair, index) {
var nowFuPos = pair.nowFuPos
var nowSpPos = pair.nowSpPos
for (var i = 0 ; i < nowFuPos.length ; i++) {
if (nowSpPos.length > 0) {
posTbl.rows.push([index, nowFuPos[i].symbol, nowFuPos[i].marginLevel, nowFuPos[i].amount, nowSpPos[0].symbol, nowSpPos[0].amount])
} else {
posTbl.rows.push([index, nowFuPos[i].symbol, nowFuPos[i].marginLevel, nowFuPos[i].amount, \"--\", \"--\"])
}
}
})
return posTbl
}
self.returnTbl = function() {
var fuExName = \"[\" + self.fuEx.getExName() + \"]\"
var spExName = \"[\" + self.spEx.getExName() + \"]\"
var combiTickersTbl = {
type : \"table\",
title : \"combiTickersTbl\",
cols : [\"期货\", \"代码\" + fuExName, \"卖一\", \"买一\", \"现货\", \"代码\" + spExName, \"卖一\", \"买一\", \"正对冲差价\", \"反对冲差价\", \"正对冲\", \"正对冲平仓\"],
rows : []
}
_.each(self.pairs, function(pair, index) {
var spSymbolInfo = self.spEx.getSymbolInfo(pair.spTicker.originalSymbol)
combiTickersTbl.rows.push([
pair.fuTicker.symbol,
pair.fuTicker.originalSymbol,
pair.fuTicker.ask1,
pair.fuTicker.bid1,
pair.spTicker.symbol,
pair.spTicker.originalSymbol,
pair.spTicker.ask1,
pair.spTicker.bid1,
pair.plusDiff,
pair.minusDiff,
{\'type\':\'button\', \'cmd\': \'plus:\' + String(index), \'name\': \'正套\'},
{\'type\':\'button\', \'cmd\': \'cover_plus:\' + String(index), \'name\': \'平正套\'}
])
})
var accsTbl = {
type : \"table\",
title : \"accs\",
cols : [\"代码\" + fuExName, \"初币\", \"初冻币\", \"初钱\", \"初冻钱\", \"币\", \"冻币\", \"钱\", \"冻钱\",
\"代码\" + spExName, \"初币\", \"初冻币\", \"初钱\", \"初冻钱\", \"币\", \"冻币\", \"钱\", \"冻钱\"],
rows : []
}
_.each(self.pairs, function(pair) {
var arr = [pair.fuTicker.originalSymbol, pair.initFuAcc.Stocks, pair.initFuAcc.FrozenStocks, pair.initFuAcc.Balance, pair.initFuAcc.FrozenBalance, pair.nowFuAcc.Stocks, pair.nowFuAcc.FrozenStocks, pair.nowFuAcc.Balance, pair.nowFuAcc.FrozenBalance,
pair.spTicker.originalSymbol, pair.initSpAcc.Stocks, pair.initSpAcc.FrozenStocks, pair.initSpAcc.Balance, pair.initSpAcc.FrozenBalance, pair.nowSpAcc.Stocks, pair.nowSpAcc.FrozenStocks, pair.nowSpAcc.Balance, pair.nowSpAcc.FrozenBalance]
for (var i = 0 ; i < arr.length ; i++) {
if (typeof(arr[i]) == \"number\") {
arr[i] = _N(arr[i], 6)
}
}
accsTbl.rows.push(arr)
})
var symbolInfoTbl = {
type : \"table\",
title : \"symbolInfos\",
cols : [\"合约代码\" + fuExName, \"量精度\", \"价格精度\", \"乘数\", \"最小下单量\", \"现货代码\" + spExName, \"量精度\", \"价格精度\", \"乘数\", \"最小下单量\"],
rows : []
}
_.each(self.pairs, function(pair) {
var fuSymbolInfo = self.fuEx.getSymbolInfo(pair.fuTicker.originalSymbol)
var spSymbolInfo = self.spEx.getSymbolInfo(pair.spTicker.originalSymbol)
symbolInfoTbl.rows.push([fuSymbolInfo.symbol, fuSymbolInfo.amountPrecision, fuSymbolInfo.pricePrecision, fuSymbolInfo.multiplier, fuSymbolInfo.min,
spSymbolInfo.symbol, spSymbolInfo.amountPrecision, spSymbolInfo.pricePrecision, spSymbolInfo.multiplier, spSymbolInfo.min])
})
var allPairs = []
_.each(self.fuExTickers, function(fuTicker) {
_.each(self.spExTickers, function(spTicker) {
if (fuTicker.symbol == spTicker.symbol) {
allPairs.push({symbol: fuTicker.symbol, fuSymbol: fuTicker.originalSymbol, spSymbol: spTicker.originalSymbol, plus: fuTicker.bid1 - spTicker.ask1})
}
})
})
_.each(allPairs, function(pair) {
var findPair = null
_.each(self.allPairs, function(selfPair) {
if (pair.fuSymbol == selfPair.fuSymbol && pair.spSymbol == selfPair.spSymbol) {
findPair = selfPair
}
})
if (findPair) {
findPair.minPlus = pair.plus < findPair.minPlus ? pair.plus : findPair.minPlus
findPair.maxPlus = pair.plus > findPair.maxPlus ? pair.plus : findPair.maxPlus
pair.minPlus = findPair.minPlus
pair.maxPlus = findPair.maxPlus
} else {
self.allPairs.push({symbol: pair.symbol, fuSymbol: pair.fuSymbol, spSymbol: pair.spSymbol, plus: pair.plus, minPlus: pair.plus, maxPlus: pair.plus})
pair.minPlus = pair.plus
pair.maxPlus = pair.plus
}
})
return [combiTickersTbl, accsTbl, symbolInfoTbl]
}
self.onexit = function() {
_G(\"pairs\", self.pairs)
_G(\"allPairs\", self.allPairs)
Log(\"执行扫尾处理,数据保存\", \"#FF0000\")
}
self.init = function() {
var fuExName = self.fuEx.getExName()
var spExName = self.spEx.getExName()
var gFuExName = _G(\"fuExName\")
var gSpExName = _G(\"spExName\")
if ((gFuExName && gFuExName != fuExName) || (gSpExName && gSpExName != spExName)) {
throw \"交易所对象发生变化,需要重置数据\"
}
if (!gFuExName) {
_G(\"fuExName\", fuExName)
}
if (!gSpExName) {
_G(\"spExName\", spExName)
}
self.allPairs = _G(\"allPairs\")
if (!self.allPairs) {
self.allPairs = []
}
var arrPair = _G(\"pairs\")
if (!arrPair) {
arrPair = []
}
var arrStrPair = self.symbolPairs.split(\",\")
var timeStamp = new Date().getTime()
_.each(arrStrPair, function(strPair) {
var arrSymbol = strPair.split(\"|\")
var recoveryPair = null
_.each(arrPair, function(pair) {
if (pair.fuSymbol == arrSymbol[0] && pair.spSymbol == arrSymbol[1]) {
recoveryPair = pair
}
})
if (!recoveryPair) {
var pair = {
fuSymbol : arrSymbol[0],
spSymbol : arrSymbol[1],
fuTicker : {},
spTicker : {},
plusDiff : null,
minusDiff : null,
canTrade : false,
initFuAcc : null,
initSpAcc : null,
nowFuAcc : null,
nowSpAcc : null,
nowFuPos : null,
nowSpPos : null,
fuMarginLevel : null
}
self.pairs.push(pair)
Log(\"初始化:\", pair)
} else {
self.pairs.push(recoveryPair)
Log(\"恢复:\", recoveryPair)
}
self.fuEx.pushSubscribeSymbol(arrSymbol[0])
self.spEx.pushSubscribeSymbol(arrSymbol[1])
if (!self.pairs[self.pairs.length - 1].initFuAcc) {
self.fuEx.goGetAcc(arrSymbol[0], timeStamp)
var nowFuAcc = self.fuEx.getAcc(arrSymbol[0], timeStamp)
self.pairs[self.pairs.length - 1].initFuAcc = nowFuAcc
self.pairs[self.pairs.length - 1].nowFuAcc = nowFuAcc
}
if (!self.pairs[self.pairs.length - 1].initSpAcc) {
self.spEx.goGetAcc(arrSymbol[1], timeStamp)
var nowSpAcc = self.spEx.getAcc(arrSymbol[1], timeStamp)
self.pairs[self.pairs.length - 1].initSpAcc = nowSpAcc
self.pairs[self.pairs.length - 1].nowSpAcc = nowSpAcc
}
Sleep(300)
})
Log(\"self.pairs:\", self.pairs)
_.each(self.pairs, function(pair) {
var fuSymbolInfo = self.fuEx.getSymbolInfo(pair.fuSymbol)
if (!fuSymbolInfo) {
throw pair.fuSymbol + \",品种信息获取失败!\"
} else {
Log(pair.fuSymbol, fuSymbolInfo)
}
var spSymbolInfo = self.spEx.getSymbolInfo(pair.spSymbol)
if (!spSymbolInfo) {
throw pair.spSymbol + \",品种信息获取失败!\"
} else {
Log(pair.spSymbol, spSymbolInfo)
}
})
_.each(self.pairs, function(pair) {
pair.fuMarginLevel = self.fuMarginLevel
var ret = self.fuEx.setMarginLevel(pair.fuSymbol, self.fuMarginLevel)
Log(pair.fuSymbol, \"杠杆设置:\", ret)
if (!ret) {
throw \"初始设置杠杆失败!\"
}
})
}
self.init()
return self
}
var manager = null
function main() {
if(isReset) {
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log(\"重置所有数据\", \"#FF0000\")
}
if (isOKEX_V5_Simulate) {
for (var i = 0 ; i < exchanges.length ; i++) {
if (exchanges[i].GetName() == \"Futures_OKCoin\" || exchanges[i].GetName() == \"OKEX\") {
var ret = exchanges[i].IO(\"simulate\", true)
Log(exchanges[i].GetName(), \"切换模拟盘\")
}
}
}
var fuConfigureFunc = null
var spConfigureFunc = null
if (exchanges.length != 2) {
throw \"需要添加两个交易所对象!\"
} else {
var fuName = exchanges[0].GetName()
if (fuName == \"Futures_OKCoin\" && isOkexV5) {
fuName += \"_V5\"
Log(\"使用OKEX V5接口\")
}
var spName = exchanges[1].GetName()
fuConfigureFunc = $.getConfigureFunc()[fuName]
spConfigureFunc = $.getConfigureFunc()[spName]
if (!fuConfigureFunc || !spConfigureFunc) {
throw (fuConfigureFunc ? \"\" : fuName) + \" \" + (spConfigureFunc ? \"\" : spName) + \" not support!\"
}
}
var fuEx = $.createBaseEx(exchanges[0], fuConfigureFunc)
var spEx = $.createBaseEx(exchanges[1], spConfigureFunc)
manager = createManager(fuEx, spEx, symbolPairs, cmdHedgeAmount, fuMarginLevel, fuMarginReservedRatio)
while(true) {
manager.process()
Sleep(interval)
}
}
function onerror() {
if (manager) {
manager.onexit()
}
}
function onexit() {
if (manager) {
manager.onexit()
}
}
由于多品种策略比较适合使用IO来设计,所以代码中用到了名为MultiSymbolCtrlLib
的一个模板类库(封装,通过IO调用交易所接口)。所以策略是不能回测的,不过可以用模拟盘测试(虽说实盘也跑了有2个月了,测试、熟悉阶段还是用模拟盘跑跑)。
在上手测试之前首先说说参数设计方面。
策略参数不多,比较重要的参数有:
LTC-USDT-211231|LTC_USDT,BTC-USDT-211231|BTC_USDT
这里就是设置策略都监控那些组合,例如以上的设置是监控期货交易所的莱特币合约(LTC-USDT-211231)和现货交易所的莱特币(LTC_USDT)。一个组合期货合约和现货交易对用|
符号隔开,形成一个组合。不同组合使用,
符号隔开。注意这里的符号都是英文输入法状态哦!
那可能你问我合约代码要怎么找,这些合约代码、现货交易对使用的都是交易所定义的。并非FMZ平台上定义的。
例如LTC-USDT-211231
这个合约目前是次季度合约,在FMZ上叫next_quarter
,OKEX的接口系统就是叫它LTC-USDT-211231
。WexApp模拟盘对于莱特币/USDT交易对写法就是LTC_USDT
。所以这里具体怎么填写,就看交易所里定义的名称。
其它的都是设置模拟盘、重置数据、使用OKEX V5接口(因为也兼容V3)等功能,不是特别重要。
第一个交易所对象添加期货的交易所,第二个添加现货交易所对象。
期货交易所使用OKEX的V5接口模拟盘,现货交易所使用wexApp模拟盘。
点了下BTC组合的正套按钮,开仓了。
又点了下正套平仓。
亏了!!!看来利润差价小的时候平仓覆盖不了手续费,需要算下手续费、大概滑点,然后合理规划平仓的差价,再平仓。
策略源码:https://www.fmz.com/strategy/314352
有兴趣的可以使用、魔改。
买好币上币库:https://www.kucoin.com/r/1f7w3