Dengan meningkatnya pertukaran terdesentralisasi (DEX) dalam bidang perdagangan cryptocurrency, pedagang kuantitatif telah beralih ke platform ini secara bertahap untuk perdagangan otomatis yang efisien. Sebagai salah satu platform perdagangan terdesentralisasi paling populer, dYdX menyediakan fungsi perdagangan yang kuat dan mendukung perdagangan kontrak abadi berjangka. Versi terbaru v4 mengoptimalkan kinerja dan pengalaman pengguna, menjadikannya pilihan pertama bagi banyak pedagang kuantitatif.
Artikel ini akan memperkenalkan cara mempraktikkan perdagangan kuantitatif di dYdX v4, termasuk cara menggunakan API untuk perdagangan, mendapatkan data pasar, dan mengelola akun.
dYdX v3
Dengan Ethereum, perdagangan menghasilkan hadiah, yang merupakan hadiahdYdX
tokens.Exchange DEX protokol dYdX v3 sebelumnya telah offline.
Setelah membuka halaman Aplikasi, ada tombol untuk terhubung ke dompet di sudut kanan atas.
Jika Anda ingin menguji dan membiasakan diri dengan lingkungan jaringan uji terlebih dahulu, Anda dapat menggunakan jaringan uji:
Selain itu, klik tombol menghubungkan dompet di sudut kanan atas, scan kode untuk menghubungkan dompet, dan verifikasi tanda tangan. Setelah dompet terhubung dengan sukses, alamat dydx v4 akan dihasilkan secara otomatis. Alamat ini akan ditampilkan di sudut kanan atas halaman Aplikasi. Kliknya dan menu akan muncul. Ada operasi seperti pengisian ulang, penarikan, dan transfer. Salah satu perbedaan antara dYdX mainnet (lingkungan produksi) dan testnet adalah bahwa ketika Anda mengklik recharge di testnet, aset 300 USDC akan secara otomatis dibebankan menggunakan keran untuk pengujian. Jika Anda ingin melakukan transaksi nyata di dYdX, Anda perlu membebankan aset USDC. Recharge juga sangat nyaman dan kompatibel dengan beberapa aset dan rantai untuk pengisian ulang.
Alamat akun dYdX v4
Alamat akun dYdX v4 berasal dari alamat dompet. Alamat akun dYdX v4 terlihat seperti:dydx1xxxxxxxxxxxxxxxxxxxxq2ge5jr4nzfeljxxxx
, yang merupakan alamat yang dimulai dengan dydx1.
Mnemonik
Anda dapat menggunakan tombol
Mnemonics dapat dikonfigurasi langsung di platform FMZ atau disimpan secara lokal di docker.
Lingkungan testnet berbeda dari lingkungan mainnet dalam beberapa aspek.
subAccountNumber >= 128
, jika sub-account ID tidak memiliki posisi, aset akan secara otomatis dibersihkan ke sub-account yang subAccountNumber adalah 0.
Selama pengujian, ditemukan bahwa jaringan uji tidak memiliki mekanisme ini (atau kondisi pemicu berbeda dan tidak dipicu dalam jaringan uji).DYDX
, testnetDv4TNT
Jaringan utama:
Alamat indexer:https://indexer.dydx.trade
ID rantai:dydx-mainnet-1
REST Node:https://dydx-dao-api.polkachu.com:443
Testnet:
Alamat indexer:https://indexer.v4testnet.dydx.exchange
ID rantai:dydx-testnet-4
REST Node:https://dydx-testnet-api.polkachu.com
Protokol dYdX v4 dikembangkan berdasarkan ekosistem kosmos.
Layanan indexer menyediakan protokol REST dan Websocket.
Protokol REST Antarmuka protokol REST mendukung kueri informasi pasar, informasi akun, informasi posisi, informasi pesanan dan kueri lainnya, dan telah dikelompokan sebagai antarmuka API terpadu pada platform FMZ.
Protokol WebSocket Di platform FMZ, Anda dapat menggunakan fungsi Dial untuk membuat koneksi Websocket dan berlangganan informasi pasar.
Perlu dicatat bahwa indexer dydx v4 memiliki masalah yang sama dengan pertukaran terpusat, yaitu, pembaruan data tidak begitu tepat waktu.Sleep(n)
) operasi tertentu sebelum pertanyaan.
Berikut adalah contoh penggunaan fungsi Dial untuk membuat koneksi Websocket API dan berlangganan data buku pesanan:
function dYdXIndexerWSconnManager(streamingPoint) {
var self = {}
self.base = streamingPoint
self.wsThread = null
// subscription
self.CreateWsThread = function (msgSubscribe) {
self.wsThread = threading.Thread(function (streamingPoint, msgSubscribe) {
// Order book
var orderBook = null
// Update order book
var updateOrderbook = function(orderbook, update) {
// Update bids
if (update.bids) {
update.bids.forEach(([price, size]) => {
const priceFloat = parseFloat(price)
const sizeFloat = parseFloat(size)
if (sizeFloat === 0) {
// Delete the buy order with price
orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
} else {
// Update or add a buy order
orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
orderbook.bids.push({price: price, size: size})
// Sort by price descending
orderbook.bids.sort((a, b) => parseFloat(b.price) - parseFloat(a.price))
}
})
}
// Update asks
if (update.asks) {
update.asks.forEach(([price, size]) => {
const priceFloat = parseFloat(price)
const sizeFloat = parseFloat(size)
if (sizeFloat === 0) {
// Delete the sell order with price
orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
} else {
// Update or add a sell order
orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
orderbook.asks.push({price: price, size: size})
// Sort by price ascending
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) + "`")
}
}
Pesan yang paling umum digunakan dalam transaksi adalah pesan pesanan, pesan pembatalan pesanan, dan pesan transfer.
{
"@type": "/dydxprotocol.clob.MsgPlaceOrder",
"order": {
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": xxx,
"orderFlags": 64,
"clobPairId": 1
},
"side": "SIDE_BUY",
"quantums": "2000000",
"subticks": "3500000000",
"goodTilBlockTime": 1742295981
}
}
Perintah batas:
Dalam fungsi order yang terkapsul pada platform FMZ, nilai orderFlags yang digunakan untuk order limit adalah:ORDER_FLAGS_LONG_TERM = 64 # Long-term order
Menurut keterbatasan protokol dydx v4, periode validitas pesanan terpanjang digunakan, yaitu 90 hari (semua jenis pesanan pada dydx v4 memiliki periode validitas).
Peraturan pasar:
Dalam fungsi order yang terkapsul pada platform FMZ, nilai orderFlags yang digunakan untuk order pasar adalah:ORDER_FLAGS_SHORT_TERM = 0 # Short-term order
, sesuai dengan rekomendasi dari protokol dydx v4:
// Rekomendasi ditetapkan untuk harga oracle - 5% atau lebih rendah untuk MENJUAL, harga oracle + 5% untuk BUY
Karena ini bukan pesanan pasar yang sebenarnya, harga oracle digunakan, ditambah atau dikurangi 5% slippage sebagai pesanan pasar. Periode validitas order jangka pendek juga berbeda dari order jangka panjang.
Pasangan Perdagangan alamat rekening arus dydx Nomor sub-akun (sub-akunNumber) clientId (digenerasi secara acak) clobPairId (ID simbol transaksi) Perintah Bendera goodTilData (milisekund)
{
"@type": "/dydxprotocol.clob.MsgCancelOrder",
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": 2585872024,
"orderFlags": 64,
"clobPairId": 1
},
"goodTilBlockTime": 1742295981
}
ID pesanan yang dikembalikan oleh antarmuka pesanan platform FMZ harus diteruskan.
{
"@type": "/dydxprotocol.sending.MsgCreateTransfer",
"transfer": {
"sender": {
"owner": "xxx"
},
"recipient": {
"owner": "xxx",
"number": 128
},
"amount": "10000000"
}
}
Banyak sub-akun dapat dibuat di bawah alamat dydx v4 saat ini. Sub-akun dengan subAccountNumber 0 adalah sub-akun pertama yang dibuat secara otomatis. ID sub-akun dengan subAccountNumber lebih besar atau sama dengan 128 digunakan untuk perdagangan posisi terisolasi, yang membutuhkan setidaknya 20 aset USDC. Sebagai contoh, Anda dapat pergi dari subAccountNumber 0 -> 128, atau dari subAccountNumber 128 -> 0. Transfer memerlukan Biaya Gas. Biaya Gas dapat USDC atau dydx token.
Konten di atas menjelaskan beberapa rincian kemasan secara singkat. Selanjutnya, mari kita berlatih penggunaan spesifik. Di sini kita menggunakan jaringan uji dYdX v4 untuk demonstrasi. Jaringan uji pada dasarnya sama dengan jaringan utama, dan ada keran otomatis untuk menerima aset uji. Operasi penyebaran docker tidak akan diulang. Buat uji perdagangan langsung di FMZ.
Setelah terhubung ke aplikasi dYdX v4 dengan sukses dengan menggunakan dompet cryptocurrency (saya menggunakan dompet imToken di sini), klaim aset uji Anda dan kemudian ekspor mnemonic untuk akun dYdX v4 saat ini Anda (diturunkan dari dompet Anda).
Mengkonfigurasi mnemonic pada platform FMZ. Di sini kita menggunakan metode file lokal untuk mengkonfigurasinya (Anda juga dapat mengisinya secara langsung dan mengkonfigurasinya ke platform. Mnemonic dikonfigurasi setelah enkripsi, bukan dalam teks biasa).
Tempatkan di direktori folder ID perdagangan langsung di bawah direktori docker. Tentu saja, itu juga dapat ditempatkan di direktori lain (jalur tertentu perlu ditulis selama konfigurasi).
Isi kotak suntingan mnemonik:file:///mnemonic.txt
, jalur aktual yang sesuai adalah:docker directory/logs/storage/594291
.
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
// Read account information test
Log(exchange.GetAccount())
}
Baca informasi akun jaringan uji:
{
"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
}
Tidak beralih ke jaringan uji, diuji dengan jaringan utama
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) + "`")
}
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
// Limit order, pending order
var idSell = exchange.CreateOrder("ETH_USD.swap", "sell", 4000, 0.002)
var idBuy = exchange.CreateOrder("ETH_USD.swap", "buy", 3000, 0.003)
// Market order
var idMarket = exchange.CreateOrder("ETH_USD.swap", "buy", -1, 0.01)
Log("idSell:", idSell)
Log("idBuy:", idBuy)
Log("idMarket:", idMarket)
}
Halaman aplikasi dYdX v4:
Jaringan pengujian menempatkan dua pesanan sebelumnya, menguji mendapatkan pesanan saat ini yang sedang menunggu, dan membatalkan pesanan.
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
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() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
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() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
// subAccountNumber 0 -> 128 : 20 USDC, Gas Fee is adv4tnt, i.e. dydx token
// var ret = exchange.IO("transferUSDCToSubaccount", 0, 128, "adv4tnt", 20)
// Log("ret:", ret)
// Switch to subaccount subAccountNumber 128 and read account information to check
exchange.IO("subAccountNumber", 128)
var account = exchange.GetAccount()
Log("account:", account)
}
Informasi log:
Beralih ke subaccount yang subAccountNumber adalah 128, dan data yang dikembalikan oleh GetAccount adalah:
{
"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
}
Bisa dilihat bahwa sub-akun dengan sub-akun Nomor 128 telah mentransfer 20 USDC.
Menurut perintah, mendapatkan TxHash dan menguji metode IO memanggil REST node
Bagaimana mendapatkan TxHash dari order? Objek pertukaran dydx akan cache TxHash, yang dapat ditanyakan dengan menggunakan ID order. Namun, setelah strategi dihentikan, peta hash order tx yang di-cache akan dihapus.
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
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)
// To clear the mapping table, use: 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)
}
}
Pesan yang ditanyakan melalui TxHash:
var ret =exchange.IO("api",
GET , /cosmos/tx/v1beta1/txs/ + txHash)
Kontennya terlalu panjang, jadi berikut adalah beberapa kutipan untuk demonstrasi:
{
"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": []
},
...
Tes di atas didasarkan pada docker terbaru. Anda perlu mengunduh docker terbaru untuk mendukung dYdX v4 DEX
Terima kasih atas dukungannya dan terima kasih sudah membaca.