Розробка візуалізації order flow

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

Розроблення Footprint Chart

Footprint Chart — це свічка з внутрішньою структурою: на кожному ціновому рівні показано скільки контрактів було куплено та продано. Це не просто OHLCV — це delta аналіз, який показує реальну взаємодію покупців та продавців усередину кожної свічки.

Що таке Footprint та навіщо

Звичайна свічка показує: відкрилась на $42,000, закрилась на $42,150, обсяг 120 BTC. Footprint показує що відбувалось всередину: на рівні $42,050 було 8.5 BTC покупок та 2.1 BTC продаж, на $42,100 — 3.2 покупок та 12.4 продаж. Це "footprint" — слід ринку.

Ключові концепції:

  • Ask volume: агресивні покупки (сделки виконані за ask ціною)
  • Bid volume: агресивні продажі (сделки виконані за bid ціною)
  • Delta: ask volume - bid volume. Позитивна — панування покупців
  • Imbalance: рівень, де один тип значно переважає (зазвичай 300%+ threshold)
  • Point of Control (POC): цінний рівень з максимальним обсягом усередину свічки

Збір даних: класифікація сделок

Footprint будується з tick data — кожної окремої сделки. Потрібно класифікувати кожну сделку як buy (aggressive) або sell (aggressive):

type Trade struct {
    Price     decimal.Decimal
    Quantity  decimal.Decimal
    Timestamp int64
    IsBuy     bool  // true = aggressive buy (executed at ask)
}

// Класифікація за tick rule або quote rule
type TradeClassifier struct {
    lastPrice decimal.Decimal
    lastBid   decimal.Decimal
    lastAsk   decimal.Decimal
}

// Quote rule: більш точний метод (потребує bid/ask у момент сделки)
func (tc *TradeClassifier) ClassifyByQuote(trade RawTrade) bool {
    midPrice := tc.lastBid.Add(tc.lastAsk).Div(decimal.New(2, 0))
    return trade.Price.GreaterThanOrEqual(midPrice) // >= mid = buy
}

// Tick rule: fallback коли bid/ask недоступні
func (tc *TradeClassifier) ClassifyByTick(trade RawTrade) bool {
    if trade.Price.GreaterThan(tc.lastPrice) {
        return true  // uptick = buy
    }
    if trade.Price.LessThan(tc.lastPrice) {
        return false // downtick = sell
    }
    // Zero tick — використовуємо попередню класифікацію
    return tc.lastWasBuy
}

Біржи часто надають направлення сделки напрямо в trade data. Binance: поле isBuyerMaker — якщо true, то maker був buyer (taker був seller). Логіка інвертована:

# Binance aggTrades: isBuyerMaker=True → maker на bid стороні → агресивна ПРОДАЖА
# isBuyerMaker=False → maker на ask стороні → агресивна ПОКУПКА
def classify_binance_trade(trade: dict) -> bool:
    return not trade['isBuyerMaker']  # True = aggressive buy

Агрегація Footprint свічки

type FootprintLevel struct {
    Price     decimal.Decimal
    BidVol    decimal.Decimal  // агресивні продажі
    AskVol    decimal.Decimal  // агресивні покупки
    Delta     decimal.Decimal  // AskVol - BidVol
}

type FootprintCandle struct {
    Timestamp  int64
    Open       decimal.Decimal
    High       decimal.Decimal
    Low        decimal.Decimal
    Close      decimal.Decimal
    Volume     decimal.Decimal
    Delta      decimal.Decimal  // суммарна delta свічки
    Levels     map[string]*FootprintLevel  // price -> level data
    POC        decimal.Decimal  // рівень з макс. обсягом
    BuyPOC     decimal.Decimal  // рівень з макс. ask volume
    SellPOC    decimal.Decimal  // рівень з макс. bid volume
}

type FootprintBuilder struct {
    tickSize  decimal.Decimal  // шаг ціни для групування (e.g. 10 USD for BTC)
    candles   map[int64]*FootprintCandle  // timestamp -> candle
    mu        sync.Mutex
}

func (fb *FootprintBuilder) AddTrade(trade Trade, timeframe time.Duration) {
    fb.mu.Lock()
    defer fb.mu.Unlock()
    
    // Вичисляємо bucket для таймфрейму
    bucket := (trade.Timestamp / int64(timeframe)) * int64(timeframe)
    
    candle := fb.getOrCreateCandle(bucket, trade.Price)
    
    // Групуємо ціну по тік-розміру
    priceBucket := trade.Price.Div(fb.tickSize).Floor().Mul(fb.tickSize)
    
    level := fb.getOrCreateLevel(candle, priceBucket)
    
    if trade.IsBuy {
        level.AskVol = level.AskVol.Add(trade.Quantity)
    } else {
        level.BidVol = level.BidVol.Add(trade.Quantity)
    }
    level.Delta = level.AskVol.Sub(level.BidVol)
    
    // Оновлюємо OHLCV
    candle.Volume = candle.Volume.Add(trade.Quantity)
    candle.Delta = candle.Delta.Add(trade.IsBuyDelta(trade.Quantity))
    
    if trade.Price.GreaterThan(candle.High) { candle.High = trade.Price }
    if trade.Price.LessThan(candle.Low)     { candle.Low  = trade.Price }
    candle.Close = trade.Price
    
    // Оновлюємо POC
    candle.POC = fb.findPOC(candle)
}

func (fb *FootprintBuilder) findPOC(candle *FootprintCandle) decimal.Decimal {
    var maxVol decimal.Decimal
    var poc decimal.Decimal
    for price, level := range candle.Levels {
        total := level.AskVol.Add(level.BidVol)
        if total.GreaterThan(maxVol) {
            maxVol = total
            poc, _ = decimal.NewFromString(price)
        }
    }
    return poc
}

Обнаруження імбалансів

Imbalance — ключовий паттерн footprint. Рівень з ask volume у 3 рази більше bid volume — скляний підлога (покупці домінували). Рівень з bid у 3 рази більше ask — скляний стелі.

type ImbalanceDetector struct {
    threshold decimal.Decimal  // зазвичай 300% (3x)
}

type Imbalance struct {
    Price     decimal.Decimal
    Type      string          // "bid" або "ask"
    Ratio     decimal.Decimal
    Volume    decimal.Decimal
}

func (id *ImbalanceDetector) FindImbalances(candle *FootprintCandle) []Imbalance {
    var imbalances []Imbalance
    
    sortedLevels := candle.SortedLevels() // по цені ascending
    
    for i, level := range sortedLevels {
        if i == 0 { continue }
        below := sortedLevels[i-1]
        
        // Порівнюємо ask поточного рівня з bid рівня внизу
        // "Stacked imbalance" — кілька підряд
        if level.AskVol.IsPositive() && below.BidVol.IsPositive() {
            ratio := level.AskVol.Div(below.BidVol).Mul(decimal.New(100, 0))
            if ratio.GreaterThan(id.threshold) {
                imbalances = append(imbalances, Imbalance{
                    Price:  level.Price,
                    Type:   "ask",
                    Ratio:  ratio,
                    Volume: level.AskVol,
                })
            }
        }
    }
    
    return imbalances
}

Frontend рендеринг Footprint

Footprint складніший ніж звичайна свічка: кожен цінний рівень містить числа. На 1-хвилинній свічці з тіком $10 для BTC це може бути 20–30 рівнів.

Canvas рендеринг

HTML Canvas — єдиний варіант для продуктивного рендерингу сотень свічок з деталізацією.

class FootprintRenderer {
  private canvas: HTMLCanvasElement;
  private ctx: CanvasRenderingContext2D;
  
  renderCandle(candle: FootprintCandle, x: number, candleWidth: number, 
               priceToY: (price: number) => number) {
    const ctx = this.ctx;
    const levels = candle.getSortedLevels();
    const levelHeight = Math.abs(priceToY(levels[0].price) - priceToY(levels[1]?.price || levels[0].price - candle.tickSize));
    
    for (const level of levels) {
      const y = priceToY(level.price);
      
      // Фонова плашка — обсяг візуалізується шириною
      const maxLevelVol = candle.maxLevelVolume;
      const askWidth = (level.askVol / maxLevelVol) * (candleWidth * 0.45);
      const bidWidth = (level.bidVol / maxLevelVol) * (candleWidth * 0.45);
      
      // Ask сторона (права)
      ctx.fillStyle = 'rgba(0, 177, 94, 0.3)';
      ctx.fillRect(x + candleWidth/2, y, askWidth, levelHeight - 1);
      
      // Bid сторона (ліва)
      ctx.fillStyle = 'rgba(232, 66, 66, 0.3)';
      ctx.fillRect(x + candleWidth/2 - bidWidth, y, bidWidth, levelHeight - 1);
      
      // POC виділяємо
      if (level.price === candle.poc) {
        ctx.strokeStyle = '#FFD700';
        ctx.lineWidth = 1;
        ctx.strokeRect(x, y, candleWidth, levelHeight - 1);
      }
      
      // Числа: bid × ask
      if (levelHeight > 12) {  // малюємо текст тільки якщо є місце
        ctx.fillStyle = '#6b7087';
        ctx.font = `${Math.min(levelHeight - 2, 10)}px JetBrains Mono`;
        ctx.textAlign = 'left';
        ctx.fillText(formatVol(level.bidVol), x + 2, y + levelHeight - 3);
        ctx.textAlign = 'right';
        ctx.fillText(formatVol(level.askVol), x + candleWidth - 2, y + levelHeight - 3);
      }
      
      // Imbalance підсвітка
      if (level.imbalanceType === 'ask') {
        ctx.fillStyle = 'rgba(0, 177, 94, 0.8)';
        ctx.fillRect(x, y, 3, levelHeight);
      } else if (level.imbalanceType === 'bid') {
        ctx.fillStyle = 'rgba(232, 66, 66, 0.8)';
        ctx.fillRect(x, y, 3, levelHeight);
      }
    }
  }
  
  renderDeltaBar(candle: FootprintCandle, x: number, candleWidth: number, baseY: number) {
    const ctx = this.ctx;
    const delta = candle.delta;
    const maxDelta = this.maxAbsDelta;
    const barWidth = Math.abs(delta / maxDelta) * (candleWidth / 2);
    const color = delta >= 0 ? '#00B15E' : '#E84242';
    
    ctx.fillStyle = color;
    if (delta >= 0) {
      ctx.fillRect(x + candleWidth / 2, baseY, barWidth, 8);
    } else {
      ctx.fillRect(x + candleWidth / 2 - barWidth, baseY, barWidth, 8);
    }
  }
}

Delta профіль свічки

Cumulative delta по внутрішнім барам свічки показує хід боротьби покупців та продавців:

function calculateCumulativeDelta(trades: Trade[], bucketSize: number): CumDeltaPoint[] {
  const points: CumDeltaPoint[] = [];
  let cumDelta = 0;
  
  for (const trade of trades) {
    cumDelta += trade.isBuy ? trade.quantity : -trade.quantity;
    points.push({ ts: trade.timestamp, price: trade.price, cumDelta });
  }
  
  return points;
}

Зберігання Footprint даних

Footprint дані намного об'ємніші за звичайні OHLCV. Для BTC/USDT 1m з тіком $10 — ~15 рівнів на свічу. За день = 1440 свічок × 15 рівнів × 2 значення = 43,200 записів на день тільки для одного таймфрейму.

Оптимальне зберігання — TimescaleDB із стисненням:

CREATE TABLE footprint_levels (
    candle_ts   TIMESTAMPTZ NOT NULL,
    pair_id     SMALLINT NOT NULL,
    timeframe   VARCHAR(10) NOT NULL,
    price_level NUMERIC(18,2) NOT NULL,
    bid_vol     NUMERIC(18,8) NOT NULL,
    ask_vol     NUMERIC(18,8) NOT NULL,
    PRIMARY KEY (candle_ts, pair_id, timeframe, price_level)
);

SELECT create_hypertable('footprint_levels', 'candle_ts');
SELECT add_compression_policy('footprint_levels', INTERVAL '1 day');

Стиснення TimescaleDB зменшує розмір даних в 5–20 разів — критично для footprint обсягів.

Типи Footprint візуалізацій

Тип Відображення Застосування
Bid×Ask Числа на кожному рівні Детальний аналіз
Delta Тільки delta рівня Швидке читання
Volume Profile Гістограма горизонтально Ключові рівні
Imbalance Тільки помічені рівні Сигнали

Сроки розроблення

  • Класифікатор сделок + footprint builder: 2–3 тижні
  • TimescaleDB зберігання + агрегація: 2–3 тижні
  • Canvas renderer для footprint: 4–6 тижнів
  • Imbalance detection + POC: 1–2 тижні
  • WebSocket streaming + real-time: 2–3 тижні
  • UI controls (timeframe, tick size, display mode): 2–3 тижні

Повний Footprint Chart з історичними даними та real-time оновленнями: 3–4 місяці.