Розробка торговельного терміналу для криптовалют
Терміналу криптовалютної торгівлі — це інтерфейс, через який трейдер взаємодіє з ринком: бачить графіки, стакан, стрічку угод, керує ордерами та позиціями. Розробка серйозного терміналу — це багатомісячний проект, що вимагає глибокого розуміння як вимог UX трейдерів, так і технічних особливостей роботи з біржевими даними в реальному часі.
Ключові компоненти терміналу
Система графіків — серце терміналу. Відображення OHLCV свічок, технічних індикаторів, обсягів. TradingView Lightweight Charts — стандартний вибір для користувацьких рішень. Для повнофункціональних терміналів — TradingView Advanced Charts через Data Feed API або повністю користувацька реалізація на Canvas/WebGL.
Стакан ордерів — стакан заявок у реальному часі. Візуалізація рівнів bid/ask, depth chart (кумулятивний обсяг), групування за тиком. Критично: оновлення повинні відбуватися без перерисування всього компонента — лише змінені рядки.
Форма ордера — форма виставлення ордерів. Підтримка типів: market, limit, stop-limit, trailing stop. Розрахунок розміру у % від депозиту, у базовій/котираній валюті, за кількістю лотів.
Панель позицій/ордерів — керування відкритими позиціями та активними ордерами. Швидке закриття, зміна параметрів ордерів.
Стрічка угод — стрічка останніх угод з підсвічуванням крупних угод.
Портфель/Баланс — зведена інформація за рахунком: залишки, коефіцієнт маржі, P&L.
Архітектура frontend
// Структура React-додатку терміналу
interface TerminalLayout {
left: {
symbolSearch: SymbolSearchPanel;
watchlist: WatchlistPanel;
};
center: {
chart: ChartPanel;
orderBook: OrderBookPanel;
tradeFeed: TradeFeedPanel;
};
right: {
orderForm: OrderFormPanel;
positions: PositionsPanel;
orders: OrdersPanel;
balance: BalancePanel;
};
bottom: {
orderHistory: OrderHistoryPanel;
alerts: AlertsPanel;
};
}
Змінний розмір розташування через react-grid-layout або react-mosaic — трейдери хочуть налаштовувати розміщення панелей.
Керування станом WebSocket
Терміналу отримує дані з кількох потоків WebSocket одночасно. Управління цим станом — нетривіальне завдання:
// Zustand сховище для ринкових даних
import { create } from 'zustand';
interface MarketDataStore {
orderBook: OrderBook | null;
trades: Trade[];
currentPrice: number | null;
updateOrderBook: (diff: OrderBookDiff) => void;
addTrade: (trade: Trade) => void;
}
export const useMarketDataStore = create<MarketDataStore>((set, get) => ({
orderBook: null,
trades: [],
currentPrice: null,
updateOrderBook: (diff) => set((state) => {
if (!state.orderBook) return state;
const newBids = new Map(state.orderBook.bids);
const newAsks = new Map(state.orderBook.asks);
for (const [price, qty] of diff.bids) {
if (qty === 0) newBids.delete(price);
else newBids.set(price, qty);
}
for (const [price, qty] of diff.asks) {
if (qty === 0) newAsks.delete(price);
else newAsks.set(price, qty);
}
return {
orderBook: { bids: newBids, asks: newAsks, timestamp: diff.timestamp }
};
}),
addTrade: (trade) => set((state) => ({
trades: [trade, ...state.trades].slice(0, 1000), // останні 1000 угод
currentPrice: trade.price,
})),
}));
Продуктивність: віртуалізація та WebWorkers
Стакан ордерів з 200+ рівнями, який оновлюється 10 разів на секунду — серйозне навантаження на DOM. Рішення:
Віртуалізація списку (react-virtual, tanstack/virtual) — рендеримо лише видимі рядки. 500 рядків у DOM → 20 видимих рядків.
Web Workers для обчислень — важкі обчислення (агрегація стакану, розрахунок індикаторів) виносяться в Worker, щоб не блокувати потік UI:
// orderbook.worker.ts
self.onmessage = (e: MessageEvent) => {
const { type, data } = e.data;
if (type === 'PROCESS_DIFF') {
const processed = applyDiff(data.currentBook, data.diff);
const aggregated = aggregateByTick(processed, data.tickSize);
self.postMessage({ type: 'BOOK_UPDATED', data: aggregated });
}
};
requestAnimationFrame регулювання — оновлюємо DOM не частіше 60 разів на секунду, буферизуючи вхідні оновлення.
Backend: сервіс Gateway
Терміналу не підключається безпосередньо до бірж — це архітектурна помилка для виробничих систем. Проміжний сервіс Gateway:
- Мультиплексує одне з'єднання біржевого WebSocket для багатьох клієнтів
- Кешує поточний стан стакану
- Автентифікує підключення клієнтів
- Обмежує rate limits
from fastapi import FastAPI, WebSocket
from starlette.websockets import WebSocketDisconnect
import asyncio
app = FastAPI()
class MarketDataGateway:
def __init__(self):
self.subscribers: dict[str, list[WebSocket]] = {}
self.book_cache: dict[str, OrderBook] = {}
async def subscribe(self, symbol: str, ws: WebSocket):
if symbol not in self.subscribers:
self.subscribers[symbol] = []
asyncio.create_task(self.connect_to_exchange(symbol))
self.subscribers[symbol].append(ws)
# Відправляємо поточний снимок новому підписнику
if symbol in self.book_cache:
await ws.send_json(self.book_cache[symbol].to_dict())
async def broadcast(self, symbol: str, data: dict):
dead_connections = []
for ws in self.subscribers.get(symbol, []):
try:
await ws.send_json(data)
except Exception:
dead_connections.append(ws)
for ws in dead_connections:
self.subscribers[symbol].remove(ws)
Інтеграція TradingView
TradingView Advanced Charts (платна ліцензія) — стандарт для професійних терміналів. Користувацький адаптер DataFeed:
const dataFeed: IDatafeedChartApi = {
onReady: (callback) => {
callback({
supported_resolutions: ['1', '5', '15', '60', '240', 'D', 'W'],
supports_marks: true,
supports_time: true,
});
},
getBars: async (symbolInfo, resolution, periodParams, onHistoryCallback) => {
const candles = await api.getCandles(
symbolInfo.name,
resolution,
periodParams.from,
periodParams.to
);
onHistoryCallback(candles.map(toTradingViewBar), { noData: candles.length === 0 });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback) => {
wsGateway.on(`candle:${symbolInfo.name}:${resolution}`, onRealtimeCallback);
},
};
Мобільна версія
Повнофункціональний терміналу на мобільному — інша UX-задача. Основні паттерни:
- Проведення між розділами (chart/orderbook/orders) замість мультипанельного розташування
- Bottom sheet для форми ордера
- Спрощений стакан (лише 10-20 рівнів)
- Push-сповіщення для сигналів про ціну та виконання ордерів
React Native з WebView для графіка TradingView або нативна реалізація через react-native-canvas для простих графіків.
Продуктивність та SLA
Вимоги до продуктивності:
| Метрика | Ціль |
|---|---|
| Затримка оновлення стакану | < 100ms від біржі до UI |
| Затримка подання ордера | < 200ms |
| FPS рендерування графіку | 60 fps |
| Час початкового завантаження | < 3 сек |
| Повторне з'єднання WebSocket | < 2 сек |
Моніторинг затримки на стороні клієнта через performance.now() та відправка метрик до аналітики — необхідна частина виробничого моніторингу.







