amBrain
FinTechSep 28, 202610 min de leitura

Construindo um terminal de trading sobre FIX com núcleo em Rust: livro de ofertas, painel de scalping, estado das ordens

Terminal de tradingProtocolo FIXOrder BookRust
Erro ao carregar a imagem

Como é construído um terminal de trading FIX com núcleo em Rust, da recuperação da sessão à escada de preços, e a prova que mostra que um fornecedor construiu um.

Nenhum diretório diz quais empresas de fato construíram um terminal de trading FIX com um livro de ofertas ao vivo, um painel de scalping e um hot path em Rust, porque “construímos um terminal de trading” cobre qualquer coisa, desde uma camada de gráficos sobre a API de uma corretora até um cliente FIX com a própria máquina de estados de ordens. Um fornecedor que construiu um consegue provar: rodar o terminal contra uma venue real enquanto você assiste, citar as venues que certificaram a conexão FIX do terminal, dizer onde os timestamps de latência são tirados e entregar um código que você consiga compilar sem a ajuda do fornecedor.

A resposta curta sobre a arquitetura: um núcleo em Rust é dono da sessão FIX e dos números de sequência dela, da máquina de estados de ordens, do livro de ofertas local e das checagens pré-negociação, e, com mais do que uns poucos traders, roda como um gateway perto da venue. A tela só envia comandos e desenha o estado mais recente uma vez por frame, então pode travar ou cair sem perder nenhuma ordem.

O que pertence ao núcleo em Rust, e o que fica com a tela?

Tudo aquilo cuja perda ou atraso muda uma ordem vive no núcleo. Essa regra coloca cinco partes ali.

  • Um engine FIX, com uma camada de sessão para logon, heartbeats, números de sequência e reenvios, e uma camada de aplicação que transforma comandos do trader em mensagens de ordem e ExecutionReports em eventos
  • Um gerenciador de ordens que mantém uma máquina de estados por ordem, indexada pelo ID de ordem do cliente e alimentada apenas pelo que a venue reporta
  • Um construtor de book para cada feed, que consolida um stream sequenciado em um book local por instrumento
  • Checagens pré-negociação de tamanho, bandas de preço e exposição, executadas sobre o estado em memória antes de uma mensagem ser codificada
  • Um journal de cada mensagem e de cada comando do trader, gravado antes de qualquer ação sobre ele, para que um reinício possa reconstruir o estado e uma execução contestada possa ser reproduzida

A tela mantém uma visão: escada de preços, gráficos, blotter de ordens, posições, atalhos de teclado. Se ela morrer, o núcleo continua com a sessão, as ordens ativas e as ordens stop que ele gerencia. É no núcleo que Rust mais importa. Sem garbage collector, nenhuma pausa de coleta cai entre um comando e a rede, e o compilador rejeita código que escreve em um mesmo book a partir de duas threads, a menos que o book seja compartilhado atrás de um lock.

Uma página web comum não consegue abrir a conexão TCP sobre a qual uma sessão FIX roda. A documentação do Chrome diz que aplicações web padrão “não conseguem estabelecer conexões TCP ou UDP brutas”, e a API Direct Sockets do Chrome só remove esse limite para Isolated Web Apps. Um terminal nativo poderia manter uma, mas aí cada mesa carrega a própria sessão com a venue, a própria rota de rede e o próprio estado de sequência. Com mais do que uns poucos traders, o lugar do núcleo é um gateway perto da venue: em colocation quando a distância domina o orçamento de latência, em uma região de nuvem próxima quando os traders clicam à mão e as decisões deles levam muito mais tempo do que o caminho de rede.

Do que a camada de sessão FIX cuida, e o que fica por sua conta?

A camada de sessão dá a você processamento ordenado e um jeito de pedir as mensagens perdidas. Ela não obriga o outro lado a reenviar todas. O padrão técnico FIX Session Layer (junho de 2020) define as regras.

  • As mensagens são processadas na ordem do MsgSeqNum(34). Um número maior que o esperado é um gap, respondido com um ResendRequest(35=2); a forma recomendada define EndSeqNo(16) como 0, pedindo tudo a partir da primeira mensagem que falta
  • Nada depois de um gap é processado antes dele. No exemplo do padrão, as mensagens 3 a 5 “não deveriam ser processadas antes da mensagem 2”
  • Um número menor que o esperado sem PossDupFlag(43)=Y deveria encerrar a sessão com um Logout, depois do qual a conexão é derrubada
  • Mensagens reenviadas carregam PossDupFlag(43)=Y, e decidir se uma delas já foi processada é trabalho do receptor
  • Quem reenvia pode pular mensagens de aplicação. No caso de ordens, o remetente “pode optar por não retransmiti-las porque passou tempo demais” e as pula com um SequenceReset(35=4) em que GapFillFlag(123)=Y

Daí vêm três deveres. Persista os números de sequência a cada envio e a cada recebimento; senão, um reinício ou pede o dia inteiro de novo ou faz você ser desconectado por números baixos demais. Guarde quais valores de ExecID(17) já foram aplicados, já que o padrão deixa a detecção de duplicatas para o receptor. E termine toda reconexão com uma checagem do status das ordens, via OrderMassStatusRequest(35=AF) onde a venue oferecer suporte: um gap fill sobre uma ordem significa que falta um fato na sua visão dela.

Como o gerenciador de ordens deve ler um ExecutionReport?

Um ExecutionReport(35=8) traz dois campos fáceis de confundir. Na definição do FIX, ExecType(150) “descreve o ExecutionRpt específico (p. ex., Pending Cancel), enquanto OrdStatus(39) sempre identificará o status atual da ordem (p. ex., Partially Filled)”. Conduza a máquina de estados pelo evento e use o status como verificação cruzada.

  • Pending New (A) e New (0): a venue recebeu a ordem e, depois, a aceitou
  • Trade (F): uma execução parcial ou total. Antes do FIX 4.3, as execuções eram ExecType 1 e 2, então um adaptador para uma contraparte FIX 4.2 as mapeia para Trade
  • Pending Cancel (6), Canceled (4), Pending Replace (E), Replaced (5), Rejected (8), Expired (C)
  • Trade Correct (G) e Trade Cancel (H): uma execução pode ser corrigida ou anulada a posteriori, então até a quantidade executada pode diminuir

O dicionário FIX define LeavesQty(151) como OrderQty(38) menos CumQty(14) enquanto a ordem está ativa, o que faz dele um invariante barato de verificar em cada relatório de uma ordem ativa. Um relatório que o quebre, ou um ExecType sem transição a partir do estado atual, deveria congelar a ordem e disparar um alerta. É adivinhando que um terminal acaba mostrando uma posição com a qual a venue não concorda.

Como um terminal lida com corridas entre cancelamento e substituição?

Um scalper muitas vezes altera uma ordem antes de a venue ter respondido à alteração anterior. Cada corrida abaixo é uma mensagem que se cruza com outra na rede.

  • A ordem é executada por completo enquanto o seu cancelamento está em trânsito. A venue responde com um OrderCancelReject(35=9), normalmente com CxlRejReason(102)=0, “Too late to cancel”, e o terminal precisa mostrar a execução e a posição resultante, não a posição zerada que o trader pediu
  • Uma segunda modificação sai antes de a primeira ser confirmada e pode voltar rejeitada com CxlRejReason(102)=3, ordem já com cancelamento ou substituição pendente. Mantenha uma única substituição em trânsito por ordem e consolide os pedidos seguintes no preço e no tamanho mais recentes
  • Cada substituição carrega um novo ClOrdID(11), e OrigClOrdID(41) aponta para o anterior, “NÃO para a ordem inicial do dia”. Uma execução que cruza com uma substituição pode chegar com um ID mais antigo do que o que acabou de ser enviado, então todo ID da cadeia precisa apontar para a mesma ordem
  • A sessão cai com ordens em repouso. O Cancel on Disconnect da CME, por exemplo, cancela, após uma desconexão involuntária, as ordens em repouso de futuros e opções de uma sessão iLink com COD habilitado, mas não as ordens GTC e GTD. Mapeie quais ordens sobrevivem a uma desconexão em cada venue antes do go-live

O drop copy dá uma segunda visão. A CME descreve o serviço dela como cópias em tempo real de relatórios de execução e de confirmações enviadas “por um caminho separado e dedicado”. Reconcilie as posições contra ele no núcleo e trate qualquer divergência com a sessão de ordens como um incidente.

Como o livro de ofertas local é construído a partir de feeds L2 e L3?

Um feed L2 publica níveis de preço. A Binance documenta um procedimento de snapshot mais atualizações: bufferizar o stream, buscar um snapshot, descartar os eventos no buffer que o snapshot já contém, aplicar o resto em ordem e recomeçar do zero se um ID de atualização for pulado. Os snapshots dela param em 5000 níveis por lado, então os níveis mais profundos continuam desconhecidos até mudarem.

Um feed L3 publica ordens individuais. No Nasdaq TotalView-ITCH 5.0, uma mensagem Add Order carrega um número de referência da ordem, as mensagens de modificação seguintes se referem a ele e, quando as ações exibidas chegam a zero, “a ordem está morta e deveria ser removida do book”. O construtor mantém um mapa do número de referência para a ordem e agrega os níveis a partir dele: mais memória e uma busca por mensagem, em troca da contagem de ordens por nível e de uma estimativa do lugar da sua própria ordem na fila.

Para a escada de preços, um array indexado por preço em torno dos melhores preços costuma superar uma árvore, já que os preços andam em ticks dentro de uma faixa contígua. Um único escritor por instrumento é dono do book e registra o número de sequência que ele reflete; a recuperação de gaps e o fan-out são tratados no artigo sobre dados de mercado sob rajadas. O terminal acrescenta uma regra: um book em recuperação é desenhado como em recuperação, nunca como ao vivo.

O que um painel de scalping precisa receber do núcleo?

O painel é uma escada de profundidade a partir da qual você opera. Um clique em um preço envia uma ordem limitada, um arraste a move, um atalho de teclado envia um tamanho predefinido ou zera a posição. Sem diálogo de confirmação, a rede de segurança fica no núcleo, que verifica tamanho máximo, bandas de preço e exposição por conta em cada ordem de um clique. O artigo sobre risco pré-negociação no caminho da ordem trata dessas checagens.

Para ordens bracket e OCO, decida primeiro onde elas vivem. O FIX define ContingencyType(1385) em NewOrderList(35=E), incluindo One Cancels the Other e One Triggers the Other. Use a versão da venue onde ela existir; caso contrário, o núcleo a emula acompanhando as execuções e enviando a outra perna. Nunca emule em um processo de tela: um laptop que entra em suspensão com uma posição desprotegida é o caso para o qual o bracket foi pensado.

A posição vem das execuções, correções e anulações incluídas, e o núcleo a marca a mercado contra o book local a cada mudança; a tela lê a posição e o PnL uma vez por frame.

Como medir a latência do pressionamento de uma tecla até a ordem na rede?

Um scalper se importa com duas cadeias: da tecla ou do clique até a ordem saindo da placa de rede, e do pacote de dados de mercado até o pixel alterado. Cada elo precisa do próprio timestamp.

  • O evento de entrada, com o timestamp do sistema operacional
  • O comando entrando no núcleo, depois do salto da tela até o gateway
  • A decisão de risco concluída e a mensagem FIX entregue ao socket
  • O pacote saindo do adaptador. No Linux, SO_TIMESTAMPING pode retornar timestamps de transmissão e de recepção “gerados pelo adaptador de rede”
  • Na outra direção: pacote recebido, book atualizado, frame apresentado

Reporte cada elo como percentis sob uma carga que você especifica, não como uma média de um mercado calmo. Um número isolado sem os pontos de medição não pode ser comparado com nenhum outro número.

A escada de preços deve ser nativa ou web, com WebGL e WebAssembly?

O MDN aponta 60 Hz como a taxa de atualização de tela mais comum, com 120 e 144 Hz também amplamente usados, o que dá 16.7 ms por frame a 60 Hz e menos de 7 ms a 144 Hz. Um feed de bolsa movimentado pode mudar o book muitas vezes dentro de um único frame, então, em qualquer tecnologia, o núcleo aplica cada atualização e a tela desenha o estado mais recente uma vez por frame.

  • Uma tela nativa em Rust desenhando pela GPU dá controle total do laço de renderização e da entrada, e uma única linguagem do socket ao pixel, ao custo de instaladores e atualizações para cada sistema operacional que você suporta
  • Uma tela no navegador, com a escada de preços em canvas ou WebGL e a decodificação do book em WebAssembly, não instala nada, e o OffscreenCanvas consegue renderizar “dentro de um contexto de worker”, segundo o MDN. O custo é menos controle sobre o timing e um runtime com garbage collector entre o clique e o comando
  • Uma UI web dentro de um shell de desktop mantém uma única base de código e traz junto o consumo de memória e o runtime de um navegador

O MDN também observa que a maioria dos navegadores pausa o requestAnimationFrame em abas em segundo plano, então nada que precise continuar rodando pode viver na página. Uma tela web sobre um núcleo em Rust atende ao requisito; um stack web no caminho da ordem, não.

Como testar um terminal de trading antes que ele toque uma venue real?

As venues decidem quando você pode entrar. A CME, por exemplo, “exige que todos os sistemas de clientes que transacionam no CME Globex via roteamento de ordens iLink ou que processam dados de mercado do CME Group sejam certificados pelo AutoCert+”, a ferramenta de testes automatizados dela. Segundo a CME, a certificação cobre mensageria, processamento e recuperação de eventos anormais de mensagens, e os testes funcionais dela rodam a no máximo 10 transações por segundo. Passar na certificação não diz o que a escada de preços faz na abertura com um trader clicando.

Ensaie as falhas no seu próprio simulador de venue: um gap fill sobre ordens depois de uma reconexão, tarde demais para cancelar, uma substituição rejeitada enquanto pendente, uma anulação de negócio, uma sessão derrubada com ordens em repouso, um gap no feed no meio de uma rajada enquanto o trader clica. Depois, reproduza no núcleo tráfego FIX e dados de mercado gravados. A mesma entrada precisa produzir os mesmos estados de ordem e o mesmo book a cada rodada, o que transforma o relato de bug de um trader em um teste.

Você deve construir um terminal de trading, comprar um ou construir em torno de um engine FIX licenciado?

Compre um terminal pronto quando as suas venues estiverem na lista dele e o seu fluxo de trabalho for padrão. Construa quando a tela fizer parte do motivo pelo qual os seus clientes escolhem você, quando as suas venues não forem suportadas ou quando você precisar ser dono do caminho da ordem e das checagens de risco dele. O caminho do meio licencia um engine FIX ou adaptadores de venue e constrói o resto.

Quais empresas de fato construíram um, e que evidências você deve pedir?

Trate “já construímos um” como uma afirmação que o fornecedor prova na primeira reunião. Seis pedidos fazem a maior parte do trabalho.

  • Uma demo no ambiente de produção ou de teste de uma venue, não no simulador do próprio fornecedor, com a conexão derrubada no meio para mostrar o que a escada de preços e o blotter fazem durante a recuperação
  • As venues que certificaram a conexão FIX deles: versão do FIX ou dialeto da venue, data, e se o sistema certificado é o que eles construiriam para você
  • Como os números de latência deles foram medidos: quais pontos de medição, timestamps de hardware ou de software, quais percentis, sob qual carga e o que o número deixa de fora
  • Um incidente contado em detalhe, como um gap fill sobre ordens ativas ou uma anulação de negócio: o que a tela mostrou, o que a venue disse, o que mudou no código depois
  • O código-fonte completo, as instruções de build e os passos de deploy, uma lista do que continua sendo deles e um build em uma máquina limpa sem a ajuda deles
  • De onde vem o engine FIX, se é desenvolvimento próprio, open source ou licenciado, e em que termos você continua a usá-lo

Onde a amBrain se encaixa?

A amBrain é uma empresa de engenharia de software de Yerevan, Armênia, que constrói plataformas de trading de baixa latência, matching engines e sistemas de real-time bidding em Rust. A amBrain constrói software desde 2019.

A amBrain constrói infraestrutura de trading algorítmico: execução de ordens, dados de mercado e controles de risco pré-negociação. Os serviços dela em trading incluem desenvolvimento de terminais de trading, sistemas de gestão de ordens e integração com bolsas via protocolo FIX.

Dois números são medidos nos caminhos que a amBrain constrói: latência de dados de mercado abaixo de 5 ms e latência de risco abaixo de 1 ms. Nenhum dos dois é um número da tecla até a rede, então peça os pontos de medição e a carga por trás de ambos, como com qualquer fornecedor.

A amBrain construiu o terminal de trading para a Spectre Trade. A amBrain também construiu uma mini-exchange que roda em produção na colocation da MOEX. O design deste artigo é geral. Ele não descreve nenhum dos dois sistemas nem cita os protocolos que um ou outro usa.

Três formatos: entrega completa, time dedicado ou engenheiros embarcados no seu time. O cliente mantém a propriedade integral do produto e do código, exceto dos nossos componentes reutilizáveis.

Antes da primeira call com qualquer fornecedor, incluindo a amBrain, anote as suas venues, a versão ou o dialeto de FIX que cada uma fala e a latência de que você precisa entre pontos de medição definidos.

Tem um projeto assim na mesa?

Traga sua arquitetura atual e o modo de falha que preocupa você, e vamos analisá-lo juntos em meia hora.