Melhor execução
Toda cotação RFQ registra o preço que cada exchange habilitada oferecia naquele instante, no campo evaluatedTickers. Esta página explica primeiro o que o campo contém e como o preço de cada exchange é calculado e, ao final, como a exchange é selecionada — que é o que determina a leitura correta do registro. O campo é retornado por createQuote, fetchQuote, fetchOrder e fetchOrders, e acompanha a ordem publicada no canal watchOrders.
Para que serve
Sem esse registro, a resposta da cotação informa apenas a exchange selecionada e o preço final. Não há como distinguir, ao analisar a operação depois de executada, entre dois cenários: a exchange selecionada era a de melhor preço naquele momento, ou havia exchange com preço melhor e a operação seguiu por outro caminho.
O evaluatedTickers é um registro comprobatório, não um mecanismo de decisão. Ele não influencia a escolha da exchange nem o preço praticado: documenta o cenário vigente quando a cotação foi emitida, para que a decisão possa ser verificada depois.
O registro é gravado na cotação e propagado para a ordem gerada a partir dela, permanecendo disponível durante todo o ciclo de vida da operação. Não há parâmetro nem cabeçalho para habilitá-lo: ele é produzido em toda cotação.
Campos
evaluatedTickers é um array com uma entrada por exchange avaliada, na ordem da sequência de prioridade.
| Campo | O que é | Presença |
|---|---|---|
sequence | Posição da exchange na sequência de prioridade, iniciando em 1. A posição 1 é a exchange eleita para a operação — que nem sempre é a que executou, ver Desvio por contingência | Sempre |
sequenceType | Por que a exchange está nesta posição: GENERAL_BEST_PRICE (posição definida por preço), SPECIFIC_PRIORITY_EXCHANGE (prioridade configurada para o par ou a operação) ou GENERAL_PRIORITY_EXCHANGE (prioridade configurada da conta) | Sempre, em cotações emitidas a partir da entrada em produção |
exchange | Identificador da exchange avaliada | Sempre |
timestamp | Última atualização do ticker daquela exchange, em epoch de segundos. Indica a idade do preço avaliado | Sempre |
ask | Preço final de compra, com spread e taxas aplicados | Só em compras, quando há preço válido |
bid | Preço final de venda, com spread e taxas aplicados | Só em vendas, quando há preço válido |
deviationReason | Por que esta exchange não levou a operação, quando ela foi acionada e falhou | Só na consulta de ordem e no canal watchOrders, e só quando houve desvio por contingência |
Preços são strings truncadas em 2 casas decimais. Apenas um dos dois campos de preço é retornado por operação: compra retorna ask, venda retorna bid, nunca os dois.
O timestamp é epoch em segundos, enquanto createdAt, updatedAt e expiresAt da cotação são epoch em milissegundos. Ao comparar as duas grandezas, converter antes.
Como o preço de cada exchange é calculado
Os valores não são o ticker cru da exchange. Cada um é recalculado com o mesmo tratamento aplicado ao preço oficial da cotação, para que sejam comparáveis entre si e com o preço praticado:
- Ticker da exchange — preço vigente, no lado correspondente à operação.
- Spread RFQ — identificação da faixa aplicável ao volume e aplicação do percentual.
- Taxas — taxas operacionais previstas para a exchange.
- Truncamento — 2 casas decimais, convertido para string.
A faixa de spread aplicável é a de maior valor mínimo dentre as que o volume alcança. Sem faixa configurada, o preço segue sem spread — nesse caso operationalFees.rfqSpread vem com applied: false e reason: "no_ranges_configured". A base de enquadramento é o quoteAmount nas compras e amount × preço nas vendas.
O customerSpread não entra nos valores de evaluatedTickers. Ele é aplicado depois, sobre o preço final da cotação. Por isso o price da cotação não bate com o ask da posição 1: para conciliar, use priceWithoutCustomerSpread.
Como a exchange é selecionada
Esta seção determina como o registro deve ser lido.
A ordem das exchanges é definida por um processo dedicado, que reavalia periodicamente as exchanges habilitadas para cada par e lado e persiste a sequência. Essa sequência é ordenada pelo ticker da exchange, sem spread.
Quando o critério é de preço, a cotação reordena essa sequência no momento em que é emitida, pelo preço final ao cliente — o mesmo valor publicado em ask/bid no evaluatedTickers. O motivo é que o spread RFQ é configurado por exchange e a faixa aplicável depende do volume da operação, informação que a sequência persistida não tem: quando a diferença de spread entre duas exchanges supera a diferença de preço na exchange, a mais barata na exchange deixa de ser a mais barata para o cliente. A exchange da primeira posição da sequência reordenada é a eleita.
Quando o critério é de prioridade configurada, nada é reordenado: a sequência é usada como está. O campo sequenceType de cada entrada diz qual dos dois casos se aplica.
O critério aplicado em cada operação vem no campo rule, presente na cotação e na ordem:
Valor de rule | Critério |
|---|---|
selected as priority at best price by general rule | Preço. Menor ask primeiro nas compras, maior bid primeiro nas vendas |
selected as priority by general rule | Prioridade configurada geral. A ordem segue a configuração da conta, não o preço do momento |
selected as priority by specific rule | Prioridade configurada específica. Regra dedicada ao par ou à operação |
selected as priority | Critério não classificado. A seleção seguiu a sequência vigente |
Três consequências para a análise:
- Quando o critério é de prioridade configurada, a primeira posição reflete a configuração acordada, não necessariamente o melhor preço do conjunto. O registro documenta transparência de preços, e a aferição deve considerar o critério contratado.
- Quando o critério é de preço, decisão e registro passaram a usar o mesmo número: a ordenação é feita sobre o preço final publicado no
evaluatedTickers, no instante da cotação. A posição 1 é, portanto, o melhor preço final entre as exchanges elegíveis. Antes desta mudança a ordenação vinha do ciclo periódico de reavaliação, e o registro podia apresentar exchange com preço melhor que a selecionada quando o mercado se movia no intervalo. - Ainda pode haver preço melhor numa posição posterior à 1, por três motivos declarados. A taxa de pool não entra no critério de ordenação, embora componha o custo da operação. Um candidato inelegível — mercado desabilitado na conta, ou volume abaixo do mínimo da exchange na compra — é enviado para o fim independentemente do preço, para permanecer disponível como contingência. E uma exchange com ticker desatualizado vai para o fim pela regra de 1 hora acima. Nenhum dos três caracteriza falha de execução: nos dois últimos, a posição é decidida antes do preço, de propósito.
Na ordenação por preço, exchanges com ticker desatualizado vão para o fim da sequência, independentemente do preço — o critério evita que preço desatualizado seja tratado como melhor oferta. A regra é aplicada nos dois momentos: na reavaliação periódica e também na reordenação feita na cotação.
O limite é de 1 hora, e pode ser mais estrito no ambiente em uso. Quando o ambiente recusa cotação por idade de preço, a ordenação passa a usar esse mesmo limite — nunca um maior. O efeito é a operação seguir por uma exchange com preço mais recente, em vez de a cotação ser recusada: preferimos entregar preço bom a não entregar preço.
Como interpretar
| Cenário | Leitura |
|---|---|
| Critério de preço; posição 1 com o melhor preço | Melhor execução confirmada no instante da cotação |
| Critério de preço; outra exchange com preço melhor, diferença pequena | Movimento de mercado dentro do ciclo de reordenação |
| Critério de prioridade; outra exchange com preço melhor | Conforme a parametrização da conta: a execução seguiu o critério contratado |
Outra exchange melhor, com timestamp bem mais antigo | Preço menos atual; a comparação direta não é adequada |
Entrada sem ask nem bid | Exchange sem preço válido para o lado da operação — não era alternativa executável |
| Exchange habilitada ausente do array | Sem ticker disponível no momento — não era alternativa executável |
| Array vazio | Nenhuma exchange tinha preço, ou não foi possível compor o registro |
Entrada com deviationReason | A exchange foi acionada e falhou. Em ordem executada, a operação seguiu para a próxima; em ordem rejeitada, todas falharam |
Ao comparar preços entre exchanges, considere sempre o timestamp junto do valor: entradas com horários distintos descrevem momentos de mercado distintos.
Desvio por contingência
Quando a exchange eleita não consegue executar, a operação segue para a próxima da sequência de prioridade. Isso é a contingência, e nesse caso a exchange que executou não é a da posição 1.
Na consulta de ordem, esse cenário é identificado assim:
- Qual era a exchange prioritária: a entrada com
sequenceigual a 1. - Qual executou: o campo
executedExchangeda ordem. Em contas que operam com a Liqi como contraparte única, o campoexchangeda ordem retornaliqie não serve para essa comparação; use sempre oexecutedExchange. - Por que houve desvio: o
deviationReasonda entrada que falhou.
Quando executedExchange coincide com a exchange da posição 1, não houve desvio. Quando divergem, o deviationReason da posição 1 explica a diferença — é o que permite justificar, no monitoramento de melhor execução, uma ordem que tinha preço melhor em outra exchange.
A presença do campo indica que aquela exchange foi acionada e falhou. Ela não diz, sozinha, o desfecho da operação:
- Em ordem executada, a operação seguiu para a exchange seguinte e concluiu lá.
- Em ordem rejeitada, todas as exchanges acionadas falharam. Nesse caso mais de uma entrada traz o campo, e a mensagem da última coincide com o
errorMessageda ordem.
O deviationReason reproduz a mensagem registrada no momento da falha. É um texto descritivo, destinado à leitura humana e à evidência da operação — não um código estável para tratamento programático: a redação pode variar entre exchanges e ao longo do tempo, e não deve ser usada como chave de decisão automatizada.
Duas ausências esperadas, que não significam "não houve desvio":
- Exchange sem ticker no momento da cotação é omitida do array. Se justamente ela falhar, o desvio ocorre sem entrada correspondente.
- Exchange desabilitada entre a cotação e a execução não chega a ser acionada, então não gera motivo — e ainda assim a execução pode sair fora da posição 1.
O campo não aparece na cotação (createQuote e fetchQuote): contingência é um evento da execução da ordem, e na cotação ela ainda não ocorreu. Ele aparece no canal watchOrders do WebSocket, que publica a ordem no mesmo formato da consulta REST.
Regras de preenchimento
- Exchange sem ticker é omitida do array. A cotação não é interrompida.
- Exchange sem o lado da operação entra sem o campo de preço, preservando
sequence,exchangeetimestamp. - Preço zero ou negativo é tratado como ausente, e a entrada é emitida sem preço.
- Se não for possível compor o registro, ele vem vazio e a cotação é concluída normalmente — a emissão nunca é bloqueada por indisponibilidade do registro.
- Um array vazio não distingue entre "nenhuma exchange tinha preço" e "não foi possível compor". Em conciliações que dependam dessa distinção, acione o suporte com o identificador da operação.
Cotações e ordens anteriores à disponibilização do recurso não possuem o campo: evaluatedTickers vem ausente da resposta, e não como array vazio.
Exemplo
Cotação de compra de BTC/BRL por R$ 10,00 em sandbox. As exchanges com sufixo simulated são simuladores de teste e seus preços não refletem mercado real.
{
"id": "01KZEF8JSPPWC4172T1HHYNCHS",
"exchange": "liqi",
"evaluatedTickers": [
{ "sequence": 1, "exchange": "b3simulated", "timestamp": 1786118422, "ask": "349040.13" },
{ "sequence": 2, "exchange": "foxbitsimulated", "timestamp": 1786118367, "ask": "331125.82" },
{ "sequence": 3, "exchange": "cryptocom", "timestamp": 1786118365, "ask": "345901.07" },
{ "sequence": 4, "exchange": "cainvest2", "timestamp": 1786118408, "ask": "330469.26" },
{ "sequence": 5, "exchange": "coinbaseprime", "timestamp": 1786118365, "ask": "343170.89" }
],
"amount": 0.00002865,
"quoteAmount": 10,
"side": "buy",
"symbol": "btc/brl",
"price": 349014.62,
"status": "pending",
"operationalFees": {
"customerSpread": "5.00",
"customerSpreadValue": "0.4761",
"rfqSpread": {
"reason": "no_ranges_configured",
"applied": false,
"percentage": "0.00",
"originalPrice": "349014.62"
}
},
"priceWithoutCustomerSpread": 332394.87,
"rule": "exchange 'b3simulated' selected as priority by general rule"
}
Leitura desta cotação:
- As cinco exchanges habilitadas para o par tinham ticker, e nenhuma foi omitida.
- O
ruleindica prioridade configurada, não preço. A posição 1 não apresenta o menoraskdo conjunto, o que é coerente com esse critério: a ordem seguiu a configuração da conta. rfqSpread.applied: falseporque não havia faixa configurada, então o preço não recebeu spread RFQ.- O
price(349014.62) é opriceWithoutCustomerSpread(332394.87) acrescido docustomerSpreadde 5%. Os valores deevaluatedTickersnão incluem esse spread. - No modo liqi-managed o campo
exchangeda cotação retornaliqi, enquanto oevaluatedTickerspreserva a identificação das exchanges avaliadas.
Em uma venda o critério de preço se inverte: a primeira posição corresponde ao maior bid.
Exemplo de desvio por contingência
Recorte da consulta de uma ordem em que a exchange eleita não conseguiu executar:
{
"id": "58c906ff-ae4b-4ad8-884b-2a638b97d115",
"exchange": "coinbaseprime",
"status": "executed",
"evaluatedTickers": [
{
"sequence": 1,
"exchange": "cryptocom",
"ask": "328900.15",
"timestamp": 1720059168,
"deviationReason": "Insufficient funds in pool"
},
{
"sequence": 2,
"exchange": "coinbaseprime",
"ask": "329356.40",
"timestamp": 1720059169
}
]
}
Leitura desta ordem:
- A exchange eleita era a
cryptocom, na posição 1, e ela tinha o melhoraskdo conjunto. - Quem executou foi a
coinbaseprime, como mostra o campoexchangeda ordem — diferente da posição 1, então houve desvio. - O
deviationReasonda posição 1 informa o motivo: a exchange foi acionada e não tinha saldo suficiente no momento. - A operação executou a 329356.40 em vez de 328900.15 não por escolha de preço, mas porque a exchange de melhor preço não pôde executar. É essa a justificativa que o registro fornece ao monitoramento de melhor execução.
Disponibilidade
O evaluatedTickers está disponível em sandbox e em produção.
O deviationReason passa a ser retornado a partir da versão do serviço que o introduz, em cada ambiente — não há chave de habilitação por conta nem por requisição. Em produção, portanto, ele aparece junto com o deploy dessa versão, que é combinado previamente. Enquanto não estiver em produção, o campo pode sofrer ajuste, e a mudança será comunicada com antecedência.