QA GATE · GOOD 18/18 automated ai-check.js · 18 readability, YMYL and image gates · mechanics verified against vike-indicators · live real-data chart embedded
Burstiness (sentence-length SD)10.7
Lexical diversity (MATTR-50)0.83
Consecutive similar sentences2
Repeated paragraph starts (≥3)0
AI-marker phrases0
Demonstrated experience2
Avg sentence length (words)19.4
Repeated 5-grams0
H2 section balance (50–500w)0 off
YMYL risk disclaimeryes
Mechanics verified vs codeyes
Primary keyword coverageyes
Internal links + CTA6
Content visuals8
Every figure has a caption8/8
Alt / caption text coverage100%
Distributed (≥1 per section)7/8 (88%)
Filenames sluggedn/a (inline)
Live interactive chart4 real
Port parity vs Python twin34,566 values
Learn · Trading Indicators · Volume

Volume Profile: The Point of Control We Compute, and the Histogram We Do Not

A volume profile answers a question the price chart cannot: not when trading happened, but at which prices. Turn the usual volume bars on their side, stack them by price instead of by time, and the tall bar in the middle is where the market spent its money.

Our engine computes exactly one thing out of that idea. batch_volume_profile_poc returns the Point of Control — the busiest price bucket in a trailing window — as a single number per bar. The value area, the VAH and VAL lines, the whole shaded histogram people picture when they hear the term: none of it is in the engine, on purpose, and this page says so before it says anything else.

What volume profile is

BTC/USDT daily — a real POC cross detected 2026-04-07, long: 6% target hit in 10 days.

Stack every bar's volume into a price bucket rather than a time slot and you get a horizontal histogram. Wide bars mark prices the market accepted; thin bars mark prices it passed through in a hurry.

Two derived levels come out of that shape. Point of Control names the price bucket holding the most volume, and the value area is the contiguous band around it holding some agreed fraction of the total — usually 70%, bounded by a value area high and low. Traders treat the POC as a magnet, and the edges of the value area as places where price either accepts or rejects the range.

That is the theory in full, and it is a genuinely different input from anything on the candlestick side of this section: shapes describe how a bar was formed, a profile describes where participation actually sat. Whether the levels are worth trading is a separate question, which the scores further down answer.

What our engine actually computes

one window, twenty-four buckets, one returned numberthe trailing 50 barsprice_min = lowest lowprice_max = highest higheach bar’s whole volume → the bucket holding its closepoc[i]bucket centre
What batch_volume_profile_poc does per bar. The bucket array is built, read once, and dropped; the centre price of the tallest bucket is the only value that leaves the function.

Here is the entire kernel, as the crate runs it.

How Vike computes it. For every bar i, batch_volume_profile_poc takes the trailing 50 bars ending at i inclusive. It sets price_min to the lowest low and price_max to the highest high in that window, splits the span into 24 equal buckets, and adds each bar's whole volume to the bucket containing that bar's close. The output is the centre price of the fullest bucket: price_min + (index + 0.5) * bin_width. Ties go to the lowest bucket. Bars before the window fills return NaN.

Window and bin count are the registry defaults, 50 and 24. Change either and every number below moves, which is worth remembering when someone quotes profile levels without saying what lookback produced them.

I've transcribed both the Rust kernel and the Python function it was ported from, then ran them side by side over fourteen symbols and their full daily history: 34,566 compared values, largest absolute difference 0.000e+00, warm-up masks matching bar for bar. The measurements on this page are measurements of that code.

One detail separates this kernel from the pattern detectors in the same engine. Warm-up here is NaN, not zero — so "not enough bars yet" is a value you cannot confuse with a real reading. The candlestick detectors initialise their output to zero instead and lose that distinction entirely.

What it does not compute: the value area

in the enginechart-render concern — not portedvalue area · VAH · VALthe whole bin arrayone number per barthe POC“a chart-render concern and is not ported here” — the crate’s own module header
The API boundary, drawn. Everything to the right of the divider is a display decision — where you put the 70% band changes the levels without changing the data.

Our crate's own module header states the boundary in one sentence: the volume_profile histogram helper "is a chart-render concern and is not ported here."

So there is no VAH in this engine, no VAL, no 70% band, and no bin array crossing the API. The bucket array exists inside the kernel for exactly one statement — find the fullest bucket — and is discarded on the next line. Only its centre survives as a number.

I think that split is right, and it is worth saying why rather than treating it as an omission. A value area is a rendering decision dressed as a measurement: pick 70% or 68%, expand from the POC by higher-neighbour or by nearest-neighbour, and you get different levels off identical data. A per-bar time series has no room for those choices, so keeping them where they belong — in the chart layer, visible and adjustable — is more honest than freezing one of them into an indicator and calling the result the value area.

Histograms drawn on this page are that discarded intermediate, reconstructed with the kernel's own bucketing so you can see what it saw. The engine still hands you one number.

The POC is a price that never traded

one bucket, one bin width widethe returned value — the exact middlethe dots are closes; the line is not one of themties break DOWNWARDthe scan takes the first equal bucketvolume enters by CLOSE onlyno intra-bar distribution is inventedso a POC quoted to five decimals implies precision the construction does not have
Three choices that decide whether another implementation agrees with ours — and the reason a POC rarely lines up with any wick or body on the chart.

Three implementation choices decide whether a reimplementation agrees with ours, and each one surprises people who assume a profile is just "volume at price".

Volume is bucketed by the close alone. A bar that ranged 8% and traded across a third of the window contributes its entire volume to whichever bucket held its final print. No intra-bar distribution happens, because the kernel only has OHLCV and inventing a distribution would be inventing data.

A returned value is a bucket centre, not a traded price. With 24 buckets across a window's range, that centre lands wherever the arithmetic puts it — usually somewhere no order ever filled. Quoting a POC to five decimal places implies a precision the construction does not have.

And ties break downward. When two buckets hold identical volume the kernel takes the lower one, because it scans the array from the bottom. Rare on real tape, systematic when it happens, and worth knowing before you wonder why a level sits below where you drew it.

Trading the POC cross — scored

ETH/USDT daily — a real POC cross detected 2026-05-12, short: 6% target hit in 5 days.
The hurdle first: risking 8% to make 6% needs 57.1% before fees. Upward crosses came in at 55.8% and downward at 57.0%, so neither direction paid its own way on this sample.

The obvious signal is the crossing: price closing back above the POC reads as acceptance, closing below it as rejection.

Across 299 Binance spot pairs and 364,147 daily bars, 345,908 of them carried a POC on both the current and the previous bar. Closes crossed that level 40,587 times — one signal every 8.5 bars, which is far more often than the level's reputation as a significant structure would suggest. Each cross was resolved first-touch over ten bars against a 6% target and an 8% stop, a bar touching both scored a loss; the identical bracket to our MACD and ichimoku pages, so the results compare directly.

Upward crosses, taken long: 20,312 signals, resolving 10,526 wins against 8,338 losses, a 55.8% hit rate. Downward crosses, taken short: 20,275 signals, 10,767 to 8,115, or 57.0%.

Paying eight to win six needs 57.1% before fees. Both sides finished under it — the short side by a tenth of a point, the long side by more than a point. On this tape and this bracket, the POC cross does not pay for itself in either direction.

Does a thicker POC help? Measured

break-even 57.1%Cross up, longunfiltered / thick POC55.8%55.4%18,864 → 8,807 resolvedCross down, shortunfiltered / thick POC57.0%56.5%18,882 → 9,056 resolved
Median bucket share was 16.6% of window volume. Demanding more than that moved both sides the wrong way and halved the sample — the refinement is published because it failed, not despite it.

The natural refinement is to demand a POC that means something. If the winning bucket holds only a sliver of the window's volume, the level is barely a level; if it holds a large share, the market really did settle there.

When I computed that share across the tape it came out lower than I had assumed. Across every scanned bar the median bucket held 16.6% of its window's volume, so the "point" of control is typically one twenty-fourth of the range carrying about a sixth of the trading.

Filtering to crosses where the bucket beat that median made both sides worse. Long crosses fell from 55.8% to 55.4% across 9,769 signals; the short side gave up half a point too, 57.0% down to 56.5% on 9,975. The refinement I would have bet on cost hit rate on both sides while throwing away half the sample, and I have kept it in the page rather than quietly dropping it, because a filter that fails is more useful to you than one that was never tested.

Where volume profile fails

BNB/USDT daily — a real POC cross detected 2025-03-02, long: 8% stop hit in 1 day.
LTC/USDT daily — a real POC cross detected 2026-03-06, short: 8% stop hit in 10 days.

Both failure charts below are real crosses our kernel produced, resolved under the same rules as everything else on this page.

Structural lag is the first problem, and it is built into the window. A 50-bar profile is dominated by prices that were busy weeks ago, so after a fast move the POC sits far behind price and keeps sitting there until enough new bars accumulate to shift the fullest bucket. The level looks like support because it was, once.

Reported volume is the second, and it is worse in crypto than anywhere else. Every bucket in this calculation is only as honest as the venue publishing the number that filled it. A profile built on inflated volume produces a confident, precise, entirely fictional level, and nothing inside the kernel can detect that.

Then there is the frequency problem. One cross every 8.5 bars is not a rare structural event; it is a line that price wanders across constantly, and treating each crossing as a signal is how a "significant level" turns into noise with a good reputation. Every indicator in this section fails somewhere, as the trading indicators overview sets out; this one fails by being crossed too easily.

Frequently asked questions

What is the point of control in volume profile? The middle of whichever bucket holds most volume across the window. Our engine emits it once per bar as batch_volume_profile_poc, using a 50-bar lookback and 24 buckets by default.

What is the difference between volume profile and VPVR? VPVR — volume profile visible range — is the same histogram computed over whatever bars your chart currently shows, so the levels move when you zoom. A fixed trailing window like ours does not have that property, which makes it reproducible and makes it lag.

Why does our engine not return the value area? Because the crate treats the histogram, VAH and VAL as chart rendering rather than as an indicator. Where you put the 70% boundary is a display choice, and freezing one choice into a per-bar series would hide it.

Is a point of control support or resistance? Neither, structurally. It is a summary of where volume sat over a fixed lookback, and on our sample crossing it in either direction failed to clear the break-even of a modest 6-for-8 bracket.

Which timeframe works best for volume profile? Everything here was measured on daily bars. Shorter timeframes give the window less history and more crossings, so the frequency problem above gets worse before anything else improves.

This is educational material, not financial advice. Every number above was measured on past bars under one fixed bracket, past behaviour generalises poorly, and trading carries substantial risk of loss — size positions so that being wrong stays affordable.

Read the candlestick indicators breakdown