Market depth chart development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Market depth chart development
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Depth of Market (DOM) Visualization Development

DOM (Depth of Market), also known as Level 2 data, is a visualization of the entire order book, not just the best price. Professional traders read DOM like a book: they see walls of liquidity, volume absorption, spoofing. A well-implemented DOM is one of the key arguments for attracting professionals to an exchange.

DOM Structure

DOM displays two columns: bid (buys) and ask (sells) with aggregated volumes at each price level.

     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

A "wall" (wall) — an abnormally large volume at a level — often indicates a support/resistance zone. Traders track how these volumes appear, change, and disappear.

WebSocket Update Implementation

DOM requires minimal latency. Updates via WebSocket diff, client maintains local copy:

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. Load snapshot
    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. Subscribe to 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 — need new snapshot
      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();
  }
  
  // Returns top N levels in desired format
  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 Component Rendering

High-frequency DOM updates (up to 20–50 per second on active pairs) require optimized rendering. Using standard React state with re-render on each update will cause performance issues.

import { useRef, useCallback } from 'react';

// Direct DOM manipulation for hot path
const DOMRow = React.memo(({ price, size, total, maxTotal, side, highlight }: RowProps) => {
  const rowRef = useRef<HTMLDivElement>(null);
  
  // Update DOM directly without 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 animation on change
  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>
  );
});

Professional DOM Visual Features

Change Highlighting

// 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;
}

Tick Grouping

// User switches grouping: 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

Cumulative volume shows total liquidity up to each level — you can see how deep the book is:

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

Performance

On active pairs (BTC/USDT on major exchanges) DOM can update 10–50 times/sec. Constraints:

  • Throttle updates: no more than 10 renders/sec for DOM (human eye doesn't perceive faster)
  • Virtual scrolling: if showing > 50 levels — FlashList or react-window
  • Canvas rendering: for maximum performance can render DOM to Canvas instead of HTML
// Throttle rendering to 10 fps
const throttledRender = useCallback(
  throttle((domData: DOMData) => {
    setDisplayData(domData);
  }, 100),  // 100ms = 10 fps
  []
);

Development of DOM visualizer with real-time updates, tick grouping and change highlighting: 3–5 weeks.