> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xarchive.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Session volume profile

> The point of control and value area each session developed from 00:00 UTC, bar by bar, with each session's profile as a histogram.

<Frame caption="BTC on 5-minute bars in the 0xArchive Terminal, 2026-10-16.">
  <img src="https://mintcdn.com/0xarchive-e895b8e7/9s8lXyorBK0TOIRb/images/terminal/examples/session-volume-profile.webp?fit=max&auto=format&n=9s8lXyorBK0TOIRb&q=85&s=5bad24e85882ee92b2394cae29eb048c" alt="Session volume profile on a BTC chart" width="1038" height="664" data-path="images/terminal/examples/session-volume-profile.webp" />
</Frame>

<Card title="Open on a chart" icon="chart-candlestick" href="https://terminal.0xarchive.io/?symbol=BTC&interval=5m&scripts=example%3Asession-volume-profile" horizontal>
  Load it on BTC 5m in the terminal, then change its settings or open it in the script editor.
</Card>

The session's volume profile from every trade since 00:00 UTC: where its point of control (the price that traded most) and its value area (the 70% of volume around it) stood on each bar, and each session's profile as a histogram from its first bar.

## How it works

[`profile.since(time)`](/terminal/guides/order-flow#volume-by-price) gives every trade from the session's start to the current bar, and `valueArea` finds the area by TradingView's rule. The lines use about 200 rows over the session's range, so they move in small steps; each histogram uses 48, one box each, with `border: null` for no outline. A finished session's is drawn once, when its last bar closes; today's is redrawn as the session grows. A new session's first bar draws no lines, so one session never joins the next. Trades reach back to the start of the UTC day before yesterday, so the lines start there.

## The script

```ts session-volume-profile.ts theme={"theme":"github-dark"}
// The session's volume profile from every trade since 00:00 UTC: where it developed its point of
// control (the price that traded most) and its value area (the 70% of volume around it), bar by
// bar, and each session's profile as a histogram from its first bar. Trades reach back to the
// start of the UTC day before yesterday, so the lines and histograms start there.
indicator('Session volume profile', { overlay: true, shortName: 'Session VP', maxBoxes: 200 });

const share = input.float('Value area (%)', 70, { min: 10, max: 100, step: 5 });
const showHistogram = input.bool('Histogram', true);
const histogramBars = input.int('Histogram width (bars)', 30, { min: 5, max: 200 });

const chart = bars();
const profile = volumeProfile();
const day = state.bar<number>(na);
const firstBar = state.bar<number>(0);

const high = plot.line('VAH', { color: color.study(12), style: 'dashed' });
const low = plot.line('VAL', { color: color.study(12), style: 'dashed' });
const poc = plot.line('POC', { color: color.study(0), width: 2 });
const band = plot.fill('Value area', high, low, { color: color.withAlpha(color.study(12), 0.06) });

/** 1, 2, 2.5 or 5 times a power of ten, at or above `raw`: rows that read as round prices. */
function roundUp(raw: number): number {
  const power = Math.pow(10, Math.floor(Math.log10(raw)));
  for (const step of [1, 2, 2.5, 5, 10]) if (step * power >= raw) return step * power;
  return 10 * power;
}

/** A session's profile as boxes from its first bar: 48 rows, each as long as its volume allows. */
function histogram(session: Profile) {
  const coarse = roundUp(Math.max((session.high - session.low) / 48, chart.close * 1e-6));
  const rows = session.rows({ rowSize: coarse });
  const area = session.valueArea(share, { rowSize: coarse });
  if (rows === null || area === null) return;
  let most = 0;
  for (const row of rows) most = Math.max(most, row.total);
  for (const row of rows) {
    if (row.total === 0) continue;
    const inside = row.low >= area.low && row.high <= area.high;
    const isPoc = row.low <= area.poc && area.poc < row.high;
    const length = Math.max(1, Math.round((row.total / most) * histogramBars));
    draw.box({
      from: { bar: firstBar.value, price: row.low },
      to: { bar: firstBar.value + length, price: row.high },
      fill: color.withAlpha(isPoc ? color.study(0) : color.study(12), isPoc ? 0.6 : inside ? 0.42 : 0.2),
      border: null,
    });
  }
}

chart.onBar(() => {
  const start = clock.startOf(chart.time, 'day');
  // A new session breaks the lines: its first bar draws nothing, so one session never joins the next.
  if (start !== day.value) {
    day.value = start;
    firstBar.value = chart.index;
    return;
  }
  const session = profile.since(start);
  if (!session.available || session.total === 0) return;
  // About 200 rows over the session's range, so the lines move in small steps.
  const fine = roundUp(Math.max((session.high - session.low) / 200, chart.close * 1e-6));
  const area = session.valueArea(share, { rowSize: fine });
  if (area === null) return;
  high.set(area.high);
  low.set(area.low);
  poc.set(area.poc);
  // Today's histogram grows with the session; a finished session's is drawn once, on its last bar.
  if (showHistogram && chart.isLast) histogram(session);
});

chart.onClose(() => {
  if (!showHistogram || chart.isLast) return;
  const start = clock.startOf(chart.time, 'day');
  if (clock.startOf(chart.time + chart.interval.ms, 'day') === start) return;
  const session = profile.since(start);
  if (session.available && session.total > 0) histogram(session);
});
```

## Next step

See the next example, [EMA](/terminal/examples/ema).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.