Разработка системы хранения order book snapshots

Хранение снимков биржевого стакана — задача, с которой сталкиваются компании, работающие с высоконагруженными торговыми данными. Мы разрабатываем системы хранения order book snapshots, используя эффективные алгоритмы сжатия и современные решения, такие как ClickHouse. Наша команда реализует проект под ключ — от проектирования архитектуры до внедрения и последующей поддержки, обеспечивая надёжное и масштабируемое решение для вашего бизнеса.
Часто задаваемые вопросы

Мы — команда Web3-инженеров с 10+ летним опытом разработки криптоинфраструктуры. Нами реализовано более 20 проектов для бирж и трейдинговых фирм, включая системы хранения order book с нагрузкой до 5000 обновлений в секунду. В криптобиржах order book — один из самых нагруженных источников данных. Трейдеры требуют низкую задержку, а аналитики — полную историю. Неправильное хранение приводит к огромным затратам на инфраструктуру.

Order book — самые информативные и самые сложные в хранении биржевые данные. Полный стакан BTC/USDT на Binance содержит 5000 уровней с обеих сторон, обновляется 5–10 раз в секунду и генерирует сотни мегабайт данных в час. При наивном подходе (хранение каждого среза) объём достигает 100 ГБ в сутки только для одного символа. Правильная система балансирует полноту данных с практическими ограничениями. Наше решение использует комбинацию полных снапшотов и дельт (diff), что даёт сжатие в 50 раз без потери разрешения.

Какой формат хранения order book выбрать?

Прежде чем проектировать хранилище, важно понять, какие данные реально нужны. В таблице ниже — сравнение основных форматов.

Тип данных Размер (на одно обновление) Частота записи Использование
Full snapshot 8–15 KB 1 раз в минуту Восстановление состояния, бэкапы
Depth snapshot (20 уровней) 200–500 байт 1–5 раз в секунду Торговые стратегии, визуализация
Order book diff 150–300 байт каждое обновление Секундное разрешение между снапшотами
Mid-price + spread 40 байт каждое обновление Долгосрочный анализ, мониторинг

На практике системы хранят комбинацию: полные снапшоты для восстановления и дельты для исторической точности.

Формат хранения: Delta Encoding

Delta encoding — критичный элемент для уменьшения объёмов. Вместо полного стакана мы сохраняем только изменения относительно предыдущего состояния.

Snapshot @ T=0:
bids: [(43250.0, 1.5), (43249.5, 2.0), (43249.0, 0.8)]
asks: [(43251.0, 1.2), (43251.5, 3.0), (43252.0, 0.5)]
Diff @ T=1 (только изменения):
bids_updated: [(43250.0, 2.1)] # объём изменился
bids_removed: [(43249.5, 0)] # уровень исчез
bids_added: [(43248.5, 1.0)] # новый уровень
asks_updated: []
asks_removed: []
asks_added: [(43251.75, 0.3)]

Полный снапшот: ~8 KB. Diff: ~200 байт. При 5 обновлениях в секунду и снапшоте раз в 60 секунд — 300 diffs + 1 snapshot = ~60 KB/мин вместо 3 MB/мин. Выигрыш — 50 раз.

Почему ClickHouse — оптимальный выбор?

Используем ClickHouse с custom serialization. Колоночное хранение и поддержка массивов кортежей идеально подходят для структуры стакана. ZSTD-сжатие даёт дополнительное сокращение объёма. Согласно документации ClickHouse, колоночное хранение и ZSTD могут сжимать числовые данные в 2-3 раза эффективнее LZ4.

CREATE TABLE orderbook_snapshots (
    exchange LowCardinality(String),
    symbol LowCardinality(String),
    snapshot_time DateTime64(3, 'UTC'),
    depth UInt16,
    bids Array(Tuple(Decimal(24,8), Decimal(24,8))),
    asks Array(Tuple(Decimal(24,8), Decimal(24,8)))
) ENGINE = MergeTree()
PARTITION BY (exchange, toYYYYMM(snapshot_time))
ORDER BY (exchange, symbol, snapshot_time);

CREATE TABLE orderbook_diffs (
    exchange LowCardinality(String),
    symbol LowCardinality(String),
    diff_time DateTime64(3, 'UTC'),
    first_update_id UInt64,
    last_update_id UInt64,
    bids_changes Array(Tuple(Decimal(24,8), Decimal(24,8))),
    asks_changes Array(Tuple(Decimal(24,8), Decimal(24,8)))
) ENGINE = MergeTree()
PARTITION BY (exchange, toYYYYMM(diff_time))
ORDER BY (exchange, symbol, diff_time);

CREATE TABLE orderbook_metrics (
    exchange LowCardinality(String),
    symbol LowCardinality(String),
    ts DateTime64(3, 'UTC'),
    mid_price Decimal(24,8),
    spread Decimal(24,8),
    spread_bps Decimal(10,4),
    bid_1 Decimal(24,8),
    ask_1 Decimal(24,8),
    bid_vol_10 Decimal(24,8),
    ask_vol_10 Decimal(24,8),
    imbalance Decimal(10,6)
) ENGINE = MergeTree()
PARTITION BY (exchange, toYYYYMM(ts))
ORDER BY (exchange, symbol, ts)
SETTINGS default_codec = ZSTD(3);

Восстановление состояния стакана

Ключевая операция — восстановление стакана на произвольный момент времени. Реализуется через последовательное применение дельт от последнего снапшота.

class OrderBookReplay:
    def __init__(self, storage: OrderBookStorage):
        self.storage = storage

    async def reconstruct_at(self, exchange: str, symbol: str, target_ts: int) -> OrderBook:
        snapshot = await self.storage.get_last_snapshot_before(exchange, symbol, target_ts)
        if not snapshot:
            raise ValueError("No snapshot available before target timestamp")
        diffs = await self.storage.get_diffs(exchange, symbol, from_ts=snapshot.timestamp, to_ts=target_ts)
        book = OrderBook.from_snapshot(snapshot)
        for diff in diffs:
            book.apply_diff(diff)
        return book

class OrderBook:
    def apply_diff(self, diff: OrderBookDiff):
        for price, qty in diff.bids_changes:
            if qty == 0:
                self.bids.pop(price, None)
            else:
                self.bids[price] = qty
        for price, qty in diff.asks_changes:
            if qty == 0:
                self.asks.pop(price, None)
            else:
                self.asks[price] = qty

Важен порядок применения дельт и проверка через update_id — у Binance каждый diff имеет lastUpdateId, следующий должен начинаться с lastUpdateId+1. Разрыв означает пропущенные данные.

Сжатие и оптимизация

Перед записью в ClickHouse применяем:

  • Delta encoding для цен: храним разницу от лучшего bid/ask в базисных пунктах (bps). Целые числа сжимаются лучше.
  • Binary serialization: Protocol Buffers или MessagePack вместо JSON. Выигрыш 3–5x по размеру и скорости.
  • ClickHouse compression: алгоритм ZSTD(3) для данных Decimal и Float — на 20% эффективнее дефолтного LZ4.

Потоковая запись

Ingestion pipeline работает параллельно: снапшоты каждые 60 секунд, дельты буферизируются и сохраняются батчами по 100 штук.

class OrderBookIngester:
    SNAPSHOT_INTERVAL = 60
    DIFF_BATCH_SIZE = 100

    def __init__(self, storage):
        self.storage = storage
        self.diff_buffer = []
        self.last_snapshot_time = 0

    async def on_orderbook_update(self, book: OrderBook, diff: OrderBookDiff):
        now = time.time()
        if now - self.last_snapshot_time >= self.SNAPSHOT_INTERVAL:
            await self.storage.save_snapshot(book.to_snapshot())
            self.last_snapshot_time = now
        self.diff_buffer.append(diff)
        if len(self.diff_buffer) >= self.DIFF_BATCH_SIZE:
            await self.storage.save_diffs(self.diff_buffer)
            self.diff_buffer.clear()

Аналитические запросы

После накопления данных открываются возможности для анализа. Например, средний спред по часам или корреляция imbalance с движением цены.

-- Средний спред BTC/USDT по часам за выбранный месяц
SELECT toStartOfHour(ts) AS hour,
       avg(spread_bps) AS avg_spread_bps,
       avg(imbalance) AS avg_imbalance
FROM orderbook_metrics
WHERE exchange = 'binance'
  AND symbol = 'BTC/USDT'
  AND ts BETWEEN '2024-01-01' AND '2024-02-01'
GROUP BY hour
ORDER BY hour;

-- Корреляция imbalance с последующим движением цены
WITH book AS (
    SELECT ts, imbalance, mid_price
    FROM orderbook_metrics
    WHERE exchange = 'binance'
      AND symbol = 'BTC/USDT'
),
future AS (
    SELECT b.ts,
           b.imbalance,
           (f.mid_price - b.mid_price) / b.mid_price * 10000 AS fwd_return_bps
    FROM book b
    ASOF JOIN book f
        ON b.symbol = f.symbol
        AND f.ts BETWEEN b.ts + INTERVAL 1 MINUTE AND b.ts + INTERVAL 2 MINUTE
)
SELECT round(imbalance, 1) AS imbalance_bucket,
       avg(fwd_return_bps) AS avg_1min_return_bps,
       count() AS count
FROM future
GROUP BY imbalance_bucket
ORDER BY imbalance_bucket;

Мониторинг и качество данных

Критически важно отслеживать разрывы в последовательностях дельт. Система валидации сравнивает lastUpdateId каждого diff с firstUpdateId следующего и алертит при пробелах. Gap между снапшотами делает восстановление невозможным.

Метрики для мониторинга: частота записи снапшотов на символ, задержка от биржевого timestamp до записи в ClickHouse, размер буфера дельт, процент пропущенных обновлений.

Чек-лист проверки качества данных - Проверить последовательность update_id в диффах - Убедиться, что интервал снапшотов не превышает 60 секунд - Мониторить задержку записи (должна быть < 1 секунды) - Регулярно восстанавливать стакан тестового символа и сравнивать с последним снапшотом

Процесс работы

Этап Длительность Результат
Анализ требований 2-3 дня Техническое задание, прототип схемы
Проектирование схемы 3-5 дней ER-диаграмма, выбор инструментов
Реализация pipeline 5-10 дней Работающий ingestion, тесты
Разработка API 3-5 дней Документация, примеры запросов
Мониторинг и отладка 2-3 дня Дашборды, алерты
Документация и обучение 1-2 дня README, инструкции

Ориентировочные сроки — от 2 до 4 недель в зависимости от сложности. Стоимость рассчитывается индивидуально после ознакомления с задачей.

Что входит в работу

  • Проектирование схемы хранения под вашу нагрузку (частота обновлений, количество символов, требования к задержке).
  • Реализация ingestion pipeline на Python с интеграцией через WebSocket или REST API.
  • Разработка API для доступа к историческим данным (восстановление стакана, выборка дельт, агрегаты).
  • Документация по восстановлению и аналитическим запросам.
  • Обучение команды.
  • Поддержка в течение месяца после запуска.

Если вас интересует оптимизация хранения биржевых данных — обратитесь к нам за предварительной оценкой. Свяжитесь с нами для оценки вашего проекта. Закажите разработку системы хранения ордербука под ключ — получите консультацию по архитектуре и срокам.

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

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

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    816
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142