Розробка візуалізації кластерного аналізу обсягів

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

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

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

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1288
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

Розроблення візуалізації кластерного аналізу обсягів

Кластерний аналіз обсягів (volume cluster analysis, також Volume Profile, Footprint chart) — це способ розподілити торговельний обсяг за цінними рівнями, а не за часом. Замість запитання "скільки було обсягу о 14:00?" ставиться запитання "скільки було обсягу по цені $43,500?". Це фундаментально інший погляд на ринок, показуючий зони реального інтересу учасників.

Теорія: що показує об'ємний кластер

Volume Profile vs звичайний обсяг

Стандартний volume bar на графіку показує суммарний обсяг за період (свічу). Volume profile розподіляє цей обсяг по цінах усередину періоду:

Ціна $43,800 │████████████████████ 1,240 BTC
Ціна $43,750 │███████████████ 870 BTC
Ціна $43,700 │████████████████████████████ 2,100 BTC ← POC
Ціна $43,650 │████████████ 680 BTC
Ціна $43,600 │██████████████ 780 BTC

POC (Point of Control) — цінний рівень з максимальним обсягом. Ринок провів тут найбільше часу та/або обсягу. Сильний рівень підтримки/опору.

VAH / VAL (Value Area High / Low) — границі зони вартості, де прошло 70% обсягу (стандарт — одне стандартне відхилення від POC).

HVN / LVN (High Volume Node / Low Volume Node) — зони притяжуння та зони швидкого проходження ціни відповідно.

Footprint chart

Footprint (відпечаток) — це поліпшення volume profile, додаючи розділення на buy volume та sell volume для кожної ціновної ячейки:

$43,700 │ 890B × 1,210S │ delta: -320
$43,650 │ 1,450B × 680S │ delta: +770  ← поглинення продавців
$43,600 │ 340B × 1,890S │ delta: -1,550 ← агресивні продавці

Delta = Buy Volume - Sell Volume. Позитивна дельта = покупці агресивніші. Дивергенції між ціною та дельтою — часто передвісники розворотів.

Imbalance — коли bid/ask обсяг на сусідніх рівнях відрізняється на >300% (параметр можна налаштовувати). Вказує на агресивне поглинення.

Джерела даних

Проблема: публічні API не дають tick data

Binance, Bybit, OKX публічно надають K-line (OHLCV) дані, але не tick-by-tick trades з bid/ask розбивкою. Для повного footprint потрібні aggTrades (агреговані сделки) або raw trades.

Binance aggTrades WebSocket:

import asyncio
import websockets
import json

class FootprintCollector:
    def __init__(self, symbol: str):
        self.symbol = symbol.lower()
        self.price_levels = {}  # price -> {buy: 0, sell: 0}
        self.tick_size = 10  # об'єднуємо в кластери по $10

    async def collect(self):
        url = f"wss://stream.binance.com:9443/ws/{self.symbol}@aggTrade"

        async with websockets.connect(url) as ws:
            async for message in ws:
                trade = json.loads(message)
                await self.process_trade(trade)

    async def process_trade(self, trade: dict):
        price = float(trade["p"])
        quantity = float(trade["q"])
        is_buyer_maker = trade["m"]  # True = агресивний продавець

        # Округляємо до кластера
        cluster_price = round(price / self.tick_size) * self.tick_size

        if cluster_price not in self.price_levels:
            self.price_levels[cluster_price] = {"buy": 0.0, "sell": 0.0}

        if is_buyer_maker:
            # Maker = лімітний ордер. Агресивний = ринковий продавець
            self.price_levels[cluster_price]["sell"] += quantity
        else:
            self.price_levels[cluster_price]["buy"] += quantity

Важливий нюанс: is_buyer_maker = True означає, що buyer був у book (лімітний), а seller прийшов з market ордером. Тобто агресивна сторона — продавець. Це часто плутають.

Історичні дані

Для побудови історичного volume profile:

Джерело Глибина Якість Вартість
Binance aggTrades REST До 1000 записів за запит Хорошо Безплатно
Tardis.dev Повна історія Відмінна (tick data) $50-500/мес
Kaiko До 7 років Інституціональна $1,000+/мес
Власний сбірач З моменту старту Повний контроль Інфраструктура

Для production: власний сборщик даних (aggTrades streaming → TimescaleDB) плюс історичний бэкфіл через REST API при запуску.

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

Backend: сбір та агрегація

aggTrade WebSocket ──► Trade Collector ──► Kafka Topic (raw_trades)
                                               │
                                  ┌────────────┤
                                  ▼            ▼
                         Volume Aggregator  Footprint Builder
                                  │            │
                                  ▼            ▼
                           TimescaleDB    TimescaleDB
                         (volume_profile) (footprint_data)
                                  │            │
                                  └────────┬───┘
                                           ▼
                                     WebSocket API ──► Frontend

TimescaleDB ідеален для цього: PostgreSQL з гіпертаблицями для time-series. Time partitioning + continuous aggregates для передвичислених таймфреймів.

-- Гіпертаблиця для raw trades
CREATE TABLE trades (
    time TIMESTAMPTZ NOT NULL,
    symbol VARCHAR(20) NOT NULL,
    price NUMERIC(20, 8) NOT NULL,
    quantity NUMERIC(20, 8) NOT NULL,
    side VARCHAR(4) NOT NULL,  -- 'buy' | 'sell'
    trade_id BIGINT
);
SELECT create_hypertable('trades', 'time');

-- Матеріалізований volume profile
CREATE MATERIALIZED VIEW volume_profile_1h AS
SELECT
    time_bucket('1 hour', time) AS bucket,
    symbol,
    round(price / 10) * 10 AS price_cluster,  -- $10 кластер
    SUM(CASE WHEN side = 'buy' THEN quantity ELSE 0 END) AS buy_volume,
    SUM(CASE WHEN side = 'sell' THEN quantity ELSE 0 END) AS sell_volume,
    SUM(quantity) AS total_volume
FROM trades
GROUP BY bucket, symbol, price_cluster;

Continuous aggregate автоматично оновлює агрегат при додаванні нових даних без повного перерахунку.

Розрахунок Volume Profile метрик

from dataclasses import dataclass
from typing import List, Dict
import statistics

@dataclass
class VolumeLevelData:
    price: float
    buy_volume: float
    sell_volume: float
    total_volume: float

    @property
    def delta(self) -> float:
        return self.buy_volume - self.sell_volume

    @property
    def delta_percent(self) -> float:
        if self.total_volume == 0:
            return 0
        return (self.delta / self.total_volume) * 100


class VolumeProfileCalculator:
    def __init__(self, levels: List[VolumeLevelData]):
        self.levels = sorted(levels, key=lambda x: x.price)
        self._poc: VolumeLevelData = None

    @property
    def poc(self) -> VolumeLevelData:
        """Point of Control — рівень з макс. обсягом"""
        if not self._poc:
            self._poc = max(self.levels, key=lambda x: x.total_volume)
        return self._poc

    @property
    def value_area(self) -> tuple:
        """Value Area — 70% обсягу навколо POC"""
        total = sum(l.total_volume for l in self.levels)
        target = total * 0.70

        accumulated = self.poc.total_volume
        poc_idx = self.levels.index(self.poc)
        up_idx = poc_idx
        down_idx = poc_idx

        while accumulated < target:
            can_up = up_idx < len(self.levels) - 1
            can_down = down_idx > 0

            up_vol = self.levels[up_idx + 1].total_volume if can_up else 0
            down_vol = self.levels[down_idx - 1].total_volume if can_down else 0

            if up_vol >= down_vol and can_up:
                up_idx += 1
                accumulated += up_vol
            elif can_down:
                down_idx -= 1
                accumulated += down_vol
            else:
                break

        return self.levels[up_idx].price, self.levels[down_idx].price  # VAH, VAL

    def get_hvn_lvn(self, threshold_percentile: float = 70) -> Dict:
        """Високообсяжні та низькообсяжні вузли"""
        volumes = [l.total_volume for l in self.levels]
        threshold_high = statistics.quantiles(volumes, n=100)[threshold_percentile - 1]
        threshold_low = statistics.quantiles(volumes, n=100)[100 - threshold_percentile - 1]

        return {
            "hvn": [l for l in self.levels if l.total_volume >= threshold_high],
            "lvn": [l for l in self.levels if l.total_volume <= threshold_low]
        }

Frontend візуалізація

Canvas-based рендеринг

Стандартні бібліотеки типу Recharts або Chart.js не справляються з рендерингом тисяч price levels у реальному часі. Потрібен Canvas 2D або WebGL рендер.

Lightweight Charts (TradingView) підтримує кастомні series через Plugin API — це кращий варіант, якщо платформа вже використовує цей графік:

import { createChart, ISeriesApi } from 'lightweight-charts';

class VolumeProfilePlugin {
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;

  draw(data: VolumeProfileLevel[], priceRange: PriceRange) {
    const maxVolume = Math.max(...data.map(d => d.totalVolume));

    data.forEach(level => {
      const y = this.priceToY(level.price, priceRange);
      const barWidth = (level.totalVolume / maxVolume) * this.maxBarWidth;

      // Buy volume — зелений
      const buyWidth = barWidth * (level.buyVolume / level.totalVolume);
      this.ctx.fillStyle = 'rgba(38, 166, 154, 0.6)';
      this.ctx.fillRect(0, y, buyWidth, this.levelHeight);

      // Sell volume — червоний
      this.ctx.fillStyle = 'rgba(239, 83, 80, 0.6)';
      this.ctx.fillRect(buyWidth, y, barWidth - buyWidth, this.levelHeight);

      // POC виділяємо
      if (level.isPoc) {
        this.ctx.strokeStyle = '#FFD700';
        this.ctx.lineWidth = 2;
        this.ctx.strokeRect(0, y, barWidth, this.levelHeight);
      }
    });
  }
}

Продуктивність: при 1000+ рівнях використовуємо requestAnimationFrame з throttle до 60fps, перерисовуємо тільки змінені рівні (dirty checking). WebGL через PixiJS дає ще 5-10x прирост при дуже високій деталізації.

Real-time оновлення

WebSocket архітектура для live footprint:

class FootprintWebSocket {
  private ws: WebSocket;
  private pendingUpdates: Map<number, LevelUpdate> = new Map();
  private renderScheduled = false;

  onUpdate(update: LevelUpdate) {
    // Буферизуємо оновлення
    const existing = this.pendingUpdates.get(update.price) || emptyLevel;
    this.pendingUpdates.set(update.price, merge(existing, update));

    if (!this.renderScheduled) {
      this.renderScheduled = true;
      requestAnimationFrame(() => {
        this.flushUpdates();
        this.renderScheduled = false;
      });
    }
  }

  private flushUpdates() {
    // Застосовуємо всі накопичені оновлення за один render frame
    this.pendingUpdates.forEach((update, price) => {
      this.chart.updateLevel(price, update);
    });
    this.pendingUpdates.clear();
  }
}

Батчинг оновлень в requestAnimationFrame критично важливий: без нього кожна сделка вызивает перерисовку, що при 1000 trades/sec вбиває продуктивність браузера.

Додаткові індикатори на основі volume profile

Cumulative Volume Delta (CVD) — накопичена сума delta по всім періодам. CVD дивергенція з ціною = сигнал слабості тренду.

VWAP (Volume Weighted Average Price) — середневзважена за обсягом ціна:

VWAP = Σ(Price_i × Volume_i) / Σ(Volume_i)

Інституціональні алгоритми часто використовують VWAP як benchmark виконання.

TPO (Time Price Opportunity) — кожна буква на TPO chart = 30 хвилин торгівлі на даному цінному рівні. Класика Market Profile від CME Group.

Об'ємний кластерний аналіз — це серйозна аналітична надстройка, яка залучає професійних трейдерів. Хорошо реалізований footprint chart — конкурентне переважання платформи, яке складно скопіювати швидко.