Розробка візуалізації order flow (Footprint Chart)
Ми розробляємо візуалізацію order flow на основі Footprint Chart — це свічка з внутрішньою структурою, де на кожному ціновому рівні показано скільки контрактів було куплено та продано. Це не просто OHLCV — це дельта аналіз, який показує реальну взаємодію покупців і продавців всередині кожної свічки. Наш досвід понад 5 років у створенні трейдингових інструментів дозволяє реалізувати складні рішення під ключ.
Звичайна свічка показує: відкрилося на $42 000, закрилося на $42 150, об'єм 120 BTC. Footprint показує що відбувалося всередині: на рівні $42 050 було 8.5 BTC покупок і 2.1 BTC продажів, на $42 100 — 3.2 покупок і 12.4 продажів. Це "footprint" — слід ринку.
Ключові концепції: Ask volume, Bid volume, Delta (ask - bid). Позитивна дельта — dominance покупців. Imbalance — рівень, де один тип значно превалює (зазвичай 300%+ threshold). Point of Control (POC) — ціновий рівень з максимальним об'ємом всередині свічки.
Замовте розробку footprint chart — оцінимо ваш проект за день.
Що таке Footprint і чому він важливий для торгівлі?
Footprint дає трейдеру інформацію, недоступну в звичайних свічках. Наприклад, ви бачите, що на рівні $42 100 продажі в 4 рази перевищують покупки — це сигнал до розвороту. Використовуючи footprint, можна точніше визначати рівні підтримки та опору. За даними наших проектів, використання footprint збільшує точність входу в позицію на 20–30% порівняно з класичним аналізом.
Як класифікувати угоди для footprint?
Footprint будується з tick data — кожної окремої угоди. Потрібно класифікувати кожну угоду як buy (aggressive) або sell (aggressive). Для цього використовуємо quote rule або tick rule як fallback.
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). Логіка інвертована: !isBuyerMaker означає агресивну покупку.
| Метод класифікації | Точність | Необхідні дані |
|---|---|---|
| Quote rule | Висока | bid/ask в момент угоди |
| Tick rule | Середня | Тільки ціна |
| Біржова мітка (isBuyerMaker) | Висока | Поле від біржі |
Чому footprint дає в 10 разів більше інформації, ніж OHLCV?
Одна свічка OHLCV — це 4 числа. Footprint свічка може містити десятки чисел на кожному ціновому рівні. Це дозволяє бачити мікроструктуру ринку: хто домінує на кожному тіку. Порівняння: OHLCV дає загальний об'єм, footprint — розподіл об'єму за цінами. Для алгоритмічної торгівлі це незамінно.
Агрегація 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 // крок ціни для групування (наприклад 10 USD для 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 складніший за звичайну свічку: кожен ціновий рівень містить числа. 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);
ctx.fillStyle = 'rgba(0, 177, 94, 0.3)';
ctx.fillRect(x + candleWidth/2, y, askWidth, levelHeight - 1);
ctx.fillStyle = 'rgba(232, 66, 66, 0.3)';
ctx.fillRect(x + candleWidth/2 - bidWidth, y, bidWidth, levelHeight - 1);
if (level.price === candle.poc) {
ctx.strokeStyle = '#FFD700';
ctx.lineWidth = 1;
ctx.strokeRect(x, y, candleWidth, levelHeight - 1);
}
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);
}
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 дані набагато об'ємніші за звичайні OHLCV. Для BTC/USDT 1m з тіком $10 — ~15 рівнів на свічку. За день = 1440 свічок × 15 рівнів × 2 значення = 43 200 записів на день тільки для одного таймфрейму. Оптимальне зберігання — TimescaleDB зі стисненням, що зменшує розмір у 5–20×.
Детальніше про налаштування tick size
Tick size визначає крок ціни для групування рівнів. Для BTC/USDT зазвичай використовується 10 USD. Для альткоїнів крок може бути 0.01 USD. Вибір залежить від волатильності та ліквідності.Типи Footprint візуалізацій
| Тип | Відображення | Застосування |
|---|---|---|
| Bid×Ask | Числа на кожному рівні | Детальний аналіз |
| Delta | Тільки delta рівня | Швидке читання |
| Volume Profile | Гістограма горизонтально | Ключові рівні |
| Imbalance | Тільки помічені рівні | Сигнали |
Замовте індивідуальну візуалізацію order flow — отримайте консультацію інженера безкоштовно.
Процес роботи над проектом
- Аналіз вимог та джерел даних (біржі, API)
- Проектування схеми зберігання та архітектури класифікатора
- Реалізація core: класифікатор, builder, детектор імбалансів
- Frontend: canvas рендерер з підтримкою zoom/scroll
- Інтеграція з реальними даними через WebSocket
- Тестування на історичних даних (backtesting)
- Деплой та моніторинг
Що входить у результат
- Вихідний код з коментарями
- Документація по API та архітектурі
- Приклади використання з тестовими даними
- Доступ до репозиторію та CI/CD
- Навчання команди замовника
- Підтримка протягом 3 місяців після здачі
Терміни та вартість
Терміни залежать від обсягу даних та складності. Орієнтовно:
- Базовий footprint chart для однієї пари: 2–3 місяці
- Повноцінна платформа з multiple timeframes, алертами: 4–6 місяців
Вартість розраховується індивідуально. Наші інженери мають 10+ років досвіду в розробці трейдингових систем, ми реалізували понад 15 проектів для фондів та проп-трейдерів. Зв'яжіться з нами для оцінки вашого проекту — отримайте консультацію безкоштовно.







