Design of a cash hedging strategy for digital currency (1)

Author: The Little Dream, Created: 2021-07-19 17:38:24, Updated: 2023-09-20 10:35:16

img

Design of a cash hedging strategy for digital currency (1)

For beginners, hedging strategies are a very good practice strategy. This article implements a simple but practical digital currency spot hedging strategy, hopefully giving beginners some design experience.

Design some functions, policy interface parameters according to policy needs

First of all, it should be clear that the strategy that we are going to design is a digital currency spot hedging strategy, we have designed the simplest hedge, only the higher-priced exchanges sell between the two spot exchanges, the lower-priced exchanges buy and thus earn the difference. When the higher-priced exchanges are all bills (because the higher-priced coins are all sold), the lower-priced exchanges are all coins (because the lower-priced exchanges are all coins) cannot be hedged.

The price, quantity, and precision of the order are limited, and there is a minimum order limit. In addition to the minimum order limit, the strategy outside of the limit also considers the maximum order size of the hedge, and there will not be enough order volume if the order size is too large. It is also necessary to consider how to convert the exchange rate if the two exchange rates are different.

Based on these considerations, the strategy needs to design several parameters:

  • Hedging differences:hedgeDiffPrice, when the spread exceeds this value, trigger a hedge operation.
  • Minimum hedging:minHedgeAmountThis is the lowest amount of money that can be hedged.
  • Maximum hedging:maxHedgeAmountThe maximum amount of the hedge (the number of coins) is the maximum amount of the hedge.
  • A price accuracy:pricePrecisionAThe price precision of the order (in small digits) on the A exchange.
  • The following unit accuracy:amountPrecisionA, A is the precision of the unit (smaller digit) of the exchange.
  • Price accuracy:pricePrecisionBThe price accuracy of the B exchange order (in small digits).
  • B is the following unit accuracy:amountPrecisionBThis is the lowest single-digit accuracy on the B exchange.
  • The exchange rate:rateA, the exchange rate conversion of the first exchange object added, the default 1 not converted.
  • Exchange rate:rateB, the exchange rate conversion of the second exchange object added, the default 1 is not converted.

A hedging strategy requires that the number of coins in both accounts is constant (i.e. no directional position is held, so it is neutral), so there is a balancing logic in the strategy that always detects the balance. When detecting the balance, we avoid having to obtain asset data from both exchanges. We need to write a function to use it.

  • updateAccs
    function updateAccs(arrEx) {
        var ret = []
        for (var i = 0 ; i < arrEx.length ; i++) {
            var acc = arrEx[i].GetAccount()
            if (!acc) {
                return null
            }
            ret.push(acc)
        }
        return ret 
    }
    

If an order is not completed after the current order, we need to cancel it in a timely manner, and we cannot keep the order hanging. This operation needs to be handled both in the balancing module and in the hedging logic, so we also need to design an order full-withdrawal function.

  • cancelAll
    function cancelAll() {
        _.each(exchanges, function(ex) {
            while (true) {
                var orders = _C(ex.GetOrders)
                if (orders.length == 0) {
                    break
                }
                for (var i = 0 ; i < orders.length ; i++) {
                    ex.CancelOrder(orders[i].Id, orders[i])
                    Sleep(500)
                }
            }
        })
    }
    

When balancing the number of coins, we need to find the price that accumulates to a certain number of coins in some depth data, so we need a function like this to handle it.

  • getDepthPrice
    function getDepthPrice(depth, side, amount) {
        var arr = depth[side]
        var sum = 0
        var price = null
        for (var i = 0 ; i < arr.length ; i++) {
            var ele = arr[i]
            sum += ele.Amount
            if (sum >= amount) {
                price = ele.Price
                break
            }
        }
        return price
    }
    

And then we need to design and write a specific hedging sub-order operation, which we need to design as a concurrent sub-order:

  • hedge
    function hedge(buyEx, sellEx, price, amount) {
        var buyRoutine = buyEx.Go("Buy", price, amount)
        var sellRoutine = sellEx.Go("Sell", price, amount)
        Sleep(500)
        buyRoutine.wait()
        sellRoutine.wait()
    }
    

Finally, we're going to finish the design of the equilibrium function, which is a little bit more complicated.

  • keepBalance
    function keepBalance(initAccs, nowAccs, depths) {
        var initSumStocks = 0
        var nowSumStocks = 0 
        _.each(initAccs, function(acc) {
            initSumStocks += acc.Stocks + acc.FrozenStocks
        })
        _.each(nowAccs, function(acc) {
            nowSumStocks += acc.Stocks + acc.FrozenStocks
        })
      
        var diff = nowSumStocks - initSumStocks
        // 计算币差
        if (Math.abs(diff) > minHedgeAmount && initAccs.length == nowAccs.length && nowAccs.length == depths.length) {
            var index = -1
            var available = []
            var side = diff > 0 ? "Bids" : "Asks"
            for (var i = 0 ; i < nowAccs.length ; i++) {
                var price = getDepthPrice(depths[i], side, Math.abs(diff))
                if (side == "Bids" && nowAccs[i].Stocks > Math.abs(diff)) {
                    available.push(i)
                } else if (price && nowAccs[i].Balance / price > Math.abs(diff)) {
                    available.push(i)
                }
            }
            for (var i = 0 ; i < available.length ; i++) {
                if (index == -1) {
                    index = available[i]
                } else {
                    var priceIndex = getDepthPrice(depths[index], side, Math.abs(diff))
                    var priceI = getDepthPrice(depths[available[i]], side, Math.abs(diff))
                    if (side == "Bids" && priceIndex && priceI && priceI > priceIndex) {
                        index = available[i]
                    } else if (priceIndex && priceI && priceI < priceIndex) {
                        index = available[i]
                    }
                }
            }
            if (index == -1) {
                Log("无法平衡")            
            } else {
                // 平衡下单
                var price = getDepthPrice(depths[index], side, Math.abs(diff))
                if (price) {
                    var tradeFunc = side == "Bids" ? exchanges[index].Sell : exchanges[index].Buy
                    tradeFunc(price, Math.abs(diff))
                } else {
                    Log("价格无效", price)
                }
            }        
            return false
        } else if (!(initAccs.length == nowAccs.length && nowAccs.length == depths.length)) {
            Log("错误:", "initAccs.length:", initAccs.length, "nowAccs.length:", nowAccs.length, "depths.length:", depths.length)
            return true 
        } else {
            return true 
        }
    }
    

These functions are designed according to the policy needs, and below you can start designing the main functions of the policy.

Design of the strategy main function

The strategy on FMZ is frommainThe function starts executing at.mainIn the beginning part of the function, we're going to do some initialization work on the strategy.

  • Name of the exchange Since many of the strategies are used to access exchange objects, such as obtaining markets, placing orders, etc.; so using a longer name every time can be a problem, the trick is to use a simple name instead, for example:

    var exA = exchanges[0]
    var exB = exchanges[1]
    

    This makes it easier to write code later.

  • Currency exchange rate, accuracy related design

      // 精度,汇率设置
      if (rateA != 1) {
          // 设置汇率A
          exA.SetRate(rateA)
          Log("交易所A设置汇率:", rateA, "#FF0000")
      }
      if (rateB != 1) {
          // 设置汇率B
          exB.SetRate(rateB)
          Log("交易所B设置汇率:", rateB, "#FF0000")
      }
      exA.SetPrecision(pricePrecisionA, amountPrecisionA)
      exB.SetPrecision(pricePrecisionB, amountPrecisionB)
    

    If the exchange rate parameterrateArateBThere's a setting of 1 (the default is 1), which israteA != 1orrateB != 1The exchange rate conversion will not be set up, so it will not trigger.

  • Reset all data

    img

    Sometimes a policy needs to be deleted from all logs and blank records when it starts. A policy interface parameter can be designed.isReset, and then re-design the part of the code initialized in the policy, for example:

      if (isReset) {   // 当isReset为真时重置数据
          _G(null)
          LogReset(1)
          LogProfitReset()
          LogVacuum()
          Log("重置所有数据", "#FF0000")
      }
    
  • Restore original account data, update current account data In order to judge the balance, the strategy requires continuous recording of the initial accounting assets used in comparison to the current ones.nowAccsThis is the variable that records the current account data using the functions that we just designed.updateAccsGet the account data of the current exchange.initAccsFor the purpose of recording the initial account balance (data such as the number of coins in exchange A and exchange B, the number of coins quoted)initAccsFirst use_G()Function recovery ((_G) The _G function records data permanently and can return data that has been recorded. See API documentation:LinksIf you can't find it, assign and use the current account information._GThe function registers.

    For example, the following code:

      var nowAccs = _C(updateAccs, exchanges)
      var initAccs = _G("initAccs")
      if (!initAccs) {
          initAccs = nowAccs
          _G("initAccs", initAccs)
      }
    

Transaction logic, the main loop in the main function

The code in the main loop is the process executed by the strategy logic every round, and the continuous repetition of the execution constitutes the main loop. Let's look at the process executed by the program in the main loop each time.

  • Get market data to judge the effectiveness of market data

          var ts = new Date().getTime()
          var depthARoutine = exA.Go("GetDepth")
          var depthBRoutine = exB.Go("GetDepth")
          var depthA = depthARoutine.wait()
          var depthB = depthBRoutine.wait()
          if (!depthA || !depthB || depthA.Asks.length == 0 || depthA.Bids.length == 0 || depthB.Asks.length == 0 || depthB.Bids.length == 0) {
              Sleep(500)
              continue 
          }
    

    Here you can see the concurrent functions using the FMZ platform.exchange.GoI created a call.GetDepth()Simultaneous objects of the interfacedepthARoutinedepthBRoutine│ When creating these two concurrent objects, callGetDepth()The interface also happened immediately, when both requests for deep data were sent to the exchange in the past. Then call.depthARoutinedepthBRoutineThe objectwait()How to get deep data.
    After obtaining the deep data, it is necessary to examine the deep data to determine its effectiveness.continueThe sentence re-executes the main loop.

  • Use价差值Are the parameters差价比例The parameters?

          var targetDiffPrice = hedgeDiffPrice
          if (diffAsPercentage) {
              targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
          }
    

    The parameters we've done are this. The parameters of FMZ can be based on some parameters.DisplayOrHidingSo we can do a parameter to decide whether to use.价格差Or,差价比例

    img

    Adding a parameter to the policy interface parameterdiffAsPercentageThe other two parameters that are displayed or hidden based on this parameter are:hedgeDiffPrice@!diffAsPercentageWhen,diffAsPercentageTo show this parameter as false.hedgeDiffPercentage@diffAsPercentageWhen,diffAsPercentageThis parameter is shown as true. So, after designing it, we selected the right ones.diffAsPercentageParameters, which are used to trigger a hedge in terms of the price difference.diffAsPercentageThe parameter is the price difference as a hedge trigger condition.

  • Determining the trigger conditions of a hedge

          if (depthA.Bids[0].Price - depthB.Asks[0].Price > targetDiffPrice && Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount) >= minHedgeAmount) {          // A -> B 盘口条件满足            
              var price = (depthA.Bids[0].Price + depthB.Asks[0].Price) / 2
              var amount = Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount)
              if (nowAccs[0].Stocks > minHedgeAmount && nowAccs[1].Balance / price > minHedgeAmount) {
                  amount = Math.min(amount, nowAccs[0].Stocks, nowAccs[1].Balance / price, maxHedgeAmount)
                  Log("触发A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, price, amount, nowAccs[1].Balance / price, nowAccs[0].Stocks)  // 提示信息
                  hedge(exB, exA, price, amount)
                  cancelAll()
                  lastKeepBalanceTS = 0
                  isTrade = true 
              }            
          } else if (depthB.Bids[0].Price - depthA.Asks[0].Price > targetDiffPrice && Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount) >= minHedgeAmount) {   // B -> A 盘口条件满足
              var price = (depthB.Bids[0].Price + depthA.Asks[0].Price) / 2
              var amount = Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount)
              if (nowAccs[1].Stocks > minHedgeAmount && nowAccs[0].Balance / price > minHedgeAmount) {
                  amount = Math.min(amount, nowAccs[1].Stocks, nowAccs[0].Balance / price, maxHedgeAmount)
                  Log("触发B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, price, amount, nowAccs[0].Balance / price, nowAccs[1].Stocks)  // 提示信息
                  hedge(exA, exB, price, amount)
                  cancelAll()
                  lastKeepBalanceTS = 0
                  isTrade = true 
              }            
          }
    

    There are several conditions for triggering a hedge: 1, the hedging spread is satisfied first, and is only hedged when the spread of the transaction satisfies the set of the spread parameters. 2, the transactional hedge must satisfy the minimum hedge set in the parameter, because the minimum amount of down payment that different exchanges may limit is different, so the smallest of the two is taken. 3. The assets in the exchange that sold the operation are enough to sell, and the assets in the exchange that bought the operation are enough to buy. When these conditions are met, the hedging function is executed. Before the main function, we declare a variable in advance.isTradeTo mark whether a hedge has occurred, here if a hedge is triggered, set the variable totrueAnd reset the global variables.lastKeepBalanceTSSet to 0 (lastKeepBalanceTS is used to mark the timestamp of the most recent balance operation, set to 0 will trigger the balance operation immediately) and then undo all pending lists.

  • Balancing operation

          if (ts - lastKeepBalanceTS > keepBalanceCyc * 1000) {
              nowAccs = _C(updateAccs, exchanges)
              var isBalance = keepBalance(initAccs, nowAccs, [depthA, depthB])
              cancelAll()
              if (isBalance) {
                  lastKeepBalanceTS = ts
                  if (isTrade) {
                      var nowBalance = _.reduce(nowAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
                      var initBalance = _.reduce(initAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
                      LogProfit(nowBalance - initBalance, nowBalance, initBalance, nowAccs)
                      isTrade = false 
                  }                
              }            
          }
    

    You can see that the equilibrium function is executed regularly, but if the hedge is triggered after the operation, the balance function is executed regularly.lastKeepBalanceTSA balancing operation reset to 0 is triggered immediately. The gain is calculated after the balancing is successful.

  • Status bar information

          LogStatus(_D(), "A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, " B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, " targetDiffPrice:", targetDiffPrice, "\n", 
              "当前A,Stocks:", nowAccs[0].Stocks, "FrozenStocks:", nowAccs[0].FrozenStocks, "Balance:", nowAccs[0].Balance, "FrozenBalance", nowAccs[0].FrozenBalance, "\n", 
              "当前B,Stocks:", nowAccs[1].Stocks, "FrozenStocks:", nowAccs[1].FrozenStocks, "Balance:", nowAccs[1].Balance, "FrozenBalance", nowAccs[1].FrozenBalance, "\n", 
              "初始A,Stocks:", initAccs[0].Stocks, "FrozenStocks:", initAccs[0].FrozenStocks, "Balance:", initAccs[0].Balance, "FrozenBalance", initAccs[0].FrozenBalance, "\n", 
              "初始B,Stocks:", initAccs[1].Stocks, "FrozenStocks:", initAccs[1].FrozenStocks, "Balance:", initAccs[1].Balance, "FrozenBalance", initAccs[1].FrozenBalance)
    

    The status bar is not particularly complex in design, showing the current time, showing the difference from exchange A to exchange B and exchange B to exchange A. It shows the current hedging target difference. It shows asset data for exchange A and asset data for exchange B.

Processing of pairs of transactions in different currencies

In the parameters, we designed the conversion of exchange rate values parameters, and at the beginning of the strategy,mainWe also designed the exchange rate conversion part of the initial operation of the function.SetRateThe exchange rate conversion function needs to be executed first. This is because this function affects two levels:

  • The price conversion in all market data, order data, and stock data.
  • The exchange of currency in the account assets. For example, the current trading pair isBTC_USDTThe price is the same.USDTIn addition, the account can also be used as a currency for account assets.USDTIf I want to convert the value to CNY, set it in the code.exchange.SetRate(6.8)I'll just take it.exchangeAll the data obtained by the functions under this exchange object is converted into CNY. In exchange for why the coin is priced,SetRateFunction forwardingThe exchange rate of the current currency to the target currency

The full strategy:Hedging strategies for different currencies (Teaching)


Related

More

Squirrels in the UkraineThat's great.