Ordem a Limite — status finais e retry
As rotas de ordem a limite ainda não estão ativas. Este guia e as páginas de
API estão publicados para você preparar a integração; a Liqi avisa quando as
rotas entrarem em Sandbox e, depois, em Produção. Chamadas feitas antes disso
respondem 404.
Uma ordem a limite fica registrada e aberta (OPEN) e só executa quando o
mercado atinge o preço definido em limitPrice, dentro do prazo de validade
(expiresAt / expiresInDays). Enquanto isso não acontece, a ordem é avaliada a
cada ciclo pelo motor de execução.
Este guia explica como criar e cancelar uma ordem a limite pela API, os
dois desfechos de encerramento sem execução — EXPIRED e REJECTED — e a
política de retry (re-arm) que decide quando uma falha volta para a fila e
quando ela encerra a ordem de forma definitiva.
Como criar uma ordem a limite
Abra uma ordem a limite com createLimitOrder
(POST /orders/limit/orders). No corpo da requisição:
| Campo | Obrigatório? | O que é |
|---|---|---|
id | Sim | Chave de idempotência do cliente (client order id). Ver detalhe abaixo. |
profileId | Sim | Cliente (profile) dono da ordem. |
symbol | Sim | Par de negociação — ex.: BTC/BRL, ETH/BRL, USDC/BRL. |
side | Sim | buy (compra) ou sell (venda). |
limitPrice | Sim | Preço-alvo. Na compra executa quando o mercado cai até ele; na venda, quando sobe até ele. |
quoteAmount ou amount | Sim | Tamanho da ordem, e o campo depende do lado: compra usa quoteAmount (valor na moeda de cotação); venda usa amount (quantidade na cripto). Ver abaixo. |
expiresInDays / expiresAt | Não | Prazo de validade (GTD). Um ou o outro; omitir assume o prazo padrão da conta. |
Não é uma escolha livre entre os dois. Compra (side: buy) exige quoteAmount
e venda (side: sell) exige amount. Mandar os dois, nenhum, ou o campo do
outro lado é recusado com 400 — uma compra que envia amount recebe a mensagem
Field quoteAmount is required for buy orders.
id é obrigatório e garante idempotência — de forma assíncronaO id é uma chave de idempotência fornecida por você (client order id), e é ele
que vira o orderId da ordem. Um novo POST com o mesmo id para a mesma
conta não cria uma segunda ordem — mas a recusa não vem na resposta: a
requisição repetida também responde 201 com status: OPEN. A duplicata é
identificada em seguida e encerrada em REJECTED
(ver ciclo de vida), ficando de pé apenas uma ordem. O
motivo fica registrado para auditoria, mas nenhum endpoint devolve esse texto:
o que você lê é o status da ordem (ver
como o motivo do encerramento aparece).
Na prática: o reenvio é seguro (não duplica), mas o 201 não confirma que
aquela chamada foi a que sobreviveu. Se a resposta se perder e você reenviar com o
mesmo id, consulte a ordem pelo orderId para saber o desfecho.
Como cancelar uma ordem a limite
- Uma ordem:
cancelLimitOrder(PATCH /orders/limit/orders/{orderId}/cancel). - Em lote:
cancelLimitOrdersBatch(POST /orders/limit/orders/cancel).
Com a ordem em OPEN, o cancelamento registra o pedido e a ordem termina em
CANCELLED (ver ciclo de vida).
A diferença está no estado intermediário, e ela é intencional:
CANCELLING | CANCELLED | |
|---|---|---|
Uma ordem (cancelLimitOrder) | sim, assim que o pedido é aceito | sim, quando o pod conclui |
Em lote (cancelLimitOrdersBatch) | não | sim, um evento por ordem cancelada |
O lote responde "pedido aceito" sem percorrer as ordens do seletor — é o que
permite cancelar um símbolo inteiro, ou a conta inteira, com uma chamada só. Como
ele não percorre, também não marca CANCELLING em cada ordem.
Não espere CANCELLING depois de um cancelamento em lote. O que chega é o
CANCELLED de cada ordem, conforme o pod as processa.
409 não quer dizer que o pedido foi perdidoCancelamento de ordem que não está em OPEN responde 409, e o 409 tem dois
significados diferentes:
- em execução (
TRIGGERED,PROCESSING) — o pedido fica registrado e é honrado automaticamente se a ordem voltar paraOPENpelo re-arm. Não reenvie: acompanhe a ordem. - já finalizada — o pedido não é registrado, e não há mais o que cancelar.
Reserva de saldo na venda
Uma ordem a limite de venda (side: sell) reserva o saldo do ativo base no
momento da criação, e não só quando dispara. A quantidade reservada é o amount
da ordem (a quantidade da cripto — por exemplo, o BTC em BTC/BRL), nunca
amount × limitPrice.
Na prática, ao criar a venda o saldo daquele ativo sai de free e entra em
used no seu saldo da carteira: ele continua seu, mas fica
bloqueado para aquela ordem enquanto ela está aberta, e por isso não pode ser
usado por outra ordem. O efeito visível no saldo é o mesmo de uma venda a mercado; a
diferença é o momento, que na ordem a limite é a criação e não a execução.
A reserva vale apenas para a venda. Uma ordem a limite de compra (side: buy) não bloqueia saldo na criação: o valor em moeda de cotação (por exemplo,
BRL) permanece livre até a execução.
O que acontece com o saldo reservado depende do desfecho da ordem:
| Desfecho da ordem | Efeito no saldo reservado |
|---|---|
Execução (EXECUTED / CLOSED) | O saldo reservado é consumido — a cripto reservada sai da carteira para liquidar a venda. |
Encerramento sem executar (EXPIRED, CANCELLED, REJECTED) | O saldo é devolvido: volta de used para free e fica disponível de novo. |
Saldo insuficiente na criação
Se, na criação de uma venda, não houver saldo livre suficiente para reservar o
amount pedido, a ordem não fica aberta: ela termina em REJECTED (por
saldo insuficiente). Como cada venda aberta mantém seu saldo reservado, abrir
várias vendas cujo total ultrapasse o saldo disponível faz as que couberem
reservarem normalmente e a excedente ser recusada com REJECTED.
Ciclo de vida resumido
OPEN ──(preço atingido)──▶ TRIGGERED ──(execução)──▶ EXECUTED ──▶ CLOSED
▲ │
└──────────── re-arm (falha recuperável) ──┘
OPEN ──(prazo vencido, expiresAt < now)──▶ EXPIRED
OPEN ──(cancelamento pedido)──▶ CANCELLING ──▶ CANCELLED
qualquer ponto ──(recusa definitiva)──▶ REJECTED
OPEN,TRIGGERED,PROCESSING,EXECUTED— estados ativos (a ordem ainda está em andamento).CANCELLING— cancelamento em andamento: a rota registrou o pedido e o pod conclui (encerra a observação e gravaCANCELLED). Nesse estado a ordem já não executa mais.CLOSED,EXPIRED,CANCELLED,REJECTED— estados finalizados (a ordem não executa mais).
EXECUTED não é o fimEXECUTED significa que a ordem foi preenchida na exchange, e não que o ciclo
terminou: a liquidação segue em andamento e o campo statusGroup ainda vem como
ACTIVE. O estado final de uma ordem executada é CLOSED. Quem para de
consultar em EXECUTED para antes do desfecho.
Acompanhamento de status
As mudanças de status da ordem a limite acontecem de forma assíncrona (disparo, re-arme, conclusão do cancelamento, expiração), e o acompanhamento é por consulta REST:
GET /orders/fetchOrder— lê do registro e responde o estado atual da ordem. É a consulta a usar para confirmar um desfecho.GET /orders/limit/orders— listagem, eventualmente consistente: serve para varrer e reconciliar, não para confirmar o que acabou de acontecer.
Para reconciliar, use orderId + status + updatedAt: a mesma ordem pode ser lida
várias vezes, e updatedAt é o que diz qual leitura é a mais recente.
watchOrders passou a cobrir a ordem a limiteO WebSocket publica todas as mudanças de status da ordem a limite: abertura,
disparo, re-arm, CANCELLING, CANCELLED, EXPIRED e REJECTED. Cada evento
carrega a ordem completa, no mesmo formato do fetchOrder — com type: "limit"
e os campos de limite.
Esta seção dizia o contrário até agosto de 2026, quando o canal ainda não cobria
essas transições. Se a sua integração faz polling do fetchOrder para saber de
cancelamento ou expiração, ele deixou de ser necessário.
O canal continua sendo complemento, e não substituto da consulta: entrega é de
melhor esforço, e a fonte do estado é o REST. Para reconciliar, use fetchOrder.
EXPIRED — encerramento por tempo
A ordem venceu (expiresAt < now) sem atingir o alvo. É um encerramento por
tempo, não por erro: o mercado simplesmente não chegou ao limitPrice dentro da
validade. Não há falha de execução envolvida.
REJECTED — recusa definitiva
REJECTED é o encerramento por recusa, e não por tempo. Ele vem de duas
origens diferentes, e vale distinguir as duas:
- A política de retry do fluxo de execução, que classifica a falha de uma
tentativa em três categorias: recuperável (re-arma, volta para
OPEN), irrecuperável (encerra emREJECTED) ou genérica com teto de tentativas (re-arma, mas encerra emREJECTEDse persistir — ver Erro genérico da exchange). A lista de condições irrecuperáveis começa vazia e cresce por operação, conforme novos casos definitivos são mapeados; a terceira categoria, essa sim, encerra a ordem quando o erro se repete. - As recusas fora dessa política, na criação e em pontos do fluxo de execução
que não passam pelo retry. São essas que produzem
REJECTEDhoje, e estão listadas abaixo.
REJECTEDA lista de condições irrecuperáveis está vazia, então nenhuma falha encerra a ordem
por essa via — a rejeição da exchange, por exemplo, re-arma. Mas a política de
retry produz REJECTED em um caso: o erro genérico que se repete três vezes
(ver Erro genérico da exchange).
Mas REJECTED continua acontecendo, por caminhos que não passam por essa
política. Hoje são estes:
| Quando | Caso |
|---|---|
| Logo após a criação, sem chegar a executar | id duplicado; venda sem saldo suficiente para reservar |
| Na entrada do fluxo de execução | Recusa não transitória (uma recusa transitória, como mercado inativo, mantém a ordem em OPEN sem registrar tentativa); ordem sem a quantidade correspondente ao lado |
| Durante o fluxo de execução, antes da tentativa na exchange | Cliente (profile) com compra ou venda desabilitada |
| Após três tentativas de execução | Erro genérico da exchange (INTERNAL SERVER ERROR) que se repetiu nas três — ver a seção abaixo |
| Residual | Falha que a própria política de retry não consegue tratar encerra a ordem em REJECTED |
Ou seja: REJECTED pode chegar durante a execução. Trate-o como desfecho
possível em qualquer ponto, e não só na criação.
Erro genérico da exchange: três tentativas
Quando a exchange responde com um erro genérico (INTERNAL SERVER ERROR), a Liqi
não consegue afirmar duas coisas ao mesmo tempo: nem que o erro é definitivo, nem que
a ordem não chegou a ser aberta do outro lado.
Esse desconhecimento é o que define o tratamento. A ordem a limite não pode ser encerrada por um erro isolado — uma instabilidade momentânea da exchange não deve custar a ordem do cliente. Mas re-armar indefinidamente depois de um desfecho desconhecido tem o risco oposto: executar a mesma ordem mais de uma vez.
O acordo entre os dois é um teto:
- a ordem é tentada até três vezes (a original mais duas);
- entre uma tentativa e outra ela volta para
OPEN, com oexpiresAtoriginal preservado — o prazo não é reiniciado; - se o erro genérico se repetir nas três, a ordem termina em
REJECTED.
Na prática, um erro realmente transitório se resolve dentro das três tentativas. Um erro permanente que apenas se apresenta como genérico encerra a ordem em vez de ficar girando até o vencimento, que pode ser de até 30 dias.
Entre uma tentativa e outra a ordem fica em OPEN como qualquer outra, e o
expiresAt continua valendo. Se o prazo vencer nesse intervalo, o desfecho é
EXPIRED; se as três tentativas se esgotarem antes, é REJECTED. Vale o que
acontecer primeiro.
Como o motivo do encerramento aparece
O motivo de cada mudança de status é registrado para auditoria, e é por ele que o suporte da Liqi investiga um caso. Nenhum endpoint devolve esse texto hoje:
- O
GET /orders/limit/orderstrazstatus, e não o motivo. - O
GET /orders/fetchOrdertrazerrorMessagepreenchido apenas em rejeição escrita pelo fluxo de execução. Nas recusas da própria ordem a limite —idduplicado, venda sem saldo, expiração, cancelamento — ele vem vazio.
Na prática: o status é a informação de desfecho disponível na API. Para saber o
motivo de um caso específico, acione o suporte com o orderId.
Re-arm — falha recuperável volta para OPEN
Quando a execução falha de forma recuperável, a ordem é re-armada: volta
para OPEN (e não para TRIGGERED), ficando novamente elegível para ser
avaliada e disparada no próximo ciclo.
Cada etapa do ciclo é carimbada na trilha da ordem, o que permite auditar o que aconteceu em cada tentativa:
avaliou → disparou → tentou → falhou → re-armou
Uma mesma ordem pode passar por vários ciclos de re-arm antes de finalmente
executar, expirar por tempo (EXPIRED) ou, no caso de uma falha irrecuperável,
ser rejeitada (REJECTED).
Margem pós-execução — proteção contra preenchimento ruim
Além do gatilho de preço, há uma margem pós-execução: se a execução sair muito
distante do limitPrice — além da tolerância configurada na sua conta — a ordem
também volta para OPEN em vez de aceitar um preenchimento ruim. Essa tolerância
é definida pela Liqi na habilitação da conta e não é devolvida por nenhum endpoint;
consulte o suporte para saber o valor em vigor.
A margem é assimétrica, sempre a favor do cliente:
| Lado | Condição que devolve a ordem para OPEN |
|---|---|
Compra (buy) | Preenchimento acima do teto (limitPrice + tolerância) |
Venda (sell) | Preenchimento abaixo do piso (limitPrice − tolerância) |
Ou seja, uma compra não é aceita acima do teto e uma venda não é aceita abaixo do piso. Nesses casos a ordem re-arma e tenta novamente, protegendo o cliente de um preço pior do que o contratado.
Resumo
| Status | Terminal? | Significado |
|---|---|---|
OPEN | Não | Registrada e aguardando o preço-alvo (estado para onde o re-arm devolve) |
TRIGGERED | Não | Preço atingido, execução em andamento |
CANCELLING | Não | Cancelamento pedido e em andamento (o pod conclui para CANCELLED) |
EXECUTED | Não | Preenchida na exchange, liquidação em andamento (statusGroup ainda ACTIVE) |
CLOSED | Sim | Encerrada com execução concluída — estado final de uma ordem executada |
EXPIRED | Sim | Prazo vencido (expiresAt < now) sem atingir o alvo |
CANCELLED | Sim | Cancelamento concluído |
REJECTED | Sim | Recusa na criação (venda sem saldo, id duplicado) ou recusa definitiva dentro do fluxo de execução — lista dos casos. Falha classificada pela política de retry não cai aqui: re-arma |
Falha classificada pela política de retry → re-arm → OPEN (nova tentativa), e
hoje isso vale para a rejeição da exchange e para a falha desconhecida. Recusa na
criação, ou recusa definitiva dentro do fluxo de execução → REJECTED. Fim do prazo
→ EXPIRED. Preenchimento fora da margem → re-arm → OPEN. EXECUTED não é
terminal; o fim é CLOSED. Venda reserva o ativo base na criação (sai de free
para used); saldo devolvido em EXPIRED/CANCELLED/REJECTED e consumido na
execução. O motivo do encerramento não é devolvido por nenhum endpoint.