# Order flow

> Footprint bars, volume profile, market profile, delta, cumulative delta and relative volume.

Section: Charts. Source: https://ortexcharts.com/docs/order-flow

## The Position This Library Takes

Most charting libraries estimate directional volume. They do not know which side was the
aggressor, so they infer it from where the price moved inside the bar, which is the tick
rule from the 1990s. The delta, cumulative delta and footprint numbers that come out of it
disagree with the platforms that read exchange aggressor flags, and traders notice.

ORTEX Charts takes the other route. **The host supplies the aggressor data and the library
renders it exactly.** Where the data is missing it estimates, marks the result as estimated
in the tooltip, and offers a strict mode that shows a gap rather than a guess.

That is a real requirement, not a preference: footprint bars, exact volume profiles and true
delta need per-price bid and ask volume from your feed. Without it you get the same
estimates everyone else produces, honestly labeled.

## The Data Model

Three optional shapes carry order-flow information into the library.

```ts
// On a bar: volume that lifted the offer and volume that hit the bid.
interface Bar {
  time: number; open: number; high: number; low: number; close: number; volume: number;
  buyVolume?: number;
  sellVolume?: number;
}

// Per bar, per price level: a footprint.
interface FootprintBar {
  time: number;
  levels: Array<{ price: number; bid: number; ask: number }>;   // ascending by price
}

// On a tick: the aggressor side when the feed reports it.
interface Tick { time: number; price: number; size: number; side?: "buy" | "sell" }
```

`bid` is volume that traded at the bid, meaning aggressive sellers. `ask` is volume that
lifted the offer, meaning aggressive buyers.

`buyVolume` and `sellVolume` travel through every path in the engine: row conversion, bar
aggregation, the realtime append buffers, `update`, and the aligned columns indicators read.
Two consequences follow from how the columns are allocated:

- A series created **without** aggressor volumes ignores them on later bars. History cannot
  be retro-fitted, so set the first `setData` with the columns you intend to use.
- A series created **with** them writes `NaN` for a bar that lacks them, which is what
  `method: "aggressor"` shows as a gap.

## Footprint Bars

```ts
const fp = chart.setFootprint(flow, {
  tickSize: 0.01,
  layout: "bidAsk",
  imbalanceRatio: 3,
});

fp.updateBar(liveFootprintBar);   // realtime
```

For every bar wider than `minBarWidth` the primitive draws one cell per price level: the bid
half shaded in the down color and the ask half in the up color by share of the busiest level
in that bar, the numbers when the cell is at least nine pixels high, diagonal imbalances
outlined, the bar point of control boxed, and delta plus total volume under the bar.

When bars are narrower than `minBarWidth` the cells are unreadable, so the primitive draws a
thin per-bar delta strip along the bottom of the pane instead. The information stays visible
at every zoom level rather than disappearing.

Hovering a cell shows its bid, ask, delta and total.

| Option | Default | What it does |
|---|---|---|
| `minBarWidth` | `28` | Below this bar width in pixels, the delta strip replaces the cells. |
| `cluster` | `1` | Merge this many consecutive price levels into one cell. |
| `tickSize` | `0` | Level height in price units; zero derives it from the smallest gap between levels. |
| `layout` | `"bidAsk"` | `bidAsk` prints bid and ask per cell, `delta` prints ask − bid, `volume` prints the total. |
| `imbalanceRatio` | `3` | Outline a level whose ask is at least this many times the bid one level below, and the mirror. Zero disables it. |
| `heat` | `true` | Shade cells by volume relative to the busiest level of the bar. |
| `showTotals` | `true` | Print delta and total under each bar. |
| `showPoc` | `true` | Outline the bar point of control. |
| `buyColor` | `null` | Overrides the theme up color. |
| `sellColor` | `null` | Overrides the theme down color. |
| `stripHeight` | `4` | Height of the fallback delta strip in pixels. |

`setFootprint` does two extra things worth knowing. It gives the main bars their aggressor
volumes if they arrived without them, so Delta and CVD become exact; and it feeds every
volume profile added with `addVolumeProfile`, so those become exact too.

_Live example: [footprint](https://ortexcharts.com/examples/footprint)_

### Building Footprints From Ticks

If your feed gives sided ticks rather than pre-aggregated levels:

```ts
import { footprintFromTicks, mergeFootprint } from "@ortex-charts/financial";
import { parseResolution } from "@ortex-charts/math";

const flow = footprintFromTicks(ticks, parseResolution("1"), 0.01, "America/New_York");
const barsWithFlow = mergeFootprint(bars, flow);

chart.setData(barsWithFlow);
chart.setFootprint(flow, { tickSize: 0.01 });
```

Ticks without a `side` fall back to the tick rule, which is the estimate every platform
without aggressor data uses. Pass sided ticks for exact results.

`footprintTotals(bar)` gives `{ buy, sell, total, delta, poc }` for one footprint bar, which
is what a host-drawn summary row wants.

## Volume Profile and Market Profile

```ts
const vp = chart.addVolumeProfile({ mode: "visible", tickSize: 0.01, splitDelta: true });

vp.setRange(fromTime, toTime);                                 // switch to a fixed range
vp.setOptions({ mode: "session" });                            // one profile per trading day
vp.setOptions({ kind: "tpo", tpoPeriodMs: 30 * 60_000 });      // market profile
vp.profiles();                                                 // the computed rows, POC and value area
```

### Modes

- `visible` recomputes for the bars currently on screen, cached per range and data version.
- `fixed` uses `range`, which is what you set from a date-range drawing or a preset button.
- `session` draws one profile per zoned trading day, anchored at the first bar of the day.

### Kinds

- `volume` counts volume at price: a volume profile.
- `tpo` counts one letter per `tpoPeriodMs` period per row: a market profile. Letters are
  drawn when rows are at least nine pixels high and a histogram replaces them below that.

### Accuracy

With footprint levels attached, volume lands in the row that contains its price and the
buy-and-sell split is real. The computed profile reports `exact: true`.

Without them, each bar volume is spread evenly over the rows its range covers and split by
where the close sits in that range. The profile reports `exact: false` and the tooltip says
"estimated". This is the same approximation other libraries make; the difference is that it
is labeled.

### Point of Control and Value Area

The point of control is the row with the most volume, or the most letters for a market
profile. The value area grows outward from it, taking the larger neighbor at each step,
until it holds `valueArea` of the total, which defaults to 70 percent.

### Options

| Option | Default | What it does |
|---|---|---|
| `kind` | `"volume"` | `volume` or `tpo`. |
| `mode` | `"visible"` | `visible`, `fixed` or `session`. |
| `range` | `null` | `[fromTime, toTime]` for `fixed`. |
| `rows` | `24` | Number of price rows when `tickSize` is zero. |
| `tickSize` | `0` | Row height in price units; capped at 400 rows. |
| `width` | `0.3` | Widest bar as a fraction of the plot width, or of the session width. |
| `position` | `"right"` | Which edge the profile grows from. |
| `valueArea` | `0.7` | Share of volume inside the value area. |
| `showPoc` | `true` | Draw the point-of-control line. |
| `showValueArea` | `true` | Draw the value area more opaquely. |
| `splitDelta` | `false` | Draw buy and sell volume as two stacked segments. |
| `showLabels` | `true` | Print the row total when there is room. |
| `color`, `buyColor`, `sellColor` | `null` | Override the theme. |
| `opacity` | — | Fill opacity of the profile. |
| `tpoPeriodMs` | — | Market profile period; one letter per period. |

Profiles draw below the series, so they never cover the candles. Hovering a row shows its
price range, volume, buy and sell split, and delta.

_Live example: [volume-profile](https://ortexcharts.com/examples/volume-profile)_

### Computing a Profile Without Drawing One

`computeProfile` is the pure function behind the primitive and is what the tests cover. Use
it when you want the numbers for a table, an export or a rule. It takes bar columns, an
inclusive bar-index range, options, and an optional map of footprint bars keyed by bar time.

```ts
import { computeProfile, type FootprintBar } from "@ortex-charts/financial";

const raw = chart.main.raw;
if (!raw || !("high" in raw)) throw new Error("no bars yet");

const byTime = new Map<number, FootprintBar>(flow.map((f) => [f.time, f]));

const profile = computeProfile(
  { time: raw.time, high: raw.high, low: raw.low, close: raw.close, volume: raw.volume },
  0,
  raw.length - 1,
  { rows: 40, valueArea: 0.7 },
  byTime,
);

console.log(profile.rows[profile.poc], profile.vaLow, profile.vaHigh, profile.exact);
```

The primitive already keeps its own computed results, so `vp.profiles()` is the cheaper
route when a profile is on screen anyway.

## The Three Indicators

| Id | Pane | What it shows |
|---|---|---|
| `delta` | Separate, histogram colored by sign, level line at zero | Per-bar buy minus sell volume. |
| `cvd` | Separate, line | Cumulative delta. |
| `rvol` | Separate, histogram, level line at one | Volume over the mean of the previous `length` bars, the current bar excluded. |

`delta` and `cvd` share a `method` input:

- `auto` uses the real aggressor columns when they exist and estimates otherwise.
- `aggressor` uses only real data and writes `NaN` where it is missing, so a gap in the
  histogram is a gap in the feed rather than a zero.
- `estimate` always uses the tick-rule estimate, which is useful when you want the number
  other platforms would show.

```ts
chart.addIndicator("delta", { method: "aggressor" });
chart.addIndicator("cvd", { method: "auto" });
chart.addIndicator("rvol", { length: 50 });
```

`indicators.barDelta(columns, method)` is exported for hosts that want the numbers without a
pane.

_Live example: [delta-cvd](https://ortexcharts.com/examples/delta-cvd)_

## Putting It Together

```ts
import { createFinancialChart, mergeFootprint } from "@ortex-charts/financial";

const chart = createFinancialChart(el, {
  timeZone: "America/New_York",
  resolution: "1",
  session: "0930-1600",
  data: mergeFootprint(bars, flow),
  seriesOptions: { priceFormat: { type: "price", precision: 2, minMove: 0.01 } },
});

chart.setFootprint(flow, { tickSize: 0.01, layout: "bidAsk", imbalanceRatio: 3 });
chart.addVolumeProfile({ mode: "session", tickSize: 0.01, splitDelta: true });
chart.addIndicator("delta", { method: "aggressor" });
chart.addIndicator("cvd");
```

## What Is Not Here

- Footprint is a primitive drawn over whichever main series is shown, not a series type of
  its own, so there is no delta-colored candle style yet.
- Composite profiles across several symbols are not supported.
- Profiles do not animate as developing profiles during [bar replay](https://ortexcharts.com/docs/replay).
- The toolbar has no built-in profile toggle. Adding one is a
  [`ToolbarAction`](https://ortexcharts.com/docs/toolbar) that calls `addVolumeProfile`.
