Реализация экрана торговли (Trading View) в мобильном приложении биржи
Мы разрабатываем экран торговли для биржевого мобильного приложения — один из самых технически плотных UI. Свечной график с сотнями точек данных, стакан котировок с обновлением в реальном времени, форма ордера с мгновенной валидацией — всё это должно работать на 60 fps при WebSocket-потоке с 10 обновлениями в секунду. Производительность отрисовки напрямую влияет на деньги пользователя. Наш опыт — более 5 лет в мобильной разработке биржевых приложений, реализовано 30+ проектов. Гарантируем стабильную работу под нагрузкой.
Как обеспечить 60 fps при WebSocket-потоке?
Критически важна стратегия throttling входящих данных. WebSocket может присылать 20–50 обновлений в секунду для активных пар. Обновлять UI на каждое — перегрузка main thread. Правильный подход: накапливаем обновления в ConcurrentQueue на background thread, применяем к UI с частотой 60fps (через CADisplayLink на iOS или Choreographer на Android).
// iOS: накопление обновлений private var pendingOrderBookUpdates: [OrderBookUpdate] = [] private let updateQueue = DispatchQueue(label: "orderbook.updates") func handleWebSocketMessage(_ update: OrderBookUpdate) { updateQueue.async { [weak self] in self?.pendingOrderBookUpdates.append(update) } } // CADisplayLink callback — на main thread, 60fps @objc func applyPendingUpdates() { var updates: [OrderBookUpdate] = [] updateQueue.sync { updates = pendingOrderBookUpdates; pendingOrderBookUpdates.removeAll() } guard !updates.isEmpty else { return } // Применяем batch updates к UI applyToOrderBook(updates) } WebSocket и управление потоком данных
Биржевые данные приходят по WebSocket — подписка на пары, получение тиков и обновлений стакана. На iOS — URLSessionWebSocketTask (нативный, с iOS 13) или Starscream для более гибкого управления reconnect-логикой. На Android — OkHttp WebSocket или Ktor WebSocket client. При потере соединения: overlay «Нет подключения» поверх графика (не заменяем весь экран), последние известные данные остаются видимыми. При восстановлении: автоматический reconnect с exponential backoff (1с, 2с, 4с, 8с, max 30с), REST запрос актуального snapshot стакана и последних свечей, seamless resume без сброса позиции графика.
Модель данных стакана
Order book — это sorted structure: bids (покупки) отсортированы по убыванию цены, asks (продажи) по возрастанию. Используем SortedArray или TreeMap (Java) / AVLTree (Swift кастомная реализация) для O(log n) вставки и удаления при обновлениях. Обновление стакана — не полная замена, а patch: новый уровень, изменённый volume, удалённый уровень (volume = 0). Для iOS стакана — NSFetchedResultsController-паттерн без CoreData: хранить bids: [(price: Decimal, size: Decimal)] и asks: [(price: Decimal, size: Decimal)], при изменении — diffing через DeepDiff или Swift's CollectionDifference для минимальных UITableView updates.
Почему кастомный Metal-рендеринг лучше готовых библиотек?
Стандартный UIKit слишком медленен для интерактивного свечного графика с 1000+ свечей. Сравнение подходов:
| Подход | Производительность | Сложность | Применение |
|---|---|---|---|
| Core Graphics | 8–12 мс на 500 свечей | Низкая | До 1000 свечей |
| CALayer per свеча | Катастрофа | Средняя | Не рекомендуется |
| Metal | 1–2 мс на 10000 свечей | Высокая | Профессиональные приложения |
| LightweightCharts (WebView) | Зависит от WebView | Низкая | Быстрый старт |
Metal-рендеринг в 10 раз быстрее Core Graphics при 2000+ свечах. Для профессиональных trading-приложений с анимированным scalable графиком используем MetalKit. MTLBuffer с vertex data для свечей, vertex shader рисует прямоугольники в пространстве экрана. 10 000 свечей — 1–2 мс render time.
Жесты и навигация по графику
Pinch для масштабирования, pan для перемещения по временной оси. UIPinchGestureRecognizer + UIPanGestureRecognizer одновременно через gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) -> true. При масштабировании — visibleRange меняется, подгружаем исторические свечи для нового диапазона через REST API. Crosshair при long press — UILongPressGestureRecognizer с minimumPressDuration = 0.1. При движении пальца — обновляем позицию crosshair и OHLCV-тултип с данными свечи под курсором.
Как реализовать infinite scroll графика?
- Определить
visibleRangeна основе contentOffset и ширины свечи. - При приближении к левой границе (например, offset < 3 свечи) отправить запрос на сервер за историческими данными.
- Полученные свечи добавить в начало датасета без сброса текущей позиции — обновляем
contentOffsetна ширину добавленных свечей. - Аналогично для правой стороны — подгрузка более новых данных при скролле вправо.
Форма ордера
UITextField с inputView — кастомная цифровая клавиатура вместо системной. На ней — кнопки 25%, 50%, 75%, 100% от доступного баланса для быстрого ввода. Decimal для хранения, не Double. Мгновенная валидация при вводе: минимальный объём, шаг цены (tick size), доступный баланс. Combine publisher(for: \.text) на UITextField с debounce(0.1) и map { Decimal(string: $0) }. Предварительный расчёт стоимости ордера в реальном времени: total = price * amount с учётом комиссии.
Buy/Sell переключение
Анимированный переход между buy и sell режимом: background цвет формы плавно меняется (зелёный ↔ красный), текст кнопки, иконки направления. На iOS — UIView.animate(withDuration: 0.2) с изменением backgroundColor. Haptic .impact(.medium) при переключении.
Подтверждение ордера
Bottom sheet с деталями ордера перед отправкой. UISheetPresentationController (iOS 15+) или кастомный bottom sheet. При подтверждении — кнопка переходит в loading state (UIActivityIndicatorView вместо текста), блокируется повторное нажатие. После ответа API — success animation (зелёная галочка с scale animation) или error с конкретным текстом ошибки из API.
Производительность и оптимизация
Order book — UITableView с бесконечным potential height. UITableView.performBatchUpdates() для анимированного добавления/удаления/обновления строк. При 10 обновлениях/сек — batching изменений: не вызываем performBatchUpdates на каждый тик, собираем за 100 мс и применяем разом. Ячейка стакана — кастомный UITableViewCell с depth bar (визуальная полоса пропорциональная объёму на уровне). Depth bar — UIView с constraint на width, обновляемый через UIView.animate. Не перерисовываем всю ячейку — только меняем constant constraint.
Профилирование
Xcode Instruments > Time Profiler для поиска hot spots в цикле обновлений. Core Animation instrument для frame drops. Целевые метрики: <16 мс на обработку WebSocket batch, <5 мс на обновление UI, 0 dropped frames при скролле стакана. На Android — Android Profiler в режиме CPU+Memory+Network одновременно. systrace для анализа длинных frames в Choreographer.
Что входит в работу
- Интеграция WebSocket с throttling и reconnect-логикой
- Реализация свечного графика (Core Graphics или LightweightCharts)
- Order book с batch updates и depth bar
- Форма ордера с валидацией и подтверждением
- Документация по интеграции и исходный код UI-компонентов
- Техническая поддержка в течение месяца после деплоя
Сроки ориентировочно
Базовая реализация (WebSocket + график + стакан + форма ордера) — 5 дней. Расширенный функционал (кастомный Metal-рендеринг, индикаторы, аналитика) — дополнительно 5–10 дней. Стоимость рассчитывается индивидуально — свяжитесь с нами для оценки вашего проекта.







