
이전 글에서 우리는 간단한 그리드 전략을 만들기 위해 함께 작업했습니다. 이 글에서는 이 전략을 다양한 스팟 그리드 전략으로 업그레이드하고 확장하여 실제 전투에서 이 전략을 테스트합니다. 목적은 ‘성배’를 찾는 것이 아니라, 전략을 설계할 때 다양한 문제와 해결책을 탐구하는 것입니다. 이 글에서는 이 전략을 설계하는 데 있어서 제가 겪은 몇 가지 경험을 설명하겠습니다. 이 글의 내용은 약간 복잡하며 프로그래밍에 대한 특정 기초가 필요합니다.
이 기사는 이전 기사와 마찬가지로 Inventor Quantization(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.002ETH_USDT 거래 쌍을 관리합니다.LTCUSDT:20:0.1LTC_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)
})
}

_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 onerror() { // 平台系统定义的异常退出函数,在程序发生异常时触发执行
onExit()
}
백테스트 시스템은 주문 수량과 주문 정확도에 대해 그렇게 엄격한 제한을 부과하지 않지만 실제 거래에서는 각 거래소에서 주문 가격과 주문 수량에 대한 엄격한 기준을 가질 수 있으며 각 거래 쌍에 대한 이러한 기준도 매우 엄격합니다. 제한은 없습니다. 동일함. 그래서 초보자들은 종종 백테스트 시스템을 테스트하고 실제 시장에서 거래를 트리거할 때 온갖 문제를 봅니다. 그런 다음 오류 메시지를 읽지 않고 온갖 미친 문제[개머리]를 경험합니다.
품종이 다양한 경우 이 요구 사항은 더 복잡해집니다. 단일 제품 전략의 경우 정확도와 같은 정보를 지정하기 위해 매개변수를 설계할 수 있습니다. 그러나 다중 제품 전략을 설계할 때 이 정보를 매개변수에 쓰면 매개변수가 매우 부풀려지는 것처럼 보일 것이라는 것은 자명합니다.
이 시점에서 거래소 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()
// 需要实现的接口
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
US는 200명 남짓이었고, 운영을 시작하자마자 큰 일방적 시장에 부딪혔고 천천히 회복되었습니다. 스팟 그리드의 가장 큰 장점은 “푹신푹신하게 잠을 잘 수 있다!“는 것입니다. 안정성은 괜찮아요. 5월 27일 이후로는 건드리지 않았어요. 당장은 선물 그리드를 시도할 생각도 없어요.