Several DEX exchanges, including dydx_v4, hyperliquid, vertex, and aevo, have been encapsulated and connected on FMZ platform. As the competition for price difference arbitrage in centralized exchanges becomes increasingly fierce, many quantitative traders have turned their attention to decentralized exchanges. In this article, we will discuss the design and implementation of price difference monitoring between DEX and CEX.

The first step of the hedging arbitrage strategy is to count the price difference of the target portfolio, and observe and analyze whether there are trading opportunities. Therefore, designing and implementing a price difference monitoring strategy is the first basic work. Our design requirements are:

  • The programming language used is Javascript.
  • Use the encapsulated REST interface.
  • DEX selection: hyperliquid, vertex.
  • CEX selection: binance, bybit.
  • Requesting order book data uses multi-threaded concurrent requests.
  • The test products should be the mainstream products shared by all exchanges as much as possible: ETH, BTC spot trading pairs/perpetual contracts
  • Try to simplify the design and provide basic implementation using simple and easy-to-understand code.

Code Implementation

The code is less than 200 lines, and the designed function is to calculate the real-time price difference of a certain product in different exchanges only. At the beginning of the strategy operation, all exchange objects configured for the strategy will be classified into DEX group and CEX group. Each loop is performed through the multi-threaded function Thread encapsulated by the FMZ platform, and requests the REST interface concurrently: the order book interface GetDepth, and records the requested buy order list and sell order list data of the required product. Then, the DEX group and the CEX group are combined into a price difference combination (DEX-CEX combination, that is, arbitrage pair), and the price difference is calculated.

Determination of exchange type:
During initialization, the added exchange object will be judged to determine whether it is spot or futures.

Different exchanges may have different names for a certain underlying asset, so the name of the product needs to be adjusted:
The program needs to be adjusted according to the naming rules of symbols in different exchanges. For example, the trading pairs of Vertex contracts are all named: XXX_USD, which is actually a perpetual contract based on USDC. The name of ETH in Vertex's spot is WETH.

Get precision:
During initialization, the entire market information is obtained. According to the specific symbol requested, the corresponding precision can be queried for subsequent data precision processing operations.

Data request
Before all the depth data is obtained, the program will wait and keep checking whether there are any exchanges whose data has not been updated. If the data has not been obtained, the program will sleep for 100 milliseconds.
The requested order book data is recorded in an object created by threading.Dict() (used for interaction with concurrent threads), and the data is reset at the end of each loop.

Summary
The implementation of this strategy shows how to monitor the price differences of multiple exchanges in real time, and calculate possible arbitrage opportunities. Through reasonable symbol correction, deep data capture, precision control, and multi-threaded operation, the system can efficiently monitor the price difference in real time. For strategy learners, understanding the implementation ideas of the code can help you master how to use the API to obtain trading data, how to process data from multiple exchanges, how to calculate and output transaction price difference, and how to apply these technologies in actual tradings.

let symbolList = []

functioncreateEx(idx, exs) {
    let self = {}
    
    let cexEidList = ["Binance", "Bybit", "Futures_Binance", "Futures_Bybit"]
    let dexEidList = ["Vertex", "Hyperliquid", "Futures_Hyperliquid", "Futures_Vertex"]

    self.name = exs[idx].GetName()
    self.idx = idx
    self.e = exs[idx]
    self.depths = threading.Dict()
    self.markets = self.e.GetMarkets()

    if (!self.markets) {
        throw"GetMarkets error"
    }

    if (dexEidList.includes(self.name)) {
        self.type = "DEX"
    } elseif (cexEidList.includes(self.name)) {
        self.type = "CEX"
    } else {
        throw"not support " + self.name
    }

    if (self.name.startsWith("Futures_")) {
        self.isFutures = true
    } else {
        self.isFutures = false
    }

    self.correctSymbol = function(symbol) {        
        if (self.name == "Vertex") {
            let correctList = {"BTC_USDC": "WBTC_USDC", "ETH_USDC": "WETH_USDC"}
            if (typeof(correctList[symbol]) != "undefined") {
                return correctList[symbol]
            }
        } elseif (self.name == "Hyperliquid") {
            let correctList = {"BTC_USDC": "UBTC_USDC"}
            if (typeof(correctList[symbol]) != "undefined") {
                return correctList[symbol]
            }
        } elseif (self.name == "Futures_Hyperliquid") {
            return symbol.replace("_USDC", "_USD")
        }
        
        return symbol
    }

    self.reqDepth = function(symbol) {
        symbol = self.correctSymbol(symbol)
        threading.Thread(function(idx, symbol, threadingDict) {
            let depth = exchanges[idx].GetDepth(symbol)
            if (depth) {
                threadingDict.set(symbol, depth)
            } else {
                threadingDict.set(symbol, null)
            }
        }, self.idx, symbol, self.depths)
    }
    
    self.getPrecision = function(symbol) {
        symbol = self.correctSymbol(symbol)
        let marketInfo = self.markets[symbol]
        if (marketInfo) {
            return [marketInfo.PricePrecision, marketInfo.AmountPrecision]
        } else {
            return [8, 8]
        }
    }

    self.init = function() {
        self.depths = threading.Dict()
    }

    self.getDepth = function(symbol) {
        symbol = self.correctSymbol(symbol)
        return self.depths.get(symbol)
    }

    return self
}

functioncreateManager(symbolList, exs) {
    let self = {}

    self.symbolList = symbolList
    self.exchanges = []
    self.hedgePair = []

    self.initHedgePair = function () {
        for (let i in exs) {
            let ex = createEx(i, exs)
            self.exchanges.push(ex)
        }

        let arrDEX = self.exchanges.filter(item => item.type == "DEX")
        let arrCEX = self.exchanges.filter(item => item.type == "CEX")

        for (let dex of arrDEX) {
            for (let cex of arrCEX) {
                self.hedgePair.push({"dex": dex, "cex": cex})
            }
        }
    }

    self.calcHedgeData = function () {
        let beginTimestamp = newDate().getTime()
        for (let e of self.exchanges) {
            for (let symbol of self.symbolList) {
                e.reqDepth(symbol)
            }
        }

        while (true) {
            let isWait = falsefor (let e of self.exchanges) {
                for (let symbol of self.symbolList) {
                    let depth = e.getDepth(symbol)
                    if (depth == null || typeof(depth) == "undefined") {
                        isWait = true
                    }
                }
            }
            if (isWait) {
                Sleep(100)
            } else {
                break
            }
        }

        let tbls = []
        for (let symbol of self.symbolList) {
            let tbl = {"type": "table", "title": symbol + "price difference", "cols": ["pair", "bid-ask", "ask-bid", "dex ask", "dex bid", "cex ask", "cex bid"], "rows": []}
            for (let p of self.hedgePair) {
                let dex = p["dex"]
                let cex = p["cex"]

                let pricePrecision = Math.max(dex.getPrecision(symbol)[0], cex.getPrecision(symbol)[0])

                let dexDepth = dex.getDepth(symbol)
                let cexDepth = cex.getDepth(symbol)
                if (dexDepth && cexDepth) {
                    p["bid-ask"] = _N(dexDepth.Bids[0].Price - cexDepth.Asks[0].Price, pricePrecision)
                    p["ask-bid"] = _N(dexDepth.Asks[0].Price - cexDepth.Bids[0].Price, pricePrecision)

                    // Output information, observe the testLog(dex.name, cex.name, symbol, "bid-ask:", p["bid-ask"], ", ask-bid", p["ask-bid"])

                    p[dex.name + "-ask"] = dexDepth.Asks[0].Price + "/" + dexDepth.Asks[0].Amount
                    p[dex.name + "-bid"] = dexDepth.Bids[0].Price + "/" + dexDepth.Bids[0].Amount
                    p[cex.name + "-ask"] = cexDepth.Asks[0].Price + "/" + cexDepth.Asks[0].Amount
                    p[cex.name + "-bid"] = cexDepth.Bids[0].Price + "/" + cexDepth.Bids[0].Amount
                } else {
                    p["bid-ask"] = "--"
                    p["ask-bid"] = "--"
                    p[dex.name + "-ask"] = "--"
                    p[dex.name + "-bid"] = "--"
                    p[cex.name + "-ask"] = "--"
                    p[cex.name + "-bid"] = "--"
                }

                let pairName = dex.name + "-" + cex.name
                tbl["rows"].push([pairName, p["bid-ask"], p["ask-bid"], p[dex.name + "-ask"], p[dex.name + "-bid"], p[cex.name + "-ask"], p[cex.name + "-bid"]])
            }
            tbls.push(tbl)
        }
                
        for (let e of self.exchanges) {
            e.init()
        }

        let endTimestamp = newDate().getTime()
        return [tbls, (endTimestamp - beginTimestamp) + "millisecond"]
    }

    self.initHedgePair()
    return self
}

functionmain() {
    LogReset(1)
    let loopCount = 0

    symbolList = strSymbolList.split(",")
    let m = createManager(symbolList, exchanges)
    while (true) {
        let ret = m.calcHedgeData()
        loopCount++
        LogStatus(_D(), "time consuming:", ret[1], ", loop times:", loopCount, "\n", "`" + JSON.stringify(ret[0]) + "`")
        Sleep(1000)
    }
}

Parameter design:

Spot Market

Monitor a spot product:

  • BTC_USDC Bitcoin to USDC spot

Log information:

Contract Market

Monitor two varieties:

  • ETH_USDC.swap Ethereum perpetual contract
  • BTC_USDC.swap Bitcoin perpetual contract

Log information:

END

Expansion direction:

  • Threshold monitoring and encapsulation of trading logic.
  • Calculate the fees and costs, and calculate the reasonable hedging price difference range.
  • Use the websocket interface to obtain ticker data.

The FMZ platform will continue to enhance its technical support for decentralized exchanges (DEX) and decentralized finance (DeFi), and iterate and update functions and products continuously.

Thank you for reading.

From: Design and Implementation of DEX-CEX Exchange Price Difference Monitoring Based on FMZ Quant

Leave a Reply

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