In the last article, we made a simple grid strategy together. In this article, we upgraded and expanded this strategy into a multi species spot grid strategy, and let this strategy be tested in practice. The purpose is not to find a "holy grail", but to discuss various problems and solutions when designing strategies. This article will explain some of my experience in designing this strategy. The content of this article is slightly complicated and it requires a certain foundation in programming.

Design thinking based on strategic needs

This article, like the previous one, still discusses design based on the FMZ Quant (FMZ.COM).

To put it bluntly, I think this grid strategy can not only do BTC_USDT, but also LTC_USDT/EOS_USDT/DOGE_USDT/ETC_USDT /ETH_USDT. Anyway, the spot trading pairs and the varieties that want to run are all traded on the grid at the same time.

Hmm~ It feels good to capture the volatile market of multiple species.
The requirement sounds very simple, and the problem comes when designing.

First, the market quotations of multiple varieties are obtained. This is the first problem to be solved. After consulting the exchange's API documentation, I found that most exchanges provide aggregated market interfaces.
OK, use the aggregated market interface to obtain data.

The second problem encountered is account assets. Because it is a multi species strategy, it is necessary to consider the management of each trading pair asset separately. And we need to get data for all assets at once, and record them. Why do we need to get the account asset data? Why do we need to separate the records of each pair?
Because you need to judge the available assets when placing an order. Is it necessary to obtain it before judging it?
And you need to calculate the profitis, is it also necessary to record an initial account asset data first, then obtain the current account asset data and compare it with the initial one to calculate the profit and loss?
Fortunately, the asset account interface of the exchange usually returns all currency asset data, we only need to obtain it once, and then process the data.

Strategy parameter design. The parameter design of multi species is quite different from the parameter design of single-variety, although the trading logic of each variety of multi-variety is the same, it is possible that the parameters during trading are different. For example, in the grid strategy, you may want to trade 0.01 BTC each time when doing BTC_USDT trading pair, but it is obviously inappropriate to use this parameter (trading 0.01 coins) when doing DOGE_USDT. Of course, you can also deal with the USDT amount. But there will still be problems. What if you want to trade 1000U for BTC_USDT and 10U for DOGE_USDT? The demand can never be satisfied.
There may be someone who will think about this problem and then ask: "I can set several sets of parameters to control the parameters of different trading pairs to be done separately."
This is still not flexible enough to meet the needs, how many sets of parameters are good to set? Three sets of parameters are set, what if I want to make 4 varieties? Do I have to modify the strategy and increase the parameters?...
Therefore, when designing the parameters of the multi species strategy, it is necessary to fully consider the needs of such differentiated parameters. One solution is to design the parameters as ordinary strings or JSON strings.
For example:


Among them, "|" divides the data of each species, which means that ETHUSDT:100:0.002 controls the ETH_USDT trading pair, and LTCUSDT:20:0.1 controls the LTC_USDT trading pair. The middle "|" is used to divide.
ETHUSDT:100:0.002, where ETH_USDT indicates what the trading pair you want to do, 100 is the grid spacing, 0.002 is the number of ETH coins traded in each grid, and the ":" is to divide these data (of course, these parameter rules are made by the strategy designer, you can design anything according to your needs).
These strings contain the parameter information of each species you want to do. Parse these strings in the strategy, and assign values ​​to the variables of the strategy to control the trading logic of each species. How to parse it? Still use the above example.

function createBaseEx(e, funcConfigure) {
    var self = {}
    self.e = e 
    self.funcConfigure = funcConfigure = e.GetName()
    self.type ="Futures_") ? "Futures" : "Spot"
    self.label = e.GetLabel()
    // Interfaces to be implemented
    self.interfaceGetTickers = null   // Create a function to asynchronously obtain a thread of aggregated market data
    self.interfaceGetAcc = null       // Create a function that asynchronously obtains account data thread
    self.interfaceGetPos = null       // Get a position
    self.interfaceTrade = null        // Create concurrent orders
    self.waitTickers = null           // Waiting for concurrent market data 
    self.waitAcc = null               // Waiting for account concurrent data
    self.waitTrade = null             // Waiting for order concurrent data
    self.calcAmount = null            // Calculate the order volume based on data such as trading pair accuracy
    self.init = null                  // Initialization work, obtaining data such as accuracy
    // Execute the configuration function to configure the object

    // Check whether the interfaces agreed by configList are implemented
    _.each(configList, function(funcName) {
        if (!self[funcName]) {
            throw "interface" + funcName + "unimplemented"
    return self

$.createBaseEx = createBaseEx
$.getConfigureFunc = function(exName) {
    dicRegister = {
        "Futures_OKCoin" : funcConfigure_Futures_OKCoin,    // Implementation of OK futures
        "Huobi" : funcConfigure_Huobi,
        "Futures_Binance" : funcConfigure_Futures_Binance,
        "Binance" : funcConfigure_Binance,
        "WexApp" : funcConfigure_WexApp,                    // Implementation of wexApp
    return dicRegister

In the template, it is written for specific exchanges, take FMZ's simulated bot WexApp as an example:

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("")

    self.waitTickers = function waitTickers() {
        var ret = []
        var arr = JSON.parse(self.routineGetTicker.wait()).data
        _.each(arr, function(ele) {
                bid1: parseFloat(, 
                bid1Vol: parseFloat(-1),
                ask1: parseFloat(ele.sell), 
                ask1Vol: parseFloat(-1),
                symbol: formatSymbol([0],
                type: "Spot", 
        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(, function(ele) {
            if (ele.currency == arr[1]) {
                // baseCurrency
                acc.Stocks = parseFloat(
                acc.FrozenStocks = parseFloat(ele.frozen)
            } else if (ele.currency == arr[2]) {
                // quoteCurrency
                acc.Balance = parseFloat(
                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 trading pair information
        var symbolInfo = self.getSymbolInfo(symbol)
        if (!symbol) {
            throw symbol + ", the trading pair information cannot be checked"
        var tradeAmount = null 
        var equalAmount = null  // Number of coins recorded
        if (type == self.OPEN_LONG || type == self.COVER_SHORT) {
            tradeAmount = _N(amount * price, parseFloat(symbolInfo.pricePrecision))
            // Check the minimum trading volume
            if (tradeAmount < symbolInfo.min) {
                Log(, " tradeAmount:", tradeAmount, "less than", symbolInfo.min)
                return false 
            equalAmount = tradeAmount / price
        } else {
            tradeAmount = _N(amount, parseFloat(symbolInfo.amountPrecision))
            // Check the minimum trading volume
            if (tradeAmount < symbolInfo.min / price) {
                Log(, " tradeAmount:", tradeAmount, "less than", symbolInfo.min / price)
                return false 
            equalAmount = tradeAmount
        return [tradeAmount, equalAmount]

    self.init = function init() {   // Functions that deal with conditions such as accuracy automatically
        var ret = JSON.parse(HttpQuery(""))
        _.each(, function(symbolInfo) {
                symbol: symbolInfo.pair,
                amountPrecision: parseFloat(symbolInfo.basePrecision),
                pricePrecision: parseFloat(symbolInfo.quotePrecision),
                multiplier: 1,
                min: parseFloat(symbolInfo.minQty),
                originalInfo: symbolInfo

Then using this template in a strategy is simple:

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 get tickers
    var tickers = ex.getTickers()
    Log("tickers:", tickers)
    // Test to obtain account information
    ex.goGetAcc(symbol, ts)
    _.each(arrTestSymbol, function(symbol) {        
        _.each(tickers, function(ticker) {
            if (symbol == ticker.originalSymbol) {
                // print ticker data
                Log(symbol, ticker)

        // print asset data
        var acc = ex.getAcc(symbol, ts)
        Log("acc:", acc.symbol, acc)

Strategy real bot

It is very simple to design and write a strategy based on the above template. The entire strategy is about 300+ lines and implements a digital currency spot multi-species grid strategy.

It's losing money currentlyT_T, the source code will not be released for the time being.

Here are a few registration codes, if you are interested, you can use the wexApp to try:

Buy address:
Registration code: 

Just over 200 U, when I just started running, I encountered a big one-sided market, but I recovered slowly. The biggest advantage of spot grids is: "I Can fall asleep!"
The stability is not bad. It has not been modified since May 27th, and futures grid has not dared to try temporarily.

Leave a Reply

Your email address will not be published. Required fields are marked *