С быстрым ростом децентрализованных бирж (DEX) в области торговли криптовалютами количественные трейдеры постепенно обращаются к этим платформам для эффективной автоматизированной торговли.
В этой статье будет представлено, как практиковать количественную торговлю на dYdX v4, в том числе как использовать его API для торговли, получения рыночных данных и управления счетами.
dYdX v3
с Ethereum, торговля генерирует вознаграждения, которые являются вознаграждениемdYdX
tokens.Предыдущий обмен DEX протокола dYdX v3 был отключен.
После открытия страницы приложения в правом верхнем углу расположена кнопка для подключения к кошельку.
Если вы хотите сначала протестировать и ознакомиться с средой тестовой сети, вы можете использовать тестовую сеть:
Кроме того, нажмите кнопку подключения кошелька в правом верхнем углу, сканируйте код для подключения кошелька и проверьте подпись. После успешного подключения кошелька автоматически будет сгенерирован адрес dydx v4. Этот адрес будет отображаться в правом верхнем углу страницы приложения. Нажмите на него, и всплывет меню. Есть такие операции, как подзарядка, снятие и передача. Одно из отличий между основным сетью dYdX (производственная среда) и тестовой сетью заключается в том, что при нажатии на кнопку подзарядки на тестовой сети 300 USDC активов будут автоматически заряжены с помощью крана для тестирования. Если вы хотите совершать реальные транзакции на dYdX, вам нужно заряжать активы в USDC. Подзарядка также очень удобна и совместима с несколькими активами и цепями для подзарядки.
Адрес учетной записи dYdX v4
Адрес учетной записи dYdX v4 получен из адреса кошелька.dydx1xxxxxxxxxxxxxxxxxxxxq2ge5jr4nzfeljxxxx
, который является адресом, начинающимся с dydx1. Этот адрес можно запросить в блокчейн-эксплорерах.
Мнемоника
Вы можете использовать кнопку
При использовании объекта обмена dydx v4 будет прочитано содержание файла, записывающего мнемонику, что будет продемонстрировано в практической части этой статьи.
Окружающая среда тестовой сети в некоторых аспектах отличается от среды основной сети.
subAccountNumber >= 128
, если на подсчете ID нет позиций, активы автоматически очищаются на подсчет, номер подсчета которого равен 0.
Во время испытаний было установлено, что испытательная сеть не имеет этого механизма (или условия запуска отличаются, и она не была задействована в испытательной сети).DYDX
, испытательная сетьDv4TNT
Главная сеть:
Адрес индексатора:https://indexer.dydx.trade
Идентификатор цепи:dydx-mainnet-1
РЕСТ-узел:https://dydx-dao-api.polkachu.com:443
Пробная сеть:
Адрес индексатора:https://indexer.v4testnet.dydx.exchange
Идентификатор цепи:dydx-testnet-4
РЕСТ-узел:https://dydx-testnet-api.polkachu.com
Протокол dYdX v4 разработан на основе экосистемы космоса.
Услуга индексатора предоставляет протоколы REST и Websocket.
Протокол REST Интерфейс протокола REST поддерживает запрос на информацию о рынке, информацию о счете, информацию о позиции, информацию о заказах и другие запросы, и был инкапсулирован как единый интерфейс API на платформе FMZ.
Протокол WebSocket На платформе FMZ вы можете использовать функцию Dial для создания соединения Websocket и подписки на информацию о рынке.
Следует отметить, что индексер dydx v4 имеет ту же проблему, что и централизованные биржи, то есть обновления данных не так своевременны.Sleep(n)
) определенные операции перед запросом.
Вот пример использования функции Dial для создания API-соединения Websocket и подписки на данные книги заказов:
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) + "`")
}
}
Наиболее часто используемые сообщения в транзакциях - это сообщения о заказе, сообщения об отмене заказа и сообщения о переводе.
{
"@type": "/dydxprotocol.clob.MsgPlaceOrder",
"order": {
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": xxx,
"orderFlags": 64,
"clobPairId": 1
},
"side": "SIDE_BUY",
"quantums": "2000000",
"subticks": "3500000000",
"goodTilBlockTime": 1742295981
}
}
Лимитный порядок:
В функции ордеров, встроенной на платформе FMZ, значение orderFlags, используемое для предельных ордеров, составляет:ORDER_FLAGS_LONG_TERM = 64 # Long-term order
Согласно ограничениям протокола dydx v4, используется самый длинный срок действия заказа, который составляет 90 дней (все типы заказов на dydx v4 имеют периоды действия).
Рыночный порядок:
В функции заказов, включенной в платформу FMZ, значение orderFlags, используемое для рыночных заказов, составляет:ORDER_FLAGS_SHORT_TERM = 0 # Short-term order
, согласно рекомендациям протокола dydx v4:
// Рекомендую установить цену Oracle - 5% или ниже для SELL, цену Oracle + 5% для BUY
Поскольку это не настоящий рыночный ордер, используется цена оракула, плюс или минус 5% скольжение как рыночный ордер. Период действия краткосрочных ордеров также отличается от долгосрочных ордеров.
Торговые пары адрес текущего счета dydx Номер субсчета (субсчета) ClientId (случайный) clobPairId (идентификатор символа транзакции) порядокФлаги goodTilData (миллисекунды)
{
"@type": "/dydxprotocol.clob.MsgCancelOrder",
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": 2585872024,
"orderFlags": 64,
"clobPairId": 1
},
"goodTilBlockTime": 1742295981
}
Идентификатор заказа, возвращаемый интерфейсом заказа платформы FMZ, должен быть передан.
{
"@type": "/dydxprotocol.sending.MsgCreateTransfer",
"transfer": {
"sender": {
"owner": "xxx"
},
"recipient": {
"owner": "xxx",
"number": 128
},
"amount": "10000000"
}
}
Многие подсчеты могут быть созданы под текущим адресом dydx v4. Подсчет с подсчетомNo0 является первым автоматически созданным подсчетом. ID подсчета с подсчетомNo128 больше или равно 128 используется для торговли изолированной позицией, которая требует не менее 20 активов USDC. Например, вы можете перейти от подсчета No0 -> 128 или от подсчета No128 -> 0. Переводы требуют оплаты за газ. За газ можно платить USDC или токены dydx.
Вышеприведенный контент кратко объясняет некоторые детали упаковки. Далее, давайте практикуем конкретное использование. Здесь мы используем тестовую сеть dYdX v4 для демонстрации. Тестовая сеть в основном такая же, как и основная сеть, и есть автоматический кран для приема тестовых активов. Операция развертывания докера не будет повторяться. Создайте живое торговое испытание на FMZ.
После успешного подключения к приложению dYdX v4 с помощью криптовалютного кошелька (я использую кошелек imToken здесь), воспользуйтесь своими тестовыми активами, а затем экспортируйте мнемонику для текущей учетной записи dYdX v4 (выведенной из вашего кошелька).
Конфигурировать мнемонику на платформе FMZ. Здесь мы используем метод локального файла для его конфигурации (вы также можете заполнить его напрямую и настроить его на платформе. Мнемоника настроена после шифрования, а не в простом тексте).
Разумеется, он также может быть размещен в других каталогах (конкретный путь должен быть написан во время настройки).
Заполните рамку редактирования:file:///mnemonic.txt
, соответствующий фактический путь: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())
}
Прочитайте информацию о учетной записи сети тестирования:
{
"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
}
Не переключился на тестовую сеть, испытанную с основной сетью
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)
}
Страница приложения dYdX v4:
Испытательная сеть размещает два заказа заранее, тестирует получение текущих ожидаемых заказов и отменяет заказы.
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)
}
Информация о журнале:
Переключитесь на подсчет, чьё подсчетное число 128, и данные, возвращенные 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
}
Видно, что на подсчете с подсчетом No 128 было перечислено 20 долларов США.
Согласно приказу, получить TxHash и проверить метод IO вызвать REST узла
Как получить TxHash заказа? Объект обмена dydx будет кэшировать TxHash, который можно запросить с помощью идентификатора заказа. Однако после того, как стратегия будет остановлена, кэшированная карта хэша заказа tx будет удалена.
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)
}
}
Сообщения, запрошенные через TxHash:
var ret =exchange.IO("api",
GET , /cosmos/tx/v1beta1/txs/ + txHash)
Содержание слишком длинное, поэтому вот некоторые выдержки для демонстрации:
{
"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": []
},
...
Вышеприведенные тесты основаны на новейшей версии докера. Вам нужно скачать новейшую версию докера для поддержки dYdX v4 DEX
Спасибо за поддержку и за чтение.