MarketTrace
M1PositioningM2FootprintM3LiquidationsM4Funding
Methodology · v3.0.0 · updated 2026-05-23

Cross-exchange footprint

How the footprint chart combines Binance, Bybit, OKX and Hyperliquid perpetual futures depth + trade flow into per-minute bid × ask cells: sources, normalization, temporal alignment, wall classification, edge cases, limits.

See the live widget at /perpetuals/footprint.

Data sources

Four exchange feeds, public depth streams only. No API keys required.

Normalization: prices and quantities

Cross-exchange aggregation only works once every source speaks the same units. Two adjustments happen at ingest, before anything reaches the aggregator.

Quantity to base coin. Binance and Bybit return quantities in the base currency directly (BTC for BTC-USDT perp, etc.). OKX returns size in contracts, where one contract is ctVal units of the base coin. For BTC-USDT-SWAP ctVal = 0.01; for DOGE-USDT-SWAP it's 1000. The OKX feed resolves ctVal from the public instruments endpoint at startup and applies the multiplier on every level before publishing. Without this step, DOGE depth would read 1000× too deep on the cross-exchange view.

Price into per-asset buckets. Each exchange uses a slightly different tick size; the aggregator rounds prices down to the nearest bucket boundary so adjacent ticks across exchanges merge into the same wall. Per-asset primary buckets:

btc:  $1.0
eth:  $0.10
sol:  $0.05
bnb:  $0.10
xrp:  $0.001
doge: $0.0001

Multi-bucket views

Each cycle the same raw depth is binned into two bucket sizes per asset in parallel: the primary tick (above) and a 5× coarser variant. The user picks which to display; the footprint re-wires to the right bucket without reconnecting.

For BTC that means a $1 view (fine-grained, dense near mid) and a $5 view. With 200 bucket bins per side at $1 the footprint reaches ±$200 from mid; the $5 view reaches ±$1 000, which is where round-number magnets ($79 k / $80 k / $81 k) live. The same logic scales per asset: ETH publishes $0.10 + $0.50, DOGE publishes $0.0001 + $0.001.

Aggregation: sum + per-exchange split

For each bucket, the aggregator sums quantities across the three sources and keeps the per-exchange contribution alongside the total. That lets the UI render a single wall row plus a stacked breakdown on hover, without a second round-trip.

for src in (binance, bybit, okx):
    for (price, qty_base) in src.depth.bids:
        bucket = floor(price / BUCKET_SIZE) × BUCKET_SIZE
        bid_buckets[bucket].total      += qty_base
        bid_buckets[bucket].by[src]    += qty_base
    # asks: same, then sort by ascending price
top_bids = sorted(bid_buckets, by price desc)[:200]
top_asks = sorted(ask_buckets, by price asc)[:200]

The output payload includes the source list and per-exchange best bid / best ask separately. We do not expose a single consolidated best bid and ask; see Limits.

Cadence and spoof resolution

The aggregator publishes at 10 Hz (one snapshot every ~100 ms). Producer feeds write at up to ~20 Hz, but the binding constraint is exchange push rate (~50-100 ms native).

At 100 ms cell width on the footprint, walls and spoofs land like:

Sub-100 ms spoofs are not detected. That would require tick-by-tick (TBT) channels which are tier-locked across all three venues for our access level.

Temporal alignment

Each producer extracts the exchange event timestamp from its native depth stream (Binance's E field, Bybit's top-level ts, OKX's per-entry ts) and propagates it alongside the producer's own publish time.

At the aggregator each snapshot exposes the per-source event timestamp and the cross-exchange skew (max minus min of event times across the OK sources). The footprint snapshot tsstays the aggregator's polling moment so the timeline is uniformly spaced; the skew is surfaced in the tooltip when ≥ 100 ms (red ≥ 300 ms) so "consensus" in a single cell is honestly bounded.

Typical observed skew: < 100 ms in steady-state; spikes to 300-500 ms on basis-stress moments where one venue's feed briefly lags. Without this attribution a wall placed on Binance at T+0 and Bybit at T+200 ms would read as a single instantaneous consensus, which it isn't.

Stale handling

Each producer writes its source key with a 60-second TTL plus a timestamp. The aggregator computes age per source on every cycle and excludes any source older than 60 seconds from the union view; the excluded source still appears in the sources array with a stale status so the FE renders a degraded indicator.

On the FE: the source pills in the toolbar show colour + status, and the footprint continues with the remaining sources. A persistent stale state usually means the producer hit a watchdog reset or the WebSocket dropped without a clean reconnect, and the page degrades visibly rather than silently.

Limits and known gotchas

Versioning

Initial release v1.0.0 on 2026-05-08 (4 Hz aggregation, 50 levels). v2.0.0 on 2026-05-09: bumped to 10 Hz, 200 levels, multi-bucket views ($1 + $5 per asset), per-source event-timestamp alignment with cross-exchange skew exposure. v2.1.0 on 2026-05-21: added Hyperliquid as a fourth source via multi-resolution l2Book subscriptions (raw 20-level cap is too short on its own; coarser nSigFigs strips extend reach while keeping native granularity near mid). v3.0.0 on 2026-05-23: rebranded from cross-exchange order-book heatmap methodology to footprint methodology after the M2 heatmap UI was retired. Substrate (sources, normalization, alignment) is unchanged — the footprint init aggregator reads the same `prod:agg:<asset>:history` keys. Multi-bucket variants (`prod:agg:<asset>:b<size>:*`) were dropped at the same time (the heatmap was their only consumer). Material changes to the compute path bump the version and update the published date. Cosmetic edits do not.