Bequant
Институциональная криптобиржа с собственным matching-движком. Торговый терминал на 10K событий в минуту.
Метрики
Контекст
Bequant — институциональная криптобиржа. Не клон Robinhood, а площадка для маркет-мейкеров и prop-десков с собственной quant-инфраструктурой. Matching-движок — свой. Торговый терминал должен ощущаться как Bloomberg, не как Binance.
Когда я пришёл во фронт, у продукта было три неудобных проблемы: стакан рендерил 4-5 FPS под нагрузкой, экран портфеля делал по 14 REST-запросов на загрузку, а загрузка KYC-документов была переписана 3 раза в разных частях UI.
Что я сделал
- Переписал стакан и feed трейдов на WebSocket-диффы + виртуализацию. Держим 10 000 событий рынка в минуту без потерь кадров. Throttling на React-редьюсере, не на сокете — данные не теряются, просто батчатся UI-апдейты.
- Спроектировал GraphQL-агрегацию поверх существующих REST-сервисов. Портфельный экран: с 14 round-trip → 1 GraphQL-запрос. Нагрузка на backend на этом маршруте упала на 45% за 30 дней замеров.
- Внедрил многоуровневое Redis-кэширование: rate-limits на edge, market data на API-gateway, сессии — в service-layer. Держали 50K активных пользователей в пике без деградации backend.
- Перевёл CI с самописного shell-пайплайна на GitLab CI с параллельным запуском тестов. 800+ юнит-тестов и Cypress E2E теперь идут 9 минут вместо 3 часов.
- Поднял Prometheus + Grafana и PagerDuty-алерты на SLO. MTTR production-инцидентов снизился с 45 до 8 минут — большая часть выигрыша в том, чтобы за первые 2 минуты понимать, какой сервис сломался.
- Безопасность: OAuth 2.0, 2FA, защита от JWT-replay, CSRF на state-changing эндпоинтах, клиентское AES-GCM шифрование KYC-документов до загрузки в S3.
Технические решения
Диффы стакана: движок шлёт дельты, не снапшоты. UI держит канонический стакан в нормализованном Redux-слайсе и применяет диффы одним dispatch на анимационный кадр. Re-render — через React.memo с кастомным equality по парам price/size.
GraphQL — прагматичный выбор: REST-сервисы остались, GraphQL — тонкий BFF на Apollo. Без миграционного налога, выигрыш сразу.
Cypress для E2E, хотя команда тогда смотрела в сторону Playwright — time-travel debugger и встроенный network stubbing сэкономили часы на flaky-тестах против matching-движка.
Клиентское шифрование KYC: ключи через PBKDF2 из пароля юзера, на сервер не уходят. Если S3 утечёт — документы нечитаемы. Trade-off: при reset пароля документы не восстановить — это прямо в UI прописано.
Из кода
query Portfolio($userId: ID!) {
user(id: $userId) {
id
balances {
currency
free
locked
btcEquivalent
}
openOrders {
id
symbol
side
price
size
filled
createdAt
}
recentTrades(limit: 20) {
id
symbol
side
price
size
fee
executedAt
}
pnl(timeframe: D1) {
realized
unrealized
percentChange
}
}
}
# 1 round-trip replaces 14 REST calls.
type OrderBookDiff = {
bids: [price: string, size: string][]; // size="0" means delete
asks: [price: string, size: string][];
sequence: number;
};
const applyDiff = (state: OrderBookState, diff: OrderBookDiff) => {
if (diff.sequence !== state.sequence + 1) {
// Out-of-order or gap: resync via REST snapshot.
return { ...state, needsResync: true };
}
for (const [price, size] of diff.bids) {
if (size === "0") delete state.bids[price];
else state.bids[price] = size;
}
for (const [price, size] of diff.asks) {
if (size === "0") delete state.asks[price];
else state.asks[price] = size;
}
state.sequence = diff.sequence;
};
Результат
Терминал держит институциональную нагрузку. Backend-косты ощутимо просели после GraphQL-консолидации. Релиз-цикл — с 3-часового ритуала до 25-минутной фоновой рутины.
Менторил 3 джунов; двое выросли до middle за год.