4
关注
1111
关注者

基于FMZ量化的DEX-CEX交易所差价监控设计与实现

创建于: 2025-02-21 10:40:52, 更新于: 2025-02-21 13:53:00
comments   0
hits   354

基于FMZ量化的DEX-CEX交易所差价监控设计与实现

在FMZ上陆续已经封装接入了dydx_v4、hyperliquid、vertex、aevo几家DEX交易所,在中心化交易所差价套利竞争日益激烈的当下,已经不少量化交易者把目光投向了去中心化交易所,本篇我们就来一起探讨DEX-CEX之间的差价监控的设计与实现。

对冲套利这种策略的第一步就是统计目标组合的价差,观察分析是否有交易机会。所以设计、实现一个差价监控策略就是第一项基础工作,我们的设计需求有:

  • 编程语言使用Javascript。
  • 使用封装的REST接口。
  • DEX选取:hyperliquid、vertex。
  • CEX选取:binance、bybit。
  • 请求订单薄数据采取多线程并发请求。
  • 测试品种尽量选取各个交易所共有的主流品种:ETH、BTC现货交易对/永续合约
  • 尽量简化设计、用简单易懂的代码给出基本实现。

代码实现

代码不到200行,设计的功能仅仅是计算某个品种在不同交易所的实时差价。策略运行初始时会将配置给策略的所有交易所对象分类,分为DEX组和CEX组。每次轮询通过FMZ平台封装的多线程函数Thread,并发请求REST接口:订单薄接口GetDepth,记录请求到的所需品种的买单列表、卖单列表数据。然后根据DEX组和CEX组两两组合成差价组合(DEX-CEX组合,即套利对),进而计算价差。

交易所类型判定: 在初始化时会对添加的交易所对象判断,判断是现货还是期货。

在不同交易所可能对于某一个标的物的命名有差异,所以需要对于品种名称进行调整: 程序需要根据不同交易所对符号的命名规则进行调整。例如,Vertex合约的交易对命名均为:XXX_USD,实际是USDC本位的永续合约。Vertex的现货中ETH的名称为WETH。

获取精度: 初始化时获取全市场信息,根据请求的具体symbol,可以查询到对应精度用于后续数据的精度处理操作。

数据请求 在所有深度数据都获取完之前,程序会等待,并不断检查是否有交易所的数据尚未更新。如果数据尚未获取到,程序会休眠 100 毫秒。 请求到的订单薄数据,记录在一个threading.Dict()创建的对象中(用于和并发线程交互),每次轮询结束都重置数据。

总结 这个策略的实现展示了如何实时监控多个交易所的价格差异,计算出可能的套利机会。通过合理的符号校正、深度数据抓取、精度控制以及多线程操作,系统能够高效地进行实时差价监控。对于策略学习者来说,理解此代码的实现思路可以帮助掌握如何使用API获取交易数据、如何处理多交易所的数据、如何计算和输出交易差价,并如何在实际交易中应用这些技术。

let symbolList = []

function createEx(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"
    } else if (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]
            }
        } else if (self.name == "Hyperliquid") {
            let correctList = {"BTC_USDC": "UBTC_USDC"}
            if (typeof(correctList[symbol]) != "undefined") {
                return correctList[symbol]
            }
        } else if (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
}

function createManager(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 = new Date().getTime()
        for (let e of self.exchanges) {
            for (let symbol of self.symbolList) {
                e.reqDepth(symbol)
            }
        }

        while (true) {
            let isWait = false
            for (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 + "差价", "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)

                    // 输出信息、观察测试
                    Log(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 = new Date().getTime()
        return [tbls, (endTimestamp - beginTimestamp) + "毫秒"]
    }

    self.initHedgePair()
    return self
}

function main() {
    LogReset(1)
    let loopCount = 0

    symbolList = strSymbolList.split(",")
    let m = createManager(symbolList, exchanges)
    while (true) {
        let ret = m.calcHedgeData()
        loopCount++
        LogStatus(_D(), "耗时:", ret[1], ", 轮询次数:", loopCount, "\n", "`" + JSON.stringify(ret[0]) + "`")
        Sleep(1000)
    }
}

参数设计:

基于FMZ量化的DEX-CEX交易所差价监控设计与实现

现货市场

监控一个现货品种:

  • BTC_USDC 比特币兑USDC现货

基于FMZ量化的DEX-CEX交易所差价监控设计与实现

合约市场

监控两个品种:

  • ETH_USDC.swap 以太坊永续合约
  • BTC_USDC.swap 比特币永续合约

基于FMZ量化的DEX-CEX交易所差价监控设计与实现

END

扩展方向: - 阈值监控、封装交易逻辑。 - 手续费、成本计算,计算合理对冲差价区间。 - 使用websocket接口获取行情数据。

FMZ平台将持续增强对去中心化交易所(DEX)和去中心化金融(DeFi)的技术支持,不断迭代更新功能和产品。

感谢您的阅读。

更多内容