Skip to content

Order flow

Footprint bars

Bid and ask volume at every price inside the bar, with imbalances marked and a delta strip underneath.

Footprint bars@ortex-charts/financial
Loading footprint bars…
Bid and ask volume at every price inside the bar, with imbalances marked and a delta strip underneath.

What this example is for

Footprint bars show the volume that traded at each price inside every bar, split into what lifted the offer and what hit the bid, with imbalances marked. Reach for this when your feed carries aggressor data and your users trade intraday. It is drawn from real bid and ask volumes rather than inferred from where the bar closed, which is the difference that makes it worth having.

footprint.ts
import { createFinancialChart, footprintFromTicks, footprintTotals, mergeFootprint } from "@ortex-charts/financial";
import { SAMPLE_LOGO } from "@/lib/constants";
import { parseResolution, ticksToBars, type Bar, type Tick } from "@ortex-charts/math";

const RESOLUTION = parseResolution("30");
const TICK = 0.1; // Height of one footprint cell, in price units.

// The public sample feed carries prices only: no sizes and no aggressor
// side. Every print here is therefore given an illustrative size, and
// `footprintFromTicks` splits bid from ask with the tick rule, which is the
// estimate every platform without aggressor flags is built on. A feed that
// reports the aggressor sets `side` on each tick and the split is exact.
const flow = footprintFromTicks(prints, RESOLUTION, TICK, TIME_ZONE);
const bars = ticksToBars(prints, RESOLUTION, TIME_ZONE);

const chart = createFinancialChart(box, {
    legend: { logo: SAMPLE_LOGO }, // the company mark beside the symbol in the legend
    theme,
    timeZone: TIME_ZONE,
    resolution: "30",
    // `mergeFootprint` copies each bar's aggressor totals onto the bar, so
    // the delta and CVD indicators read them instead of guessing.
    data: mergeFootprint(bars, flow),
    volume: false,
    seriesOptions: { priceFormat: { type: "price", precision: 2, minMove: 0.01 } },
    timeScale: { barSpacing: 58 },
});

const footprint = chart.setFootprint(flow, { tickSize: TICK, imbalanceRatio: 3, showTotals: true, showPoc: true });

chart.chart.subscribeCrosshairMove((e) => {
    const bar = e.time === null ? undefined : footprint.flowAt(e.time);
    if (!bar) return;
    const t = footprintTotals(bar);
    status(
        `Bid ${t.sell.toLocaleString()}   Ask ${t.buy.toLocaleString()}   Delta ${t.delta >= 0 ? "+" : ""}${t.delta.toLocaleString()}   Point of control ${t.poc.toFixed(2)}`,
    );
});