Реалізація графіка свічок (Candlestick Chart) у мобільному додатку біржі

TRUETECH займається розробкою, підтримкою та обслуговуванням мобільних додатків iOS, Android, PWA. Маємо великий досвід та експертизу для публікації мобільних додатків до популярних маркетів Google Play, App Store, Amazon, AppGallery та інші.

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1735 послуг
Реалізація графіка свічок (Candlestick Chart) у мобільному додатку біржі
Складний
~5 днів
Часті запитання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    495

Реалізація графіка свічок (Candlestick Chart) у мобільному приложенні біржі

Candlestick chart — технічно найскладніший тип графіка для мобільного приложення. Не через математику OHLC, а через набір вимог, які обов'язкові для біржевого UX: плавний zoom/pan по 10 000+ свічам, crosshair з координатами при касанні, оновлення в реальному часі без перерисовки всього графіка, переключення таймфреймів без мигання, коректна робота в ландшафтній та портретній орієнтації. Усі ці вимоги разом — і більшість готових бібліотек починають ломатися.

Чому стандартні бібліотеки не підходять

fl_chart — немає candlestick з коробки. Можна зібрати з кастомних BarChartRod, але це костиль без zoom та прийнятної продуктивності.

syncfusion_flutter_charts — є SfCartesianChart з CandleSeries, підтримує zoom/pan, оновлення даних. Це робочий варіант для більшості завдань. Але комерційна ліцензія ($995+/рік для одного розробника) робить його нецільовим для стартапів.

TradingView Lightweight Charts у WebView — найпоширеніший підхід у production-приложеннях крупних бирж. Бібліотека написана для production trading UI, оптимізована для великих обсягів даних, підтримує всі потрібні можливості. Overhead від WebView — є, але на сучасних пристроях незначний.

Нативна реалізація через CanvasCustomPainter (Flutter) або CALayer (iOS) або Canvas (Android). Максимальна продуктивність, повний контроль. Вимагає значної розробки. Оправдано, якщо candlestick — центральний елемент продукту.

Нативна реалізація на Flutter через CustomPainter

Для приложення, де chart — головний екран, нативна реалізація дає 60fps на будь-якому пристрої. Ключові компоненти:

Структура даних

class Candle {
  final int timestamp;  // Unix timestamp у ms
  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; // зберігає offset та scale

  @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);
    }
  }

  void _drawCandle(Canvas canvas, Candle c, int index, double width, double height, PriceRange range) {
    final x = index * width + width / 2;
    final paint = Paint()
      ..color = c.isBullish ? const Color(0xFF26A69A) : const Color(0xFFEF5350)
      ..strokeWidth = 1.5;

    // Фитіль (wick)
    final highY = range.toY(c.high, height);
    final lowY = range.toY(c.low, height);
    canvas.drawLine(Offset(x, highY), Offset(x, lowY), paint);

    // Тело свічи
    final openY = range.toY(c.open, height);
    final closeY = range.toY(c.close, height);
    final bodyPaint = Paint()..color = paint.color;

    final bodyRect = Rect.fromLTRB(
      x - width * 0.35, min(openY, closeY),
      x + width * 0.35, max(openY, closeY),
    );
    // Для hollow candles (контур для bullish):
    if (c.isBullish) {
      canvas.drawRect(bodyRect, bodyPaint..style = PaintingStyle.stroke);
    } else {
      canvas.drawRect(bodyRect, bodyPaint..style = PaintingStyle.fill);
    }
  }

  @override
  bool shouldRepaint(CandlestickPainter old) =>
      old.candles != candles || old.controller != controller;
}

Gesture handling: pan та pinch-zoom

GestureDetector з onScaleStart/Update для pinch-zoom, onPanUpdate для скролу по часовій осі:

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)),
)

Clamp scale — важливо: без обмеження користувач уйде в режим, де одна свіча займає весь екран.

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,
)

Real-time оновлення останної свічи

WebSocket підключення до біржі дає tick-дані. При отриманні нового тика — оновлюємо тільки останню свічу, не перебудовуємо весь список:

void onTickReceived(Tick tick) {
  if (_candles.isEmpty) return;
  final last = _candles.last;

  // Оновлюємо OHLC останної свічи
  _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);
  }

  // Notify тільки painter, не весь екран
  _chartController.notifyListeners();
}

ValueNotifier + ValueListenableBuilder тільки навколо CustomPaint — перерисовується тільки canvas, не AppBar та бічні панелі.

Переключення таймфреймів

Кнопки 1m / 5m / 15m / 1h / 4h / 1D / 1W. При переключенні — запитуємо новий набір свічей, застосовуємо crossfade-анімацію, щоб убрати «мигання»:

AnimatedSwitcher(
  duration: const Duration(milliseconds: 200),
  child: CandlestickWidget(
    key: ValueKey(selectedTimeframe), // при смене ключа — анімація
    candles: _candles,
  ),
)

Технічні індикатори (опціонально)

MA (Moving Average), EMA, Bollinger Bands, RSI — кожен рисується як додатковий шар у CustomPainter. RSI та MACD — на окремому нижньому CustomPaint з фіксованою висотою та спільною горизонтальною віссю часу з основним графіком.

Що входить у роботу

  • Реалізація 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 тижнів. Вартість розраховується індивідуально.