Розробка відображення глибини ринку (depth chart)

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка відображення глибини ринку (depth chart)
Середній
~3-5 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

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

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1288
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

Розроблення візуалізації DOM (Depth of Market)

DOM (Depth of Market), також відомий як Level 2 дані — це візуалізація усього стакана ордерів, а не тільки найкращої ціни. Професійні трейдери читають DOM як книгу: бачать стіни ліквідності, поглинення обсягів, спуфінг. Хорошо реалізований DOM — один з ключових аргументів для залучення професіоналів на біржу.

Структура DOM

DOM відображає два стовпці: bid (покупки) та ask (продажі) з агрегованими обсягами на кожному ціновому рівні.

     BID                ASK
Volume    Price    Price    Volume
  0.5   42,100  | 42,101   1.2
  1.8   42,095  | 42,102   0.7
  3.2   42,090  | 42,105   4.5  ← wall
  0.4   42,085  | 42,110   0.9
  2.1   42,080  | 42,115   1.1

"Стіна" (wall) — аномально великий обсяг на рівні — часто вказує на зону підтримки/опору. Трейдери стежать як ці обсяги з'являються, змінюються та зникають.

Реалізація WebSocket оновлень

DOM потребує мінімальної latency. Оновлення через WebSocket diff, клієнт підтримує локальну копію:

interface DOMState {
  bids: Map<string, string>;  // price -> size
  asks: Map<string, string>;
  sequence: number;
}

class DOMManager {
  private state: DOMState = { bids: new Map(), asks: new Map(), sequence: 0 };
  private ws: WebSocket;
  
  async initialize(pair: string) {
    // 1. Завантажуємо снепшот
    const snap = await fetch(`/api/v1/markets/${pair}/orderbook?depth=100`).then(r => r.json());
    snap.bids.forEach(([p, s]: string[]) => this.state.bids.set(p, s));
    snap.asks.forEach(([p, s]: string[]) => this.state.asks.set(p, s));
    this.state.sequence = snap.sequence;
    
    // 2. Підписуємось на diff updates
    this.ws = new WebSocket(`wss://api.exchange.com/ws`);
    this.ws.send(JSON.stringify({ op: 'subscribe', channel: `orderbook.${pair}.100` }));
    this.ws.onmessage = (e) => this.applyUpdate(JSON.parse(e.data));
  }
  
  private applyUpdate(msg: OrderBookDiff) {
    if (msg.seq !== this.state.sequence + 1) {
      this.reinitialize();  // gap — потрібен новий снепшот
      return;
    }
    
    msg.bids.forEach(([p, s]: string[]) => {
      if (s === '0') this.state.bids.delete(p);
      else this.state.bids.set(p, s);
    });
    msg.asks.forEach(([p, s]: string[]) => {
      if (s === '0') this.state.asks.delete(p);
      else this.state.asks.set(p, s);
    });
    this.state.sequence = msg.seq;
    
    this.notifyRenderers();
  }
  
  // Повертає топ N рівнів у потрібному форматі
  getTopLevels(depth: number = 20) {
    const bids = [...this.state.bids.entries()]
      .map(([p, s]) => [parseFloat(p), parseFloat(s)] as [number, number])
      .sort((a, b) => b[0] - a[0])
      .slice(0, depth);
    
    const asks = [...this.state.asks.entries()]
      .map(([p, s]) => [parseFloat(p), parseFloat(s)] as [number, number])
      .sort((a, b) => a[0] - b[0])
      .slice(0, depth);
    
    return { bids, asks };
  }
}

Рендеринг DOM компонента

Високочастотні оновлення DOM (до 20–50 разів/сек на активних парах) потребують оптимізованого рендерингу. Використання звичайного React state з re-render при кожному оновленні дасть проблеми з продуктивністю.

import { useRef, useCallback } from 'react';

// Прямий DOM manipulation для hot path
const DOMRow = React.memo(({ price, size, total, maxTotal, side, highlight }: RowProps) => {
  const rowRef = useRef<HTMLDivElement>(null);
  
  // Оновлюємо DOM напрямку без React re-render
  const update = useCallback((newSize: string, newTotal: number) => {
    if (!rowRef.current) return;
    const sizeEl = rowRef.current.querySelector('.size');
    const depthEl = rowRef.current.querySelector('.depth-bar') as HTMLElement;
    if (sizeEl) sizeEl.textContent = newSize;
    if (depthEl) depthEl.style.width = `${(newTotal / maxTotal) * 100}%`;
  }, [maxTotal]);
  
  // Flash анімація при зміні
  const flash = useCallback((direction: 'up' | 'down') => {
    rowRef.current?.classList.add(`flash-${direction}`);
    setTimeout(() => rowRef.current?.classList.remove(`flash-${direction}`), 300);
  }, []);
  
  return (
    <div ref={rowRef} className={`dom-row ${side}`}>
      <div className="depth-bar" style={{ width: `${(total/maxTotal)*100}%` }} />
      <span className="price">{formatPrice(price)}</span>
      <span className="size">{formatSize(size)}</span>
      <span className="total">{formatSize(total)}</span>
    </div>
  );
});

Візуальні особливості професійного DOM

Підсвітка змін

// Detect changes between updates
function diffDOMStates(prev: DOMLevel[], curr: DOMLevel[]) {
  const changes: Map<string, 'increased' | 'decreased' | 'new' | 'removed'> = new Map();
  
  const prevMap = new Map(prev.map(l => [l.price, l.size]));
  const currMap = new Map(curr.map(l => [l.price, l.size]));
  
  for (const [price, size] of currMap) {
    const prevSize = prevMap.get(price);
    if (!prevSize) changes.set(price, 'new');
    else if (size > prevSize) changes.set(price, 'increased');
    else if (size < prevSize) changes.set(price, 'decreased');
  }
  
  for (const price of prevMap.keys()) {
    if (!currMap.has(price)) changes.set(price, 'removed');
  }
  
  return changes;
}

Тик групування

// Користувач переключає групування: 1, 5, 10, 25, 100
function groupByTick(levels: DOMLevel[], tickSize: number): DOMLevel[] {
  const grouped = new Map<number, number>();
  
  for (const { price, size } of levels) {
    const bucket = Math.floor(price / tickSize) * tickSize;
    grouped.set(bucket, (grouped.get(bucket) ?? 0) + size);
  }
  
  return [...grouped.entries()]
    .map(([price, size]) => ({ price, size }))
    .sort((a, b) => b.price - a.price);
}

Cumulative volume visualization

Накопичувальний обсяг показує суммарну ліквідність до кожного рівня — видно, наскільки глибокий стакан:

function addCumulative(levels: DOMLevel[]): DOMLevelWithCum[] {
  let cumulative = 0;
  return levels.map(level => {
    cumulative += level.size;
    return { ...level, cumulative };
  });
}

Продуктивність

На активних парах (BTC/USDT на крупних біржах) DOM може оновлюватися 10–50 разів/сек. Обмеження:

  • Throttle updates: не більше 10 рендерів/сек для DOM (людське око не сприймає швидше)
  • Virtual scrolling: якщо показуємо > 50 рівнів — FlashList або react-window
  • Canvas rendering: для максимальної продуктивності можна рендерити DOM у Canvas замість HTML
// Throttle рендеринга до 10 fps
const throttledRender = useCallback(
  throttle((domData: DOMData) => {
    setDisplayData(domData);
  }, 100),  // 100ms = 10 fps
  []
);

Розроблення DOM візуалізатора з real-time оновленнями, групуванням тиків та підсвіткою змін: 3–5 тижнів.