Volume Profile POC, VAH, VAL: Pine v6 Direct Access
You're watching the tape when a candle wicks into some invisible level and reverses. You pull up Volume Profile after the fact and see the wick tagged yesterday's POC — this Volume Profile POC has been sitting in plain sight the whole time, you just keep noticing it in hindsight.
Until Pine v6, you had two ways to automate this: grab second-level ticks with request.security_lower_tf() and rebuild the distribution yourself, or switch to the volume footprint chart type and eyeball it. Pine Script (TradingView's built-in strategy language) v6 added a third — request.footprint() turns POC, VAH, and VAL into numbers your script can actually read.
This post isn't a tour of Volume Profile's deeper folklore — HVN/LVN strategy meaning, Market Profile's letter encoding, how to stack composite profiles, all skipped. It handles three narrow things: what POC / VAH / VAL each mark, what the three arguments to request.footprint() actually mean, and three things that will bite you the first time you use it.
va_percent (default 70%) of total volume. Pine v6's request.footprint() lets you call footprint.poc(), footprint.vah(), and footprint.val() directly and get back three volume_row IDs — but only on Premium and Ultimate plans, and only once per script.Nail Down Volume Profile POC First: What POC, VAH, and VAL Each Mark
Volume Profile takes volume that's normally shown along the time axis and rearranges it into horizontal bars along the price axis — how long each price row is tells you how much volume traded inside that row. All three acronyms are pulled out of that histogram.
| POC | VAH | VAL | ||
|---|---|---|---|---|
| Full name | Point of Control | Value Area High | Value Area Low | |
| Definition | The single row with the largest volume in the range | The highest row collected while walking outward from POC until va_percent (default 70%) is reached | Same, but the lowest row | |
| What it marks | Where most trading happened, a psychological anchor | Upper edge of the Value Area | Lower edge of the Value Area |
The algorithm the docs give for Value Area is "similar to the algorithm used by our Volume Profile indicators" — which is a conclusion derived from the official definition, not something TradingView spells out in a single sentence: the docs never say "this is the Market Profile one-sigma 70% rule", only that the default is 70% and it's tunable. If you see 68% or 68.27% written somewhere else, that's someone pulling a statistical sigma out and calling it the same thing — it's not TradingView's number, and whether it lines up with actual traded volume is on you to verify.
Why POC Lives Inside a Single Candle — Footprint and Volume Profile Aren't the Same Chart
When people say "Volume Profile" the picture in their head is usually TradingView's Fixed Range Volume Profile or Session Volume Profile — a distribution accumulated over some span of time (a day, a session, whatever range you draw) (the general Volume Profile use case is covered in this post). Footprint isn't that: it turns a single candle into its own miniature Volume Profile. The candle's price range is sliced into rows, and each row records the buy/sell volume traded inside that candle at that price.
The consequence for automation: the POC you get from request.footprint() in Pine v6 is the POC of "this candle", not "the entire trading day". Want the daily POC? Attach the script to a daily timeframe. Want the weekly POC? Attach it to weekly — whatever timeframe of profile you want, you have to attach the script to that timeframe. That's the current shape of this API (the Application Programming Interface, the set of functions TradingView exposes to scripts).
request.footprint() an argument for "accumulate across N candles". If you want to stack it, you have to walk candle by candle, expand reqFootprint.rows(), and sum buy/sell for the same price row yourself — this version of the API doesn't do that step for you. The other option is to overlay the Fixed Range Volume Profile indicator on the chart, read the numbers off by eye, and feed them into the script as constants (not precise, but runnable today).The Three Arguments to request.footprint: ticks_per_row, va_percent, imbalance_percent
The signature is short:
request.footprint(ticks_per_row, va_percent, imbalance_percent) → series footprintAll three arguments are simple — constants that have to be decided at script start; you can't pass a series value in dynamically. This limitation is stated explicitly in the docs, and breaking it fails compilation.
| Argument | Type | Default | Meaning |
|---|---|---|---|
ticks_per_row | simple int (required) | None | Row width = ticks_per_row × syminfo.mintick. mintick=0.1 with 100 means each row is $10 wide |
va_percent | simple float | 70 | How much of total volume to collect before stopping (this defines the Value Area) |
imbalance_percent | simple float | 300 | How many times larger the bigger side must be than the smaller in adjacent rows to count as imbalance; default is 3x |
Arguments for request.footprint. Source: TradingView Pine Script v6 'Requesting data from other timeframes and contexts' page, request.footprint section (verified 2026-08).
The imbalance formula the docs give is straightforward: max(buy, sell) ≥ (imbalance_percent / 100) × min(buy, sell) — the default of 300 means "the majority side has to be at least three times the minority side". So dialing imbalance_percent down (say, 150) picks up more imbalances; dialing it up (say, 500) leaves only the most extreme ones.
A Minimal Runnable Script for POC / VAH / VAL
Here's the smallest skeleton that plots the three prices on the chart, matching the request.footprint example in the Pine Script v6 concept docs:
//@version=6
indicator("POC / VAH / VAL demo", overlay = true)
int ticksInput = input.int(100, "Ticks per row", minval = 1)
footprint fp = request.footprint(ticksInput)
bool hasData = not na(fp)
// footprint.poc / vah / val return volume_row IDs; one more hop to get a price
float pocMid = hasData
? (volume_row.up_price(footprint.poc(fp)) + volume_row.down_price(footprint.poc(fp))) / 2
: na
float vahTop = hasData ? volume_row.up_price(footprint.vah(fp)) : na
float valBot = hasData ? volume_row.down_price(footprint.val(fp)) : na
plot(pocMid, "POC", color.new(color.orange, 0), 2, plot.style_circles)
plot(vahTop, "VAH", color.new(color.aqua, 0))
plot(valBot, "VAL", color.new(color.aqua, 0))The key point: footprint.poc(), footprint.vah(), and footprint.val() all return volume_row IDs, not prices — you get actual prices by piping the ID through volume_row.up_price() (top edge of that row) or volume_row.down_price() (bottom edge). POC is usually represented by its midpoint (average of top and bottom); for VAH and VAL, taking the outer edge (VAH uses up_price, VAL uses down_price) matches the "upper/lower boundary" intuition.
Check na first: request.footprint() returns na on candles with no footprint data, and the id parameter on footprint.*() doesn't accept na. Skip this check and you get a runtime error instead of na — the script stops dead on that candle.
Three Traps to Know Before You Start
Trap 1: requires Premium or Ultimate plan
This is a hard constraint. Official wording: "Volume footprints are available exclusively to users who have a Premium or Ultimate plan." If you publish the script and a subscriber isn't on one of those tiers, the chart just goes empty — Pine doesn't tell them why, they just think the script is broken. Put this restriction in the script description before you release.
Trap 2: request.footprint can only be called once per script
Call it a second time and you get a runtime error. Want to compare "does POC move if I change ticks_per_row?" — you can't, you have to change the argument and rerun the whole script, one config at a time. Stashing the footprint ID in var/varip doesn't help either; the limit is on the function call, not on where you store the ID (the var/varip mechanics are dissected in Why Pine Script Alerts Don't Remember Anything).
Trap 3: on 1D and up, total_volume can disagree with volume
This one fails quietly. The docs are explicit: "On timeframes higher than or equal to '1D', a footprint's total volume might differ significantly from the value of the volume variable." The reason is that EOD (end-of-day) data can include block trades and OTC prints, while intraday feeds don't. Footprint runs on the intraday feed; the built-in volume variable runs on the EOD feed — the two are describing slightly different things by definition. Your strategy works fine on hourly or minute bars, then you switch to daily and the numbers stop lining up — this kind of bug doesn't shout, it fails quietly.
Run This Decision Before Using POC as Support or Resistance
Two things to sort out first: what the signal actually represents, and whether your subscribers can even receive it.
request.footprint() is what you want — this is what it's forfootprint.rows() candle by candle and stack it yourself, or overlay Fixed Range Volume Profile on the chart and feed the numbers in by eye. This version of the API doesn't do it for youA safer way to use these levels in practice: treat footprint.vah()/footprint.val() as a signal filter rather than as an entry — the previous candle closed above VAH and delta is positive, only then does the long side get to fire. That framing doesn't lean on the strong claim that "POC is an absolute support"; it just uses the Value Area boundary as an extra gate. If you do use POC as support/resistance, remember to widen slippage assumptions in your backtest — the price where the most people have already traded is also usually the most crowded price, and your order isn't first in line.
The Honest Section: Where This Data Tops Out
request.footprint() gives you "the footprint of this candle" — it's not a full order flow toolchain. Real order flow data — tick-level buy/sell classification, cross-session cumulative distribution, live changes in market delta — none of that ships through this API. To do that kind of analysis you either fall back to request.security_lower_tf() and stitch second-level bars together yourself, or move to a dedicated order flow platform. TradingView hasn't promised request.footprint() will grow to fill that gap; this is a read of the current v6 API surface, not a line from the official docs.
Another ceiling: per-bar delta accuracy depends on volume categorization — TradingView classifies by looking at each intrabar (a shorter interval inside the candle, e.g. 1s / 1min / 60min depending on chart timeframe): close above open counts as buy, below as sell; only when the two are equal does it fall back to comparing against the previous intrabar's close. Real order flow needs order-book depth to classify perfectly; this approximation is good enough for most timeframes but drifts in very fast markets or thin-liquidity symbols. It's the shared ceiling on any delta analysis built on TradingView tape.
Frequently Asked Questions
Configuration and compatibility
What do I get if I try this on a Basic or Pro plan?
request.footprint() returns na, everything downstream turns into na, and nothing plots on the chart.Is the footprint POC the same as the POC from the Session Volume Profile indicator?
Wiring it into TVSBot
What does the TVSBot side need to do to receive a POC signal?
alert() send a JSON payload over. TVSBot's current webhook schema requires secret, strategy (the strategy slot you created in the dashboard), action (also accepts the side alias, i.e. the name Pine users are used to), symbol, and exchange; non-close actions also need qty. Other fields (market_type, order_type, qty_type, etc.) have defaults and can be omitted. The specific price you computed in Pine (the POC number itself) doesn't go into the payload — TVSBot decides whether to place an order based on the trigger, not on the price. The price logic stays on the Pine side. That's a deliberate split of responsibilities, not something that hasn't been wired up yet.Reading the signal
What happens if I change va_percent from 70 to 100?
va_percent = 100 means the entire candle's volume gets counted into the Value Area — VAH becomes the candle's high, VAL becomes its low, both equal to high/low, and the signal loses its meaning. Dialing it down to 30 makes the VA too narrow, framing only a few rows around POC, and it usually can't be used as a range boundary.Why is delta sometimes positive but price goes down?
Get started
Webhook the POC / VAH / VAL signals your Pine script computes over to TVSBot — use your own API key (the credential your exchange issues you), dry-run first, and set account-level risk controls yourself.
Start free- Volume Profile / VWAP / TWAP Complete Guide — The Secret of Institutional Order Flow (2026)
- Why Pine Script Alerts Don't Remember Anything (and Why That Breaks Naive Automation)
- Pine Script for Beginners — Write Your First Strategy in 30 Minutes (v6 Complete Tutorial)
- You Generated a Pine Script Strategy With AI — Here's What It Takes to Actually Trade It
- TradingView Webhook Complete Tutorial — From Zero to Auto-Trading (2026)