지난 기사에서 우리는 간단한 그리드 전략을 함께 만들었습니다. 이 기사에서는 이 전략을 멀티 심볼 스팟 그리드 전략으로 업그레이드하고 확장했으며, 이 전략을 실무에서 테스트하도록 했습니다. 목적은 "성스러운 잔"을 찾는 것이 아니라 전략을 설계하는 과정에서 여러 가지 문제와 해결책을 논의하는 것입니다. 이 기사는 전략을 설계하는 데에 대한 저의 일부 경험을 설명합니다. 이 기사의 내용은 약간 복잡하며 프로그래밍에 대한 특정 기초가 필요합니다.
이 기사에서는 이전과 마찬가지로 FMZ 양자 거래 플랫폼을 기반으로 한 디자인에 대해 논의합니다 (FMZ.COM).
복수 기호
솔직히 말해서, 전력망 전략은BTC_USDT
, 또한LTC_USDT
/EOS_USDT
/DOGE_USDT
/ETC_USDT
/ETH_USDT
어쨌든, 현금 거래 쌍을 위해, 동시에 거래하려는 모든 기호의 그리드 거래를 실행합니다.
그래, 여러 개의 기호의 진동하는 시장 코트를 포착하는 것이 좋습니다.
단순하게 들리지만, 디자인을 시작하면 어려워집니다.
주문을 할 때 사용 가능한 자산을 판단해야하기 때문에 판단 전에 데이터를 얻는 것이 필요하지 않습니까? 또한 수익률을 계산해야 합니다. 먼저 초기 자산 데이터를 기록해야 할까요? 그리고 그 다음 현금 계좌의 자산 데이터를 얻고 초기 데이터와 비교하여 이익과 손실을 계산해야합니까? 다행히도, 플랫폼의 자산 계정 인터페이스는 일반적으로 모든 통화 자산 데이터를 반환합니다. 그래서 우리는 단지 한 번만 데이터를 얻어야 합니다. 그리고 데이터를 처리합니다.
ETHUSDT:100:0.002|LTCUSDT:20:0.1
ETHUSDT:100:0.002
거래 쌍 ETH_USDT를 제어하고,LTCUSDT:20:0.1
LTC_USDT 트레이딩 쌍을 제어합니다. 중간에 있는 ETHUSDT:100:0.002
,
이 문자열은 이미 당신이 작동해야 하는 각 기호의 매개 변수 정보를 포함하고 있습니다. 당신은 문자열을 분석하고 각 기호의 거래 논리를 제어하기 위해 전략의 변수에 값을 할당할 수 있습니다. 어떻게 분석합니까? 위에서 언급한 예를 사용해보죠.
function main() {
var net = [] // the recorded grid parameters; when specifically running the grid trading logic, use the data from here
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] // trading pair name
var diff = parseFloat(arr[1]) // grid spacing
var amount = parseFloat(arr[2]) // grid order amount
net.push({symbol : symbol, diff : diff, amount : amount})
})
Log("Grid parameter data:", 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) // the recorded grid parameters; when specifically running the grid trading logic, use the data from here
_.each(net, function(pair) {
Log("Trading pair:", pair.symbol, pair)
})
}
_G()
FMZ Quant에 있는 함수, 또는 동작 함수를 사용DBExec()
데이터베이스에서, 당신은 FMZ API 문서를 검색 할 수 있습니다. 자세한 사항.예를 들어, 우리는 그 함수를 사용하여 청소 함수를 설계하고자 합니다._G()
, 그리드 데이터를 저장하기 위해.
var net = null
function main() { // strategy main function
// first read the stored net
net = _G("net")
// ...
}
function onExit() {
_G("net", net)
Log("Execute the clean-up processing, and save the data", "#FF0000")
}
function onexit() { // the onexit function defined by the platform system, which will be triggered when clicking the bot to stop
onExit()
}
function onerror() { // the onerror function defined by the platform system, which will be triggered when the program exception occurs
onExit()
}
백테스트 시스템은 주문량과 주문 정밀도에 대한 엄격한 제한이 없지만, 봇에서는 각 플랫폼이 주문 가격과 주문량에 대한 엄격한 기준을 가지고 있으며, 다른 거래 쌍에는 다른 제한이 있습니다. 따라서 초보자는 종종 백테스트 시스템에서 OKEX를 테스트합니다. 전략이 봇에서 실행되면 거래가 시작되면 다양한 문제가 발생하고 오류 메시지의 내용이 읽히지 않아 다양한 미친 현상이 나타납니다.
여러 기호의 경우, 요구 사항은 더 복잡합니다. 단일 기호 전략의 경우, 정밀도와 같은 정보를 지정하기 위해 매개 변수를 설계할 수 있습니다. 그러나, 여러 기호 전략을 설계할 때, 매개 변수에 정보를 작성하면 매개 변수가 매우 지루해질 것이 분명합니다.
이 시점에서, 당신은 문서에 거래 쌍 관련 정보에 대한 인터페이스가 있는지 확인하기 위해 플랫폼의 API 문서를 확인해야합니다. 이러한 인터페이스가있는 경우, 정확성과 같은 정보를 얻기 위해 전략에서 자동 액세스 인터페이스를 설계하고 거래의 거래 쌍 정보로 구성 할 수 있습니다. (단순히, 정확성은 플랫폼에서 자동으로 얻어지고 전략 매개 변수에 따라 조정됩니다.)
위의 분석을 바탕으로, 우리는 전략, 플랫폼 메커니즘 및 인터페이스 사이의 결합을 줄이기 위해 템플릿 라이브러리를 설계합니다.
템플릿 라이브러리를 이렇게 디자인할 수 있습니다 (코드의 일부가 생략되었습니다):
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()
// the interfaces that need to be implemented
self.interfaceGetTickers = null // create a function that asynchronously obtains the aggregated market quote threads
self.interfaceGetAcc = null // create a function that asynchronously obtains the account data threads
self.interfaceGetPos = null // obtain positions
self.interfaceTrade = null // create concurrent orders
self.waitTickers = null // wait for the concurrent market quote data
self.waitAcc = null // wait for the account concurrent data
self.waitTrade = null // wait for order concurrent data
self.calcAmount = null // calculate the order amount according to the trading pair precision and other data
self.init = null // initialization; obtain the precision and other data
// execute the configuration function, to configure objects
funcConfigure(self)
// detect whether all the interfaces arranged by configList can be implemented
_.each(configList, function(funcName) {
if (!self[funcName]) {
throw "interface" + funcName + "not implemented"
}
})
return self
}
$.createBaseEx = createBaseEx
$.getConfigureFunc = function(exName) {
dicRegister = {
"Futures_OKCoin" : funcConfigure_Futures_OKCoin, // the implementation of OKEX Futures
"Huobi" : funcConfigure_Huobi,
"Futures_Binance" : funcConfigure_Futures_Binance,
"Binance" : funcConfigure_Binance,
"WexApp" : funcConfigure_WexApp, // the implementation of 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) {
// obtain the trading pair information
var symbolInfo = self.getSymbolInfo(symbol)
if (!symbol) {
throw symbol + ",trading pair information not found"
}
var tradeAmount = null
var equalAmount = null // record the symbol amount
if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
tradeAmount = _N(amount * price, parseFloat(symbolInfo.pricePrecision))
// detect the minimum trading amount
if (tradeAmount < symbolInfo.min) {
Log(self.name, " tradeAmount:", tradeAmount, "less than", symbolInfo.min)
return false
}
equalAmount = tradeAmount / price
} else {
tradeAmount = _N(amount, parseFloat(symbolInfo.amountPrecision))
// detect the minimum trading amount
if (tradeAmount < symbolInfo.min / price) {
Log(self.name, " tradeAmount:", tradeAmount, "less than", symbolInfo.min / price)
return false
}
equalAmount = tradeAmount
}
return [tradeAmount, equalAmount]
}
self.init = function init() { // the function that automatically processes conditions like precision, etc.
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()
// test to obtain the market quotes
ex.goGetTickers()
var tickers = ex.getTickers()
Log("tickers:", tickers)
// test to obtain the account information
ex.goGetAcc(symbol, ts)
_.each(arrTestSymbol, function(symbol) {
_.each(tickers, function(ticker) {
if (symbol == ticker.originalSymbol) {
// print the market quote data
Log(symbol, ticker)
}
})
// print asset data
var acc = ex.getAcc(symbol, ts)
Log("acc:", acc.symbol, acc)
})
}
위 템플릿을 기반으로 전략을 설계하고 작성하는 것은 매우 간단합니다. 전체 전략에는 약 300 줄 이상의 코드가 있습니다. 이는 암호화폐 스팟 멀티 심볼 그리드 전략을 구현합니다.
지금, 손실이 있습니다T_T
, 그래서 소스 코드는 제공되지 않습니다.
여러 가지 등록 코드가 있습니다. 관심이 있다면 wexApp에서 시도해 볼 수 있습니다.
Purchase Address: https://www.fmz.com/m/s/284507
Registration Code:
adc7a2e0a2cfde542e3ace405d216731
f5db29d05f57266165ce92dc18fd0a30
1735dca92794943ddaf277828ee04c27
0281ea107935015491cda2b372a0997d
1d0d8ef1ea0ea1415eeee40404ed09cc
단지 200 달러 이상이었고, 보트가 시작되었을 때, 그것은 큰 일방적인 시장에 맞닥뜨렸습니다. 손실을 덮기 위해 시간이 필요합니다. 스팟 그리드 전략의 가장 큰 장점은: