Architektur

Wie es auf einem gewöhnlichen Laptop schnell bleibt.

Trading-Oberflächen sind schwer: lange Historien, Ticks ohne Pause, viele Charts auf einer Seite. TradeCanvas erledigt nur so viel Arbeit, wie jedes Bild braucht, im Hauptthread, ohne Code von Drittanbietern. Diese Seite zeigt, wie, und wo noch daran gearbeitet wird.

In Arbeit: Die Engine ist schnell genug für den Einsatz, aber noch nicht so schnell, wie sie werden wird. Der letzte Abschnitt listet auf, was noch offen ist.

01

Kleine Pakete, eine Richtung

Jedes Paket hängt nur von denen darunter ab. Nichts außerhalb von TradeCanvas läuft auf Ihrer Seite: Die Framework-Komponenten nehmen React, Vue oder Svelte als Peer-Abhängigkeit, und der WebGL-Renderer ist ein eigener Chunk, der erst geladen wird, wenn ein Chart ihn anfordert.

0 Laufzeitabhängigkeiten von Drittanbietern

02

Der Weg eines Ticks

Ein Kurs kommt aus Ihrem Feed und erreicht den Bildschirm in einem Bild. Jede Stufe erledigt nur den Teil, der sich geändert hat.

  1. 1Ihr FeedDataAdapterWebSocket, REST, ein Broker: alles mit Historie und Stream.
  2. 2Stream-ManagerStreamManagerErst die Historie, dann live. Antworten auf eine veraltete Anfrage werden verworfen; ein verstummter Feed fällt innerhalb von 30 s auf.
  3. 3Tick-AggregatorTickAggregatorTicks landen in einem Ringpuffer für 1.000 und werden in die Kerze ihrer Zeiteinheit eingerechnet.
  4. 4IndikatorenIndicatorEngineAb der letzten Kerze neu berechnet, nicht ab dem Anfang der Serie.
  5. 5ViewportViewportDurchlaufen werden nur die Kerzen auf dem Bildschirm; die Preisskala ergibt sich allein aus ihnen.
  6. 6Render-SchleifeRenderLoopWie viele Ticks auch ankommen, die Szene wird höchstens einmal pro Animationsbild gezeichnet.
  7. 7Canvas 2D oder WebGLRenderEngineZwei Canvas und darunter eine GPU-Canvas, wenn WebGL aktiv ist.
03

Ein Tick kostet eine Kerze, nicht die ganze Historie

Ändert ein Tick die letzte Kerze, rechnet jeder Indikator ab dieser Kerze weiter (und ab den wenigen davor, auf die seine Formel zurückblickt), statt die Serie neu zu berechnen. Passen die Kerzen, die er sich merkt, nicht mehr, fällt er auf eine vollständige Neuberechnung zurück, damit keine Korrektur verloren geht.

Etwa 20 Indikatoren haben ein eigenes inkrementelles Update, und 31 weitere bauen auf einer Basis auf, die Punkt für Punkt weiterrechnet. Indikatoren, die die Linie eines anderen Indikators lesen, rechnen ebenfalls weiter.

0 / 64 Gemessen auf 100.000 Kerzen mit vier Indikatoren: etwa 0,001 ms pro Tick
04

Herausgezoomt: eine Spalte pro Pixel

Werden Kerzen schmaler als ein Pixel, verschwendet es Arbeit, jede einzeln zu zeichnen, denn das Auge sieht es nicht. Kerzen, Balken, Hoch-Tief und Volumen werden als eine Spalte pro Pixel gezeichnet, mit dem ersten Eröffnungskurs, dem höchsten Hoch, dem tiefsten Tief und dem letzten Schlusskurs der Kerzen darunter. Linien und Flächen behalten etwa zwei Punkte pro Pixel (LTTB); Indikatorlinien und Bänder zeichnen einen Strich pro Pixelspalte.

Kerzen im Bild
313
Kerzen pro Pixel
0,39
gezeichnete Formen
313

Eine Kerze ist nie schmaler als ein Viertel eines CSS-Pixels, daher zeigt ein Chart mit einer Million Kerzen ganz herausgezoomt höchstens etwa vier Kerzen pro Pixel: etwa 6.400 auf einem 1.600 px breiten Chart. Das Verschieben durch den Rest misst der Benchmark mit einer Million Kerzen.

05

Zwei Canvas, damit ein Hover fast nichts kostet

Die Szene (Raster, Serie, Indikatoren, Zeichnungen, Orders) liegt auf einer Canvas; Fadenkreuz, Legende, Achsenbeschriftungen und Countdown liegen auf einer schlanken Canvas darüber. Eine Mausbewegung zeichnet nur die obere neu. Ein Daten-Update zeichnet die Szene einmal neu, im nächsten Bild.

Jede bildschirmgroße Canvas kostet den Browser ein Überblenden pro Bild, daher hält die Engine ihre Zahl klein: Eine hintere Canvas für reines 2D-Zeichnen gibt es nur, solange etwas sie braucht.

06

WebGL durch Aufzeichnen, nicht durch Umschreiben

Der Chart zeichnet mit Canvas-2D-Aufrufen. Mit aktivem WebGL gehen diese Aufrufe an einen Recorder, der Pfade, Füllungen und Striche in GPU-Befehle in Gerätepixeln umsetzt. Was die GPU nicht identisch zeichnen kann, bleibt der Reihe nach auf Canvas 2D, sodass das Bild in beiden Fällen gleich ist.

  • renderer: 'auto' fragt den Browser nach einem Kontext ohne größere Leistungseinbußen, daher bleibt ein Rechner, dessen WebGL in Software laufen würde, bei Canvas 2D.
  • Höchstens 8 Charts pro Seite halten einen WebGL-Kontext (setMaxWebGLCharts); die übrigen warten in einer Warteschlange und zeichnen mit Canvas 2D.
  • Verliert die GPU den Kontext, zeichnet der Chart mit Canvas 2D weiter und baut sich auf WebGL neu auf, sobald der Kontext zurückkommt.
  • Kerzen werden in den Shadern und auf der CPU nach derselben Regel auf ganze Gerätepixel gerundet, daher setzen beide Renderer sie auf dieselben Pixel.
07

Gebaut für schwache Rechner

  • Standardmäßig Canvas 2DWebGL ist optional, bis es auf mehr Geräten gemessen wurde. Auf integrierter Grafik mit hohem Pixelverhältnis ist das Compositing des Browsers selbst der Engpass, nicht das Zeichnen.
  • Eine Größenänderung pro BildResize-Events werden zu einem pro Bild gebündelt, und eine unveränderte Größe bewirkt nichts.
  • Historie in SeitenÄltere Kerzen werden beim Zurückscrollen zu je 500 nachgeladen, eine Anfrage nach der anderen, mit Pause nach einem Fehler.
  • Kein Aufblitzen beim LadenDer Ladeschleier wartet 200 ms, daher blinkt ein schneller Symbolwechsel nie.
  • Klein im DownloadDer Headless-Chart hat mit gzip 176 KB, das komplette Widget 315 KB, der WebGL-Renderer bei Bedarf weitere 18 KB.
  • Tafeln, die an Ort und Stelle tickenDie Kurstafel bo-grid fasst Ticks zu einer Änderung pro Bild zusammen und zeichnet nur die geänderten Zellen neu.
08

Was wir noch verbessern

Die Engine ist nicht fertig. Das sind die Kosten, die wir aus dem Quellcode kennen, und sie sind das Nächste, woran wir arbeiten.

  1. Indikatoren aktualisieren sich bei jeder Kerzenänderung, nicht einmal pro Bild: Ein Feed, der viele Ticks pro Bild schickt, rechnet öfter, als er zeichnet.
  2. Kerzen werden als Array von Objekten gehalten. Ein spaltenweiser Speicher aus typisierten Arrays bräuchte weniger Speicher und ließe sich bei sehr langen Historien schneller durchlaufen.
  3. Alles läuft im Hauptthread. Ein Worker für die Indikatorberechnung existiert im Code, ist aber noch nicht an den Chart angebunden.
  4. Die Tiefen-Heatmap hält ihre Schnappschüsse in einem einfachen Array und durchsucht in jedem Bild jede Stufe nach ihrem Maximum; das Volumenprofil legt seine Buckets bei jedem Zeichnen neu an.
  5. Einige Objekte werden noch in jedem Bild neu angelegt (Pfade, kleine Arrays), was dem Garbage Collector Arbeit macht.
  6. Bei 2560×1400 und einem Pixelverhältnis von 2 auf integrierter Grafik braucht allein das Compositing etwa 23 ms, daher liegen WebGL-Bilder dort im Schnitt bei 29 ms.
Auf Ihrem Rechner messen →Das Audit spielt diese Szenen in Ihrem Browser ab: eine Million Kerzen mit Canvas 2D und WebGL, Live-Ticks auf 100.000 Kerzen und eine Tafel mit 1.000 Symbolen.