Práticas Quantitativas em DEX (1) – Guia do dYdX v4

Criado em: 2024-12-24 17:09:32, atualizado em: 2024-12-26 21:41:46
comments   0
hits   233

[TOC]

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

Prefácio

Com a rápida ascensão das exchanges descentralizadas (DEX) no universo de negociação de criptomoedas, traders quantitativos estão gradualmente migrando para estas plataformas para execução automatizada eficiente. O dYdX, como uma das principais plataformas de trading descentralizado, oferece funcionalidades robustas para contratos perpétuos de futuros. Sua versão v4 traz otimizações de performance e experiência do usuário, consolidando-se como escolha preferencial para muitos profissionais de trading algorítmico.

Este artigo abordará práticas de trading quantitativo no dYdX v4, incluindo:

  • Utilização de APIs para execução de ordens
  • Obtenção de dados de mercado
  • Gestão de contas
  • Alternância entre ambientes de teste
  • Consulta de informações de mercado e posições
  • Administração de subcontas
  • Requisições de métodos de nó

dYdX v4 DEX

  • Interface do App na Testnet

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

  • Similar ao dYdX v3, transações geram recompensas em tokens dYdX.

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

Conexão de Carteira, Autenticação e Configurações

A versão anterior dYdX v3 foi descontinuada. O endereço atual do App v4:

https://dydx.trade/trade/ETH-USD

Após acessar, clique no botão “Connect Wallet” no canto superior direito para autenticação via QR code.

Para testes na testnet:

https://v4.testnet.dydx.exchange/trade/ETH-USD

O processo de conexão é idêntico. Após autenticação bem-sucedida, um endereço dYdX v4 será gerado (formato: dydx1xxxxxxxxxxxxxxxxxxxxq2ge5jr4nzfeljxxxx), visível no menu superior. Principais funcionalidades:

  • Depósitos na testnet recebem 300 USDC via faucet automático
  • Na mainnet, são necessários depósitos reais de USDC
  • Suporte a múltiplos ativos e redes para depósitos

Gerenciamento de Credenciais

  • Endereço dYdX v4 Derivado do endereço da carteira conectada, auditável em blockchain explorers.

  • Frase Mnemônica Acessível via menu “Export Secret Phrase”. Essencial para configuração na plataforma FMZ:

    • Armazenamento direto nas configurações
    • Ou gestão local via arquivos seguros

Diferenças entre Mainnet e Testnet

Principais distinções operacionais:

  1. Transferência entre Subcontas

    • Mainnet: Mecanismo automático de sweep para subcontas ≥128 sem posições
    • Testnet: Comportamento diferenciado (sem trigger observado)
  2. Denominação de Tokens

    • Token nativo: DYDX (mainnet) vs Dv4TNT (testnet)
  3. Configurações de Rede Exemplo de endpoints:

Mainnet

   Indexador: https://indexer.dydx.trade
   Chain ID: dydx-mainnet-1
   Nó REST: https://dydx-dao-api.polkachu.com:443

Testnet

   Indexador: https://indexer.v4testnet.dydx.exchange
   Chain ID: dydx-testnet-4
   Nó REST: https://dydx-testnet-api.polkachu.com

Arquitetura do Protocolo dYdX v4

Baseado no ecossistema Cosmos, o sistema compreende dois componentes principais:

  1. Indexador: Responsável por dados de mercado e conta via REST/WebSocket
  2. Blockchain dYdX: Processamento de ordens, cancelamentos e transações

Indexador

Fornece interfaces dual protocol:

  • REST API Consultas de book de ordens, posições e histórico. Integrado na FMZ via wrappers padronizados.

  • WebSocket Conexões persistentes para dados em tempo real. Exemplo de implementação:

function dYdXIndexerWSconnManager(streamingPoint) {
    var self = {}
    self.base = streamingPoint
    self.wsThread = null

    // Assinar canais
    self.CreateWsThread = function (msgSubscribe) {
        self.wsThread = threading.Thread(function (streamingPoint, msgSubscribe) {
            // Livro de ordens
            var orderBook = null 

            // Atualizar livro
            var updateOrderbook = function(orderbook, update) {
                // Atualizar lados de compra
                if (update.bids) {
                    update.bids.forEach(([price, size]) => {
                        const priceFloat = parseFloat(price)
                        const sizeFloat = parseFloat(size)

                        if (sizeFloat === 0) {
                            // Remover nível de preço
```javascript
orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
                        } else {
                            // Atualizar ou adicionar ordem de compra
                            orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
                            orderbook.bids.push({price: price, size: size})
                            // Ordenar por preço em ordem decrescente
                            orderbook.bids.sort((a, b) => parseFloat(b.price) - parseFloat(a.price))
                        }
                    })
                }

                // Atualizar asks
                if (update.asks) {
                    update.asks.forEach(([price, size]) => {
                        const priceFloat = parseFloat(price)
                        const sizeFloat = parseFloat(size)

                        if (sizeFloat === 0) {
                            // Remover ordem de venda com preço price
                            orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
                        } else {
                            // Atualizar ou adicionar ordem de venda
                            orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
                            orderbook.asks.push({price: price, size: size})
                            // Ordenar por preço em ordem crescente
                            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)
    }

    // Monitorar
    self.Peek = function () {
        return self.wsThread.peekMessage()
    }

    return self
}

function main() {
    // real: wss://indexer.dydx.trade/v4/ws
    // simulação: 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) + "`")
}

}


### Transmissão de mensagens de nó da cadeia dYdX

Os tipos mais utilizados em transações são: mensagens de ordem, cancelamento e transferência.

- Resumo da mensagem de ordem
  ```JSON
  {
    "@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 limitada: A função de colocação de pedidos encapsulada na plataforma FMZ utiliza o valor ORDER_FLAGS_LONG_TERM = 64 para ordens limitadas. De acordo com as restrições do protocolo dydx v4, é utilizado o período de validade máximo de 90 dias (todos os tipos de ordens no dydx v4 possuem prazo de validade).

  • Ordem a mercado: Para ordens a mercado, a plataforma FMZ utiliza ORDER_FLAGS_SHORT_TERM = 0. Seguindo a recomendação do protocolo dydx v4:

    // Recomenda-se definir para o preço do oráculo -5% ou menos para VENDA, preço do oráculo +5% para COMPRA

    Como não são verdadeiras ordens de mercado, utiliza-se o preço do oráculo com ajuste de 5% como slippage. A validade difere das ordens de longo prazo, sendo definida por altura de bloco (current block + 10 blocos).

  • ID da ordem: Como a execução ocorre diretamente na blockchain, não há ID gerado por indexadores. Para garantir unicidade e precisão na consulta, o ID retornado é composto por (separados por vírgula):

    • Par de negociação
    • Endereço da conta dydx atual
    • Número da subconta (subaccountNumber)
    • clientId (gerado aleatoriamente)
    • clobPairId (ID do instrumento)
    • orderFlags
    • goodTilData (milissegundos)
  • Resumo da mensagem de cancelamento

    {
    "@type": "/dydxprotocol.clob.MsgCancelOrder",
    "orderId": {
      "subaccountId": {
        "owner": "xxx"
      },
      "clientId": 2585872024,
      "orderFlags": 64,
      "clobPairId": 1
    },
    "goodTilBlockTime": 1742295981
    }
    

É necessário passar o ID do pedido retornado pela interface de pedidos da plataforma FMZ.

  • Resumo da mensagem de transferência
  {
    "@type": "/dydxprotocol.sending.MsgCreateTransfer",
    "transfer": {
      "sender": {
        "owner": "xxx"
      },
      "recipient": {
        "owner": "xxx",
        "number": 128
      },
      "amount": "10000000"
    }
  }

Subcontas com subAccountNumber ≥ 128 são destinadas a produtos de margem isolada, requerendo mínimo de 20 USDC. Transferências entre subcontas (ex: 0 ↔ 128) consomem taxas de rede (Gas Fee), pagáveis em USDC ou token dydx. A subconta 0 é criada automaticamente como primária.

Prática do dYdX v4 na Plataforma FMZ

O conteúdo acima explica brevemente alguns detalhes de encapsulamento. Vamos agora praticar o uso específico, utilizando a testnet do dYdX v4 para demonstração. A testnet é essencialmente idêntica à mainnet, com um faucet automático para obter ativos de teste. Não repetiremos os detalhes de implantação do custodiante, focando na criação de testes reais na FMZ.

1. Configuração

Após conectar com sucesso uma carteira de criptomoedas ao dYdX v4 App (utilizamos a carteira imToken), obtenha os ativos de teste e exporte o mnemônico da conta atual do dYdX v4 (derivada da carteira).

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

Configure o mnemônico na plataforma FMZ usando o método de arquivo local (também é possível inserir diretamente, com criptografia no armazenamento).

  • Arquivo de mnemônico: mnemonic.txt

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

Coloque-o no diretório do ID da estratégia do custodiante, ou em outro local (especifique o caminho absoluto na configuração).

  • Configuração da exchange na FMZ

https://www.fmz.com/m/platforms/add

No campo de mnemônico, insira: file:///mnemonic.txt. O caminho real corresponde a: Diretório do custodiante/logs/storage/594291.

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

2. Mudança para a testnet do dydx v4

function main() {
    // Alterar o endereço do indexador da rede de teste
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // Alterar ChainId da rede de teste 
    exchange.IO("chainId", "dydx-testnet-4")

    // Alterar endereço do nó REST da rede de teste
    exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")

    // Teste de leitura das informações da conta
    Log(exchange.GetAccount()) 
}

Resultado da leitura das informações da conta na 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
}

3. Consulta de informações de mercado

Teste realizado na mainnet sem mudança para testnet

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])
    }
### 4. Realizar ordens

```js
function main() {
    // Alterar endereço do indexador da testnet
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // Configurar ChainId da testnet
    exchange.IO("chainId", "dydx-testnet-4")

    // Configurar endpoint REST da testnet
    exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")

    // Ordem limitada, fazer oferta
    var idSell = exchange.CreateOrder("ETH_USD.swap", "sell", 4000, 0.002)
    var idBuy = exchange.CreateOrder("ETH_USD.swap", "buy", 3000, 0.003)

    // Ordem a mercado
    var idMarket = exchange.CreateOrder("ETH_USD.swap", "buy", -1, 0.01)

    Log("idSell:", idSell)
    Log("idBuy:", idBuy)
    Log("idMarket:", idMarket)
}

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

Página do aplicativo dYdX v4:

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

5. Consulta de informações de ordens

Teste prévio de duas ordens na testnet: recuperar ordens ativas e cancelar ordens.

function main() {    
    // Alterar endereço do indexador da testnet
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // Configurar ChainId da testnet
    exchange.IO("chainId", "dydx-testnet-4")

    // Configurar endpoint REST da testnet
    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) +  "`")
}

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

6. Consulta de posições

function main() {
    // Alterar endereço do indexador da testnet
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // Configurar ChainId da testnet
    exchange.IO("chainId", "dydx-testnet-4")

    // Configurar endpoint REST da testnet
    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) +  "`")
}

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

7. Gerenciamento de subcontas

function main() {
    // Alterar endereço do indexador da testnet
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // Configurar ChainId da testnet
    exchange.IO("chainId", "dydx-testnet-4")

    // Configurar endpoint REST da testnet
```por_Latn
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")

    // subAccountNumber 0 -> 128: 20 USDC, Taxa de Gas é adv4tnt, ou seja, token dydx
    var ret = exchange.IO("transferUSDCToSubaccount", 0, 128, "adv4tnt", 20)  
    Log("ret:", ret)

    // Alternar para a subconta subAccountNumber 128, ler informações da conta para verificação
    exchange.IO("subAccountNumber", 128)

    var account = exchange.GetAccount()
    Log("account:", account)
}

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

Alternando para a subconta subAccountNumber 128, dados retornados por 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
}

É possível observar que na subconta subAccountNumber 128 foram transferidos 20 USDC.

8. Obter TxHash e chamar a interface de nó REST

Como obter TxHash de ordens: método de chamada de nó REST via IO

Como obter o TxHash do pedido: o objeto de exchange dydx armazena em cache o TxHash, que pode ser consultado usando o ID do pedido. No entanto, quando a estratégia é interrompida, o mapeamento de hashes Tx dos pedidos em cache é limpo.

function main() {
    // Alterar endereço do indexer para testnet
    exchange.SetBase("https://indexer.v4testnet.dydx.exchange")

    // Alterar ChainId para testnet
    exchange.IO("chainId", "dydx-testnet-4")

    // Alterar endereço do nó REST para testnet
    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)
    
    // Para limpar a tabela de mapeamento, 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)
    }
}

Práticas Quantitativas em DEX (1) – Guia do dYdX v4

Mensagem consultada via TxHash:

var ret = exchange.IO(“api”, “GET”, “/cosmos/tx/v1beta1/txs/” + txHash)

Conteúdo muito extenso, seção demonstrativa:

{
	"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": []
			},
      ...

FIM

Este teste foi baseado na versão mais recente do hosted agent. É necessário baixar a versão mais atualizada para suportar a DEX dYdX v4

Agradecemos pelo apoio e pela leitura.