Real-Time Trading Terminal with Charts and Order Book
When a trading stream delivers 2000+ order book updates per second, the browser starts to lag, data becomes inconsistent, and the trader loses money. We solve these problems at the architecture level: proper stack selection, differential updates, and rendering optimization. With 8+ years of experience in blockchain development and 15+ implemented trading interfaces, our platforms process up to 10,000 orders per second with less than 5 ms latency. We use proven solutions: WebSocket for streams, Redis for caching, and ClickHouse for history. This ensures not a single tick is lost and the interface remains responsive.
Recently we completed a project for a crypto exchange where the data stream reached 3000 updates per second. Our solution processed it without a single missed tick, and the interface latency did not exceed 2 ms. For another exchange, we optimized the architecture so that infrastructure costs decreased by 40% through efficient use of WebSocket gateways and Redis.
Technologies Used in Professional Terminals
Frontend: React 18 + TypeScript, Zustand for state management, TradingView Lightweight Charts, react-virtual for list virtualization. Backend: FastAPI (Python) or Fastify (Node.js) as WebSocket gateway, Redis for caching and pub/sub. Storage: ClickHouse or TimescaleDB for historical OHLCV data.
Data flow: Binance WS → Exchange Connector → Redis PubSub → WS Gateway → Browser. The gateway multiplexes data for all clients, allowing hundreds of connections with minimal latency.
Order Book Implementation: Avoiding Desync
The order book is built using differential updates. First, a snapshot is loaded via REST, then changes come via WebSocket. The key issue is maintaining sequence: each diff contains U (first update id) and u (last update id). If a snapshot is missed, data becomes desynchronized. We buffer diffs and check lastUpdateId:
Order Book Manager Code
class OrderBookManager { private bids: Map<number, number> = new Map(); private asks: Map<number, number> = new Map(); private lastUpdateId: number = 0; private buffer: OrderBookDiff[] = []; async initialize(symbol: string) { const ws = this.connectToStream(`${symbol.toLowerCase()}@depth`); const snapshot = await fetchOrderBookSnapshot(symbol, 1000); this.lastUpdateId = snapshot.lastUpdateId; this.bids = new Map(snapshot.bids.map(([p, q]) => [+p, +q])); this.asks = new Map(snapshot.asks.map(([p, q]) => [+p, +q])); for (const diff of this.buffer) { if (diff.U <= this.lastUpdateId + 1 && diff.u >= this.lastUpdateId + 1) { this.applyDiff(diff); } } this.buffer = []; } applyDiff(diff: OrderBookDiff) { if (diff.U !== this.lastUpdateId + 1) { console.error('Gap detected, reinitializing...'); this.initialize(this.symbol); return; } for (const [price, qty] of diff.b) { if (+qty === 0) this.bids.delete(+price); else this.bids.set(+price, +qty); } for (const [price, qty] of diff.a) { if (+qty === 0) this.asks.delete(+price); else this.asks.set(+price, +qty); } this.lastUpdateId = diff.u; this.notifySubscribers(); } getTopLevels(depth: number = 20) { const sortedBids = [...this.bids.entries()] .sort(([a], [b]) => b - a) .slice(0, depth); const sortedAsks = [...this.asks.entries()] .sort(([a], [b]) => a - b) .slice(0, depth); return { bids: sortedBids, asks: sortedAsks }; } } Steps to implement the order book:
- Fetch the initial snapshot via REST.
- Connect to WebSocket stream.
- Buffer incoming diffs.
- Apply diffs after snapshot, checking sequence.
- Render only visible levels using virtualization.
For display, we use virtualization via @tanstack/react-virtual to render only visible rows. This allows handling 500+ levels without performance loss. Our differential update approach is 3 times faster than full reload every 100 ms, and buffering guarantees consistency even during temporary network disruptions.
Charts: TradingView Lightweight Charts vs Alternatives
For candlestick charts we use Lightweight Charts. This library is 2 times faster than Chart.js for candle rendering and provides built-in support for financial data. Comparison:
| Library | Performance | Customization | Financial Data |
|---|---|---|---|
| TradingView LWC | High (up to 60 FPS) | High | Built-in support for candles |
| Chart.js | Medium (20-30 FPS) | Medium | Requires customization |
| D3.js | High (complexity) | Maximum | Low (custom components) |
Example setup:
Trading Chart Component Code
import { createChart, CandlestickSeries } from 'lightweight-charts'; function TradingChart({ symbol }: { symbol: string }) { const chartContainerRef = useRef<HTMLDivElement>(null); const seriesRef = useRef<CandlestickSeries>(); useEffect(() => { const chart = createChart(chartContainerRef.current!, { width: chartContainerRef.current!.clientWidth, height: 400, layout: { background: { color: '#1a1a2e' }, textColor: '#d1d4dc', }, grid: { vertLines: { color: '#2d2d4e' }, horzLines: { color: '#2d2d4e' }, }, crosshair: { mode: 1 }, timeScale: { borderColor: '#485c7b', timeVisible: true }, }); const candleSeries = chart.addCandlestickSeries({ upColor: '#26a69a', downColor: '#ef5350', borderUpColor: '#26a69a', borderDownColor: '#ef5350', }); seriesRef.current = candleSeries; loadHistoricalData(symbol, '1h').then((candles) => { candleSeries.setData(candles); chart.timeScale().fitContent(); }); const unsubscribe = wsGateway.subscribe(`candle:${symbol}:1h`, (candle) => { candleSeries.update(candle); }); return () => { unsubscribe(); chart.remove(); }; }, [symbol]); return <div ref={chartContainerRef} className="chart-container" />; } Lightweight Charts is 3 times faster than Chart.js on large datasets (10,000+ candles).
Why Proper Reconnect Matters
On connection loss, automatic reconnection with exponential backoff (1 s → 30 s) minimizes downtime. We use a custom hook that handles reconnect and subscription resubmission:
WebSocket Hook Code
export function useWebSocket(url: string) { const wsRef = useRef<WebSocket | null>(null); const reconnectTimerRef = useRef<NodeJS.Timeout>(); const reconnectCountRef = useRef(0); const connect = useCallback(() => { wsRef.current = new WebSocket(url); wsRef.current.onclose = (e) => { if (!e.wasClean) { const delay = Math.min(1000 * 2 ** reconnectCountRef.current++, 30000); reconnectTimerRef.current = setTimeout(connect, delay); } else { reconnectCountRef.current = 0; } }; wsRef.current.onerror = () => wsRef.current?.close(); }, [url]); useEffect(() => { connect(); return () => { clearTimeout(reconnectTimerRef.current); wsRef.current?.close(); }; }, [connect]); return wsRef; } In practice, reconnect takes less than 2 seconds, which is critical for high-frequency trading. Learn more about the WebSocket API on MDN.
How We Optimize Performance
Critical points: throttling updates, memoizing components, rendering depth chart via Canvas. We use requestAnimationFrame for rendering to avoid blocking the thread. For example, with 50+ order book updates per second, a buffer accumulates diffs, and rendering occurs no more than once per frame. This keeps FPS at 60 even under peak loads. Our optimizations reduce CPU load by 40% compared to a naive implementation. Consequently, operational costs on server infrastructure decrease by 30%, saving up to $2,000 per month for a medium-scale exchange.
Development Process and What's Included
| Stage | Duration | Result |
|---|---|---|
| Requirements analysis | 1-2 days | Technical specification |
| Architecture | 2-3 days | Documentation |
| Implementation | 2-4 weeks | Working code |
| Testing | 1 week | Load report |
| Deployment | 2-3 days | Ready solution |
The turnkey development includes: commented frontend and backend source code, exchange integration (Binance, Bybit, OKX, etc.), WebSocket gateway setup, deployment and operations documentation, 2-hour team training, and 3 months of free support after launch.
We also provide consulting on stack and architecture selection. Contact us to discuss your project. Order turnkey web platform development and get a ready solution with low latency.







