[TOC]
Dengan kebangkitan pesat pertukaran terdesentralisasi (DEX) dalam ekosistem perdagangan kripto, pedagang kuantitatif mula beralih ke platform ini untuk melaksanakan perdagangan automatik yang efisien. dYdX sebagai salah satu platform perdagangan terdesentralisasi paling popular menyediakan fungsi perdagangan canggih termasuk kontrak perpetus, dengan versi terkini v4 yang mengoptimumkan prestasi dan pengalaman pengguna menjadi pilihan utama pedagang kuantitatif.
Artikel ini akan membincangkan praktikal perdagangan kuantitatif di dYdX v4 termasuk penggunaan API untuk perdagangan, pengambilan data pasaran dan pengurusan akaun.
dYdX v3
, perdagangan akan menjana ganjaran dalam bentuk token dYdX
.Protokol dYdX v3 DEX sebelumnya telah ditamatkan. URL semasa untuk aplikasi dYdX v4:
Setelah membuka halaman aplikasi, butang sambungan dompet terletak di penjuru kanan atas untuk menyambung melalui pengimbas QR.
Untuk ujian di persekitaran testnet, gunakan:
Proses sambungan dompet dan pengesahan tandatangan adalah serupa. Setelah berjaya disambung, alamat dYdX v4 akan dijana dan dipaparkan di penjuru kanan atas. Menu pop-up menyediakan fungsi deposit, penarikan dan pemindahan aset. Perbezaan utama antara mainnet dan testnet adalah: di testnet, butang deposit akan mengisi 300 USDC secara automatik melalui faucet. Untuk perdagangan sebenar, deposit USDC diperlukan melalui pelbagai rantaian yang disokong.
Alamat Akaun dYdX v4
Alamat dYdX v4 dijana daripada alamat dompet dengan format dydx1xxxxxxxxxxxxxxxxxxxxq2ge5jr4nzfeljxxxx
(bermula dengan dydx1). Alamat ini boleh disemak melalui blockchain explorers.
Frasa Pemulihan Frasa pemulihan boleh dieksport melalui butang “Eksport Frasa Sandi” dalam menu. Di platform FMZ, frasa ini perlu dikonfigurasikan ketika menambah pertukaran.
Frasa pemulihan boleh disimpan terus di FMZ atau disimpan secara lokal. Objek pertukaran dydx v4 akan membaca kandungan fail ini, seperti yang akan ditunjukkan dalam demonstrasi praktikal.
Persekitaran testnet mempunyai beberapa perbezaan berbanding mainnet:
Pemindahan aset sub-akaun
Mainnet mempunyai mekanisme penyucian sub-akaun untuk subAccountNumber >= 128
: aset akan dipindahkan ke sub-akaun 0 jika tiada posisi terbuka.
Testnet tidak mempunyai mekanisme ini (atau syarat pencetus berbeza).
Penamaan token
Token asli: Mainnet DYDX
vs Testnet Dv4TNT
Konfigurasi alamat (chain ID, nod REST dll) Contoh konfigurasi:
Mainnet:
Alamat Indeks: https://indexer.dydx.trade
Chain ID: dydx-mainnet-1
Nod REST: https://dydx-dao-api.polkachu.com:443
Testnet:
Alamat Indeks: https://indexer.v4testnet.dydx.exchange
Chain ID: dydx-testnet-4
Nod REST: https://dydx-testnet-api.polkachu.com
Protokol dYdX v4 dibangunkan atas ekosistem Cosmos. Sistem DEX v4 terdiri daripada dua komponen utama:
Perkhidmatan indekser menyediakan antaramuka REST dan WebSocket.
Protokol REST Menyokong pertanyaan maklumat pasaran, akaun, posisi dan pesanan. Telah dibungkus sebagai API standard di platform FMZ.
Protokol WebSocket Boleh disambung menggunakan fungsi Dial di FMZ untuk melanggan data pasaran.
Nota: Indekser dydx v4 mungkin mengalami kependaman data. Contohnya, pesanan mungkin tidak muncul serta-merta selepas penempatan. Disarankan untuk menambah Sleep(n)
selepas operasi kritikal.
Contoh penggunaan WebSocket untuk melanggan data orderbook:
function dYdXIndexerWSconnManager(streamingPoint) {
var self = {}
self.base = streamingPoint
self.wsThread = null
// Langganan
self.CreateWsThread = function (msgSubscribe) {
self.wsThread = threading.Thread(function (streamingPoint, msgSubscribe) {
// Orderbook
var orderBook = null
// Kemas kini orderbook
var updateOrderbook = function(orderbook, update) {
// Kemas kini bida
if (update.bids) {
update.bids.forEach(([price, size]) => {
const priceFloat = parseFloat(price)
const sizeFloat = parseFloat(size)
if (sizeFloat === 0) {
// Padam pesanan beli pada harga
```javascript
orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
} else {
// Kemaskini atau tambah pesanan beli
orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
orderbook.bids.push({price: price, size: size})
// Uruskan mengikut harga menurun
orderbook.bids.sort((a, b) => parseFloat(b.price) - parseFloat(a.price))
}
})
}
// Kemaskini asks
if (update.asks) {
update.asks.forEach(([price, size]) => {
const priceFloat = parseFloat(price)
const sizeFloat = parseFloat(size)
if (sizeFloat === 0) {
// Padam pesanan jual pada harga
orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
} else {
// Kemaskini atau tambah pesanan jual
orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
orderbook.asks.push({price: price, size: size})
// Uruskan mengikut harga meningkat
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)
}
// Monitor
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 Message Broadcasting
Most commonly used transaction messages include order placement, cancellation, and fund transfer.
- Order Message Abstract
```JSON
{
"@type": "/dydxprotocol.clob.MsgPlaceOrder",
"order": {
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": xxx,
"orderFlags": 64,
"clobPairId": 1
},
"side": "SIDE_BUY",
"quantums": "2000000",
"subticks": "3500000000",
"goodTilBlockTime": 1742295981
}
}
Limit Order:
The orderFlags parameter used in FMZ platform’s encapsulated order function for limit orders is set to: ORDER_FLAGS_LONG_TERM = 64 # Long-term order
. According to dYdX v4 protocol limitations, this utilizes the maximum order validity period of 90 days (all order types on dYdX v4 have expiration periods).
Market Order:
The orderFlags parameter used in FMZ platform’s encapsulated order function for market orders is set to: ORDER_FLAGS_SHORT_TERM = 0 # Short-term order
. Following dYdX v4 protocol recommendations:
// Recommend set to oracle price - 5% or lower for SELL, oracle price + 5% for BUY
As these are not true market orders, the oracle price is used with ±5% slippage tolerance. The expiration mechanism for short-term orders differs from long-term orders - short-term orders use block height expiration, recommended to be set as current block height +10 blocks.
Order ID: Since order placement occurs directly on-chain and there’s no indexer-generated order ID post-broadcast, the platform constructs unique order IDs using the following components (comma-separated):
Order Cancellation Message Abstract
{
"@type": "/dydxprotocol.clob.MsgCancelOrder",
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": 2585872024,
"orderFlags": 64,
"clobPairId": 1
},
"goodTilBlockTime": 1742295981
}
Requires passing the order ID returned by FMZ platform’s order placement interface.
{
"@type": "/dydxprotocol.sending.MsgCreateTransfer",
"transfer": {
"sender": {
"owner": "xxx"
},
"recipient": {
"owner": "xxx",
"number": 128
},
"amount": "10000000"
}
}
Current dYdX v4 addresses can create multiple subaccounts. SubaccountNumber 0 represents the first auto-created subaccount, while subaccountNumbers ≥128 are used for isolated market trading (requiring minimum 20 USDC collateral). Transfers between subaccounts (e.g., 0 ↔ 128) require Gas Fee payment in either USDC or DYDX tokens.
Kandungan di atas menerangkan beberapa butiran pembungkusan secara ringkas. Seterusnya, kita akan bersama-sama mengamalkan penggunaan konkrit menggunakan testnet dYdX v4 untuk demonstrasi. Testnet ini pada asasnya konsisten dengan mainnet dan mempunyai faucet automatik untuk mendapatkan aset ujian. Operasi penyebaran custodian tidak akan diulangi, ujian realiti dicipta di FMZ.
Selepas berjaya menyambung dompet kripto ke Aplikasi dYdX v4 (saya menggunakan dompet imToken), dapatkan aset ujian dan eksport mnemonic seed akaun dYdX v4 semasa (diperoleh dari dompet).
Konfigurasikan mnemonic seed di platform FMZ menggunakan kaedah fail tempatan (boleh juga diisi terus ke platform dengan penyulitan, bukan plaintext).
Letakkan dalam direktori folder ID realiti di bawah direktori custodian. Boleh juga diletak di direktori lain (perlu tulis path lengkap semasa konfigurasi).
Isi medan mnemonic dengan: file:///mnemonic.txt
. Path sepadan: direktori_custodian/logs/storage/594291
.
function main() {
// Tukar alamat indexer chain testnet
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Tukar ChainId chain testnet
exchange.IO("chainId", "dydx-testnet-4")
// Tukar alamat nod REST chain testnet
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
// Ujian baca maklumat akaun
Log(exchange.GetAccount())
}
Maklumat akaun testnet yang dibaca:
{
"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
}
Menggunakan mainnet untuk ujian tanpa penukaran ke testnet
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])
}
### 4、Pesanan Penempatan
```js
function main() {
// Tukar alamat indexer rangkaian ujian
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Tukar ChainId rangkaian ujian
exchange.IO("chainId", "dydx-testnet-4")
// Tukar alamat nod REST rangkaian ujian
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
// Pesanan had, letak pesanan
var idSell = exchange.CreateOrder("ETH_USD.swap", "sell", 4000, 0.002)
var idBuy = exchange.CreateOrder("ETH_USD.swap", "buy", 3000, 0.003)
// Pesanan pasaran
var idMarket = exchange.CreateOrder("ETH_USD.swap", "buy", -1, 0.01)
Log("idSell:", idSell)
Log("idBuy:", idBuy)
Log("idMarket:", idMarket)
}
Halaman Aplikasi dYdX v4:
Rangkaian ujian pra-meletak dua pesanan, ujian mendapatkan pesanan terbuka semasa, batalkan pesanan.
function main() {
// Tukar alamat indexer rangkaian ujian
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Tukar ChainId rangkaian ujian
exchange.IO("chainId", "dydx-testnet-4")
// Tukar alamat nod REST rangkaian ujian
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) + "`")
}
function main() {
// Tukar alamat indexer rangkaian ujian
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Tukar ChainId rangkaian ujian
exchange.IO("chainId", "dydx-testnet-4")
// Tukar alamat nod REST rangkaian ujian
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) + "`")
}
function main() {
// Tukar alamat indexer rangkaian ujian
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Tukar ChainId rangkaian ujian
exchange.IO("chainId", "dydx-testnet-4")
// Tukar alamat nod REST rangkaian ujian
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
// SubAccountNumber 0 -> 128: Transfer 20 USDC, Gas Fee denominado como adv4tnt (token dydx)
var ret = exchange.IO("transferUSDCToSubaccount", 0, 128, "adv4tnt", 20)
Log("ret:", ret)
// Cambiar a subcuenta 128 para verificar información de cuenta
exchange.IO("subAccountNumber", 128)
var account = exchange.GetAccount()
Log("account:", account)
}
Datos retornados por GetAccount después de cambiar a subcuenta 128:
{
"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
}
Se confirma transferencia exitosa de 20 USDC en subcuenta 128.
Consulta de TxHash utilizando ID de orden: El exchange dYdX almacena temporalmente TxHash, pero se elimina al reiniciar estrategias
function main() {
// Configurar endpoint de indexador testnet
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Especificar ChainId de testnet
exchange.IO("chainId", "dydx-testnet-4")
// Configurar nodo REST de testnet
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)
// Gurbi mapping table yana iya amfani da: 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)
}
}
Ta hanyar binciken TxHash an samo saƙon:
var ret = exchange.IO(“api”, “GET”, “/cosmos/tx/v1beta1/txs/” + txHash)
Abubuwan da aka samo suna da tsayi sosai, an zaɓi wani ɓangare don nunawa:
”`JSON
{
“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”: []
},
…
Wannan gwajin ya dogara ne akan sabon custodian, ana buƙatar sauke sabon custodian don tallafawa dYdX v4 DEX
Na gode da tallafi, na gode da karatunku.