Footprint chart visualization 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
Footprint chart visualization development
Complex
~5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1217
  • 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
    1046
  • 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

Time & Sales (Trade Tape) Development

Time & Sales (T&S) is a continuous stream of all executed trades in real-time: time, price, volume, direction. Professional traders read T&S like a market pulse — they see buyer and seller aggression, large blocks, sequence of bid/ask hits.

Trade Tape Structure

interface Trade {
  id: string;
  timestamp: number;         // unix milliseconds
  price: number;
  quantity: number;
  side: 'buy' | 'sell';     // aggressive side
  value: number;             // price * quantity in USD
  isLargeTrade: boolean;     // above significant volume threshold
}

Each T&S row contains:

  • Time: HH:MM:SS.mmm (with milliseconds for professional platforms)
  • Price: with direction highlight
  • Volume: in base asset
  • Side: Buy (green) / Sell (red)

Backend: Aggregation and Streaming

type TimeAndSalesHub struct {
    trades     chan Trade
    clients    map[string]map[*WSClient]bool  // pair -> clients
    mu         sync.RWMutex
    recentBuf  map[string]*RingBuffer  // stores last N trades for new connections
}

type RingBuffer struct {
    items []Trade
    head  int
    size  int
    mu    sync.Mutex
}

func (rb *RingBuffer) Add(trade Trade) {
    rb.mu.Lock()
    defer rb.mu.Unlock()
    rb.items[rb.head%rb.size] = trade
    rb.head++
}

func (rb *RingBuffer) GetAll() []Trade {
    rb.mu.Lock()
    defer rb.mu.Unlock()
    
    result := make([]Trade, 0, rb.size)
    start := rb.head - rb.size
    if start < 0 { start = 0 }
    
    for i := start; i < rb.head; i++ {
        result = append(result, rb.items[i%rb.size])
    }
    return result
}

func (hub *TimeAndSalesHub) OnTrade(trade Trade) {
    // Save in ring buffer
    hub.recentBuf[trade.Pair].Add(trade)
    
    // Broadcast to all subscribers
    hub.mu.RLock()
    defer hub.mu.RUnlock()
    
    data, _ := json.Marshal(trade)
    for client := range hub.clients[trade.Pair] {
        select {
        case client.send <- data:
        default:
            go client.close()
        }
    }
}

// New client gets last 100 trades immediately
func (hub *TimeAndSalesHub) OnClientConnect(client *WSClient, pair string) {
    hub.mu.Lock()
    hub.clients[pair][client] = true
    hub.mu.Unlock()
    
    // Send history
    recent := hub.recentBuf[pair].GetAll()
    for _, trade := range recent {
        data, _ := json.Marshal(trade)
        client.send <- data
    }
}

Frontend: High-performance Rendering

T&S updates very frequently — on BTC/USDT up to 10–20 trades per second during active periods. Standard React list will lag.

import { useRef, useEffect, useCallback } from 'react';

const MAX_ROWS = 200;  // max rows in tape

function TimeAndSalesList({ pair }: { pair: string }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const tradesRef = useRef<Trade[]>([]);
  const wsRef = useRef<WebSocket | null>(null);
  
  // Direct DOM manipulation for hot path — no React re-render
  const appendTrade = useCallback((trade: Trade) => {
    const container = containerRef.current;
    if (!container) return;
    
    // Create new row
    const row = document.createElement('div');
    row.className = `trade-row ${trade.side} ${trade.isLargeTrade ? 'large' : ''}`;
    
    const time = new Date(trade.timestamp);
    const timeStr = `${time.getHours().toString().padStart(2,'0')}:` +
                    `${time.getMinutes().toString().padStart(2,'0')}:` +
                    `${time.getSeconds().toString().padStart(2,'0')}`;
    
    row.innerHTML = `
      <span class="time">${timeStr}</span>
      <span class="price">${formatPrice(trade.price)}</span>
      <span class="qty">${formatQuantity(trade.quantity)}</span>
      <span class="value">$${formatVolume(trade.value)}</span>
    `;
    
    // Insert at top (new trades on top)
    container.insertBefore(row, container.firstChild);
    
    // Remove extra rows at bottom
    while (container.children.length > MAX_ROWS) {
      container.removeChild(container.lastChild!);
    }
    
    // Flash animation for large trades
    if (trade.isLargeTrade) {
      row.classList.add('flash');
      setTimeout(() => row.classList.remove('flash'), 500);
    }
  }, []);
  
  useEffect(() => {
    wsRef.current = new WebSocket(`wss://api.exchange.com/ws`);
    wsRef.current.send(JSON.stringify({ op: 'subscribe', channel: `trades.${pair}` }));
    
    wsRef.current.onmessage = (e) => {
      const trade = JSON.parse(e.data);
      appendTrade(trade);
    };
    
    return () => wsRef.current?.close();
  }, [pair, appendTrade]);
  
  return (
    <div className="time-and-sales">
      <div className="ts-header">
        <span>Time</span>
        <span>Price</span>
        <span>Size</span>
        <span>Value</span>
      </div>
      <div ref={containerRef} className="ts-body" />
    </div>
  );
}

Filtering and Highlighting

interface TSFilters {
  minValue: number;        // show only trades >= $X
  side: 'all' | 'buy' | 'sell';
  highlightLargeThreshold: number;  // threshold for highlighting large blocks
}

// CSS classes for styling
const styles = `
.trade-row {
  display: grid;
  grid-template-columns: 80px 100px 80px 80px;
  padding: 1px 8px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  border-bottom: 1px solid #1e2030;
  transition: background-color 0.2s;
}

.trade-row.buy .price { color: #00B15E; }
.trade-row.sell .price { color: #E84242; }

.trade-row.large {
  background: rgba(255, 215, 0, 0.05);
  font-weight: 600;
}

.trade-row.large.buy { background: rgba(0, 177, 94, 0.15); }
.trade-row.large.sell { background: rgba(232, 66, 66, 0.15); }

@keyframes flash-green {
  0% { background-color: rgba(0, 177, 94, 0.4); }
  100% { background-color: transparent; }
}

.trade-row.flash { animation: flash-green 0.5s ease-out; }
`;

Time-based Aggregation

For less dense markets — combine trades over short interval (100–500 ms):

class TradeAggregator {
  private buffer: Trade[] = [];
  private flushInterval: number = 100;  // ms
  private onFlush: (aggregated: AggregatedTrade[]) => void;
  
  add(trade: Trade) {
    this.buffer.push(trade);
  }
  
  private flush() {
    if (this.buffer.length === 0) return;
    
    // Group by price (tick) and side
    const groups = new Map<string, AggregatedTrade>();
    
    for (const trade of this.buffer) {
      const key = `${trade.price}:${trade.side}`;
      const existing = groups.get(key);
      
      if (existing) {
        existing.quantity += trade.quantity;
        existing.value += trade.value;
        existing.count++;
      } else {
        groups.set(key, { ...trade, count: 1 });
      }
    }
    
    this.onFlush([...groups.values()].sort((a, b) => b.timestamp - a.timestamp));
    this.buffer = [];
  }
}

Trade Flow Statistics

Useful to show aggregated stats next to the tape for last N seconds:

function TradeFlowStats({ trades }: { trades: Trade[] }) {
  const stats = useMemo(() => {
    const last60s = trades.filter(t => Date.now() - t.timestamp < 60000);
    
    const buyVolume = last60s.filter(t => t.side === 'buy')
                             .reduce((sum, t) => sum + t.value, 0);
    const sellVolume = last60s.filter(t => t.side === 'sell')
                              .reduce((sum, t) => sum + t.value, 0);
    
    return {
      buyVolume,
      sellVolume,
      delta: buyVolume - sellVolume,
      buyPercent: buyVolume / (buyVolume + sellVolume) * 100,
    };
  }, [trades]);
  
  return (
    <div className="flow-stats">
      <span className="green">Buy: ${formatVolume(stats.buyVolume)}</span>
      <FlowBar buyPct={stats.buyPercent} />
      <span className="red">Sell: ${formatVolume(stats.sellVolume)}</span>
      <span className={stats.delta > 0 ? 'green' : 'red'}>
        Δ {stats.delta > 0 ? '+' : ''}${formatVolume(Math.abs(stats.delta))}
      </span>
    </div>
  );
}

Development of Time & Sales tape with real-time WebSocket updates, filtering, large block highlighting and flow statistics: 3–4 weeks.