Order flow
Volume profile and market profile
Visible-range, fixed-range and per-session profiles with point of control and value area, plus a TPO letter profile.
Volume profile and market profile
@ortex-charts/financialLoading volume profile and market profile…
Visible-range, fixed-range and per-session profiles with point of control and value area, plus a TPO letter profile.
What this example is for
Volume profile answers where the trading actually happened rather than when. This shows all three shapes, visible range, fixed range and per session, with the point of control and the value area marked, plus a market-profile letter chart. Reach for it for support and resistance work, and for the auction view that letter profiles give.
volume-profile.ts
import { createFinancialChart, footprintFromTicks, type ProfileKind, type ProfileMode } from "@ortex-charts/financial";
import { SAMPLE_LOGO } from "@/lib/constants";
import { parseResolution, ticksToBars, type Bar, type Tick } from "@ortex-charts/math";
const RESOLUTION = parseResolution("15");
const TICK = 0.05;
const bars = ticksToBars(prints, RESOLUTION, TIME_ZONE);
// The aggressor split is derived for this demonstration; a feed that reports
// it makes both the profile and its buy/sell halves exact.
const flow = footprintFromTicks(prints, RESOLUTION, TICK, TIME_ZONE);
const chart = createFinancialChart(box, {
legend: { logo: SAMPLE_LOGO }, // the company mark beside the symbol in the legend
theme,
timeZone: TIME_ZONE,
resolution: "15",
data: bars,
volume: false,
seriesOptions: { priceFormat: { type: "price", precision: 2, minMove: 0.01 } },
});
const profile = chart.addVolumeProfile({
mode: "visible",
tickSize: TICK,
splitDelta: true,
showPoc: true,
showValueArea: true,
valueArea: 0.7,
width: 0.32,
});
// Attaching the order flow is what turns an estimated profile into an exact
// one: every trade lands in the row that holds its price, and the buy and
// sell halves of each row are counted rather than inferred from the close.
profile.setFlow(flow);
/** Profiles are computed while drawing, so read them after the frame has painted. */
function report(): void {
const first = profile.profiles()[0];
if (!first || first.poc < 0) return status("No bars in range.");
const poc = first.rows[first.poc];
const va = `${first.rows[first.vaLow].lo.toFixed(2)} to ${first.rows[first.vaHigh].hi.toFixed(2)}`;
const count = profile.profiles().length;
status(
`${count} profile${count === 1 ? "" : "s"}, ${first.rows.length} rows Point of control ${poc.lo.toFixed(2)} Value area ${va} ${first.exact ? "exact" : "estimated"}`,
);
}