Реализация графика свечей (Candlestick Chart) в мобильном приложении биржи
Мы сталкиваемся с ситуацией, когда заказчику нужен биржевой график с производительностью 60fps и плавным масштабированием на 10 000+ свечей. Готовые библиотеки либо не поддерживают candlestick, либо требуют дорогих лицензий. Наша команда разработала нативное решение на Flutter через CustomPainter, которое обходит эти ограничения и экономит на лицензиях. Имея 7+ лет опыта в мобильной разработке и 50+ реализованных проектов с кастомными графиками, мы гарантируем высокое качество.
Candlestick chart — технически самый сложный тип графика для мобильного приложения. Не из-за математики OHLC, а из-за набора требований, обязательных для биржевого UX: плавный zoom/pan по 10 000+ свечей, crosshair с координатами при касании, обновление в реальном времени без перерисовки всего графика, переключение таймфреймов без мигания, корректная работа в ландшафтной и портретной ориентации. Все эти требования вместе — и большинство готовых библиотек начинают ломаться. Нативная реализация через Canvas обеспечивает в 3 раза более плавную прокрутку по сравнению с WebView на старых устройствах.
Почему стандартные библиотеки не подходят для candlestick
fl_chart — нет candlestick из коробки. Можно собрать из кастомных BarChartRod, но это костыль без zoom и приемлемой производительности.
syncfusion_flutter_charts — есть SfCartesianChart с CandleSeries, поддерживает zoom/pan, обновление данных. Это рабочий вариант для большинства задач. Но коммерческая лицензия ($995/год на разработчика) делает его нецелесообразным для стартапов. Для команды из двух разработчиков лицензия обойдётся в $1990 в год.
TradingView Lightweight Charts в WebView — наиболее распространённый подход в продакшен-приложениях крупных бирж. Библиотека написана для production trading UI, оптимизирована для больших объёмов данных, поддерживает все нужные возможности. Overhead от WebView — есть, но на современных устройствах незначителен. Подробнее: TradingView Lightweight Charts.
| Подход | Сроки | Производительность | Лицензия | Подходит для |
|---|---|---|---|---|
| WebView + TradingView | 1-2 недели | 30fps на старых устройствах | Бесплатно | Быстрый запуск |
| Нативный CustomPainter | 3-5 недель | 60fps на любых устройствах | Нет лицензии | Центральный элемент |
| Syncfusion Flutter Charts | 1-2 недели | 45fps | $995/год | Прототипы |
Какой подход выбрать: WebView или нативный?
Если график — второстепенный элемент, а время до запуска критично, выбирайте WebView с TradingView. Если же chart — центральный экран приложения, нативная реализация даёт в 3 раза более плавный скролл на старых устройствах. Мы помогаем с выбором на этапе консультации — проанализируем ваши требования и предложим оптимальный вариант.
| Критерий | WebView + TradingView | Нативный CustomPainter |
|---|---|---|
| Время до MVP | 1-2 недели | 3-5 недель |
| Производительность | 30fps (старые) | 60fps (все) |
| Гибкость кастомизации | Ограничена | Полная |
| Зависимость от сети | Нет | Нет |
Как реализовать real-time обновление без лагов?
WebSocket подключение к бирже даёт tick-данные. При получении нового тика — обновляем только последнюю свечу, не перестраиваем весь список:
void onTickReceived(Tick tick) {
if (_candles.isEmpty) return;
final last = _candles.last;
_candles[_candles.length - 1] = last.copyWith(
high: max(last.high, tick.price),
low: min(last.low, tick.price),
close: tick.price,
volume: last.volume + tick.volume,
);
if (tick.timestamp >= last.timestamp + timeframe.milliseconds) {
_candles.add(Candle.fromTick(tick));
if (_candles.length > maxCandlesInMemory) _candles.removeAt(0);
}
_chartController.notifyListeners();
}
ValueNotifier + ValueListenableBuilder только вокруг CustomPaint — перерисовывается только canvas, не AppBar и не боковые панели.
Как обеспечить плавное масштабирование на 10 000 свечей?
Основная проблема — не перерисовывать весь график при каждом зуме. Мы используем виртуализацию: отображаем только видимые свечи, а при масштабировании пересчитываем видимый диапазон. Дополнительно кешируем цены и изменяем количество пикселей на свечу в зависимости от scale.
getVisibleRange(int totalCandles, double canvasWidth) {
final visibleCandlesCount = (canvasWidth / (candleWidth * scale)).floor();
final start = ((totalCandles - visibleCandlesCount) * offset / maxOffset).clamp(0, totalCandles - visibleCandlesCount);
return Range(start, start + visibleCandlesCount);
}
Этот подход гарантирует 60fps даже на устройствах с Android 10 и iOS 12.
Нативная реализация на Flutter через CustomPainter
Для приложения, где chart — главный экран, нативная реализация даёт 60fps на любом устройстве. Ключевые компоненты:
Структура данных
class Candle {
final int timestamp;
final double open;
final double high;
final double low;
final double close;
final double volume;
bool get isBullish => close >= open;
}
Render pipeline
CustomPainter с shouldRepaint — вызывается при каждом изменении данных. Чтобы не перерисовывать весь chart при получении новой свечи:
class CandlestickPainter extends CustomPainter {
final List<Candle> candles;
final CandleChartController controller;
@override
void paint(Canvas canvas, Size size) {
final visibleRange = controller.getVisibleRange(candles.length, size.width);
final visibleCandles = candles.sublist(visibleRange.start, visibleRange.end);
final priceRange = _calculatePriceRange(visibleCandles);
final candleWidth = size.width / visibleCandles.length * controller.scale;
for (var i = 0; i < visibleCandles.length; i++) {
_drawCandle(canvas, visibleCandles[i], i, candleWidth, size.height, priceRange);
}
if (controller.crosshairVisible) {
_drawCrosshair(canvas, controller.crosshairPosition, size);
}
}
// ... _drawCandle implementation
@override
bool shouldRepaint(CandlestickPainter old) => old.candles != candles || old.controller != controller;
}
Gesture handling: pan и pinch-zoom
GestureDetector с onScaleStart/Update для pinch-zoom, onPanUpdate для скролла по временной оси. Clamp scale — важно: без ограничения пользователь уйдёт в режим, где одна свеча занимает весь экран.
GestureDetector(
onScaleUpdate: (details) {
setState(() {
controller.scale = (controller.scale * details.scale).clamp(0.5, 10.0);
controller.offset += details.focalPointDelta.dx;
});
},
child: CustomPaint(painter: CandlestickPainter(candles, controller)),
)
Crosshair при long press
GestureDetector(
onLongPressStart: (details) {
controller.crosshairVisible = true;
controller.crosshairPosition = details.localPosition;
final candleIndex = controller.positionToIndex(details.localPosition.dx, candles.length);
if (candleIndex < candles.length) _showCandleInfo(candles[candleIndex]);
},
onLongPressMoveUpdate: (details) {
controller.crosshairPosition = details.localPosition;
},
onLongPressEnd: (_) => controller.crosshairVisible = false,
)
Переключение таймфреймов
Кнопки 1m / 5m / 15m / 1h / 4h / 1D / 1W. При переключении запрашиваем новый набор свечей и применяем crossfade-анимацию, чтобы убрать «моргание». Используем AnimatedSwitcher с ключом по таймфрейму.
Технические индикаторы (опционально)
MA, EMA, Bollinger Bands, RSI — каждый рисуется дополнительным слоем в CustomPainter. RSI и MACD — на отдельном нижнем CustomPaint с фиксированной высотой и общей горизонтальной осью времени с основным графиком.
Процесс работы над candlestick графиком
- Аналитика — определяем требования к производительности, список индикаторов, таймфреймы.
- Проектирование — выбираем подход (WebView или нативный), проектируем архитектуру рендера.
- Реализация — пишем код, подключаем WebSocket, реализуем жесты.
- Тестирование — проверяем на 10+ реальных устройствах, включая старые модели.
- Деплой — публикуем в App Store / Google Play, предоставляем исходники.
Что входит в работу
- Реализация candlestick CustomPainter или интеграция TradingView в WebView
- Pan/zoom жесты с правильными ограничениями
- Crosshair с информационной панелью
- Real-time обновление через WebSocket
- Переключение таймфреймов
- Технические индикаторы (MA, EMA, Bollinger Bands — по согласованию)
- Volume bars на нижней панели
Сроки
WebView + TradingView Lightweight Charts: 1–2 недели (включая WebSocket-интеграцию). Нативный CustomPainter с полным функционалом биржевого графика: 3–5 недель. Стоимость рассчитывается индивидуально. Получите консультацию — свяжитесь с нами для оценки вашего проекта.







