Why Relative Volume Differs Between Platforms
Relative volume rarely agrees across platforms. One AAPL session computed five ways from the same bars shows why readings split most in the first hour.
Relative volume differs between platforms for a plain reason: no two of them compute the same ratio. Each divides today's volume by an average of past volume, but each picks its own basis (the whole day so far, or only the volume printed by this clock minute on past days), its own lookback, and its own set of bars. Below, one AAPL session runs through five of those definitions from the same minute bars, and at 10:30 a.m. the readings differ by multiples.
Why relative volume differs between platforms
Relative volume, or RVOL, is volume now divided by normal volume. Every disagreement between two platforms hides inside the word normal, in one of four choices.
- The basis. A full-day basis divides the volume printed so far today by the average of complete past sessions. A same-time basis divides it by the average volume that past sessions had printed by this same minute. At 4:00 p.m. the two are identical. At 9:45 a.m. they are not close.
- The lookback. Ten sessions, twenty, fifty, or about three months (63 sessions). A short lookback tracks the stock's recent behaviour; a long one dilutes it.
- Which bars count. Pre-market prints, after-hours prints, half-days and the closing auction each sit inside or outside the ratio depending on the platform's session template.
- The clock. A screener on delayed data compares a 15-minute-old cumulative volume with an average built for the current minute.
The relative volume guide covers what the ratio is for. This post is about why two screens showing 0.4 and 1.2 for the same stock at the same minute can both be arithmetically right, the same way RSI differs between platforms: a shared name, an unshared formula.
What each platform says it computes
Where a platform documents its definition it is quoted or paraphrased here; where it does not, that is said rather than guessed.
Finviz publishes one line on its screener field:
"Ratio between current volume and 3-month average value, intraday adjusted." Finviz screener help, Relative Volume filter, as read on September 16, 2026.
The three-month lookback is documented. The intraday adjustment is not: the help page does not say how a partial day is scaled against full days, so the mid-session figure cannot be reproduced from outside.
TradingView documents two things. Its field called Relative Volume is volume divided by a simple moving average of the previous 10 bars, excluding the current one. Its indicator called Relative Volume at Time averages volume at the same time offset across a set number of past periods (10 days in TradingView's worked description) and has a calculation-mode input:
"Cumulative (default): the indicator uses the total volume accumulated since the last period anchor. Regular: the indicator uses the non-cumulative volume at the time offset." TradingView Help Center, Relative Volume at Time, as read on September 16, 2026.
The cumulative, same-time version is the one built below.
thinkorswim documents a study named RelativeVolumeStDev: volume divided by its simple moving average, expressed in standard deviations over a length set in bars, with a spike flagged above 2.0. A time-of-day-matched cumulative ratio is not among its documented built-in studies; community thinkScript versions exist and set their own lookbacks.
Only one of these documented formulas, TradingView's Relative Volume at Time, compares like clock time with like.
The same session against four lookbacks
Start with the version every end-of-day screener shares in shape: a completed session's volume over an average of completed sessions, where the only free choice is the lookback. The panel takes AAPL's session of Thursday, September 10, 2026, and divides its consolidated daily volume by the average of the 10, 20, 50 and 63 sessions before it.
| lookback | session_volume_millions | average_volume_millions | rvol_ratio |
|---|---|---|---|
| 10 sessions | 70 | 41.1 | 1.7 |
| 20 sessions | 70 | 40.6 | 1.72 |
| 50 sessions | 70 | 48.6 | 1.44 |
| 63 sessions (about 3 months) | 70 | 53.4 | 1.31 |
The exact SQL behind every number
WITH toDate('2026-09-10') AS session_day
SELECT
lookback,
round(session_volume / 1e6, 1) AS session_volume_millions,
round(average_volume / 1e6, 1) AS average_volume_millions,
round(session_volume / average_volume, 2) AS rvol_ratio
FROM
(
SELECT
date,
vol AS session_volume,
avg(vol) OVER (ORDER BY date ROWS BETWEEN 10 PRECEDING AND 1 PRECEDING) AS avg10,
avg(vol) OVER (ORDER BY date ROWS BETWEEN 20 PRECEDING AND 1 PRECEDING) AS avg20,
avg(vol) OVER (ORDER BY date ROWS BETWEEN 50 PRECEDING AND 1 PRECEDING) AS avg50,
avg(vol) OVER (ORDER BY date ROWS BETWEEN 63 PRECEDING AND 1 PRECEDING) AS avg63,
count() OVER (ORDER BY date ROWS BETWEEN 63 PRECEDING AND 1 PRECEDING) AS prior_sessions
FROM
(
SELECT date, max(toFloat64(volume)) AS vol
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'AAPL'
AND date >= session_day - 110
AND date <= session_day
GROUP BY date
)
)
ARRAY JOIN
['10 sessions', '20 sessions', '50 sessions', '63 sessions (about 3 months)'] AS lookback,
[10, 20, 50, 63] AS n,
[avg10, avg20, avg50, avg63] AS average_volume
WHERE date = session_day
AND prior_sessions >= 63
AND session_volume > 0
ORDER BY nThe session printed 70 million shares. Against the prior 10 sessions that is a relative volume of 1.7; against the prior 63, roughly the three-month window Finviz names, it is 1.31, with the 50-session reading at 1.44. Nothing about the day changed between those numbers. Only the definition of normal did, from a 41.1 million-share average over ten sessions to 53.4 million over sixty-three. When a stock has been quieter or busier lately than it was three months ago, short and long lookbacks disagree by exactly that drift, which is why the average daily volume window matters before the ratio is ever computed.
Why the gap is widest in the first hour
The lookback nudges the reading. The basis moves it by multiples, and only during the session. The next panel adds up AAPL's one-minute bars for the same September 10 session at 14 clock checkpoints, keeping bars whose Eastern clock time runs from 9:30 a.m. through the 4:00 p.m. bar that carries the closing auction print, alongside the average cumulative volume at each checkpoint over the prior 10 sessions.
| et_time | session_cumulative_millions | typical_cumulative_millions | typical_share_of_session_pct |
|---|---|---|---|
| 09:45 | 8.3 | 2.7 | 8.5 |
| 10:00 | 11.1 | 4.5 | 14.1 |
| 10:30 | 14.5 | 7.5 | 23.8 |
| 11:00 | 19 | 10.1 | 31.9 |
| 11:30 | 23.4 | 12.4 | 39.2 |
| 12:00 | 29.2 | 14.5 | 45.7 |
| 12:30 | 32.8 | 16.3 | 51.4 |
| 13:00 | 38.2 | 17.7 | 55.8 |
| 13:30 | 41.2 | 19.5 | 61.5 |
| 14:00 | 45.3 | 21 | 66.4 |
| 14:30 | 48 | 22.9 | 72.4 |
| 15:00 | 50.3 | 25.1 | 79.3 |
| 15:30 | 53.4 | 26.9 | 85.1 |
| 16:00 | 59.3 | 31.7 | 100 |
The exact SQL behind every number
WITH
toDate('2026-09-10') AS session_day,
[585, 600, 630, 660, 690, 720, 750, 780, 810, 840, 870, 900, 930, 960] AS checkpoints
SELECT
formatDateTime(toDateTime(session_day, 'UTC') + checkpoints[i] * 60, '%H:%i', 'UTC') AS et_time,
round(session_cums[i] / 1e6, 1) AS session_cumulative_millions,
round(typical_cums[i] / 1e6, 1) AS typical_cumulative_millions,
round(100 * typical_cums[i] / typical_cums[14], 1) AS typical_share_of_session_pct
FROM
(
SELECT
arrayJoin(arrayEnumerate(checkpoints)) AS i,
session_cums,
arrayMap(k -> arrayAvg(x -> arrayElement(tupleElement(x, 2), k), arraySlice(prior, 1, 10)),
arrayEnumerate(checkpoints)) AS typical_cums
FROM
(
SELECT
anyIf(cums, d = session_day) AS session_cums,
arrayReverseSort(x -> tupleElement(x, 1), groupArrayIf((d, cums), d < session_day)) AS prior
FROM
(
SELECT
d,
arrayMap(cp -> arraySum(x -> if(tupleElement(x, 1) <= cp, tupleElement(x, 2), 0), mv),
checkpoints) AS cums
FROM
(
SELECT d, groupArray((minute_of_day, vol)) AS mv
FROM
(
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York')) AS minute_of_day,
max(toFloat64(volume)) AS vol
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'AAPL'
AND window_start >= toDateTime(session_day - 20, 'America/New_York')
AND window_start < toDateTime(session_day + 1, 'America/New_York')
GROUP BY d, minute_of_day
HAVING minute_of_day >= 570 AND minute_of_day <= 960
)
GROUP BY d
)
)
HAVING length(session_cums) = 14 AND length(prior) >= 10
)
)
ORDER BY iA typical AAPL session in this window had printed 8.5% of its regular-session volume by 9:45 a.m. and 23.8% by 10:30 a.m., on its way to 31.7 million shares at the close, where the share reaches 100% by definition. Volume front-loads: a large share of the session prints in the first hour, the pace flattens through lunch (the shape in why trading volume dies at midday), and the closing auction adds a final block at 4:00 p.m.
Now divide the session's cumulative volume two ways. The same-time basis divides it by the typical cumulative volume at that checkpoint. The full-day basis divides it by the typical full-session total, which is what a screener effectively does when it sets volume so far against a plain average daily volume. Both use the identical 10-session lookback, so the basis is the only difference.
| et_time | same_time_basis_ratio | full_day_basis_ratio |
|---|---|---|
| 09:45 | 3.1 | 0.26 |
| 10:00 | 2.48 | 0.35 |
| 10:30 | 1.92 | 0.46 |
| 11:00 | 1.88 | 0.6 |
| 11:30 | 1.88 | 0.74 |
| 12:00 | 2.02 | 0.92 |
| 12:30 | 2.01 | 1.04 |
| 13:00 | 2.16 | 1.21 |
| 13:30 | 2.12 | 1.3 |
| 14:00 | 2.16 | 1.43 |
| 14:30 | 2.09 | 1.51 |
| 15:00 | 2 | 1.59 |
| 15:30 | 1.98 | 1.69 |
| 16:00 | 1.87 | 1.87 |
The exact SQL behind every number
WITH
toDate('2026-09-10') AS session_day,
[585, 600, 630, 660, 690, 720, 750, 780, 810, 840, 870, 900, 930, 960] AS checkpoints
SELECT
formatDateTime(toDateTime(session_day, 'UTC') + checkpoints[i] * 60, '%H:%i', 'UTC') AS et_time,
round(session_cums[i] / typical_cums[i], 2) AS same_time_basis_ratio,
round(session_cums[i] / typical_cums[14], 2) AS full_day_basis_ratio
FROM
(
SELECT
arrayJoin(arrayEnumerate(checkpoints)) AS i,
session_cums,
arrayMap(k -> arrayAvg(x -> arrayElement(tupleElement(x, 2), k), arraySlice(prior, 1, 10)),
arrayEnumerate(checkpoints)) AS typical_cums
FROM
(
SELECT
anyIf(cums, d = session_day) AS session_cums,
arrayReverseSort(x -> tupleElement(x, 1), groupArrayIf((d, cums), d < session_day)) AS prior
FROM
(
SELECT
d,
arrayMap(cp -> arraySum(x -> if(tupleElement(x, 1) <= cp, tupleElement(x, 2), 0), mv),
checkpoints) AS cums
FROM
(
SELECT d, groupArray((minute_of_day, vol)) AS mv
FROM
(
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York')) AS minute_of_day,
max(toFloat64(volume)) AS vol
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'AAPL'
AND window_start >= toDateTime(session_day - 20, 'America/New_York')
AND window_start < toDateTime(session_day + 1, 'America/New_York')
GROUP BY d, minute_of_day
HAVING minute_of_day >= 570 AND minute_of_day <= 960
)
GROUP BY d
)
)
HAVING length(session_cums) = 14 AND length(prior) >= 10
)
)
ORDER BY iAt 9:45 a.m. the same-time reading was 3.1 and the full-day reading was 0.26. At 10:30 a.m. they were 1.92 and 0.46. At the close both lines meet at 1.87, by construction: once the session is complete, volume by this minute and volume for the day are the same number.
The full-day line measures what fraction of an ordinary day has printed so far, multiplied by the actual relative volume; it says little about how busy the open was. Early in the session that fraction is small, so the line starts near zero and climbs toward its end-of-day value whatever the stock is doing. The gap between the two definitions is largest when the fraction is smallest, the first hour, and it narrows through the afternoon.
The five readings side by side
The last panel puts every definition on one set of rows at two moments, 10:30 a.m. ET and the 4:00 p.m. close, all from the same regular-session bars: full-day rows at three lookbacks and same-time rows at two, from 10 sessions up to 63.
| definition | at_10_30_et_ratio | at_close_ratio |
|---|---|---|
| Full-day basis, 63-session (3-month) average | 0.38 | 1.54 |
| Full-day basis, 50-session average | 0.4 | 1.62 |
| Full-day basis, 10-session average | 0.46 | 1.87 |
| Same-time basis, 50-session average | 1.52 | 1.62 |
| Same-time basis, 10-session average | 1.92 | 1.87 |
The exact SQL behind every number
WITH toDate('2026-09-10') AS session_day
SELECT
tupleElement(r, 1) AS definition,
round(tupleElement(r, 2), 2) AS at_10_30_et_ratio,
round(tupleElement(r, 3), 2) AS at_close_ratio
FROM
(
SELECT
arrayJoin([
('Full-day basis, 63-session (3-month) average', session_1030 / avg63_full, session_close / avg63_full, 1),
('Full-day basis, 50-session average', session_1030 / avg50_full, session_close / avg50_full, 2),
('Full-day basis, 10-session average', session_1030 / avg10_full, session_close / avg10_full, 3),
('Same-time basis, 50-session average', session_1030 / avg50_same, session_close / avg50_full, 4),
('Same-time basis, 10-session average', session_1030 / avg10_same, session_close / avg10_full, 5)
]) AS r
FROM
(
SELECT
anyIf(cum_1030, d = session_day) AS session_1030,
anyIf(cum_close, d = session_day) AS session_close,
arrayReverseSort(x -> tupleElement(x, 1), groupArrayIf((d, cum_1030, cum_close), d < session_day)) AS prior,
arrayAvg(x -> tupleElement(x, 3), arraySlice(prior, 1, 63)) AS avg63_full,
arrayAvg(x -> tupleElement(x, 3), arraySlice(prior, 1, 50)) AS avg50_full,
arrayAvg(x -> tupleElement(x, 3), arraySlice(prior, 1, 10)) AS avg10_full,
arrayAvg(x -> tupleElement(x, 2), arraySlice(prior, 1, 50)) AS avg50_same,
arrayAvg(x -> tupleElement(x, 2), arraySlice(prior, 1, 10)) AS avg10_same
FROM
(
SELECT
d,
sumIf(vol, minute_of_day <= 630) AS cum_1030,
sum(vol) AS cum_close
FROM
(
SELECT
toDate(toTimeZone(window_start, 'America/New_York')) AS d,
toHour(toTimeZone(window_start, 'America/New_York')) * 60
+ toMinute(toTimeZone(window_start, 'America/New_York')) AS minute_of_day,
max(toFloat64(volume)) AS vol
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'AAPL'
AND window_start >= toDateTime(session_day - 100, 'America/New_York')
AND window_start < toDateTime(session_day + 1, 'America/New_York')
GROUP BY d, minute_of_day
HAVING minute_of_day >= 570 AND minute_of_day <= 960
)
GROUP BY d
)
HAVING length(prior) >= 63 AND session_close > 0
)
)
ORDER BY tupleElement(r, 4)At 10:30 a.m. the screener-style row, full-day basis against 63 sessions, read 0.38. The chart-style row, same-time basis against 10 sessions, read 1.92 at the same minute. By the close the same two rows read 1.54 and 1.87, and what remains between them is only the lookback: each same-time row collapses onto its full-day twin the moment the session ends. Down the 10:30 column the basis dominates; down the close column only the lookback is left. The 10-session full-day row here will not match the lookback panel exactly: that panel uses consolidated daily volume, extended hours included, while these rows use regular-session bars only.
Which definition fits which job
A rule that survives the table: match the basis to the moment, and the lookback to the question.
Pre-market gap screening happens before any regular-session bar exists, so a same-time basis has nothing to compare against unless the platform keeps a pre-market-specific average, and none of the documented definitions above describes one. A full-day basis over a long lookback is the practical form here, with one caveat: the number is not comparable with 1.0. It ranks candidates against each other, and its meaning shifts minute by minute as more of the day fills in.
Intraday confirmation, asking whether a move at 10:15 a.m. is being met with unusual participation right now, is the job for the same-time basis over a short lookback of 10 to 20 sessions. Its reading is comparable with 1.0 at any minute, so a threshold set at the open still means the same thing at lunch.
After the close, the basis no longer matters and the only decision left is the lookback. A 10-session average answers "unusual versus the last two weeks"; a 63-session average answers "unusual versus the quarter". The weekly unusual volume screen uses the completed-session form for that reason.
Whichever definition a platform uses, the reading is only interpretable next to its formula. A screen that shows RVOL without its basis and lookback cannot be read against 1.0, or against another platform.
FAQ
Why is relative volume different on Finviz and TradingView?
Finviz documents a three-month average with an unpublished intraday adjustment. TradingView's Relative Volume field documents a 10-bar simple moving average, and its Relative Volume at Time indicator compares cumulative volume with the same clock time over past sessions. Different lookbacks, and during the session a different basis, so the two rarely print the same figure for the same stock.
What is a good relative volume number?
It depends on the definition. A same-time reading is comparable with 1.0 at any minute: 2.0 means twice the volume a typical session had printed by this time. A full-day reading starts near zero at the open and rises through the day, so 0.4 at 10:00 a.m. can be an ordinary open. Any threshold has to be set against the specific formula the screen uses.
Does relative volume include pre-market volume?
It depends on the platform's session template. Consolidated daily volume, the kind a full-day screener averages, includes pre-market and after-hours prints. A chart set to regular hours excludes them from both sides of the ratio. The intraday panels above use regular-session one-minute bars only, including the 4:00 p.m. bar where the closing auction prints.
Why does relative volume look low right after the open?
On a full-day basis it always does: only a small fraction of a typical day has printed by 9:45 a.m., so volume so far divided by a whole normal day is small even on a busy open. A same-time basis removes that effect by comparing the first fifteen minutes with the first fifteen minutes of past sessions.
Every panel above carries its SQL beneath it; swap the ticker or the session date and the same arithmetic runs on any name. To compute relative volume on a definition of your own, ask the question in plain English on the Strasmore terminal.