Skip to content

Repository files navigation

Pricetime

Português · English

Casa ordens de compra e venda de um mercado e registra cada negócio — o motor por trás de uma bolsa ou corretora.

ci

Painel do Pricetime: livro de ofertas, gráfico de preço, profundidade e verificação de invariantes ao vivo

Como rodar · Garantias · Arquitetura

Visão geral

Um livro de ofertas mantém, a cada instante, todas as ordens de compra e de venda ainda não executadas, e as executa quando um comprador aceita o preço de um vendedor — ou o contrário. O Pricetime é esse motor: recebe uma sequência de ordens (limit, market, IOC, FOK, além de cancelamento e substituição) e produz os negócios, respeitando a prioridade preço-tempo — melhor preço primeiro; no mesmo preço, quem chegou antes.

O difícil não é casar duas ordens. É garantir que, sobre milhões delas, o resultado esteja sempre correto: que a soma de dinheiro e de ações do sistema nunca mude, que a fila de prioridade nunca seja furada, que nenhum negócio saia a um preço pior que o topo do livro. E que a mesma sequência de ordens produza sempre o mesmo estado final, bit a bit, de modo que um mercado inteiro possa ser reconstruído a partir de uma única semente (seed) ou do log de eventos.

Essas propriedades são o produto. Por isso o documento abre pelas garantias, não pela lista de recursos: num sistema que move dinheiro, a correção é a funcionalidade, e cada garantia abaixo vem com o comando que a prova.

Garantias

Cada invariante é verificada por testes de propriedade (fast-check), que geram milhares de fluxos de ordens aleatórios e conferem a propriedade após cada comando. Todas rodam por um único comando: npm test.

Invariante Descrição Prova
Conservação A soma de caixa e a soma de ações entre todas as contas permanecem em zero — nada é criado nem destruído. npm test
Livro não cruzado A melhor compra fica sempre estritamente abaixo da melhor venda. npm test
Prioridade preço-tempo Um negócio só ocorre contra a melhor ordem elegível do lado oposto; no mesmo preço, contra a mais antiga. npm test
Topo do livro Nenhum negócio executa a um preço pior que o melhor disponível no momento. npm test
Replay determinístico A mesma sequência de comandos produz o mesmo log de eventos e o mesmo estado final, comparados por um digest estável. npm test
Event sourcing O estado reconstruído dobrando apenas o log de eventos é idêntico ao do motor vivo. npm test

As verificações estão em src/engine/invariants.ts (avaliadas também ao vivo na interface) e os testes de propriedade em src/engine/engine.test.ts.

Como rodar

Requer Node 20 ou superior.

npm install
npm run dev        # abre em http://localhost:3000

Demais comandos:

npm test           # testes unitários e de propriedade
npm run bench      # benchmark de throughput e latência
npm run typecheck  # verificação de tipos
npm run build      # build de produção

Arquitetura

Fluxo de dados: um comando entra no motor, que emite eventos; qualquer visão derivada é uma dobra sobre esse log.

comando ──▶ motor ──▶ eventos ──▶ projeções
(place/cancel/replace)          (livro, negócios, saldos — fold para reconstruir)

Módulos:

src/
  engine/         motor determinístico — puro, só inteiros, sem dependências
    book.ts       dois lados; array de preços ordenado + níveis FIFO
    engine.ts     matching, tipos de ordem, cancel/replace, liquidação
    replay.ts     projeção do log de eventos + digest estável do estado
    invariants.ts verificações de invariante (compartilhadas com os testes)
    rng.ts        PRNG sfc32 semeável (só no simulador, nunca no motor)
  sim/            simulador de fluxo de ordens semeado
  worker/         Web Worker que hospeda um motor + simulador ao vivo
  server/         event store: em memória + adapter Postgres (schema.sql)
  components/     livro, gráficos (canvas), tape, controles
  app/            Next.js App Router; feed SSE em /api/feed
bench/            harness de throughput + latência

Decisões estruturais:

  • O motor é uma máquina de estados de thread única: submit(comando) → eventos. Não há relógio de parede nem aleatoriedade; a prioridade temporal é um contador monotônico. É o que torna o replay determinístico.
  • Toda aritmética é inteira — preços em ticks, tamanhos em lotes. Sem ponto flutuante no motor, não há erro de arredondamento acumulado.
  • O mesmo código do motor roda no navegador (Web Worker) e no Node (testes e benchmark), por ser TypeScript puro sem dependências. O worker mantém o matching fora do thread principal.

Stack: Next.js 15 (App Router), React 19, TypeScript em modo strict, Tailwind CSS v4, Vitest e fast-check. Gráficos em canvas, sem biblioteca de charting.

Alternativas consideradas

  • Motor em TypeScript, não Rust→WASM. O pipeline Rust→WASM foi descartado: acrescenta complexidade de build sem ganho nesta escala e impede que o mesmo motor rode no navegador e no CI. TypeScript puro roda nos dois lados.
  • Inteiros (ticks/lotes), não ponto flutuante. Floats foram descartados porque o erro de arredondamento acumulado quebraria tanto a conservação quanto a igualdade bit a bit dos replays.
  • Livro em array de preços ordenado, não árvore balanceada. Para a profundidade de um livro real — na ordem de centenas de níveis vivos — o splice O(n) age sobre um array pequeno e cache-friendly, e o acesso é estreito (melhor nível + nível por preço), então a estrutura é trocável sem tocar no matching. Uma árvore ou skip-list entraria no lugar para livros ilimitados. Documentado em src/engine/book.ts.
  • Motor client-side em Web Worker, não server-side. Rodar o motor no navegador elimina timeout de função serverless e custo de servidor e mantém o replay determinístico no cliente.
  • Substituição = cancelar + reinserir, não amend com prioridade preservada. Qualquer mudança de preço ou aumento de tamanho recoloca a ordem no fim da fila. A regra simples mantém o log de eventos uma sequência limpa e dobrável, e a semântica sem ambiguidade.

Benchmarks

Método: um fluxo semeado de 1.000.000 de ordens é gravado e depois reexecutado contra um motor frio, cronometrando apenas o laço de submit com hrtime por ordem; as latências viram percentis. O script está em bench/run.ts e é reproduzível com npm run bench.

Execução de referência — win32/x64, Node v24.18.0, núcleo único, aritmética inteira; gravada em bench/results.json:

Métrica Valor
Throughput ~2,47M ordens/s
Latência p50 ~0,3 µs/ordem
Latência p99 ~1,3 µs/ordem
Latência p99.9 ~14 µs/ordem
Ordens casadas 1.000.000
Negócios gerados 714.897

Os valores absolutos variam com o hardware; o benchmark é commitado para poderem ser reproduzidos e comparados.

Testes

  • Semântica de matching (unitários): cada tipo de ordem, execução ao preço do maker, prioridade FIFO dentro do nível, remanescente de market/IOC/FOK, cancel e replace.
  • Invariantes (property-based, fast-check): conservação, livro não cruzado, prioridade, topo do livro, determinismo e reconstrução por event sourcing, sobre fluxos de ordens aleatórios.
  • Event store (round-trip): dobrar o log persistido reproduz o digest do motor vivo.

Rodam por npm test. O mesmo comando roda no CI (.github/workflows/ci.yml), junto de typecheck, lint, build e benchmark.

Limitações

  • Não há prevenção de auto-negociação: uma conta pode casar com a própria ordem. A conservação continua válida (o efeito líquido é zero).
  • A persistência não está ligada por padrão. O event store tem implementação em memória; o adapter Postgres (src/server/eventstore.ts, src/server/schema.sql) assume um único escritor.
  • O livro usa array de preços ordenado, adequado a profundidade limitada, não a um livro ilimitado.
  • O mercado é simulado a partir de uma seed; não há dados de mercado reais nem dinheiro real.
  • A cadência da simulação ao vivo na interface é limitada para leitura e não corresponde ao throughput de pico do benchmark.

Licença

Sem licença definida — todos os direitos reservados.

Autoria: Igor Bahia · github.com/igorjba/pricetime

About

Matching engine com prioridade preço-tempo e replay determinístico por event sourcing. Invariantes de conservação e prioridade provadas por property testing, não afirmadas.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages