Pular para o conteúdo principal

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.

CampoO que éPresença
sequencePosiçã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ênciaSempre
sequenceTypePor 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
exchangeIdentificador da exchange avaliadaSempre
timestampÚltima atualização do ticker daquela exchange, em epoch de segundos. Indica a idade do preço avaliadoSempre
askPreço final de compra, com spread e taxas aplicadosSó em compras, quando há preço válido
bidPreço final de venda, com spread e taxas aplicadosSó em vendas, quando há preço válido
deviationReasonPor que esta exchange não levou a operação, quando ela foi acionada e falhouSó 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:

  1. Ticker da exchange — preço vigente, no lado correspondente à operação.
  2. Spread RFQ — identificação da faixa aplicável ao volume e aplicação do percentual.
  3. Taxas — taxas operacionais previstas para a exchange.
  4. 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 ruleCritério
selected as priority at best price by general rulePreço. Menor ask primeiro nas compras, maior bid primeiro nas vendas
selected as priority by general rulePrioridade configurada geral. A ordem segue a configuração da conta, não o preço do momento
selected as priority by specific rulePrioridade configurada específica. Regra dedicada ao par ou à operação
selected as priorityCrité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árioLeitura
Critério de preço; posição 1 com o melhor preçoMelhor execução confirmada no instante da cotação
Critério de preço; outra exchange com preço melhor, diferença pequenaMovimento de mercado dentro do ciclo de reordenação
Critério de prioridade; outra exchange com preço melhorConforme a parametrização da conta: a execução seguiu o critério contratado
Outra exchange melhor, com timestamp bem mais antigoPreço menos atual; a comparação direta não é adequada
Entrada sem ask nem bidExchange sem preço válido para o lado da operação — não era alternativa executável
Exchange habilitada ausente do arraySem ticker disponível no momento — não era alternativa executável
Array vazioNenhuma exchange tinha preço, ou não foi possível compor o registro
Entrada com deviationReasonA 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 sequence igual a 1.
  • Qual executou: o campo executedExchange da ordem. Em contas que operam com a Liqi como contraparte única, o campo exchange da ordem retorna liqi e não serve para essa comparação; use sempre o executedExchange.
  • Por que houve desvio: o deviationReason da 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 errorMessage da 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, exchange e timestamp.
  • 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 rule indica prioridade configurada, não preço. A posição 1 não apresenta o menor ask do conjunto, o que é coerente com esse critério: a ordem seguiu a configuração da conta.
  • rfqSpread.applied: false porque não havia faixa configurada, então o preço não recebeu spread RFQ.
  • O price (349014.62) é o priceWithoutCustomerSpread (332394.87) acrescido do customerSpread de 5%. Os valores de evaluatedTickers não incluem esse spread.
  • No modo liqi-managed o campo exchange da cotação retorna liqi, enquanto o evaluatedTickers preserva 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 melhor ask do conjunto.
  • Quem executou foi a coinbaseprime, como mostra o campo exchange da ordem — diferente da posição 1, então houve desvio.
  • O deviationReason da 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.