Cryptocurrency Futures Martingale-Type Strategy Design

Author: Ninabadass, Created: 2022-04-12 17:50:07, Updated: 2022-04-12 18:06:07

Cryptocurrency Futures Martingale-Type Strategy Design

Recently, there are many Martingale-type strategies discussed in the FMZ official group, and there are not many Martingale strategies of cryptocurrency contracts on our platform. Therefore, I took this opportunity to design a simple cryptocurrency futures Martingale-type strategy. Why is it called a Martingale-type strategy? Because the potential risk of the Martingale strategy is indeed not small, it is not necessary to design according to the Martingale strategy. However, this type of strategies still have a lot of risks, and the parameter settings of the Martingale-type strategy are closely related to the risks, and the risks shall not be ignored.

In this article, we mainly explains and learns from the design of Martingale-type strategies. The strategy idea itself is already very clear, so we, as FMZ users, can consider more about the strategy design.

Obtain Total Equity

The total equity is often used when designing a cryptocurrency futures strategy, for we want to calculate the profits, especially when we need to calculate the floating profits. Since holding positions occupies the margin, pending orders also does. At the time, the API interface exchange.GetAccount() of FMZ platform is called to obtain the available assets and pending order frozen assets. In fact, most cryptocurrency futures platforms provide the data of the total equity, but this property is not uniformly packaged on FMZ.

Therefore, we separately design functions to obtain the data according to different platforms:

// OKEX V5 obtains the total equity 
function getTotalEquity_OKEX_V5() {
    var totalEquity = null 
    var ret = exchange.IO("api", "GET", "/api/v5/account/balance", "ccy=USDT")
    if (ret) {
        try {
            totalEquity = parseFloat(ret.data[0].details[0].eq)
        } catch(e) {
            Log("Fail to obtain the total equity of the account!")
            return null
        }
    }
    return totalEquity
}

// Binance Ftures 
function getTotalEquity_Binance() {
    var totalEquity = null 
    var ret = exchange.GetAccount()
    if (ret) {
        try {
            totalEquity = parseFloat(ret.Info.totalWalletBalance)
        } catch(e) {
            Log("Fail to obtain the total equity!")
            return null
        }
    }
    return totalEquity
}

The totalEquity in the code is the total equity we need. Then we write a function as the invocation entry, and call the corresponding function according to the platform name.

function getTotalEquity() {
    var exName = exchange.GetName()
    if (exName == "Futures_OKCoin") {
        return getTotalEquity_OKEX_V5()
    } else if (exName == "Futures_Binance") {
        return getTotalEquity_Binance()
    } else {
        throw "Do not support the platform"
    }
}

Design Several Auxiliary Functions

Before design the main function and the main logic, we also need to design some auxiliary functions for preparation.

  • Cancel all the current pending orders

    function cancelAll() {
        while (1) {
            var orders = _C(exchange.GetOrders)
            if (orders.length == 0) {
                break
            }
            for (var i = 0 ; i < orders.length ; i++) {
                exchange.CancelOrder(orders[i].Id, orders[i])
                Sleep(500)
            }
            Sleep(500)
        }
    }
    

    This function is believed to be familiar to those who often read the strategy example code on the FMZ strategy square, and many strategies have used the similar design. The function is to get the current pending order list, and then cancel the orders one by one.

  • The operation of placing futures orders

    function trade(distance, price, amount) {
        var tradeFunc = null 
        if (distance == "buy") {
            tradeFunc = exchange.Buy
        } else if (distance == "sell") {
            tradeFunc = exchange.Sell
        } else if (distance == "closebuy") {
            tradeFunc = exchange.Sell
        } else {
            tradeFunc = exchange.Buy
        }
        exchange.SetDirection(distance)
        return tradeFunc(price, amount)
    }
    
    function openLong(price, amount) {
        return trade("buy", price, amount)
    }
    
    function openShort(price, amount) {
        return trade("sell", price, amount)
    }
    
    function coverLong(price, amount) {
        return trade("closebuy", price, amount)
    }
    
    function coverShort(price, amount) {
        return trade("closesell", price, amount)
    }
    

    There are four directions for futures trading: open long position (openLong), open short position (openShort), close long position (coverLong), and close short position (coverShort). Therefore, we designed four order functions to correspond to these operations. If you only consider placing orders, then there are several necessary factors: direction, order price, and order amount.

    We also designed a function named: trade to handle the operation when direction (distance), order price (price) and order amount (amount) are specified.

    The function calls of open long positions (openLong), open short positions (openShort), close long positions (coverLong), and close short positions (coverShort) are ultimately completed by the trade function, that is, according to the specified direction, price and amount, place orders in the futures platforms.

Main Function

The strategy idea is very simple; take the current price as the baseline, and from a certain distance above and below the baseline to place sell orders (short) and buy orders (long). If the orders of one side are executed, cancel all the remaining orders, and then new close orders will be placed at a certain distance according to the position price, and buy orders will be placed at the updated current price, but the buy orders will not double the order amount.

  • Initial work For we want to pend orders, we need two variables to record order ID.

    var buyOrderId = null
    var sellOrderId = null
    

    Then, the option to use the OKEX_V5 simulated bot is designed in the strategy interface parameters, so some processing needs to be done in the code:

    var exName = exchange.GetName()    
    // switch to OKEX V5 simulated bot 
    if (isSimulate && exName == "Futures_OKCoin") {
        exchange.IO("simulate", true)
    }
    

    The option to reset all information is also designed in the strategy parameters, so some processing needs to be done in the code:

    if (isReset) {
        _G(null)
        LogReset(1)
        LogProfitReset()
        LogVacuum()
        Log("Reset all data", "#FF0000")
    }
    

    We only run perpetual contracts, so here we write it in an infinite loop, and we only set it to perpetual contract.

    exchange.SetContractType("swap")
    

    Also, we need to consider of the precision problems of the order price and the order amount. If the precision is not set properly, it will be lost during the strategy calculation process. If the data has a large number of decimals, it is easy to cause the order to be rejected by the platform interface.

    exchange.SetPrecision(pricePrecision, amountPrecision)
    Log("set percision", pricePrecision, amountPrecision)
    

    The simple data recovery function in the design

    if (totalEq == -1 && !IsVirtual()) {
        var recoverTotalEq = _G("totalEq")
        if (!recoverTotalEq) {
            var currTotalEq = getTotalEquity()
            if (currTotalEq) {
                totalEq = currTotalEq
                _G("totalEq", currTotalEq)
            } else {
                throw "Fail to obtain the initial equity"
            }
        } else {
            totalEq = recoverTotalEq
        }
    }
    

    If you want to specify the initial total equity of the account when running the strategy, you can set the parameter totalEq. If this parameter is set to -1, the strategy will read the stored total equity data. If there is no stored total equity data, the total equity currently read is used as the initial total equity in the strategy running progress. Later, if the total equity increases, it means that it has earned a profit; if the total equity declines, it means that there is a loss. If the total equity data is read, use the data to continue running.

  • Main Logic After the initial work is done, we finally came to the main logic part of the strategy. For the convenience of explanation, I wrote the remarks directly in the code.

      while (1) {                                  // the main logic of the strategy is designed as an infinite loop
          var ticker = _C(exchange.GetTicker)      // read the current market information first, in which we mainly use the latest trading price
          var pos = _C(exchange.GetPosition)       // read the current position data 
          if (pos.length > 1) {                    // judge the position data; due to the strategy logic, it is unlikely to have long and short positions at the same time, so if there are long and short positions at the same time, an error will be thrown
              Log(pos)
              throw "concurrently with long and short positions"                  // raise an error, and stop the strategy 
          }
          // according to the status 
          if (pos.length == 0) {                    // according to the position status, make different operations; if pos.length == 0, it means currently no position
              // when there is no position yet, calculate the equity 
              if (!IsVirtual()) {
                  var currTotalEq = getTotalEquity()
                  if (currTotalEq) {
                      LogProfit(currTotalEq - totalEq, "Current total equity:", currTotalEq)
                  }
              }
    
              buyOrderId = openLong(ticker.Last - targetProfit, amount)       // pend buy order of open long position 
              sellOrderId = openShort(ticker.Last + targetProfit, amount)     // pend sell order of open short position
          } else if (pos[0].Type == PD_LONG) {   // there are long positions; pending position and amount are 
              var n = 1
              var price = ticker.Last
              buyOrderId = openLong(price - targetProfit * n, amount)
              sellOrderId = coverLong(pos[0].Price + targetProfit, pos[0].Amount)
          } else if (pos[0].Type == PD_SHORT) {   // there are short positions; pending position and amount are different 
              var n = 1
              var price = ticker.Last
              buyOrderId = coverShort(pos[0].Price - targetProfit, pos[0].Amount)
              sellOrderId = openShort(price + targetProfit * n, amount)
          }
    
          if (!sellOrderId || !buyOrderId) {   // if opending orders of one side fails, cancel all pending orders and try again 
              cancelAll()
              buyOrderId = null 
              sellOrderId = null
              continue
          } 
    
          while (1) {  // finish pending the order, and start to monitor the order
              var isFindBuyId = false 
              var isFindSellId = false
              var orders = _C(exchange.GetOrders)
              for (var i = 0 ; i < orders.length ; i++) {
                  if (buyOrderId == orders[i].Id) {
                      isFindBuyId = true 
                  }
                  if (sellOrderId == orders[i].Id) {
                      isFindSellId = true 
                  }               
              }
              if (!isFindSellId && !isFindBuyId) {    // both buy order and sell order are detected to be executed 
                  cancelAll()
                  break
              } else if (!isFindBuyId) {   // a buy order execution is detected 
                  Log("buy order executed")
                  cancelAll()
                  break
              } else if (!isFindSellId) {  // a sell order execution is detected 
                  Log("sell order executed")
                  cancelAll()
                  break
              }
              LogStatus(_D())
              Sleep(3000)
          }
          Sleep(500)
      }
    

The entire logic and design are completely explained then.

Backtest

Let the strategy traverse the market quotes on May 19, 2021.

img

img

As we can see, the strategy similar to Martingale strategy still has certain risks.

Bot test can use OKEX V5 simulated bot to run

img

Strategy address: https://www.fmz.com/strategy/294957

The strategy is mainly used for study, so do not operate the strategy in a real bot!


More