Pular para o conteúdo
ZYNQORALABS

Checagens de risco pré-negociação no caminho da ordem

Como projetar controles de risco que rodam antes de cada ordem: quais checagens incluir, em que ordem, como manter estado sem locks e por que falhar fechado.

Por ZynqoraLabs · · 4 min de leitura

CompartilharLinkedInXWhatsApp

Controles de risco que rodam depois da execução apenas medem o estrago. Em sistemas de negociação, os controles que realmente protegem a operação ficam antes da ordem sair: no caminho entre a estratégia (ou o operador) e a bolsa. Reguladores reconhecem isso. Nos Estados Unidos, por exemplo, a Market Access Rule da SEC exige que corretoras com acesso ao mercado mantenham controles pré-negociação. E, independentemente da regulação, é a última linha de defesa contra erros de código, dados ruins e dedos rápidos.

Este artigo trata de como projetar esse portão para que ele seja correto, previsível e auditável.

Quais checagens incluir

O conjunto varia por mercado e instituição, mas um núcleo aparece em quase todos os sistemas:

  • Kill switch: bloqueio imediato de novas ordens por conta, estratégia ou sistema inteiro.
  • Tamanho máximo da ordem: em quantidade e em valor financeiro.
  • Banda de preço: rejeita ordens com preço muito distante de uma referência recente, o que pega erros de digitação e de unidade.
  • Limite de posição e exposição: a posição projetada, se a ordem for executada, precisa ficar dentro do limite.
  • Limite de frequência: número máximo de ordens por intervalo, para conter loops descontrolados.
  • Detecção de duplicatas: a mesma ordem enviada duas vezes por retentativa não pode ser executada duas vezes.
  • Lista restrita: instrumentos que a conta não pode negociar.

Ordem das checagens

Execute as checagens da mais barata para a mais cara e pare na primeira falha. O kill switch é um booleano; o cálculo de exposição pode envolver vários instrumentos. Como a maioria das rejeições acontece nas regras baratas, o caminho comum fica rápido e a latência fica previsível.

Cada checagem deve ser uma função pura: recebe a ordem, o estado da conta e a visão de mercado, e devolve aprovação ou um motivo de rejeição. Funções puras são fáceis de testar exaustivamente e de reproduzir.

checks.rs
fn within_price_band(price: Decimal, reference: Decimal, band_bps: u32) -> bool {
    // |price - reference| / reference <= band
    let deviation = (price - reference).abs() / reference;
    deviation <= Decimal::from(band_bps) / Decimal::from(10_000)
}
 
fn is_duplicate(order: &Order, recent: &RecentOrders) -> bool {
    // client_order_id is unique per session; a retry reuses it.
    recent.contains(&(order.session, order.client_order_id))
}

Note o uso de tipos decimais. Preços e valores financeiros em ponto flutuante binário produzem erros de arredondamento que, em uma comparação de limite, viram aprovações ou rejeições erradas.

Estado sem locks: um escritor por conta

Limites de posição dependem de estado: posições abertas, ordens pendentes, limites consumidos. Se várias threads atualizam esse estado com locks, a latência passa a depender da disputa entre elas, e a ordem de processamento deixa de ser determinística.

Uma alternativa robusta é particionar as contas em shards e ter um único escritor por shard. Todas as ordens de uma conta passam pela mesma thread, em sequência. Não há locks, a latência fica estável e, para os mesmos eventos de entrada, as decisões são sempre as mesmas.

Um detalhe importante: o limite deve considerar ordens pendentes, não apenas posições executadas. Caso contrário, várias ordens enviadas em sequência rápida passam individualmente e, juntas, estouram o limite.

Falhar fechado

O que fazer quando o dado de referência está desatualizado, a conexão com o serviço de posições caiu ou o estado é inconsistente? A resposta segura é rejeitar. Uma banda de preço calculada sobre um preço de cinco minutos atrás aprova exatamente as ordens que deveria barrar.

Isso exige que cada dado de entrada carregue seu horário e que o sistema conheça a idade máxima aceitável para cada um. Falhar fechado tem custo operacional, então os motivos de rejeição precisam ser claros e monitorados: uma onda de rejeições por "dado velho" é um alerta de infraestrutura, não um problema de risco.

Auditoria e replay

Toda decisão precisa ser explicável depois: "por que esta ordem foi aceita às 10:31:07?". A forma mais confiável de responder é registrar cada evento de entrada (ordens, execuções, preços de referência, mudanças de limite) em um log append-only e derivar o estado de risco a partir dele.

Com processamento determinístico, reproduzir o log recria exatamente as mesmas decisões. Isso serve para auditoria, para investigação de incidentes e para testes: um teste de regressão pode reprocessar um dia inteiro de eventos e verificar que nenhuma decisão mudou.

Como testar

Além de testes unitários por regra, três tipos de teste dão confiança real:

  1. Testes de propriedade: para qualquer sequência de ordens gerada aleatoriamente, a posição projetada nunca excede o limite.
  2. Replay determinístico: o mesmo log, processado duas vezes, produz decisões idênticas.
  3. Simulador de contraparte: um simulador de bolsa permite testar rejeições, execuções parciais e desconexões antes de qualquer conexão real.

Resumo

Um portão de risco pré-negociação bem projetado é simples por fora e rigoroso por dentro: checagens puras ordenadas por custo, um escritor por conta, tipos decimais, falha fechada diante de incerteza e um log que permite reconstruir qualquer decisão. Ele não decide o que negociar. Garante que o que for negociado esteja dentro das regras.