输入/搜索内容
8
关注
1364
关注者
DEX交易所量化实践(3)-- Vertex 协议 使用指南
交流分享
创建于 2025-02-09 16:05:08  更新于 2025-02-19 10:37:07
 0
 1252

img

本篇是关于DEX交易所量化实践的第三篇,这次要介绍Vertex协议的使用指南。

前言

在传统去中心化交易所(DEX)的框架中,量化交易者往往需要妥协:要么接受自动化做市商(AMM)模型的高滑点与低执行效率,要么陷入跨链流动性割裂、衍生品功能单一的技术困局。而Vertex Protocol的横空出世,正在用一场“去中心化+机构级”的融合实验,重新定义量化策略的链上疆域——这里没有“二选一”的难题,只有速度、深度与自由的终极平衡。

作为首个将统一多链流动性池、混合订单簿(CLOB)与嵌入式货币市场整合于一体的DEX,Vertex以“中心化体验,去中心化灵魂”为核心,为量化交易者开辟了一条独特的赛道:

速度与流动性的新定义

随着区块链技术的不断演进,传统中心化交易所(CEX)与去中心化交易所(DEX)之间的界限正逐渐模糊。Vertex Edge 作为 Vertex 平台的中枢,不仅重塑了交易速度和流动性,还通过跨链整合将卓越的订单撮合技术和自我托管优势完美结合,为全球交易者带来全新的 DeFi 体验。

  • 统一跨链流动性:打破流动性碎片
    在传统市场中,不同链之间的流动性常常存在割裂现象,导致交易者无法享受到最佳的成交价格和深度。Vertex Edge 正是在这一背景下应运而生,通过一个统一的订单簿网络,实现了跨越多条链的永久流动性同步共享。
    目前,Vertex Edge 已将永续合约流动性覆盖到包括 Arbitrum、Base、Sei、Blast、Mantle、Sonic 以及 Abstract 在内的 7 条主流链上,使交易者无需再为流动性分散而烦恼,能够以最优的价格进行交易,真正做到全球流动性无缝对接。

  • 混合订单簿交易:极速撮合与链上结算的完美平衡
    Vertex Edge 采用了混合订单簿交易模式,其核心技术包括:

    离链订单簿撮合器:利用超高速的离链撮合机制,实现订单匹配,延迟时间仅在 5-15 毫秒之间,与大多数中心化交易所媲美;
    链上风险引擎与 AMM:在每条支持的链上均部署了风险管理系统和自动化做市商(AMM),确保订单在匹配后能以安全、透明的方式进行结算。
    这种架构不仅保证了交易的极速响应,还通过链上结算为用户提供了去中心化的安全保障,让交易者既享受 CEX 级别的性能,又保持资产自我托管的独立性。

  • 统一跨边际管理:高效利用每一分资本
    Vertex 平台为用户提供了统一的跨边际管理功能,无论是现货、永续合约还是内嵌的资金市场,所有资产和持仓都可整合于一个统一的保证金账户中。
    这种设计让用户可以:

    多账户功能:在单一钱包内管理多账户,更高效地分配资金;
    杠杆现货头寸:利用全部资产作为保证金,实现更高资本效率;
    灵活风险管理:将存款、仓位与盈亏数据统一考量,从而精准调控风险暴露。

连接 Vertex

登录 「vertex protocol」 页面地址:

https://app.vertexprotocol.com/

连接钱包

vertex和大部分DEX一样,登录dapp后都需要连接钱包授权,Vertex的子账户系统是根据一个标签来区分,标签参与钱包地址计算,得出一个子账号钱包地址,同时授权这个地址可以进行下单等操作。

例如使用WalletConnect连接时的钱包地址为:0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c43,默认使用的标签为"default",通过计算得出的子账号地址为:0x7a5ec2748e9065794491a8d29dcf3f9edb8d7c4364656661756c740000000000。默认的标签即:64656661756c740000000000

vertex 支持冲入多种资产,一般选择充值USDC作为保证金,直接使用连接的钱包转账交易。需要注意的是在vertex上提现、划转、发送代币、子账户划转等操作都是会消耗USDC的,并且费用不低,所以这些操作需要谨慎调用。

切换到不同的链:

img

在 vertex 上,不同的链有不同的节点、索引器、链ID等配置信息,FMZ封装时默认的链为Arbitrum One,可以使用Log(HttpQuery("https://gateway.prod.vertexprotocol.com/v1/query?type=contracts"))查询某个链的ID、部署的合约信息等。

在FMZ上配置

登录FMZ.COM之后,在交易所配置页面,选择「加密货币」,选择vertex交易所,可以直接配置dapp上的钱包代理秘钥,当然配置钱包私钥也是可以的,在vertex上可以使用接口管理代理秘钥授权/解除授权等操作,也比较方便。

代理秘钥:

例如在vertex的DEX交易所前端页面:chrome浏览器(打开调试) -> Application -> Local Storage -> https://app.vertex -> vertex userSettings

配置vertex交易所

在FMZ上需要配置两个内容,第一个是钱包地址(不是签名用的代理秘钥的地址,一定要是连接dapp的钱包地址)。第二个是签名用的秘钥(可以是钱包私钥,也可以是代理秘钥)。由于可以使用代理秘钥,所以配置的钱包地址和秘钥并不一定是一对的。

vertex的子账号系统根据标签来识别,在FMZ上默认使用标签为default标签的主子账号,如果需要切换,在代码中可以使用:

javascript
exchange.IO("subAccountTag", "default") // 切换到主子账号 exchange.IO("subAccountTag", "test01") // 切换到标签名为 test01 的子账号

在FMZ上实践

配置好了交易所配置信息,部署一个可以访问到vertex接口的托管者程序,我们就可以开始写点儿代码进行实践操作了。

我们使用主子账户进行测试(tag为:default的子账户),我们使用Arbitrum网络。

行情、链上信息

1、获取合约市场品种信息

javascript
function main() { var markets = exchange.GetMarkets() if (!markets) { throw "get markets error" } var tbl = { type: "table", title: "test markets", cols: [ "key", "Symbol", "BaseAsset", "QuoteAsset", "TickSize", "AmountSize", "PricePrecision", "AmountPrecision", "MinQty", "MaxQty", "MinNotional", "MaxNotional", "CtVal", "CtValCcy" ], rows: [] } for (var symbol in markets) { var market = markets[symbol] tbl.rows.push([ symbol, market.Symbol, market.BaseAsset, market.QuoteAsset, market.TickSize, market.AmountSize, market.PricePrecision, market.AmountPrecision, market.MinQty, market.MaxQty, market.MinNotional, market.MaxNotional, market.CtVal, market.CtValCcy ]) } LogStatus("`" + JSON.stringify(tbl) + "`") return markets }

img

可以看到vertex上的合约品种为USDC本位合约,保证金为USDC,一张合约的价值表示一个对应币种,例如BTC_USDC.swap即BTC的USDC本位合约,一张表示一个BTC的头寸。以上代码展示了如何请求合约市场信息,输出各项内容。

2、获取深度信息(订单薄)

javascript
function main() { var depths = [{"symbol": "ETH_USDC"}, {"symbol": "SOL_USDC"}, {"symbol": "BTC_USDC"}] for (var ele of depths) { ele["depth"] = exchange.GetDepth(ele["symbol"] + ".swap") } var tbls = [] for (var ele of depths) { var tbl = {"type": "table", "title": ele["symbol"], "cols": ["level", "price", "amount"], "rows": []} var depth = ele["depth"] for (var i = 0 ; i < 3 ; i++) { tbl["rows"].push(["卖" + (i + 1), depth.Asks[i].Price, depth.Asks[i].Amount]) } tbl["rows"].reverse() for (var i = 0 ; i < 3 ; i++) { tbl["rows"].push(["买" + (i + 1), depth.Bids[i].Price, depth.Bids[i].Amount]) } tbls.push(tbl) } LogStatus("`" + JSON.stringify(tbls) + "`") }

img

3、市场成交的订单流信息

javascript
function main() { var arrTrades = [{"symbol": "ETH_USDC"}, {"symbol": "SOL_USDC"}, {"symbol": "BTC_USDC"}] for (var ele of arrTrades) { ele["trades"] = exchange.GetTrades(ele["symbol"] + ".swap") } var tbls = [] for (var ele of arrTrades) { var tbl = {"type": "table", "title": ele["symbol"], "cols": ["Time", "Price", "Amount", "side"], "rows": []} var trades = ele["trades"] for (var trade of trades) { tbl["rows"].push([_D(trade.Time), trade.Price, trade.Amount, trade.Type == 0 ? "买入" : "卖出"]) } tbls.push(tbl) } LogStatus("`" + JSON.stringify(tbls) + "`") }

img

4、K线数据

javascript
function main() { let c = KLineChart({ overlay: true }) let bars = exchange.GetRecords("SOL_USDC.swap") if (!bars) { return } bars.forEach(function(bar, index) { c.begin(bar) Log(index, bar) c.close() }) }

vertex 图表
img

FMZ策略运行画图
img

5、资金费率

javascript
function main() { var fundings = exchange.GetFundings() var tbl = { "type": "table", "title": "GetFundings", "cols": ["Symbol", "Interval", "Time", "Rate"], "rows": [], } for (var f of fundings) { tbl["rows"].push([f.Symbol, f.Interval / 3600000, _D(f.Time), f.Rate * 100 + " %"]) } LogStatus(_D(), "\n`" + JSON.stringify(tbl) + "`") }

img

资金费率周期为1小时。

6、查询链上数据,给定账户、品种的最大订单量

Max Order Size
Gets the max order size possible of a given product for a given subaccount.

javascript
function main() { // GET [GATEWAY_REST_ENDPOINT]/query?type=max_order_size&product_id={product_id}&sender={sender}&price_x18={price_x18}&direction={direction} // price_x18=3000000000000000000000 : 3000 USDC // product_id=4 : ETH_USDC.swap // sender=0x123 : e.g. 0x123 return HttpQuery("https://gateway.prod.vertexprotocol.com/query?type=max_order_size&product_id=4&sender=0x123&price_x18=3000000000000000000000&direction=short") }
  • 缩放尺度大部分为:1e18,所以:3000000000000000000000 即 3000 USDC。
  • product_id=4 交易品种ID,4就是以太坊合约, ETH_USDC.swap
  • sender 是钱包地址和子账号tag计算出的子账号地址。

最终请求返回的数据:{"status":"success","data":{"max_order_size":"170536415320344899"},"request_type":"query_max_order_size"}
可知,当前账户可用资产对于价格为3000的以太坊永续合约,卖单订单最大下单量为:0.17 ETH

7、查询钱包授权的代理秘钥信息

Linked Signer
Retrieves current linked signer of a provided subaccount

javascript
function main() { return HttpQuery("https://gateway.prod.vertexprotocol.com/query?type=linked_signer&subaccount=0x123") }

查询到授权的信息:

{"status":"success","data":{"linked_signer":"0x79119..."},"request_type":"query_linked_signer"}
"0x79119..."这个地址就是在vertex前端页面连接钱包时,授权下单交易的代理地址。这个授权可以取消、新增(通过API调用)。

交易

接下来就是本篇的重点了,忙活了半天其实就是为了在去中心化交易所上简单、快捷的进行交易。

1、下单

测试比较简单的交易,下普通的限价单。

javascript
function main() { var id1 = exchange.CreateOrder("ETH_USDC.swap", "buy", 2000, 0.1) var id2 = exchange.CreateOrder("SOL_USDC.swap", "buy", 60, 2) Log("ETH_USDC.swap id1:", id1) Log("SOL_USDC.swap id2:", id2) var orders = exchange.GetOrders("USDC.swap") var tbl = {type: "table", title: "test GetOrders", cols: ["Symbol", "Id", "Price", "Amount", "DealAmount", "AvgPrice", "Status", "Type", "Offset", "ContractType"], rows: []} for (var order of orders) { tbl.rows.push([order.Symbol, order.Id, order.Price, order.Amount, order.DealAmount, order.AvgPrice, order.Status, order.Type, order.Offset, order.ContractType]) } LogStatus("`" + JSON.stringify(tbl) + "`") }

img

2、撤单

javascript
function main() { var orders = exchange.GetOrders("USDC.swap") var tbl = {type: "table", title: "test GetOrders", cols: ["Symbol", "Id", "Price", "Amount", "DealAmount", "AvgPrice", "Status", "Type", "Offset", "ContractType"], rows: []} for (var order of orders) { tbl.rows.push([order.Symbol, order.Id, order.Price, order.Amount, order.DealAmount, order.AvgPrice, order.Status, order.Type, order.Offset, order.ContractType]) exchange.CancelOrder(order.Id) } LogStatus("`" + JSON.stringify(tbl) + "`") return exchange.GetOrders() }

img

3、查询持仓

javascript
function main() { // 使用市价单下单持仓 exchange.SetCurrency("ETH_USDC") exchange.SetContractType("swap") exchange.Buy(-1, 0.01) var positions = exchange.GetPositions() var tbl = {type: "table", title: "test GetPosition/GetPositions", cols: ["Symbol", "Amount", "Price", "FrozenAmount", "Type", "Profit", "Margin", "ContractType", "MarginLevel"], rows: []} for (var p of positions) { tbl.rows.push([p.Symbol, p.Amount, p.Price, p.FrozenAmount, p.Type, p.Profit, p.Margin, p.ContractType, p.MarginLevel]) } LogStatus("`" + JSON.stringify(tbl) + "`") }

img

img

需要注意的是,vertex API给出的数据与vertex 前端页面上显示的内容有偏差,主要是持仓均价、持仓盈亏有差异,已经反馈给vertex团队,后续可能会升级更新。

4、Trigger Order

由于 vertex 的 Trigger Order 是独立的endPoint,所以在使用exchange.IO函数下条件单时,需要指明Trigger: https://trigger.prod.vertexprotocol.com,接下来让我们继续实践操作。

  • nonce : 对于下 Trigger Order 需要传入一个 nonce 参数,vertex 有计算要求,所以为了使用方便,在封装时设计了一个方式用于获取计算生成的nonce。
  • expiration : 对于expiration参数也是同样的需求,也需要获取计算生成的expiration。
javascript
function main() { // isTrigger : true var nonce = exchange.IO("nonce", true) // 如果是 Trigger Order 订单用到的 nonce 需要指定 isTrigger : true // flag , reduceOnly var expiration = exchange.IO("expiration", "GTC", false) // 设置订单为GTC类型,非只减仓 // params var params = { "place_order": { "product_id": 4, "order": { "sender": "0x123...", "priceX18": "4100000000000000000000", "amount": "-100000000000000000", "expiration": expiration, "nonce": nonce }, "trigger": { "price_above": "4000000000000000000000" } } } return exchange.IO("api", "POST", "https://trigger.prod.vertexprotocol.com/v1/execute", "", JSON.stringify(params)) }

img

在trigger端点下,还有:

  • List Trigger Orders 接口
  • Cancel Trigger Orders
  • Cancel Product Trigger Orders

调用方式与Place Trigger Order类似,这里就不再赘述。

子账户管理

1、切换子账号tag

默认的子账号tag为:default,我们切换为一个自定义的tag:subAcc02

javascript
function main() { exchange.IO("subAccountTag", "subAcc02") return exchange.GetAccount() }

img

img

当向子账号地址转账时,vertex才会真正创建这个子账号。

2、向vertex子账号转账

需要查询账户的nonce,作为参数传入转账接口的参数中。

https://gateway.prod.vertexprotocol.com/v1/query?type=nonces&address=0x6B3f11d807809B0b1E5e3243df04a280d9F94bF4

javascript
function main() { var ret = HttpQuery("https://gateway.prod.vertexprotocol.com/v1/query?type=nonces&address=0x123...") var obj = JSON.parse(ret) var nonce = obj["data"]["tx_nonce"] Log("nonce:", nonce) var params = { "transfer_quote": { "tx": { // default -> subAcc02 "sender": "0xabc...", // default "recipient": "0xdef...", // subAcc02 "amount": "7000000000000000000", "nonce": nonce } } } return exchange.IO("api", "POST", "https://gateway.prod.vertexprotocol.com/v1/execute", "", JSON.stringify(params)) }

例如:0xabc... 对应的是tag为default的子账号地址。
0xdef... 对应的是tag为subAcc02的子账号地址。
0x123... 为钱包地址。

提币、铸造/销毁流动性代币等

1、从vertex提币到钱包

javascript
function main() { var ret = HttpQuery("https://gateway.prod.vertexprotocol.com/v1/query?type=nonces&address=0x123...") var obj = JSON.parse(ret) var nonce = obj["data"]["tx_nonce"] Log("nonce:", nonce) var params = { "withdraw_collateral": { "tx": { "sender": "0xabc...", // default "productId": 0, // USDC : 0 , precision : 6 "amount": "10000000", // 10 USDC "nonce": nonce } } } return exchange.IO("api", "POST", "https://gateway.prod.vertexprotocol.com/v1/execute", "", JSON.stringify(params)) }

注意USDC的精度。

2、铸造LP

javascript
function main() { var ret = HttpQuery("https://gateway.prod.vertexprotocol.com/v1/query?type=nonces&address=0x123...") var obj = JSON.parse(ret) var nonce = obj["data"]["tx_nonce"] Log("nonce:", nonce) var params = { "mint_lp": { "tx": { "sender": "0xabc...", // default "productId": 31, // USDT_USDC "amountBase": "10000000000000000000", "quoteAmountLow": "9999900000000000000", "quoteAmountHigh": "10100000000000000000", "nonce": nonce, } } } return exchange.IO("api", "POST", "https://gateway.prod.vertexprotocol.com/v1/execute", "", JSON.stringify(params)) }

铸造LP代币,给交易对USDT_USDC的交换池添加了流动性。

3、销毁LP

javascript
function main() { var ret = HttpQuery("https://gateway.prod.vertexprotocol.com/v1/query?type=nonces&address=0x123...") var obj = JSON.parse(ret) var nonce = obj["data"]["tx_nonce"] Log("nonce:", nonce) var params = { "burn_lp": { "tx": { "sender": "0xabc...", // default "productId": 31, // USDT_USDC "amount": "7500000000000000000", "nonce": nonce, } } } return exchange.IO("api", "POST", "https://gateway.prod.vertexprotocol.com/v1/execute", "", JSON.stringify(params)) }

Websocket接口

Websocket接口端点:wss://gateway.prod.vertexprotocol.com/v1/subscribe

vertex 的 websocket接口需要启用压缩,所以在使用Dial函数创建websocket连接时,需要指定:enableCompression=true

1、订阅公共接口

javascript
var ws = null function main() { var params = { "method": "subscribe", "stream": { "type": "book_depth", "product_id": 4 }, "id": 0 } ws = Dial("wss://gateway.prod.vertexprotocol.com/v1/subscribe|enableCompression=true") if (!ws) { Log("error") return } ws.write(JSON.stringify(params)) for (var i = 0 ; i < 10 ; i++) { var ret = ws.read() if (ret) { Log(ret) } } } function onexit() { ws.close() Log("close ws") }

img

2、订阅私有接口验证

因为私有接口需要验证签名,所以增加了一个计算签名的函数exchange.IO("signature", strPayload)

javascript
var ws = null function main() { // authenticate var expiration = new Date().getTime() + 1000 * 15 var params = { "method": "authenticate", "id": 0, "tx": { "sender": "0x123...", "expiration": String(expiration) } } var signature = exchange.IO("signature", JSON.stringify(params)) Log("signature:", signature) params["signature"] = signature ws = Dial("wss://gateway.prod.vertexprotocol.com/v1/subscribe|enableCompression=true") if (!ws) { Log("error") return } ws.write(JSON.stringify(params)) var ret = ws.read() Log(ret) // order_update var orderUpdateParams = { "method": "subscribe", "stream": { "type": "order_update", "subaccount": "0x123...", "product_id": 4 }, "id": 0 } ws.write(JSON.stringify(orderUpdateParams)) var id = exchange.CreateOrder("ETH_USDC.swap", "buy", 2300, 0.1) Log("id:", id) for (var i = 0 ; i < 10 ; i++) { var ret = ws.read(1000) if (ret) { Log(ret) } } // list var listParams = { "method": "list", "id": 0 } ws.write(JSON.stringify(listParams)) ret = ws.read() Log(ret) } function onexit() { ws.close() Log("close ws") }

img

常用功能封装

  • exchange.IO("nonce", isTrigger) : 计算nonce参数,例如Trigger订单接口需要该参数。
  • exchange.IO("expiration", flag, reduceOnly) : 计算expiration参数,例如Trigger订单接口需要该参数。
  • exchange.IO("indexerBase", url) : 切换索引器端点url。
  • exchange.IO("address", walletAddress) : 切换钱包地址。
  • exchange.IO("privateKey", key) : 切换签名所用的秘钥,可以设置为钱包私钥、代理秘钥(相对更安全)。
  • exchange.IO("subAccountTag", tag) : 切换子账户标签。
  • exchange.IO("signature", strPayload) : 计算签名。

vertex现货交易所对象特有的功能切换:

  • exchange.IO("trade_margin") : 启用现货交易市场的Margin Spot
  • exchange.IO("trade_normal") : 关闭现货交易市场的Margin Spot

现货支持

在FMZ上不仅封装了vertex永续合约交易市场,也封装了vertex现货交易市场。vertex现货的封装与vertex合约一致,在FMZ平台上添加vertex现货交易所对象即可使用。

  • 支持现货杠杆,使用exchange.IO("trade_margin")开启。
  • vertex自动借币

策略实践

接下来我们在FMZ平台仅用不到40行代码来设计一个简单的策略,参数越少普适性越强。策略用到了「画线类库」,用来画图。

策略代码:

javascript
function printProfit() { var account = _C(exchange.GetAccount) LogProfit(account.Balance) } function main() { var initPrice = -1 while (true) { // tick var t = _C(exchange.GetTicker, symbol) var nowPrice = t.Last if (nowPrice <= 0) { Log("ticker:", t) Sleep(10000) continue } if (initPrice < 0) { initPrice = nowPrice } // pos var pos = _C(exchange.GetPositions, symbol) // assets var assets = _C(exchange.GetAssets) var rate = (nowPrice - initPrice) / initPrice if (rate < -ratio) { var id = exchange.CreateOrder(symbol, "buy", -1, amount) Log("open long, id:", id) initPrice = nowPrice } else if (rate > ratio) { var id = exchange.CreateOrder(symbol, "sell", -1, amount) Log("open short, id:", id) initPrice = nowPrice } $.PlotLine("price", nowPrice) // 使用画线类库,需要在策略模板栏勾选「画线类库」来画图 LogStatus(_D(), ", initPrice:", initPrice, ", rate:", rate, "\n", pos, "\n", assets) Sleep(1000 * 60) } }

策略参数:
img

策略回测:
img
img
img
img
img

vertex 合约上默认的是单向持仓,我们直接上实盘跑跑看。

img
img

半夜策略自己开仓了,早上一看已经浮盈了(开心)。

img

策略仅供教学、测试、学习,实盘慎用。

END

以上测试,基于最新的托管者,需要下载最新的托管者才支持 Vertex DEX 聚合器。

感谢支持,感谢您的阅读。

相关推荐
评论
全部评论 (0)
暂无数据
暂无数据
  • 1
iPhone 下载
社区
回测系统
© 2015 - ∞ INVENTOR PTE LTD (SG)