Arquitetura

Como ele continua rápido em um notebook comum.

Telas de trading são pesadas: históricos longos, ticks que não param, muitos gráficos em uma página. O TradeCanvas faz só o trabalho de que cada quadro precisa, na thread principal, sem código de terceiros. Esta página mostra como, e onde ele ainda está sendo melhorado.

Trabalho em andamento: o motor já é rápido o bastante para produção, mas ainda não tão rápido quanto vai ser. A última seção lista o que sabemos que falta.

01

Pacotes pequenos, uma só direção

Cada pacote depende só dos que estão abaixo dele. Nada de fora do TradeCanvas roda na sua página: os componentes de framework recebem React, Vue ou Svelte como peers, e o renderizador WebGL é um chunk separado que só carrega quando um gráfico pede.

0 dependências de terceiros em runtime

02

O caminho de um tick

Um preço chega do seu feed e alcança a tela em um quadro. Cada etapa faz só a parte que mudou.

  1. 1Seu feedDataAdapterWebSocket, REST, uma corretora: qualquer coisa com histórico e um stream.
  2. 2Gerenciador de streamStreamManagerPrimeiro o histórico, depois o ao vivo. Respostas de uma requisição antiga são descartadas; um feed em silêncio é notado em até 30 s.
  3. 3Agregador de ticksTickAggregatorOs ticks entram em um buffer circular de 1.000 e são incorporados à barra do seu tempo gráfico.
  4. 4IndicadoresIndicatorEngineRecalculados a partir da última barra, não do início da série.
  5. 5ViewportViewportSó as barras na tela são percorridas; a escala de preço vem apenas delas.
  6. 6Loop de renderizaçãoRenderLoopNão importa quantos ticks cheguem, a cena é desenhada no máximo uma vez por quadro de animação.
  7. 7Canvas 2D ou WebGLRenderEngineDois canvas, e um canvas de GPU por baixo deles quando o WebGL está ligado.
03

Um tick custa uma barra, não o histórico inteiro

Quando um tick muda a última barra, cada indicador retoma a partir dessa barra (e das poucas anteriores que sua fórmula consulta) em vez de recalcular a série. Se as barras que ele guarda não batem mais, ele volta a um recálculo completo, então uma correção nunca passa despercebida.

Cerca de 20 indicadores têm sua própria atualização incremental, e outros 31 são construídos sobre uma base que retoma ponto a ponto. Indicadores que leem a linha de outro indicador também retomam.

0 / 64 Medido em 100.000 barras com quatro indicadores: cerca de 0,001 ms por tick
04

Com zoom afastado: uma coluna por pixel

Quando as barras ficam mais estreitas que um pixel, desenhar cada uma desperdiça um trabalho que o olho não vê. Candles, barras, máxima-mínima e volume são desenhados como uma coluna por pixel, com a primeira abertura, a máxima mais alta, a mínima mais baixa e o último fechamento das barras sob ela. Linhas e áreas mantêm cerca de dois pontos por pixel (LTTB); linhas e bandas de indicadores desenham um traço por coluna de pixels.

barras visíveis
313
barras por pixel
0,39
formas desenhadas
313

Uma barra nunca fica mais estreita que um quarto de pixel CSS, então um gráfico com um milhão de barras mostra no máximo cerca de quatro barras por pixel com o zoom totalmente afastado: cerca de 6.400 em um gráfico de 1.600 px. Deslizar pelo resto é o que o benchmark de um milhão de barras mede.

05

Dois canvas, para que um hover não custe quase nada

A cena (grade, série, indicadores, desenhos, ordens) fica em um canvas; a mira, a legenda, os rótulos dos eixos e a contagem regressiva ficam em um canvas leve acima dele. Mover o mouse redesenha só o de cima. Uma atualização de dados redesenha a cena uma vez, no próximo quadro.

Cada canvas do tamanho da tela custa ao navegador uma composição por quadro, então o motor mantém poucos: um canvas de fundo para o desenho só em 2D existe apenas enquanto algo precisa dele.

06

WebGL por gravação, não por reescrita

O gráfico desenha com chamadas de Canvas 2D. Com o WebGL ligado, essas chamadas vão para um gravador que transforma caminhos, preenchimentos e traços em comandos de GPU em pixels do dispositivo. O que a GPU não consegue desenhar igual fica no Canvas 2D, na ordem, então a imagem é a mesma de qualquer jeito.

  • renderer: 'auto' pede ao navegador um contexto sem ressalva grave de desempenho, então uma máquina cujo WebGL rodaria em software fica com o Canvas 2D.
  • No máximo 8 gráficos por página mantêm um contexto WebGL (setMaxWebGLCharts); os outros esperam numa fila e desenham com Canvas 2D.
  • Se a GPU perde o contexto, o gráfico segue com Canvas 2D e volta a montar em WebGL quando o contexto retorna.
  • Os candles são arredondados para pixels inteiros do dispositivo com a mesma regra nos shaders e na CPU, então os dois renderizadores os colocam nos mesmos pixels.
07

Feito para máquinas fracas

  • Canvas 2D por padrãoO WebGL é opcional até ser medido em mais dispositivos. Em GPU integrada com proporção de pixels alta, o gargalo é a composição do próprio navegador, não o desenho.
  • Um redimensionamento por quadroOs eventos de redimensionamento são agrupados em um por quadro, e um tamanho que não mudou não faz nada.
  • Histórico em páginasBarras antigas carregam de 500 em 500 conforme você rola para trás, uma requisição por vez, recuando depois de uma falha.
  • Sem piscar de carregamentoO véu de carregamento espera 200 ms, então uma troca rápida de ativo nunca pisca.
  • Download pequenoO gráfico headless tem 176 KB com gzip, o widget completo 315 KB, e o renderizador WebGL mais 18 KB quando usado.
  • Painéis que se atualizam no lugarO painel de cotações do bo-grid agrupa os ticks em uma atualização por quadro e redesenha só as células que mudaram.
08

O que ainda estamos melhorando

O motor não está pronto. Estes são os custos que conhecemos, vistos no código-fonte, e são as próximas coisas em que vamos trabalhar.

  1. Os indicadores se atualizam a cada atualização de barra, não uma vez por quadro: um feed que envia muitos ticks por quadro recalcula mais vezes do que desenha.
  2. As barras são guardadas como um array de objetos. Um armazenamento em colunas com arrays tipados usaria menos memória e seria percorrido mais rápido em históricos muito longos.
  3. Tudo roda na thread principal. Um worker para os cálculos dos indicadores existe no código, mas ainda não está ligado ao gráfico.
  4. O heatmap de profundidade guarda seus instantâneos em um array simples e varre todos os níveis em busca do máximo a cada quadro; o perfil de volume aloca seus intervalos a cada desenho.
  5. Alguns objetos ainda são alocados a cada quadro (caminhos, arrays pequenos), o que dá trabalho ao coletor de lixo.
  6. Em 2560×1400 e proporção de pixels 2 em GPU integrada, só a composição leva cerca de 23 ms, então ali os quadros WebGL ficam em média em 29 ms.
Meça na sua máquina →A auditoria roda estas cenas no seu navegador: um milhão de barras em Canvas 2D e WebGL, ticks ao vivo em 100.000 barras e um painel de 1.000 ativos.