上篇我們一起動手做了一個簡單的網格策略,本期文章我們把這個策略升級擴展,擴展成一個多品種現貨網格策略,并讓這個策略進行實戰測驗,目的并非要找到一個"圣杯",而是要從策略設計中探討設計策略時的各種問題思考、解決思路,本篇會講解我在設計這個策略時的一些經驗、本篇內容略微復雜,需要對程式撰寫有一定基礎,
基于策略需求的設計思考
本篇和上篇文章一樣,依然基于發明者量化(FMZ.COM)來探討設計,
多品種
說白了就是我想這個網格策略不僅做BTC_USDT,還能做LTC_USDT/EOS_USDT/DOGE_USDT/ETC_USDT/ETH_USDT,反正現貨的交易對,想跑的品種同時都做網格交易,嗯~~捕捉多個品種的震蕩行情,感徑訓不錯,需求聽著很簡單,設計的時候問題就來了,
1、首先多個品種的行情獲取,這個是第一個要解決的問題,在查閱了交易所的API檔案后,我發現一般交易所都提供聚合行情介面,OK,就用聚合行情介面獲取資料,
2、第二個遇到的問題就是賬戶資產,因為是要做多品種的策略,所以就要考慮各個交易對資產分別管理,并且要一次獲取所有資產的資料,記錄,為什么要獲取賬戶資產資料呢?還要分開各個交易對記錄?因為你需要在下單時判斷可用資產嘛,是不是要獲取一下再判斷呢?還有你需要計算收益呀,是不是也要先記錄一個最初的賬戶資產資料,然后獲取當前的賬戶資產資料并且和最初的對比算出盈虧?好在交易所的資產賬戶介面通常也是回傳所有幣種資產資料的,我們只用獲取一次,然后處理資料即可,
3、策略引數設計,多品種的引數設計和單品種的引數設計差別較大,因為多品種的各個品種交易邏輯雖然是相同的,但是有可能交易時的引數是不同的,比如網格策略,可能做BTC_USDT交易對時希望每次交易0.01個BTC,但是做DOGE_USDT的時候如果還是這個引數(交易0.01個幣)顯然是不合適的,當然你也可以按USDT金額去處理,但是依然還是會有問題,萬一就是想BTC_USDT交易1000U,DOGE_USDT交易10U呢?需求始終無法滿足,
可能還有同學會思考這個問題,然后提出:“我可以多設定幾組引數,分開控制要做的不同交易對的引數,”這依然是不能靈活滿足需求,設定幾組引數為好呢?設定了三組引數,假如我要做4個品種呢?難不成還要修改策略,增加引數…所以設計多品種策略的引數時要充分考慮到這種差異化引數的需求,一個解決辦法就是把引數設計成普通字串或者JSON字串,例如:
ETHUSDT:100:0.002|LTCUSDT:20:0.1
其中“|”分割的是每個品種的資料,意思就是ETHUSDT:100:0.002是控制ETH_USDT交易對的,LTCUSDT:20:0.1是控制LTC_USDT交易對的,中間"|"是起到分割作用,ETHUSDT:100:0.002,其中ETHUSDT表示你要做的交易對是什么,100是網格間距,0.002是每個網格交易的ETH幣數,“:”號是分割這些資料的(當然,這些引數規則是策略設計者制定的,你根據你的需求設計成什么樣都行),這些字串里面就包含了你要做的各個品種的引數資訊了,在策略中決議這些字串,具體給策略的變數賦值,用來控制各個品種的交易邏輯,那如何決議呢?還是用上面的例子,
function main() {
var net = [] // 記錄的網格引數,具體運行到網格交易邏輯時,使用這里面的資料
var params = "ETHUSDT:100:0.002|LTCUSDT:20:0.1"
var arrPair = params.split("|")
_.each(arrPair, function(pair) {
var arr = pair.split(":")
var symbol = arr[0] // 交易對名稱
var diff = parseFloat(arr[1]) // 網格間距
var amount = parseFloat(arr[2]) // 網格下單量
net.push({symbol : symbol, diff : diff, amount : amount})
})
Log("網格引數資料:", net)
}

看這樣就把引數決議了,當然你還可以直接用JSON字串,更加簡單,
function main() {
var params = '[{"symbol":"ETHUSDT","diff":100,"amount":0.002},{"symbol":"LTCUSDT","diff":20,"amount":0.1}]'
var net = JSON.parse(params) // 記錄的網格引數,具體運行到網格交易邏輯時,使用這里面的資料
_.each(net, function(pair) {
Log("交易對:", pair.symbol, pair)
})
}

4、資料持久化
可以實戰的策略和教學策略差別也較大,上篇的教學策略僅僅是初步測驗策略邏輯、設計,實戰的時候考慮的問題就更多了,在實盤時,可能開啟、停止實盤,這時,實盤運行時的資料會全部丟掉,那么如何讓實盤停止后,重啟可以繼續之前的狀態運行呢?這里就需要做實盤運行時的關鍵資料的持久化保存,以供再次啟動時讀取這些資料,繼續運行,在發明者量化交易平臺上可以使用_G()函式,或者使用資料庫操作函式DBExec(),具體可以查詢FMZ API檔案,例如我們設計一個掃尾函式,使用_G()函式,保存網格資料,
var net = null
function main() { // 策略主函式
// 首先讀取儲存的net
net = _G("net")
// ...
}
function onExit() {
_G("net", net)
Log("執行掃尾處理,保存資料", "#FF0000")
}
function onexit() { // 平臺系統定義的退出掃尾函式,在點擊實盤停止時觸發執行
onExit()
}
function one rror() { // 平臺系統定義的例外退出函式,在程式發生例外時觸發執行
onExit()
}
5、下單量精度、下單價格精度、最小下單量、最小下單金額等限制
回測系統中并未對下單量、下單精度等做那么嚴苛的限制,但是實盤的時候各個交易所對于報單時價格、下單量可以有嚴格標準的,并且各個交易對的這些限制并不相同,所以經常有萌新在回測系統測驗OK,一上實盤,觸發交易時就有各種問題,然后也沒看報錯資訊內容,出現各種抓狂的現象【手動狗頭】,對于多品種的情況,這個需求更加復雜,單品種策略,你可以設計一個引數用來指定精度等資訊,但是多品種策略設計時,顯然這些資訊寫到引數里會顯得引數十分臃腫,這個時候就需要查看交易所API檔案,看交易所檔案中有沒有交易對相關資訊的介面,如果有這些介面,就可以在策略中設計自動訪問介面獲取精度等資訊,配置到參與交易的交易對資訊中(簡單說就是精度什么的自動向交易所請求獲取,然后適配到策略引數相關的變數上),
6、不同交易所的適配
為什么把這個問題放在最后說呢?因為以上我們講的這些問題處理辦法會帶來這最后一個問題,因為我們策略計劃使用聚合行情介面,訪問交易所交易對精度等資料自適應,訪問賬戶資訊分別處理各個交易對等這些方案會因交易所不同帶來很大差別,有介面呼叫上的差別、有機制上的差別,對于現貨交易所差別還小一點,如果這個網格策略擴展成期貨的版本,各個交易所機制上的差別更大,一個處理辦法就是設計一個FMZ模板類別庫,把這些差異化的實作在類別庫中撰寫設計,減小策略本身和交易所的耦合,這么做的缺點就是需要撰寫一個模板類別庫,并且在這個模板中針對每個交易所差異具體實作,
設計一個模板類別庫
基于以上的分析,設計一個模板類別庫用來降低策略與交易所機制、介面之間的耦合性,我們可以這樣設計這個模板類別庫(部分代碼省略):
function createBaseEx(e, funcConfigure) {
var self = {}
self.e = e
self.funcConfigure = funcConfigure
self.name = e.GetName()
self.type = self.name.includes("Futures_") ? "Futures" : "Spot"
self.label = e.GetLabel()
// 需要實作的介面
self.interfaceGetTickers = null // 創建異步獲取聚合行情資料執行緒的函式
self.interfaceGetAcc = null // 創建異步獲取賬戶資料執行緒的函式
self.interfaceGetPos = null // 獲取持倉
self.interfaceTrade = null // 創建并發下單
self.waitTickers = null // 等待并發行情資料
self.waitAcc = null // 等待賬戶并發資料
self.waitTrade = null // 等待下單并發資料
self.calcAmount = null // 根據交易對精度等資料計算下單量
self.init = null // 初始化作業,獲取精度等資料
// 執行配置函式,給物件配置
funcConfigure(self)
// 檢測configList約定的介面是否都實作
_.each(configList, function(funcName) {
if (!self[funcName]) {
throw "介面" + funcName + "未實作"
}
})
return self
}
$.createBaseEx = createBaseEx
$.getConfigureFunc = function(exName) {
dicRegister = {
"Futures_OKCoin" : funcConfigure_Futures_OKCoin, // OK期貨的實作
"Huobi" : funcConfigure_Huobi,
"Futures_Binance" : funcConfigure_Futures_Binance,
"Binance" : funcConfigure_Binance,
"WexApp" : funcConfigure_WexApp, // wexApp的實作
}
return dicRegister
}
在模板中針對具體交易所實作撰寫,例如以FMZ的模擬盤WexApp為例:
function funcConfigure_WexApp(self) {
var formatSymbol = function(originalSymbol) {
// BTC_USDT
var arr = originalSymbol.split("_")
var baseCurrency = arr[0]
var quoteCurrency = arr[1]
return [originalSymbol, baseCurrency, quoteCurrency]
}
self.interfaceGetTickers = function interfaceGetTickers() {
self.routineGetTicker = HttpQuery_Go("https://api.wex.app/api/v1/public/tickers")
}
self.waitTickers = function waitTickers() {
var ret = []
var arr = JSON.parse(self.routineGetTicker.wait()).data
_.each(arr, function(ele) {
ret.push({
bid1: parseFloat(ele.buy),
bid1Vol: parseFloat(-1),
ask1: parseFloat(ele.sell),
ask1Vol: parseFloat(-1),
symbol: formatSymbol(ele.market)[0],
type: "Spot",
originalSymbol: ele.market
})
})
return ret
}
self.interfaceGetAcc = function interfaceGetAcc(symbol, updateTS) {
if (self.updateAccsTS != updateTS) {
self.routineGetAcc = self.e.Go("GetAccount")
}
}
self.waitAcc = function waitAcc(symbol, updateTS) {
var arr = formatSymbol(symbol)
var ret = null
if (self.updateAccsTS != updateTS) {
ret = self.routineGetAcc.wait().Info
self.bufferGetAccRet = ret
} else {
ret = self.bufferGetAccRet
}
if (!ret) {
return null
}
var acc = {symbol: symbol, Stocks: 0, FrozenStocks: 0, Balance: 0, FrozenBalance: 0, originalInfo: ret}
_.each(ret.exchange, function(ele) {
if (ele.currency == arr[1]) {
// baseCurrency
acc.Stocks = parseFloat(ele.free)
acc.FrozenStocks = parseFloat(ele.frozen)
} else if (ele.currency == arr[2]) {
// quoteCurrency
acc.Balance = parseFloat(ele.free)
acc.FrozenBalance = parseFloat(ele.frozen)
}
})
return acc
}
self.interfaceGetPos = function interfaceGetPos(symbol, price, initSpAcc, nowSpAcc) {
var symbolInfo = self.getSymbolInfo(symbol)
var sumInitStocks = initSpAcc.Stocks + initSpAcc.FrozenStocks
var sumNowStocks = nowSpAcc.Stocks + nowSpAcc.FrozenStocks
var diffStocks = _N(sumNowStocks - sumInitStocks, symbolInfo.amountPrecision)
if (Math.abs(diffStocks) < symbolInfo.min / price) {
return []
}
return [{symbol: symbol, amount: diffStocks, price: null, originalInfo: {}}]
}
self.interfaceTrade = function interfaceTrade(symbol, type, price, amount) {
var tradeType = ""
if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
tradeType = "bid"
} else {
tradeType = "ask"
}
var params = {
"market": symbol,
"side": tradeType,
"amount": String(amount),
"price" : String(-1),
"type" : "market"
}
self.routineTrade = self.e.Go("IO", "api", "POST", "/api/v1/private/order", self.encodeParams(params))
}
self.waitTrade = function waitTrade() {
return self.routineTrade.wait()
}
self.calcAmount = function calcAmount(symbol, type, price, amount) {
// 獲取交易對資訊
var symbolInfo = self.getSymbolInfo(symbol)
if (!symbol) {
throw symbol + ",交易對資訊查詢不到"
}
var tradeAmount = null
var equalAmount = null // 記錄幣數
if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
tradeAmount = _N(amount * price, parseFloat(symbolInfo.pricePrecision))
// 檢查最小交易量
if (tradeAmount < symbolInfo.min) {
Log(self.name, " tradeAmount:", tradeAmount, "小于", symbolInfo.min)
return false
}
equalAmount = tradeAmount / price
} else {
tradeAmount = _N(amount, parseFloat(symbolInfo.amountPrecision))
// 檢查最小交易量
if (tradeAmount < symbolInfo.min / price) {
Log(self.name, " tradeAmount:", tradeAmount, "小于", symbolInfo.min / price)
return false
}
equalAmount = tradeAmount
}
return [tradeAmount, equalAmount]
}
self.init = function init() { // 自動處理精度等條件的函式
var ret = JSON.parse(HttpQuery("https://api.wex.app/api/v1/public/markets"))
_.each(ret.data, function(symbolInfo) {
self.symbolsInfo.push({
symbol: symbolInfo.pair,
amountPrecision: parseFloat(symbolInfo.basePrecision),
pricePrecision: parseFloat(symbolInfo.quotePrecision),
multiplier: 1,
min: parseFloat(symbolInfo.minQty),
originalInfo: symbolInfo
})
})
}
}
然后策略中使用這個模板就很簡單了:
function main() {
var fuExName = exchange.GetName()
var fuConfigureFunc = $.getConfigureFunc()[fuExName]
var ex = $.createBaseEx(exchange, fuConfigureFunc)
var arrTestSymbol = ["LTC_USDT", "ETH_USDT", "EOS_USDT"]
var ts = new Date().getTime()
// 測驗獲取行情
ex.goGetTickers()
var tickers = ex.getTickers()
Log("tickers:", tickers)
// 測驗獲取賬戶資訊
ex.goGetAcc(symbol, ts)
_.each(arrTestSymbol, function(symbol) {
_.each(tickers, function(ticker) {
if (symbol == ticker.originalSymbol) {
// 列印行情資料
Log(symbol, ticker)
}
})
// 列印資產資料
var acc = ex.getAcc(symbol, ts)
Log("acc:", acc.symbol, acc)
})
}
策略實盤
基于以上模板設計撰寫策略就很簡單了,整個策略大約300+行,實作了一個數字貨幣現貨多品種網格策略,


目前虧錢T_T,原始碼就暫時不發了,發幾個注冊碼,有興趣的可以掛wexApp玩下:
購買地址: https://www.fmz.com/m/s/284507
注冊碼:
adc7a2e0a2cfde542e3ace405d216731
f5db29d05f57266165ce92dc18fd0a30
1735dca92794943ddaf277828ee04c27
0281ea107935015491cda2b372a0997d
1d0d8ef1ea0ea1415eeee40404ed09cc
就200多U,剛跑起來,就遇到一個大單邊行情,慢慢回血,現貨網格的最大優點就是:“能睡著覺!”,穩定性還湊合,從5月27日到現在沒動過它,期貨網格暫時還不敢嘗試,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/299401.html
標籤:區塊鏈
上一篇:海龜交易法則
