Com a rápida ascensão das bolsas descentralizadas (DEX) no campo da negociação de criptomoedas, os comerciantes quantitativos viraram-se gradualmente para essas plataformas para uma negociação automatizada eficiente.
Este artigo introduz como praticar negociação quantitativa no dYdX v4, incluindo como usar sua API para negociar, obter dados de mercado e gerenciar contas.
dYdX v3
Com o Ethereum, a negociação gera recompensas, que são recompensasdYdX
tokens.O exchange DEX anterior do protocolo dYdX v3 estava offline. O endereço atual do aplicativo dYdX v4 é:
Depois de abrir a página da App, há um botão para se conectar à carteira no canto superior direito.
Se você quiser testar e familiarizar-se com o ambiente da rede de teste primeiro, você pode usar a rede de teste:
Além disso, clique no botão conectar carteira no canto superior direito, digitalize o código para conectar a carteira e verifique a assinatura. Depois que a carteira for conectada com sucesso, um endereço dydx v4 será gerado automaticamente. Esse endereço será exibido no canto superior direito da página do aplicativo. Clique nele e um menu aparecerá. Existem operações como recarga, retirada e transferência. Uma das diferenças entre a rede principal dYdX (ambiente de produção) e a rede de teste é que, quando você clica em recarga na rede de teste, 300 ativos USDC serão cobrados automaticamente usando a torneira para teste. Se você quiser fazer transações reais na dYdX, precisa cobrar ativos USDC. A recarga também é muito conveniente e compatível com vários ativos e cadeias para recarga.
Endereço da conta dYdX v4
O endereço da conta dYdX v4 é derivado do endereço da carteira.dydx1xxxxxxxxxxxxxxxxxxxxq2ge5jr4nzfeljxxxx
, que é um endereço que começa com dydx1.
Mnemônica
Você pode usar o botão
Os mnemônicos podem ser configurados diretamente na plataforma FMZ ou salvos localmente no docker.
O ambiente da testnet difere do ambiente da mainnet em alguns aspectos.
subAccountNumber >= 128
, se a subconta do ID não tiver posições, os activos serão automaticamente transferidos para a subconta cujo subconta-número seja 0.
Durante o ensaio, verificou-se que a rede de ensaio não possui este mecanismo (ou as condições de activação são diferentes e não foi activada na rede de ensaio).DYDX
, rede de ensaioDv4TNT
Rede principal:
Endereço do indexador:https://indexer.dydx.trade
Identificador da cadeia:dydx-mainnet-1
Nodo REST:https://dydx-dao-api.polkachu.com:443
Rede de teste:
Endereço do indexador:https://indexer.v4testnet.dydx.exchange
Identificador da cadeia:dydx-testnet-4
Nodo REST:https://dydx-testnet-api.polkachu.com
O protocolo dYdX v4 é desenvolvido com base no ecossistema cosmos.
O serviço de indexação fornece protocolos REST e Websocket.
Protocolo REST A interface do protocolo REST suporta consulta de informações de mercado, informações de conta, informações de posição, informações de ordem e outras consultas, e foi encapsulada como uma interface API unificada na plataforma FMZ.
Protocolo WebSocket Na plataforma FMZ, você pode usar a função Dial para criar uma conexão Websocket e se inscrever em informações de mercado.
Deve-se notar que o indexador do dydx v4 tem o mesmo problema que as trocas centralizadas, ou seja, as atualizações de dados não são tão oportunas. Por exemplo, às vezes você pode não ser capaz de encontrar a ordem se você consultá-la imediatamente após a colocação de um pedido.Sleep(n)
) certas operações antes da consulta.
Aqui está um exemplo de como usar a função Dial para criar uma conexão Websocket API e assinar dados do livro de pedidos:
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) + "`")
}
}
As mensagens mais usadas em transações são mensagens de encomenda, mensagens de cancelamento de encomenda e mensagens de transferência.
{
"@type": "/dydxprotocol.clob.MsgPlaceOrder",
"order": {
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": xxx,
"orderFlags": 64,
"clobPairId": 1
},
"side": "SIDE_BUY",
"quantums": "2000000",
"subticks": "3500000000",
"goodTilBlockTime": 1742295981
}
}
Ordem limite:
Na função de ordens encapsulada na plataforma FMZ, o valor de orderFlags utilizado para ordens limite é:ORDER_FLAGS_LONG_TERM = 64 # Long-term order
De acordo com as limitações do protocolo dydx v4, é utilizado o período de validade da encomenda mais longo, que é de 90 dias (todos os tipos de encomendas no dydx v4 têm períodos de validade).
Ordem de mercado:
Na função de ordens encapsulada na plataforma FMZ, o valor do orderFlags utilizado para ordens de mercado é:ORDER_FLAGS_SHORT_TERM = 0 # Short-term order
, de acordo com as recomendações do protocolo dydx v4:
// Recomendação definida para o preço do oráculo - 5% ou menos para VENDER, preço do oráculo + 5% para COMPRAR
Como não é uma ordem de mercado verdadeira, o preço do oráculo é usado, mais ou menos 5% de deslizamento como a ordem de mercado. O período de validade das ordens de curto prazo também é diferente do de ordens de longo prazo. As ordens de curto prazo usam o período de validade de altura do bloco, que é definido para o bloco atual + 10 alturas do bloco de acordo com a recomendação do dydx v4.
Pares de negociação Endereço da conta corrente dydx Número da subconta (subcontaNumero) ClientId (gerado aleatoriamente) clobPairId (ID do símbolo da transacção) ordem goodTilData (milissegundos)
{
"@type": "/dydxprotocol.clob.MsgCancelOrder",
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": 2585872024,
"orderFlags": 64,
"clobPairId": 1
},
"goodTilBlockTime": 1742295981
}
O ID de encomenda devolvido pela interface de encomenda da plataforma FMZ deve ser transmitido.
{
"@type": "/dydxprotocol.sending.MsgCreateTransfer",
"transfer": {
"sender": {
"owner": "xxx"
},
"recipient": {
"owner": "xxx",
"number": 128
},
"amount": "10000000"
}
}
Muitas sub-contas podem ser criadas sob o endereço dydx v4 atual. A sub-conta com subAccountNumber 0 é a primeira sub-conta criada automaticamente. O ID da sub-conta com subAccountNumber maior ou igual a 128 é usado para negociação de posição isolada, que requer pelo menos 20 ativos USDC. Por exemplo, você pode ir do subAccountNumber 0 -> 128, ou do subAccountNumber 128 -> 0. As transferências exigem taxa de gás.
O conteúdo acima explica brevemente alguns detalhes de embalagem. Em seguida, vamos praticar o uso específico. Aqui usamos a rede de teste dYdX v4 para demonstração. A rede de teste é basicamente a mesma que a rede principal, e há uma torneira automática para receber ativos de teste. A operação de implantação do docker não será repetida. Crie um teste de negociação ao vivo no FMZ.
Depois de se conectar ao aplicativo dYdX v4 com sucesso usando uma carteira de criptomoeda (eu uso a carteira imToken aqui), reivindique seus ativos de teste e, em seguida, exporte o mnemônico para sua conta atual dYdX v4 (derivada de sua carteira).
Configure o mnemônico na plataforma FMZ. Aqui usamos o método de arquivo local para configurá-lo (você também pode preenchê-lo diretamente e configurá-lo para a plataforma. O mnemônico é configurado após criptografia, não em texto simples).
Coloque-o no diretório de pasta do ID de negociação ao vivo, sob o diretório do docker.
Preencha a caixa de edição mnemônica:file:///mnemonic.txt
, o caminho real correspondente é: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())
}
Leia as informações da conta da rede de teste:
{
"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
}
Não trocou para a rede de ensaio, testado com a rede 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() {
// 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)
}
Página do aplicativo dYdX v4:
A rede de ensaios realiza duas encomendas com antecedência, verifica as encomendas pendentes e cancela as encomendas.
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)
}
Informações do registo:
Troque para a subconta cujo subAccountNumber é 128, e os dados devolvidos pelo GetAccount são:
{
"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
}
Pode-se ver que a sub-conta com a subconta número 128 transferiu 20 USDC.
De acordo com a ordem, obter TxHash e testar o método de IO chamando REST nó
Como obter o TxHash de uma ordem? O objeto de troca dydx armazenará o TxHash em cache, que pode ser consultado usando o ID da ordem. No entanto, depois que a estratégia for parada, o mapa de hash da ordem tx em cache será limpo.
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)
}
}
Mensagens consultadas através do TxHash:
var ret = exchange.IO ((
api , GET , /cosmos/tx/v1beta1/txs/ + txHash)
O conteúdo é muito longo, por isso aqui estão alguns trechos para demonstração:
{
"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": []
},
...
Os testes acima são baseados no mais recente docker. Você precisa baixar o mais recente docker para suportar dYdX v4 DEX
Obrigado pelo apoio e por ler.