[TOC]
Mit dem rasanten Aufstieg dezentraler Börsen (DEX) in der Kryptowährungsbranche begannen Quantitative Händler, sich auf diese Plattformen zu wenden, um effizient und automatisiert zu handeln. Als eine der beliebtesten dezentralen Handelsplattformen bietet dYdX leistungsstarke Handelsfunktionen und unterstützt den Handel mit Futures Forever Contracts.
Dieser Artikel beschreibt, wie man quantitative Handelspraktiken auf dYdX v4 durchführt, einschließlich der Verwendung der API, um zu handeln, Marktdaten zu erhalten und Konten zu verwalten.
dYdX Testing Web App Seite
unddYdX v3
Und es gibt auch Belohnungen, Belohnungen.dYdX
Ich bin ein großer Freund von Ihnen.
Die DEX-Börse für den vorherigen dYdX v3-Protokoll ist abgesperrt. Die App-Adresse für die dYdX v4-App lautet:
Wenn Sie die App-Seite öffnen, finden Sie oben rechts eine Schaltfläche zum Anschließen einer Brieftasche und einen Scancode zum Anschließen einer Brieftasche.
Wenn Sie sich mit Testnetz-Umgebungstests vertraut machen möchten, können Sie Testnetz verwenden:
In der oberen rechten Ecke klickt man auf die Schaltfläche "Connect Wallet", "Scan Code Connect Wallet", "Signature Verification". Nach erfolgreicher Verbindung erzeugt die Wallet automatisch eine Dydx v4-Adresse, die in der oberen rechten Ecke der App-Seite angezeigt wird.
dYdX v4 Kontoadresse
Die dYdX v4-Kontoadresse ist von der Wallet-Adresse abgeleitet und sieht so aus:dydx1xxxxxxxxxxxxxxxxxxxxq2ge5jr4nzfeljxxxx
, ist die Adresse, an der dydx1 beginnt. Diese Adresse kann in Blockchain Explorers abgerufen werden.
Hilfsbegriff Mit dem "Export Passwort" Klick in der oberen rechten Ecke des Menüs können Sie die Passwörter des aktuellen dYdX-Adresskontos exportieren.
Die Hilfswörter können direkt in der FMZ-Plattform konfiguriert werden, aber auch lokal für Administratoren gespeichert werden.
Die Testnetz-Umgebung unterscheidet sich in einigen Punkten von der Hauptnetz-Umgebung.
subAccountNumber >= 128
Wenn das Subkonto der ID nicht auf Lager ist, werden die Vermögenswerte automatisch auf das Subkonto mit der SubAccountNumber 0 gelöscht.
In Tests wurde festgestellt, dass das Testnetz keinen solchen Mechanismus hat (oder die Auslöserbedingungen sind unterschiedlich und es wurde kein Auslöser im Testnetz gefunden).DYDX
TestnetzDv4TNT
Hauptseite:
Die Indexadresse ist:https://indexer.dydx.trade
Die Kette-ID:dydx-mainnet-1
REST-Knoten:https://dydx-dao-api.polkachu.com:443
Testnetz:
Die Indexadresse ist:https://indexer.v4testnet.dydx.exchange
Die Kette-ID:dydx-testnet-4
REST-Knoten:https://dydx-testnet-api.polkachu.com
Die dYdX v4 Protokolle basieren auf der Cosmos Ökosystementwicklung.
Der Indexdienst verfügt über den REST-Protokoll und den Websocket-Protokoll.
REST-Protokoll Die REST-Protokoll-Schnittstelle unterstützt Abfragen von Marktinformationen, Kontoinformationen, Lagerinformationen, Bestellinformationen, die in der FMZ-Plattform als plattformuniforme API-Schnittstelle verpackt sind.
WebSocket-Protokoll Auf der FMZ-Plattform können Sie mit der Dial-Funktion Websocket-Verbindungen, Abonnements und andere Informationen erstellen.
Ein Problem, auf das sich die Dialoge von Dydx v4 bei zentralisierten Transaktionen konzentrieren, ist, dass die Daten nicht so rechtzeitig aktualisiert werden, zum Beispiel manchmal sofort nach der Bestellung abgerufen werden.Sleep(n)
(Wartet ein paar Sekunden und fragt nach.)
Hier ist ein Beispiel, wie man mit der Dial-Funktion eine Websocket-API-Verbindung erstellt, um dünne Daten zu bestellen:
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) + "`")
}
}
Die am häufigsten verwendeten in den Transaktionen sind Bestellmeldungen, Widerrufsmeldungen und Überweisungsmeldungen.
Bestellungsnachrichten
{
"@type": "/dydxprotocol.clob.MsgPlaceOrder",
"order": {
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": xxx,
"orderFlags": 64,
"clobPairId": 1
},
"side": "SIDE_BUY",
"quantums": "2000000",
"subticks": "3500000000",
"goodTilBlockTime": 1742295981
}
}
Preisbeschränkung:
Die auf der FMZ-Plattform verpackte Auftragsfunktion und die OrderFlags für limitierte Bestellungen werden als:ORDER_FLAGS_LONG_TERM = 64 # 长期订单
Die längste Bestelldauer, also 90 Tage, wird verwendet (alle Bestelltypen auf Dydx v4 sind gültig).
Preisliste:
Die auf der FMZ-Plattform verpackte Auftragsfunktion und die Orderflags, die für Marktpreis-Aufträge verwendet werden, werden als:ORDER_FLAGS_SHORT_TERM = 0 # 短期订单
Das ist eine sehr schwierige Sache, aber es gibt viele Möglichkeiten, wie man das tun kann.
// empfehlen Sie auf Oracle Preis - 5% oder niedriger für SELL, Oracle Preis + 5% für BUY
Da es sich nicht um eine echte Marktliste handelt, wird der Preis des Predictors und der Preis minus 5% als Marktliste verwendet. Die Gültigkeitsspanne für kurzfristige Aufträge unterscheidet sich auch von der für langfristige Aufträge. Kurzfristige Aufträge haben eine hohe Gültigkeitsspanne für Blöcke, die nach den Empfehlungen von Dydx v4 auf den aktuellen Block + 10 Blockhöhen gesetzt ist.
Bestell-ID: Da die Auftragsoperation direkt auf der Kette durchgeführt wird, gibt es keine Index-generierte Auftrags-ID nach der Nachrichtenausstrahlung und kann keine Index-Auftrags-Wert als Rückgabe der Plattform-Auftragsfunktion verwendet werden. Um die Einzigartigkeit der Auftrags-ID und die Richtigkeit der Auftragssuche zu gewährleisten, besteht die zurückgegebene Auftrags-ID aus der folgenden Information:
Abzug der Nachrichten
{
"@type": "/dydxprotocol.clob.MsgCancelOrder",
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": 2585872024,
"orderFlags": 64,
"clobPairId": 1
},
"goodTilBlockTime": 1742295981
}
Die Bestell-ID, die in der FMZ-Plattform hinterlegt werden muss, wird von der Anschluss-Schnittstelle zurückgegeben.
Überweisungsnachrichten
{
"@type": "/dydxprotocol.sending.MsgCreateTransfer",
"transfer": {
"sender": {
"owner": "xxx"
},
"recipient": {
"owner": "xxx",
"number": 128
},
"amount": "10000000"
}
}
Derzeit können viele Unterkonten unter der Dydx v4-Adresse erstellt werden, wobei die SubAccountNumber 0 die erste automatisch erstellte Unterkonto ist, die SubAccountNumber höher ist als die SubaccountID, die 128 entspricht. Beispielsweise kann man von SubAccountNumber 0 -> 128 oder von SubAccountNumber 128 -> 0 abrufen.
Die oben beschriebenen Informationen beschreiben einige Packungsdetails. Im Folgenden werden wir die praktischen Anwendungen demonstrieren. Hier wird mit einem Testnetz von dYdX v4 gezeigt, das im Wesentlichen mit dem Hauptnetz übereinstimmt und einen automatischen Wasserhahn hat, der die Testwerte abrufen kann.
Nach erfolgreicher Verbindung der dYdX v4 App mit der Kryptowährungsgeldbörse (imToken-Geldbörse, die ich hier verwende) wird der Testvermögen übernommen und der aktuelle dYdX v4-Account (aus der Geldbörse abgeleitet) ausgeführt.
Konfigurieren Sie das Befehlswort auf der FMZ-Plattform, wo wir es mit der lokalen Dateikonfiguration konfigurieren (auch direkt ausfüllen und auf die Plattform konfigurieren können, das Befehlswort ist nach der Konfiguration verschlüsselt und nicht offen).
助记词文件:mnemonic.txt
Sie können sie natürlich auch in andere Verzeichnisse (wenn Sie einen bestimmten Pfad benötigen, wenn Sie sie konfigurieren) platzieren.
Konfigurieren Sie die Börse auf FMZ
Das ist ein sehr schwieriges Thema, aber es gibt viele Möglichkeiten.file:///mnemonic.txt
Der praktische Weg ist folgender:托管者所在目录/logs/storage/594291
。
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())
}
Sie können die Testnetz-Kontoinformationen hier lesen:
{
"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
}
Test mit dem Hauptnetz ohne Testnetz.
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() {
// 切换测试链的索引器地址
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)
}
Die Seite der dYdX v4 App:
Das Testnetz hängt zwei Bestellungen im Voraus auf, erhält die aktuelle Bestellung und widerruft die Bestellung.
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) + "`")
}
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) + "`")
}
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)
}
Die Daten, die GetAccount zurückgibt:
{
"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
}
Sie können sehen, dass die SubAccountNumber 128 für das Subkonto ist, das in 20 USDC umgerechnet wurde.
Erhalten von TxHash, Testmethoden für IO-REST-Nodes auf Bestellung
Wie man den TxHash eines Auftrags erhält, wird der Exchange-Objekt dydx von TxHash gespeichert und kann mit der Order-ID abgerufen werden.
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)
}
}
Nachrichten, die von TxHash gesucht wurden:
VAR RET =exchange.IO(Api,
GET , /cosmos/tx/v1beta1/txs/ + txHash)
Der Inhalt ist zu lang, und ein ausgewählter Teil zeigt:
{
"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": []
},
...
Der Test basiert auf dem neuesten Host, der für die Unterstützung von dYdX v4 DEX heruntergeladen werden muss
Danke für die Unterstützung und danke fürs Lesen.