[TOC] Je vous en prie.
Avec l'essor rapide des échanges décentralisés (DEX) dans le domaine des transactions cryptographiques, les traders quantifiés ont commencé à se tourner vers ces plateformes pour effectuer des transactions automatisées efficaces. Comme dYdX est l'une des plateformes de trading décentralisées les plus populaires, il offre des fonctionnalités de trading puissantes, prend en charge la négociation de contrats à terme sur les futures, et sa dernière version v4 optimise les performances et l'expérience utilisateur, ce qui en fait le choix préféré de nombreux traders quantifiés.
Cet article explique comment quantifier les pratiques de trading sur dYdX v4, y compris comment utiliser son API pour effectuer des transactions, obtenir des données de marché et gérer des comptes.
Page d'applications du test dYdX
avecdYdX v3
Il y a aussi des récompenses, des récompenses.dYdX
Je vous en prie, faites-moi confiance.
L'échange DEX, qui était basé sur le protocole dYdX v3, est désormais hors service.
Après avoir ouvert la page de l'application, le bouton de connexion du portefeuille apparaît en haut à droite et le code de balayage connecte le portefeuille.
Si vous souhaitez vous familiariser avec les tests environnementaux, vous pouvez utiliser le testnet:
Dans le même ordre d'idées, cliquez sur le bouton de connexion du portefeuille en haut à droite, sur le balayage du code de connexion du portefeuille, sur la confirmation de la signature. Une fois la connexion du portefeuille réussie, une adresse dydx v4 est générée automatiquement. Dans le coin supérieur droit de l'application, un menu apparaît.
Adresse du compte dYdX v4
L'adresse d'un compte dYdX v4 est dérivée de l'adresse d'un portefeuille.dydx1xxxxxxxxxxxxxxxxxxxxq2ge5jr4nzfeljxxxx
, est l'adresse du début de dydx1. Cette adresse peut être consultée dans les explorateurs de la blockchain.
Le mot de passe Vous pouvez utiliser le bouton "Exporter mot de passe" dans le menu en haut à droite pour exporter le mot de passe de l'adresse actuelle dYdX. Vous devez configurer ce mot de passe lorsque vous ajoutez une bourse sur la plate-forme FMZ.
Le mot-clé peut être configuré directement sur la plateforme FMZ, mais il peut également être conservé localement par l'administrateur, qui lit le contenu du fichier du mot-clé enregistré lors de l'utilisation d'un objet d'échange dydx v4.
Les environnements de test diffèrent de certains environnements du réseau principal.
subAccountNumber >= 128
Si le sous-compte de l'ID n'est pas détenu, les actifs sont automatiquement effacés vers le sous-compte dont le numéro de compte est 0.
Les tests ont révélé que le réseau de test n'avait pas ce mécanisme (ou que les conditions de déclenchement étaient différentes et qu'il n'y avait pas de déclenchement dans le réseau de test).DYDX
Le réseau de testDv4TNT
Le site officiel:
L'adresse de l'index est:https://indexer.dydx.trade
Identifiant de chaîne:dydx-mainnet-1
Les points REST:https://dydx-dao-api.polkachu.com:443
Le réseau de test:
L'adresse de l'index est:https://indexer.v4testnet.dydx.exchange
Identifiant de chaîne:dydx-testnet-4
Les points REST:https://dydx-testnet-api.polkachu.com
Le protocole dYdX v4 est basé sur l'écosystème cosmos.
Le service d'indexation fournit le protocole REST et le protocole Websocket.
Protocole REST L'interface du protocole REST prend en charge les requêtes d'informations de marché, d'informations de compte, d'informations de stockage, d'informations d'ordre, etc., qui sont enveloppées dans l'API unifiée de la plate-forme FMZ.
Le protocole WebSocket Sur la plateforme FMZ, vous pouvez créer des connexions Websocket, des informations sur les marchés des abonnements, etc. à l'aide de la fonction Dial.
Attention à l'indicateur de dydx v4 avec tous les mêmes problèmes de transaction centralisée, les mises à jour de données ne sont pas si opportunes, par exemple parfois une requête immédiate après la commande, peut ne pas être une requête d'ordre.Sleep(n)
Il est possible que vous ayez des problèmes de santé.
Voici un exemple d'utilisation de la fonction Dial pour créer une connexion Websocket API et souscrire des données minces:
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) + "`")
}
}
Les messages les plus couramment utilisés dans les transactions sont les messages de commande, de retrait et de virement.
Résumé de l'ordre
{
"@type": "/dydxprotocol.clob.MsgPlaceOrder",
"order": {
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": xxx,
"orderFlags": 64,
"clobPairId": 1
},
"side": "SIDE_BUY",
"quantums": "2000000",
"subticks": "3500000000",
"goodTilBlockTime": 1742295981
}
}
Liste des prix:
La fonction de sous-commande enveloppée dans la plate-forme FMZ, les OrderFlags utilisés pour les commandes à prix limité, sont:ORDER_FLAGS_LONG_TERM = 64 # 长期订单
La durée de validité de la commande la plus longue est de 90 jours (tous les types de commande sur Dydx v4 sont valables).
Liste des prix:
La fonction d'envoi d'ordres enveloppée dans la plate-forme FMZ, les ordres de prix utilisés pour les ordres d'ordres, sont les suivants:ORDER_FLAGS_SHORT_TERM = 0 # 短期订单
Le site Web de l'entreprise est disponible en anglais, français et anglais.
// Recommander fixé au prix oracle - 5% ou moins pour VENDRE, prix oracle + 5% pour acheter
Comme il ne s'agit pas d'une liste de prix réelle, on utilise le prix du prédicteur, plus un prix d'écart de 5% comme liste de prix. La durée de validité des ordres à court terme est également différente de celle des ordres à long terme. Les ordres à court terme utilisent une durée de validité élevée pour les blocs, et sont désactivés après avoir été réglés à la hauteur du bloc actuel + 10 blocs selon les recommandations de dydx v4.
Nom de la commande: Étant donné que l'opération de sous-commande est effectuée directement sur la chaîne, il n'y a pas d'ID d'ordre généré par l'index après la diffusion du message et il n'est pas possible d'utiliser l'ordre d'index comme valeur de retour de la fonction de sous-commande de la plate-forme. Afin d'assurer l'unicité de l'ID d'ordre et l'exactitude de la requête d'ordre, l'ID d'ordre retourné est constitué des informations suivantes:
Résumé de l'annonce
{
"@type": "/dydxprotocol.clob.MsgCancelOrder",
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": 2585872024,
"orderFlags": 64,
"clobPairId": 1
},
"goodTilBlockTime": 1742295981
}
L'ID de la commande doit être renvoyé à l'interface de commande de la plateforme FMZ.
Résumé du transfert
{
"@type": "/dydxprotocol.sending.MsgCreateTransfer",
"transfer": {
"sender": {
"owner": "xxx"
},
"recipient": {
"owner": "xxx",
"number": 128
},
"amount": "10000000"
}
}
De nombreux sous-comptes peuvent être créés sous l'adresse dydx v4 actuelle, dont le subAccountNumber 0 est le premier sous-compte créé automatiquement, le subAccountNumber est supérieur à 128 et l'ID du sous-compte est égal à 128 pour les transactions de variétés par lots, nécessitant un minimum d'actifs de 20 USD. Par exemple, il est possible d'utiliser le sous-compte N° 0 -> 128 ou le sous-compte N° 128 -> 0. Le détournement nécessite une consommation de Gas Fee.
Le contenu ci-dessus explique brièvement quelques détails de l'emballage, puis nous allons pratiquer l'utilisation concrète, ici, en utilisant dYdX v4 réseau de test pour une démonstration, le réseau de test est en grande partie compatible avec le réseau principal, et il y a un robinet automatique qui peut prendre les actifs de test, l'opération de déploiement de l'hôte n'est plus décrite, créer des tests en direct sur FMZ.
Après avoir connecté l'application dYdX v4 avec le portefeuille de crypto-monnaie (je utilise ici le portefeuille imToken), l'actif de test est récupéré, puis le mot de passe du compte dYdX v4 actuel (dérivé du portefeuille) est exporté.
Configurer le mot de passe dans la plateforme FMZ, où nous utilisons la configuration de fichiers locaux (vous pouvez également le remplir directement et le configurer sur la plateforme, le mot de passe est sur la configuration post-chiffrement et non explicite).
助记词文件:mnemonic.txt
Dans le répertoire des dossiers ID de disque virtuel sous le répertoire de l'administrateur, vous pouvez bien sûr les placer dans d'autres répertoires (des chemins spécifiques sont nécessaires lors de la configuration).
Configuration des échanges sur FMZ
Le texte de l'éditeur de mots-auxiliaires est rempli:file:///mnemonic.txt
Le chemin réel est le suivant:托管者所在目录/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())
}
Lire l'information sur le compte du testnet:
{
"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
}
Pas de connexion au réseau de test, test avec le réseau principal
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)
}
La page d'application dYdX v4 est ici:
Le réseau de test suspend deux commandes à l'avance, le test obtient la liste d'attente actuelle et annule la commande.
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)
}
Le nombre de sous-accounts de 128 est remplacé par le nombre de sous-accounts que GetAccount renvoie:
{
"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
}
Vous pouvez voir que le numéro de sous-compte est 128 et est converti en 20 USD.
Obtenir TxHash sur commande, tester les méthodes d'appel des nœuds REST par IO
Comment obtenir le TxHash de l'ordre, l'objet dydx cache TxHash et peut être consulté avec l'ID de l'ordre.
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)
}
}
Les messages demandés par TxHash:
Le taux de changeexchange.IO"api",
GET , /cosmos/tx/v1beta1/txs/ + txHash)
Le contenu est trop long et une partie de la démonstration est choisie:
{
"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": []
},
...
Le test ci-dessus, basé sur le dernier hôte, nécessite le téléchargement du dernier hôte pour prendre en charge dYdX v4 DEX
Merci pour votre soutien et merci de lire.