Разработка агрегатора курсов криптовалют

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка агрегатора курсов криптовалют
Средний
~1-2 недели
Часто задаваемые вопросы

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

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

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1308
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1219
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    921
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1148
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    611
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    886

Разработка агрегатора курсов криптовалют

Курс криптовалюты — это не одно число. BTC/USDT на Binance в данный момент отличается от Bybit, OKX, Kraken и от on-chain цены в Uniswap пуле. Агрегатор берёт данные из нескольких источников и вычисляет единый «справедливый» курс или показывает разброс. Для чего это нужно: прайсинг в платёжных системах, отображение в кошельках, арбитражные стратегии, риск-менеджмент, DeFi оракулы.

Источники данных и их характеристики

Источник Тип Задержка Надёжность Сложность
Binance WebSocket CEX streaming < 100мс Высокая Низкая
Coinbase Advanced API CEX streaming < 100мс Высокая Низкая
CoinGecko REST Агрегатор 30-60 сек Высокая Минимальная
CryptoCompare WebSocket Агрегатор 1-5 сек Средняя Низкая
Uniswap V3 on-chain DEX 1 блок (~12 сек) Blockchain Средняя
Chainlink Price Feeds Оракул 1 блок+ Высокая Низкая

Выбор источников зависит от задачи. Для платёжного шлюза: несколько CEX + Chainlink для верификации. Для DeFi аналитики: on-chain источники обязательны.

Архитектура системы

┌─────────────────────────────────────┐
│           Data Ingestion            │
│  Binance WS │ OKX WS │ Kraken WS   │
│  CoinGecko REST │ on-chain RPC      │
└───────────────┬─────────────────────┘
                │ raw price events
┌───────────────▼─────────────────────┐
│         Normalization Layer         │
│  Единый формат: {pair, price,       │
│   volume, source, timestamp}        │
└───────────────┬─────────────────────┘
                │
┌───────────────▼─────────────────────┐
│         Aggregation Engine          │
│  VWAP │ Median │ Outlier detection  │
└───────────────┬─────────────────────┘
                │
        ┌───────┴────────┐
        │                │
┌───────▼──────┐  ┌──────▼───────┐
│  Redis Cache │  │  TimescaleDB │
│  (hot data)  │  │  (history)   │
└───────┬──────┘  └──────────────┘
        │
┌───────▼──────────────────────────┐
│         API Layer                │
│  REST /rates │ WebSocket stream  │
└──────────────────────────────────┘

Подключение к биржам

CEX WebSocket коллекторы

Каждая биржа — отдельный коллектор с переподключением при разрыве:

import WebSocket from 'ws';
import { EventEmitter } from 'events';

interface PriceEvent {
  source: string;
  pair: string;      // 'BTC/USDT'
  price: number;
  volume24h: number;
  timestamp: number; // ms
}

class BinanceCollector extends EventEmitter {
  private ws: WebSocket | null = null;
  private pairs: string[];
  private reconnectTimer: NodeJS.Timeout | null = null;

  constructor(pairs: string[]) {
    super();
    this.pairs = pairs;
  }

  connect() {
    // Binance объединяет несколько стримов в один WS
    const streams = this.pairs
      .map(p => `${p.replace('/', '').toLowerCase()}@ticker`)
      .join('/');
    
    this.ws = new WebSocket(`wss://stream.binance.com:9443/stream?streams=${streams}`);
    
    this.ws.on('message', (data: string) => {
      const msg = JSON.parse(data);
      if (msg.stream && msg.data) {
        const ticker = msg.data;
        this.emit('price', {
          source: 'binance',
          pair: this.normalizePair(ticker.s),
          price: parseFloat(ticker.c),  // last price
          volume24h: parseFloat(ticker.v) * parseFloat(ticker.c),
          timestamp: ticker.E,
        } as PriceEvent);
      }
    });

    this.ws.on('close', () => {
      this.reconnectTimer = setTimeout(() => this.connect(), 5000);
    });

    this.ws.on('error', (err) => {
      console.error('Binance WS error:', err.message);
    });
  }

  private normalizePair(symbol: string): string {
    // 'BTCUSDT' → 'BTC/USDT'
    const quoteAssets = ['USDT', 'USDC', 'BTC', 'ETH', 'BNB'];
    for (const quote of quoteAssets) {
      if (symbol.endsWith(quote)) {
        return `${symbol.slice(0, -quote.length)}/${quote}`;
      }
    }
    return symbol;
  }
}

Аналогичные коллекторы для OKX (wss://ws.okx.com:8443/ws/v5/public), Bybit (wss://stream.bybit.com/v5/public/spot), Kraken.

On-chain цены из Uniswap V3

Spot цена из пула — sqrtPriceX96. Декодирование:

import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

const UNISWAP_V3_POOL_ABI = [
  {
    name: 'slot0',
    type: 'function',
    inputs: [],
    outputs: [
      { name: 'sqrtPriceX96', type: 'uint160' },
      { name: 'tick', type: 'int24' },
      // ...
    ],
    stateMutability: 'view',
  }
] as const;

async function getUniswapPrice(poolAddress: string): Promise<number> {
  const slot0 = await client.readContract({
    address: poolAddress as `0x${string}`,
    abi: UNISWAP_V3_POOL_ABI,
    functionName: 'slot0',
  });
  
  const sqrtPriceX96 = BigInt(slot0.sqrtPriceX96);
  // price = (sqrtPriceX96 / 2^96)^2 * (10^token0Decimals / 10^token1Decimals)
  const price = Number((sqrtPriceX96 ** 2n * BigInt(1e18)) / (2n ** 192n)) / 1e18;
  return price;
}

TWAP (Time-Weighted Average Price) из Uniswap V3 надёжнее spot-цены — манипуляция spot требует удержания большого объёма в пуле в течение нескольких блоков. Для оракулов используйте TWAP за 30 минут.

Агрегация и обнаружение аномалий

Простое среднее — плохой агрегатор: один источник с ошибочной ценой тянет среднее в сторону. Лучшие варианты:

Взвешенное по объёму (VWAP):

def vwap(prices: list[dict]) -> float:
    total_volume = sum(p['volume24h'] for p in prices)
    if total_volume == 0:
        return sum(p['price'] for p in prices) / len(prices)
    return sum(p['price'] * p['volume24h'] for p in prices) / total_volume

Медиана — устойчива к выбросам. При 5+ источниках медиана предпочтительнее среднего.

Обнаружение аномалий — обязательно:

def filter_outliers(prices: list[float], threshold: float = 0.02) -> list[float]:
    """Отбрасываем цены отклоняющиеся от медианы более чем на threshold."""
    median = statistics.median(prices)
    return [p for p in prices if abs(p - median) / median <= threshold]

2% отклонение — типичный порог. Если источник постоянно выдаёт выбросы — снижаем его вес или исключаем.

Хранение и API

Redis для актуальных курсов — hash с TTL:

import redis
import json

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def store_rate(pair: str, data: dict):
    key = f"rate:{pair}"
    r.hset(key, mapping={
        'price': data['price'],
        'sources': json.dumps(data['sources']),
        'updated_at': int(time.time() * 1000),
    })
    r.expire(key, 60)  # устаревает через 60 секунд

def get_rate(pair: str) -> dict | None:
    data = r.hgetall(f"rate:{pair}")
    if not data:
        return None
    return {
        'price': float(data['price']),
        'sources': json.loads(data['sources']),
        'updated_at': int(data['updated_at']),
    }

TimescaleDB для истории — гипертаблица с автокомпрессией:

CREATE TABLE price_history (
    time        TIMESTAMPTZ NOT NULL,
    pair        VARCHAR(20) NOT NULL,
    source      VARCHAR(30) NOT NULL,
    price       NUMERIC(30, 10) NOT NULL,
    volume_24h  NUMERIC(30, 2)
);

SELECT create_hypertable('price_history', 'time');
CREATE INDEX ON price_history (pair, time DESC);

-- Автокомпрессия данных старше 7 дней
SELECT add_compression_policy('price_history', INTERVAL '7 days');

WebSocket API для реалтайм подписчиков (кошельки, биржи, дашборды):

// Публикация через Redis Pub/Sub → WebSocket клиентам
redisSubscriber.subscribe('price_updates', (message) => {
  const update = JSON.parse(message);
  // broadcast to all subscribed WS clients for this pair
  clients.get(update.pair)?.forEach(ws => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(message);
    }
  });
});

Надёжность и отказоустойчивость

  • Circuit breaker на каждый источник: если источник выдаёт ошибки > 5 раз за 30 секунд — отключаем на 60 секунд, работаем без него.
  • Staleness check: если данные от источника не обновлялись > 30 секунд — помечаем как stale, исключаем из агрегации.
  • Minimum sources: если активных источников < 2 — возвращаем ошибку вместо потенциально неверного курса.
  • Метрики Prometheus: price_sources_active, price_update_latency_ms, price_deviation_percent — алерт при аномалиях.

Процесс разработки

Проектирование (2-3 дня): список пар и источников, требования к latency, схема хранения.

Коллекторы (3-4 дня): разработка и тестирование каждого источника, нормализация формата.

Агрегация (2-3 дня): алгоритм взвешивания, outlier detection, Redis хранение.

API (2-3 дня): REST + WebSocket, документация, rate limiting.

Нагрузочное тестирование (1-2 дня): 100+ пар одновременно, поведение при потере источника.

Итого 1-2 недели для агрегатора с 3-5 источниками и 50+ торговыми парами.