Order Flow Visualization Development (Footprint Chart)

Order Flow Visualization Development (Footprint Chart) When your trading system only sees OHLCV, you're blind to the battle inside each candle. Footprint Charts expose the microstructure: at every price level, you see exactly how many contracts were bought and sold. We've spent 10+ years building

Blockchain Development Services

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1309
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1270
  • image_logo-advance_0.webp
    B2B Advance company logo design
    719
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    1012
  • image_logo-aider_0.webp
    AIDER company logo development
    955
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1062

Order Flow Visualization Development (Footprint Chart)

When your trading system only sees OHLCV, you're blind to the battle inside each candle. Footprint Charts expose the microstructure: at every price level, you see exactly how many contracts were bought and sold. We've spent 10+ years building trading tools for funds and prop traders, and we know how to turn raw tick data into a clear footprint that gives you a real edge.

What Footprint Reveals That OHLCV Hides

A standard candle shows open, high, low, close, and total volume. A Footprint candle shows what happened at each price level: for example, at $42,050 there were 8.5 BTC of aggressive buys and 2.1 BTC of aggressive sells, while at $42,100 there were 3.2 buys and 12.4 sells. This is the actual footprint of market participants.

Key concepts: Ask volume (aggressive buys), Bid volume (aggressive sells), Delta (Ask - Bid), Imbalance (one side dominates by a threshold, typically 3×), and Point of Control (POC — the price level with maximum total volume).

How We Classify Trades for Accurate Footprint

The footprint is built from tick data — every individual trade. Each trade must be classified as aggressive buy or sell. We use the quote rule when bid/ask is available, and fall back to the tick rule. Exchanges often provide a direction flag directly (e.g., Binance's isBuyerMaker).

type Trade struct { Price decimal.Decimal Quantity decimal.Decimal Timestamp int64 IsBuy bool // true = aggressive buy (executed at ask) } // Classification by tick rule or quote rule type TradeClassifier struct { lastPrice decimal.Decimal lastBid decimal.Decimal lastAsk decimal.Decimal } // Quote rule: more accurate (requires bid/ask at trade time) 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 when bid/ask unavailable 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 — use previous classification return tc.lastWasBuy } 

Classification methods comparison

Method Accuracy Required Data
Quote rule High Bid/ask at trade time
Tick rule Medium Price only
Exchange flag (isBuyerMaker) High Field from exchange

Why Footprint Gives 10x More Information Than OHLCV

One OHLCV candle has 4 numbers. A Footprint candle contains dozens of numbers at each price level, revealing the market microstructure: who dominates at each tick. This is invaluable for algorithmic trading. Compare: OHLCV gives total volume; footprint gives volume distribution across prices.

Aggregating a Footprint Candle

type FootprintLevel struct { Price decimal.Decimal BidVol decimal.Decimal // aggressive sells AskVol decimal.Decimal // aggressive buys 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 // total delta for candle Levels map[string]*FootprintLevel // price -> level data POC decimal.Decimal // level with max volume BuyPOC decimal.Decimal // level with max ask volume SellPOC decimal.Decimal // level with max bid volume } type FootprintBuilder struct { tickSize decimal.Decimal // price step for grouping (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() // Compute bucket for time timeframe bucket := (trade.Timestamp / int64(timeframe)) * int64(timeframe) candle := fb.getOrCreateCandle(bucket, trade.Price) // Group price by tick size 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) // Update 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 // Update 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 } 

Detecting Imbalances: The Core Signal

Imbalance is the key footprint pattern. A level where ask volume is 3× bid volume is a "glass floor" (buyers dominated). A level where bid volume is 3× ask volume is a "glass ceiling".

type ImbalanceDetector struct { threshold decimal.Decimal // typically 300% (3x) } type Imbalance struct { Price decimal.Decimal Type string // "bid" or "ask" Ratio decimal.Decimal Volume decimal.Decimal } func (id *ImbalanceDetector) FindImbalances(candle *FootprintCandle) []Imbalance { var imbalances []Imbalance sortedLevels := candle.SortedLevels() // ascending by price for i, level := range sortedLevels { if i == 0 { continue } below := sortedLevels[i-1] // Compare ask of current level with bid of level below // "Stacked imbalance" — several consecutive 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 Rendering: Canvas at Scale

Footprint is more complex than standard candles: each price level contains numbers. HTML Canvas is the only performant way to render hundreds of candles with full detail.

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 Profile Within a Candle

Cumulative delta across internal bars shows the struggle between buyers and sellers:

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; } 

Data Storage and Performance

Footprint data is far bulkier than OHLCV. For BTC/USDT 1m with a $10 tick: ~15 levels per candle. That's 1,440 candles × 15 levels × 2 values = 43,200 records per day for a single timeframe. Optimal storage is TimescaleDB with compression, reducing size 5–20×. Our production systems handle 8,000 trades per second per core, enabling real-time rendering of multiple timeframes.

Detail: Choosing the tick size Tick size determines the price step for grouping levels. For BTC/USDT we typically use $10; for altcoins, $0.01. The choice depends on volatility and liquidity.

Our Process for Building a Footprint System

  1. Requirements analysis and data source identification (exchange APIs)
  2. Storage schema design and classifier architecture
  3. Core implementation: trade classifier, builder, imbalance detector
  4. Frontend: Canvas renderer with zoom and scroll
  5. Integration with real-time data via WebSocket
  6. Historical backtesting and validation
  7. Deployment with monitoring and alerting

What You Get

  • Full source code with comments
  • API and architecture documentation
  • Usage examples with test data
  • Repository access with CI/CD
  • Team training session
  • 3 months of post-delivery support

Timelines and Cost

Timelines depend on data volume and complexity. Estimates:

  • Basic footprint chart for one pair: 2–3 months
  • Full platform with multiple timeframes, alerts, and backtesting: 4–6 months

Cost is calculated individually. Our engineers have 10+ years of experience in trading system development, delivering 15+ projects for funds and prop traders. Contact us for a free project evaluation — we'll provide an estimate and a consultation.