The resource loading... loading...

DEX exchange quantitative practice ((1) -- dYdX v4 user guide

Author: Inventors quantify - small dreams, Created: 2024-12-24 17:09:32, Updated: 2024-12-26 21:41:46

[TOC]

img

The Foreword

With the rapid rise of decentralized exchanges (DEX) in the field of cryptocurrency trading, quantitative traders are gradually turning to these platforms for efficient automated trading. As one of the most popular decentralized trading platforms, dYdX offers powerful trading capabilities, support for futures perpetual contract trading, and its latest version v4 has also optimized performance and user experience, making it the preferred choice of many quantitative traders.

This article describes how to quantify trading practices on dYdX v4, including how to use its API to trade, access market data, and manage accounts.

  • Test environment switching
  • Search for market information
  • Order information, information about stocks
  • Subscribe
  • Manage child accounts
  • Requested node method

dYdX v4 DEX

  • dYdX testing web app page

    img

  • anddYdX v3In the same way, trading creates rewards, rewardsdYdXThe token.

    img

Wallet connections, login and configuration information

The previous dYdX v3 protocol DEX exchange has gone offline, and the current dYdX v4 App address is:

https://dydx.trade/trade/ETH-USD

After opening the app page, there is a button to connect the wallet in the upper right corner, and a scan code to connect the wallet.

If you want to get familiar with testing the test net environment first, you can use the test net:

https://v4.testnet.dydx.exchange/trade/ETH-USD

Also, click on the button to connect the wallet in the upper right corner, scan the wallet to connect the wallet, confirm the signature. The wallet will automatically generate a dydx v4 address after the successful connection. The app page will display this address in the upper right corner, and a menu will pop up after clicking.

  • dYdX v4 account address The dYdX v4 account address is derived from the wallet address, and the dYdX v4 account address looks like this:dydx1xxxxxxxxxxxxxxxxxxxxq2ge5jr4nzfeljxxxx, is the address where dydx1 starts. This address can be queried in blockchain explorers.

  • Helpful words You can use the "Export Password" button in the top right corner of the menu to export the password of the current dYdX address account. You need to configure this password when adding an exchange to the FMZ platform.

    The script can be configured directly on the FMZ platform, or it can be stored locally by the administrator. When using the dydx v4 exchange object, it reads the file contents of the script, which will be demonstrated in the practical part of this article.

Differences between home and test networks

The test network environment differs in some ways from the main network environment, a few of which are briefly listed below.

  • Sub-account assets are split. The main network has a sub-account cleaning mechanism.subAccountNumber >= 128If the sub-account of the ID is not held, the assets are automatically cleared to the sub-account with sub-Account Number 0. In the test, it was found that the test net did not have such a mechanism (or the trigger conditions were different and the test net did not have a trigger).
  • Some of the names of the tokens. Dydx is the name of the native token.DYDXThe test netDv4TNT
  • Address configuration, such as chain ID, node address, index address, etc.; There are a lot of nodes and configurations, here is one of them:
    • The main website: The index address is:https://indexer.dydx.tradeThe chain ID:dydx-mainnet-1The REST node:https://dydx-dao-api.polkachu.com:443

    • The test net: The index address is:https://indexer.v4testnet.dydx.exchangeThe chain ID:dydx-testnet-4The REST node:https://dydx-testnet-api.polkachu.com

dYdX v4 protocol architecture

The dYdX v4 protocol is based on the cosmos ecosystem development.

  • The index is responsible for queries such as market information, account information and so on.
  • Didyx is a blockchain that allows you to send orders, withdrawals, transfers, etc.

The index

The indexing service provides the REST protocol and the Websocket protocol.

  • The REST protocol The REST protocol interface supports market information queries, account information, stock information, order information, and other queries, which are packaged as a platform-unified API interface on the FMZ platform.

  • The WebSocket protocol The FMZ platform allows users to create Websocket connections, subscription markets and other information using the Dial function.

Note that the index of dydx v4 is the same as all centralized transactions, the data updates are not as timely, for example sometimes immediately after placing an order, it may not be possible to query the order.Sleep(n)I'll be back in a few seconds.

Here is an example of using the Dial function to create Websocket API connections and subscribe to order thin data:

function dYdXIndexerWSconnManager(streamingPoint) {
    var self = {}
    self.base = streamingPoint
    self.wsThread = null

    // 订阅
    self.CreateWsThread = function (msgSubscribe) {
        self.wsThread = threading.Thread(function (streamingPoint, msgSubscribe) {
            // 订单薄
            var orderBook = null 

            // 更新订单薄
            var updateOrderbook = function(orderbook, update) {
                // 更新 bids
                if (update.bids) {
                    update.bids.forEach(([price, size]) => {
                        const priceFloat = parseFloat(price)
                        const sizeFloat = parseFloat(size)

                        if (sizeFloat === 0) {
                            // 删除价格为 price 的买单
                            orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
                        } else {
                            // 更新或新增买单
                            orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
                            orderbook.bids.push({price: price, size: size})
                            // 按价格降序排序
                            orderbook.bids.sort((a, b) => parseFloat(b.price) - parseFloat(a.price))
                        }
                    })
                }

                // 更新 asks
                if (update.asks) {
                    update.asks.forEach(([price, size]) => {
                        const priceFloat = parseFloat(price)
                        const sizeFloat = parseFloat(size)

                        if (sizeFloat === 0) {
                            // 删除价格为 price 的卖单
                            orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
                        } else {
                            // 更新或新增卖单
                            orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
                            orderbook.asks.push({price: price, size: size})
                            // 按价格升序排序
                            orderbook.asks.sort((a, b) => parseFloat(a.price) - parseFloat(b.price))
                        }
                    })
                }

                return orderbook
            }

            var conn = Dial(`${streamingPoint}|reconnect=true&payload=${JSON.stringify(msgSubscribe)}`)
            if (!conn) {
                Log("createWsThread failed.")
                return
            }
            while (true) {
                var data = conn.read()
                if (data) {
                    var msg = null                    
                    try {
                        msg = JSON.parse(data)
                        if (msg["type"] == "subscribed") {
                            orderBook = msg["contents"]
                            threading.currentThread().postMessage(orderBook)
                        } else if (msg["type"] == "channel_data") {
                            orderBook = updateOrderbook(orderBook, msg["contents"])
                            threading.currentThread().postMessage(orderBook)
                        }
                    } catch (e) {
                        Log("e.name:", e.name, "e.stack:", e.stack, "e.message:", e.message)
                    }
                }
            }
        }, streamingPoint, msgSubscribe)
    }

    // 监听
    self.Peek = function () {
        return self.wsThread.peekMessage()
    }

    return self
}

function main() {
    // real : wss://indexer.dydx.trade/v4/ws
    // simulate : wss://indexer.v4testnet.dydx.exchange/v4/ws

    var symbol = "ETH-USD"
    var manager = dYdXIndexerWSconnManager("wss://indexer.dydx.trade/v4/ws")
    manager.CreateWsThread({"type": "subscribe", "channel": "v4_orderbook", "id": symbol})

    var redCode = "#FF0000"
    var greenCode = "#006400"
    while (true) {
        var depthTbl = {type: "table", title: symbol + " / depth", cols: ["level", "price", "amount"], rows: []}
        var depth = manager.Peek()
        if (depth) {
            for (var i = 0; i < depth.asks.length; i++) {
                if (i > 9) {
                    break
                }
                var ask = depth.asks[i]
                depthTbl.rows.push(["asks " + (i + 1) + greenCode, ask.price + greenCode, ask.size + greenCode])
            }
            depthTbl.rows.reverse()

            for (var i = 0; i < depth.bids.length; i++) {
                if (i > 9) {
                    break
                }
                var bid = depth.bids[i]
                depthTbl.rows.push(["bids " + (i + 1) + redCode, bid.price + redCode, bid.size + redCode])
            }
        }
        LogStatus(_D(), "\n`" + JSON.stringify(depthTbl) + "`")
    }
}

dYdX chain node news broadcast

The most commonly used in transactions are order messages, withdrawal messages, and transfer messages.

  • Order news summary

    {
      "@type": "/dydxprotocol.clob.MsgPlaceOrder",
      "order": {
        "orderId": {
          "subaccountId": {
            "owner": "xxx"
          },
          "clientId": xxx,
          "orderFlags": 64,
          "clobPairId": 1
        },
        "side": "SIDE_BUY",
        "quantums": "2000000",
        "subticks": "3500000000",
        "goodTilBlockTime": 1742295981
      }
    }
    
    • List of restricted prices: The order flags used in order flags for ordering on the FMZ platform are:ORDER_FLAGS_LONG_TERM = 64 # 长期订单The longest order validity is 90 days (all types of orders on Dydx v4 are valid).

    • The price list: The order flags used for ordering orders at the market price on the FMZ platform are:ORDER_FLAGS_SHORT_TERM = 0 # 短期订单The following is a list of the most commonly used Dydx protocols:

      // Recommend set to oracle price - 5% or lower for SELL, oracle price + 5% for BUY

      Since it is not a true market order, the predictor price is used, plus a 5% discount to the slippage price as the market order. The expiration setting of short orders is also different from that of long orders, short orders use a block high expiration date, which is set to the current block + 10 block height, as recommended by dydx v4.

    • Ordered ID: Since the ordering operation is performed directly on the chain, there is no index-generated order ID after the message broadcast, and it is not possible to use the index order as the return value of the platform ordering function. In order to ensure the uniqueness of the order ID and the accuracy of the order query, the returned order ID consists of the following information:

      • The deal.
      • dydx current account address
      • Sub-account number
      • clientId (randomly generated)
      • clobPairId (identifier of the transaction type)
      • orderFlags
      • goodTilData ((milliseconds))
  • Summary of the recall

    {
      "@type": "/dydxprotocol.clob.MsgCancelOrder",
      "orderId": {
        "subaccountId": {
          "owner": "xxx"
        },
        "clientId": 2585872024,
        "orderFlags": 64,
        "clobPairId": 1
      },
      "goodTilBlockTime": 1742295981
    }
    

    The order ID is returned from the FMZ platform.

  • Summary of transfer news

    {
      "@type": "/dydxprotocol.sending.MsgCreateTransfer",
      "transfer": {
        "sender": {
          "owner": "xxx"
        },
        "recipient": {
          "owner": "xxx",
          "number": 128
        },
        "amount": "10000000"
      }
    }
    

    Many sub-accounts can be created under the current Dydx v4 address, with sub-AccountNumber 0 being the first sub-account to be automatically created, sub-AccountNumber higher than or equal to 128 being the sub-account ID used for transaction of stock varieties, with a minimum asset requirement of 20 USD. For example, it can be used from subAccountNumber 0 -> 128 or from subAccountNumber 128 -> 0. Shifting requires consuming Gas Fee. Gas Fee can be used with USD/dydx tokens.

FMZ platform dYdX v4 practice

The above briefly explains some of the details of the package, next we will practice the specific use, here using the dYdX v4 test network for a demonstration, the test network is basically consistent with the main network, and there is an automatic faucet can take the test assets, the host deployment operation is no longer described, create a real-world test on FMZ.

1 and configuration

After successfully connecting to the dYdX v4 App using the cryptocurrency wallet (I'm using the imToken wallet here), take the test asset and export the current dYdX v4 account (derived from the wallet) to the backup.

img

Configure the auxiliary word on the FMZ platform, where we configure it using the local file method (you can also fill in and configure it directly to the platform, the auxiliary word is on the post-encrypted configuration, not the plaintext).

  • 助记词文件:mnemonic.txt

    img

    It can be placed in the Directory of the Disk ID folder under the Administrator directory, and of course it can also be placed in other directories (specific paths are required when configuring).

  • Configure the exchange on FMZ

    https://www.fmz.com/m/platforms/add

    In the edit box, fill in the following:file:///mnemonic.txtThe actual path is:托管者所在目录/logs/storage/594291

    img

2, switch to dydx v4 testing network

function main() {
    // 切换测试链的索引器地址
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // 切换测试链的ChainId 
    exchange.IO("chainId", "dydx-testnet-4")

    // 切换测试链的REST节点地址
    exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")

    // 读取账户信息测试
    Log(exchange.GetAccount()) 
}

Read on for the test net account information:

{
	"Info": {
		"subaccounts": [{
			"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
			"subaccountNumber": 0,
			"equity": "300.386228",
			"latestProcessedBlockHeight": "28193227",
			"freeCollateral": "300.386228",
			"openPerpetualPositions": {},
			"assetPositions": {
				"USDC": {
					"subaccountNumber": 0,
					"size": "300.386228",
					"symbol": "USDC",
					"side": "LONG",
					"assetId": "0"
				}
			},
			"marginEnabled": true,
			"updatedAtHeight": "28063818"
		}, {
			"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
			"equity": "0",
			"freeCollateral": "0",
			"openPerpetualPositions": {},
			"marginEnabled": true,
			"subaccountNumber": 1,
			"assetPositions": {},
			"updatedAtHeight": "27770289",
			"latestProcessedBlockHeight": "28193227"
		}, {
			"equity": "0",
			"openPerpetualPositions": {},
			"marginEnabled": true,
			"updatedAtHeight": "28063818",
			"latestProcessedBlockHeight": "28193227",
			"subaccountNumber": 128,
			"freeCollateral": "0",
			"assetPositions": {},
			"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez"
		}],
		"totalTradingRewards": "0.021744179376211564"
	},
	"Stocks": 0,
	"FrozenStocks": 0,
	"Balance": 300.386228,
	"FrozenBalance": 0,
	"Equity": 300.386228,
	"UPnL": 0
}

3 Market information searches

No switching to the test network, test with the main network

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"], 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])
    }
    LogStatus("`" + JSON.stringify(tbl) +  "`")
}

img

4 and below

function main() {
    // 切换测试链的索引器地址
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // 切换测试链的ChainId 
    exchange.IO("chainId", "dydx-testnet-4")

    // 切换测试链的REST节点地址
    exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")

    // 限价单,挂单
    var idSell = exchange.CreateOrder("ETH_USD.swap", "sell", 4000, 0.002)
    var idBuy = exchange.CreateOrder("ETH_USD.swap", "buy", 3000, 0.003)

    // 市价单
    var idMarket = exchange.CreateOrder("ETH_USD.swap", "buy", -1, 0.01)

    Log("idSell:", idSell)
    Log("idBuy:", idBuy)
    Log("idMarket:", idMarket)
}

img

The dYdX v4 app page:

img

5 Order information

The test net hangs two orders in advance, the test takes the current hanging order and cancels the order.

function main() {    
    // 切换测试链的索引器地址
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // 切换测试链的ChainId 
    exchange.IO("chainId", "dydx-testnet-4")

    // 切换测试链的REST节点地址
    exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")

    var orders = exchange.GetOrders()
    Log("orders:", orders)
    for (var order of orders) {
        exchange.CancelOrder(order.Id, order)
        Sleep(2000)
    }

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

img

6 Holding information inquiry

function main() {
    // 切换测试链的索引器地址
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // 切换测试链的ChainId 
    exchange.IO("chainId", "dydx-testnet-4")

    // 切换测试链的REST节点地址
    exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")

    var p1 = exchange.GetPositions("USD.swap")
    var p2 = exchange.GetPositions("ETH_USD.swap")
    var p3 = exchange.GetPositions()
    var p4 = exchange.GetPositions("SOL_USD.swap")

    var tbls = []
    for (var positions of [p1, p2, p3, p4]) {
        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])
        } 
        tbls.push(tbl)
    }

    LogStatus("`" + JSON.stringify(tbls) +  "`")
}

img

7, Sub-account management

function main() {
    // 切换测试链的索引器地址
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // 切换测试链的ChainId 
    exchange.IO("chainId", "dydx-testnet-4")

    // 切换测试链的REST节点地址
    exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")

    // subAccountNumber 0 -> 128 : 20 USDC , Gas Fee 为 adv4tnt 即 dydx token
    var ret = exchange.IO("transferUSDCToSubaccount", 0, 128, "adv4tnt", 20)  
    Log("ret:", ret)

    // 切换到子账号subAccountNumber 128 ,读取账户信息检查
    exchange.IO("subAccountNumber", 128)

    var account = exchange.GetAccount()
    Log("account:", account)
}

img

Switching to subAccountNumber for 128 subaccounts, the data returned by GetAccount:

{
	"Info": {
		"subaccounts": [{
			"subaccountNumber": 0,
			"assetPositions": {
				"USDC": {
					"size": "245.696892",
					"symbol": "USDC",
					"side": "LONG",
					"assetId": "0",
					"subaccountNumber": 0
				}
			},
			"updatedAtHeight": "28194977",
			"latestProcessedBlockHeight": "28195008",
			"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
			"freeCollateral": "279.5022142346",
			"openPerpetualPositions": {
				"ETH-USD": {
					"closedAt": null,
					"size": "0.01",
					"maxSize": "0.01",
					"exitPrice": null,
					"unrealizedPnl": "-0.17677323",
					"subaccountNumber": 0,
					"status": "OPEN",
					"createdAt": "2024-12-26T03:36:09.264Z",
					"createdAtHeight": "28194494",
					"sumClose": "0",
					"netFunding": "0",
					"market": "ETH-USD",
					"side": "LONG",
					"entryPrice": "3467.2",
					"realizedPnl": "0",
					"sumOpen": "0.01"
				}
			},
			"marginEnabled": true,
			"equity": "280.19211877"
		}, {
			"openPerpetualPositions": {},
			"assetPositions": {},
			"marginEnabled": true,
			"latestProcessedBlockHeight": "28195008",
			"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
			"subaccountNumber": 1,
			"equity": "0",
			"freeCollateral": "0",
			"updatedAtHeight": "27770289"
		}, {
			"openPerpetualPositions": {},
			"updatedAtHeight": "28194977",
			"latestProcessedBlockHeight": "28195008",
			"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
			"subaccountNumber": 128,
			"assetPositions": {
				"USDC": {
					"assetId": "0",
					"subaccountNumber": 128,
					"size": "20",
					"symbol": "USDC",
					"side": "LONG"
				}
			},
			"marginEnabled": true,
			"equity": "20",
			"freeCollateral": "20"
		}],
		"totalTradingRewards": "0.021886899964446858"
	},
	"Stocks": 0,
	"FrozenStocks": 0,
	"Balance": 20,
	"FrozenBalance": 0,
	"Equity": 20,
	"UPnL": 0
}

You can see that the sub-account number is 128, which is converted into 20 USD.

8 access TxHash and call the REST node interface

By order, get TxHash, test the method of IO calling REST nodes

How to get the TxHash of the order, the exchange object dydx caches the TxHash, which can be queried with the Order ID. However, after the policy is stopped, the cache of the order tx hash map will be empty.

function main() {
    // 切换测试链的索引器地址
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // 切换测试链的ChainId 
    exchange.IO("chainId", "dydx-testnet-4")

    // 切换测试链的REST节点地址
    exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")

    var id1 = exchange.CreateOrder("ETH_USD.swap", "buy", 3000, 0.002)
    var hash1 = exchange.IO("getTxHash", id1)
    Log("id1:", id1, "hash1:", hash1)

    var id2 = exchange.CreateOrder("ETH_USD.swap", "buy", 2900, 0.003)
    var hash2 = exchange.IO("getTxHash", id2)
    Log("id2:", id2, "hash2:", hash2)
    
    // 清空映射表可以使用:exchange.IO("getTxHash", "")
    var arr = [hash1, hash2]
    
    Sleep(10000)
    for (var txHash of arr) {
        // GET https://docs.cosmos.network   /cosmos/tx/v1beta1/txs/{hash}
        var ret = exchange.IO("api", "GET", "/cosmos/tx/v1beta1/txs/" + txHash)
        Log("ret:", ret)
    }
}

img

This is a message that was requested by TxHash:

var ret = exchange.IO("api", “GET”, “/cosmos/tx/v1beta1/txs/” + txHash)

The content is too long, and a selected section shows:

{
	"tx_response": {
		"codespace": "",
		"code": 0,
		"logs": [],
		"info": "",
		"height": "28195603",
		"data": "xxx",
		"raw_log": "",
		"gas_wanted": "-1",
		"gas_used": "0",
		"tx": {
			"@type": "/cosmos.tx.v1beta1.Tx",
			"body": {
				"messages": [{
					"@type": "/dydxprotocol.clob.MsgPlaceOrder",
					"order": {
						"good_til_block_time": 1742961542,
						"condition_type": "CONDITION_TYPE_UNSPECIFIED",
						"order_id": {
							"clob_pair_id": 1,
							"subaccount_id": {
								"owner": "xxx",
								"number": 0
							},
							"client_id": 2999181974,
							"order_flags": 64
						},
						"side": "SIDE_BUY",
						"quantums": "3000000",
						"client_metadata": 0,
						"conditional_order_trigger_subticks": "0",
						"subticks": "2900000000",
						"time_in_force": "TIME_IN_FORCE_UNSPECIFIED",
						"reduce_only": false
					}
				}],
				"memo": "FMZ",
				"timeout_height": "0",
				"extension_options": [],
				"non_critical_extension_options": []
			},
      ...

THE END

The above test, based on the latest host, requires the download of the latest host to support dYdX v4 DEX

Thank you for your support, thank you for reading.


More