Разработка тепловой карты крипторынка

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка тепловой карты крипторынка
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1062
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка тепловой карты крипторынка

Тепловая карта (heatmap) криптовалютного рынка — это визуализация изменения цен всего рынка одновременно. Каждый актив представлен прямоугольником, размер которого пропорционален капитализации, цвет отражает процентное изменение за период. Незаменимый инструмент для быстрого понимания рыночной структуры.

Алгоритм Treemap Layout

Основа heatmap — алгоритм Treemap, оптимально упаковывающий прямоугольники разного размера:

interface HeatmapCell {
  symbol: string;
  marketCap: number;
  changePercent: number;
  price: number;
  volume24h: number;
  x: number;      // вычисляется алгоритмом
  y: number;
  width: number;
  height: number;
}

class SquarifiedTreemap {
  layout(
    data: HeatmapCell[],
    bounds: {x: number; y: number; width: number; height: number}
  ): HeatmapCell[] {
    // Сортируем по убыванию размера (capitalisation)
    const sorted = [...data].sort((a, b) => b.marketCap - a.marketCap);
    const totalMarketCap = sorted.reduce((sum, d) => sum + d.marketCap, 0);

    return this.squarify(sorted, bounds, totalMarketCap);
  }

  private squarify(items: HeatmapCell[], bounds: Bounds, total: number): HeatmapCell[] {
    if (items.length === 0) return [];

    const isHorizontal = bounds.width >= bounds.height;
    const result: HeatmapCell[] = [];
    let row: HeatmapCell[] = [];
    let rowArea = 0;
    let offset = 0;

    for (const item of items) {
      const itemArea = (item.marketCap / total) * (bounds.width * bounds.height);
      const testRow = [...row, item];
      const testArea = rowArea + itemArea;

      if (this.wouldImproveAspectRatio(testRow, testArea, bounds, isHorizontal)) {
        row.push(item);
        rowArea = testArea;
      } else {
        // Финализируем текущую строку
        result.push(...this.layoutRow(row, rowArea, bounds, isHorizontal, offset, total));
        const rowThickness = rowArea / (isHorizontal ? bounds.width : bounds.height);
        offset += rowThickness;

        row = [item];
        rowArea = itemArea;
      }
    }

    if (row.length > 0) {
      result.push(...this.layoutRow(row, rowArea, bounds, isHorizontal, offset, total));
    }

    return result;
  }
}

Получение данных

import httpx
import asyncio

class MarketDataProvider:
    COINGECKO_URL = "https://api.coingecko.com/api/v3"

    async def get_heatmap_data(
        self,
        vs_currency: str = 'usd',
        top_n: int = 100
    ) -> list[dict]:
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{self.COINGECKO_URL}/coins/markets",
                params={
                    "vs_currency": vs_currency,
                    "order": "market_cap_desc",
                    "per_page": top_n,
                    "page": 1,
                    "sparkline": False,
                    "price_change_percentage": "1h,24h,7d"
                }
            )

        coins = response.json()
        return [
            {
                "symbol": c["symbol"].upper(),
                "name": c["name"],
                "market_cap": c["market_cap"] or 0,
                "change_1h": c.get("price_change_percentage_1h_in_currency", 0) or 0,
                "change_24h": c.get("price_change_percentage_24h", 0) or 0,
                "change_7d": c.get("price_change_percentage_7d_in_currency", 0) or 0,
                "volume_24h": c.get("total_volume", 0) or 0,
                "price": c["current_price"],
                "image": c["image"]
            }
            for c in coins if c["market_cap"]
        ]

Кэшируем данные: CoinGecko бесплатный API — 10-30 запросов/мин. Обновляем каждые 60 секунд:

async def get_cached_data(self) -> list[dict]:
    cache_key = "heatmap_data"
    cached = await self.redis.get(cache_key)

    if cached:
        return json.loads(cached)

    data = await self.get_heatmap_data()
    await self.redis.setex(cache_key, 60, json.dumps(data))
    return data

React визуализация

import React, { useMemo } from 'react';

const getColor = (changePercent: number): string => {
  const intensity = Math.min(Math.abs(changePercent) / 10, 1);
  if (changePercent > 0) {
    const green = Math.floor(180 * intensity + 60);
    return `rgb(0, ${green}, 0)`;
  } else {
    const red = Math.floor(180 * intensity + 60);
    return `rgb(${red}, 0, 0)`;
  }
};

const HeatmapCell: React.FC<{cell: HeatmapCell; period: '1h'|'24h'|'7d'}> = ({cell, period}) => {
  const change = period === '1h' ? cell.change1h : period === '24h' ? cell.change24h : cell.change7d;
  const bgColor = getColor(change);

  return (
    <div
      style={{
        position: 'absolute',
        left: cell.x,
        top: cell.y,
        width: cell.width - 2,
        height: cell.height - 2,
        backgroundColor: bgColor,
        border: '1px solid rgba(0,0,0,0.3)',
        overflow: 'hidden',
        cursor: 'pointer'
      }}
      title={`${cell.symbol}: ${change > 0 ? '+' : ''}${change.toFixed(2)}%`}
    >
      {cell.width > 40 && (
        <div className="p-1 text-white">
          <div className="font-bold text-xs">{cell.symbol}</div>
          {cell.height > 30 && (
            <div className={`text-xs ${change > 0 ? 'text-green-200' : 'text-red-200'}`}>
              {change > 0 ? '+' : ''}{change.toFixed(2)}%
            </div>
          )}
        </div>
      )}
    </div>
  );
};

const CryptoHeatmap: React.FC = () => {
  const [data, setData] = useState<HeatmapCell[]>([]);
  const [period, setPeriod] = useState<'1h'|'24h'|'7d'>('24h');
  const containerRef = useRef<HTMLDivElement>(null);
  const [dimensions, setDimensions] = useState({width: 1200, height: 700});

  const layoutData = useMemo(() => {
    const treemap = new SquarifiedTreemap();
    return treemap.layout(data, {x: 0, y: 0, ...dimensions});
  }, [data, dimensions]);

  return (
    <div>
      <div className="flex gap-2 mb-4">
        {(['1h', '24h', '7d'] as const).map(p => (
          <button key={p} onClick={() => setPeriod(p)}
            className={`px-3 py-1 rounded ${period === p ? 'bg-blue-600' : 'bg-gray-700'}`}>
            {p}
          </button>
        ))}
      </div>
      <div ref={containerRef} className="relative bg-gray-900"
           style={{width: dimensions.width, height: dimensions.height}}>
        {layoutData.map(cell => (
          <HeatmapCell key={cell.symbol} cell={cell} period={period} />
        ))}
      </div>
    </div>
  );
};

Тепловая карта — это killer feature для криптоплатформы. Пользователи возвращаются к ней ежедневно для мониторинга рынка. Хорошая реализация включает: фильтрацию по секторам (DeFi, Layer1, GameFi), zoom на отдельные сектора, и clickthrough к торговой паре.