A stock trading three times its usual volume can look unusual. But unusual volume does not automatically mean a bullish signal, a company-specific event or a change in market direction.
Before interpreting the activity, a researcher needs to establish what “usual” means.
One practical way to investigate that question is Relative Volume (RVOL): a comparison between observed trading volume and a historical reference.
Ositrustees is a financial data and analytical technology platform focused on market screeners, sector monitoring, chart-based research and structured market-data workflows.
In this tutorial, we will build a small relative volume screener using Python and pandas, identify instruments showing elevated activity and examine the limitations of the resulting observations.
The example uses synthetic data. It is intended to demonstrate the calculation, not represent live market information or an internal software component.
- Understanding Relative Volume A basic completed-session RVOL calculation divides the evaluated session’s total volume by the average volume over a selected number of previous completed sessions.
RVOL = completed-session volume / average volume over the previous N sessionsFor example, suppose a stock’s average volume over the previous 20 completed sessions is 500,000 shares.
The completed session being evaluated records 1,500,000 shares.
Its relative volume is:
RVOL = 1,500,000 / 500,000 = 3.0
The stock recorded three times its selected historical average volume.
That is an observation about activity, not a conclusion about its cause.
The lookback period also matters. A 10-session average and a 20-session average can produce different RVOL values for the same instrument.
For this example, we will use the previous 20 completed sessions.
- Prepare a Reproducible Dataset Install pandas if necessary:
pip install pandas
We will create three hypothetical instruments with different historical volume levels.
Each instrument has 20 baseline sessions and one additional completed session that we want to evaluate.
import pandas as pd
rows = []
instruments = [
("ALFA", 500_000, 1_500_000),
("BETA", 800_000, 960_000),
("GAMA", 300_000, 780_000),
]
for symbol, baseline, latest in instruments:
for session in range(1, 22):
volume = baseline if session <= 20 else latest
rows.append({
"symbol": symbol,
"session": session,
"volume": volume,
})
df = pd.DataFrame(rows)
df = df.sort_values(
["symbol", "session"]
).reset_index(drop=True)
The baseline volumes are deliberately constant so that the expected calculations are easy to verify.
Real market data is more variable. We will return to that limitation later.
The session numbers represent an ordered sequence of completed sessions, not calendar dates.
- Calculate the Historical Average A common implementation mistake is including the evaluated session in the historical average used to assess that same session.
Doing so changes the reference while the observation is being measured.
Instead, we shift the volume series by one observation before calculating the rolling mean.
df["avg_20_prev"] = (
df.groupby("symbol")["volume"]
.transform(
lambda s: s.shift(1)
.rolling(
window=20,
min_periods=20
)
.mean()
)
)
Three operations matter here:
- groupby("symbol") keeps the instruments separate.
- shift(1) excludes the session being evaluated.
- rolling(window=20, min_periods=20) requires 20 previous observations. The first 20 sessions for each instrument will have missing historical averages. That is expected: our dataset does not contain enough earlier observations to calculate them.
For production research, there is another important requirement: each row must represent a comparable trading session.
A 20-row average does not automatically mean 20 valid, complete market sessions.
- Calculate RVOL and Apply a Screener Next, divide each session’s volume by the historical reference.
df["rvol"] = (
df["volume"] / df["avg_20_prev"]
)
latest = (
df.groupby("symbol")
.tail(1)
.copy()
)
screened = (
latest[latest["rvol"] >= 2.0]
.sort_values("rvol", ascending=False)
)
print(
latest[
["symbol", "volume", "avg_20_prev", "rvol"]
].to_string(index=False)
)
The resulting values are:
Symbol Completed-Session Volume Previous 20-Session Average RVOL
ALFA 1,500,000 500,000 3.0
BETA 960,000 800,000 1.2
GAMA 780,000 300,000 2.6
Now display the instruments selected by the screener:
With an RVOL threshold of 2.0, the screener selects:
Symbol Completed-Session Volume Previous 20-Session Average RVOL
ALFA 1,500,000 500,000 3.0
GAMA 780,000 300,000 2.6
BETA does not qualify, even though its evaluated volume exceeds its historical average.
The threshold is a configurable research condition, not a universal definition of significant market activity.
- Verify the Result A simple automated check can help prevent calculation errors.
results = latest.set_index("symbol")["rvol"]
assert abs(results["ALFA"] - 3.0) < 1e-9
assert abs(results["BETA"] - 1.2) < 1e-9
assert abs(results["GAMA"] - 2.6) < 1e-9
assert set(screened["symbol"]) == {
"ALFA", "GAMA"
}
print("RVOL checks passed.")
These checks confirm that the calculation behaves as expected for our synthetic dataset.
They do not validate the completeness or accuracy of an external data feed. That requires separate checks.
The example was tested with pandas 2.2.3. It produced RVOL values of 3.0, 1.2 and 2.6, and all assertions passed.
- Why a High RVOL Reading Can Mislead The calculation is straightforward. The interpretation requires context.
Consider three situations.
A partially completed trading session
If today’s volume is measured at 11:00 a.m. but compared with the average volume of entire sessions, the result measures a partial session against full-day activity.
It may still be useful for a particular research question, but it is not equivalent to a completed-session RVOL calculation.
A time-of-day comparison against historical volume accumulated to the same point in the session may be more appropriate.
Different trading-session coverage
One dataset may include regular-session trading only. Another may include extended-hours transactions.
Their volume totals can differ even when both sources accurately report their respective coverage.
A screener should therefore identify which session definition is being used and apply it consistently to the evaluated session and historical reference.
Changes in the underlying data
Corporate actions, changes in instrument liquidity, missing sessions and revised historical records can affect the reference period.
For example, a stock split changes the number of shares outstanding, making it important to understand how the provider handles historical share-volume adjustments.
A high ratio may also partly reflect an unusually low historical denominator rather than extraordinary current activity.
A robust research process investigates those conditions before assigning meaning to the ratio.
- What Should Happen After Screening? An RVOL screener helps identify instruments that deserve closer investigation. It does not explain why the activity occurred.
A useful next stage is to connect the result with additional context.
First, compare the instrument with others in its sector. If several related companies show elevated activity over comparable periods, the observation may form part of a broader pattern.
Next, examine the chart. Did volume increase near the market open, around a company announcement or throughout the session?
Then review the available sources and data definitions. Are the timestamps, trading sessions and volume coverage comparable?
Finally, record what was actually observed and what remains uncertain.
This distinction between screening and interpretation is central to structured market-data research.
The example illustrates a general research principle relevant to the Ositrustees analytical focus: a screening result becomes useful when the conditions behind it can be examined and reproduced.
- From Prototype to Research Workflow Our Python example is intentionally small.
A production-oriented screener would need additional safeguards, including:
- Validation of instrument identifiers and market venues.
- Consistent session boundaries and time zones.
- Detection of duplicate, missing or incomplete observations.
- Handling of zero or invalid historical average volume.
- Documentation of historical adjustments and data revisions.
- Recording the screening threshold, lookback period and observation time. It should also distinguish between a daily RVOL measure and an intraday measure based on comparable elapsed trading periods.
Those choices determine what the result actually represents.
Final Thought
A stock with an RVOL of 3.0 is not automatically an opportunity, a warning or evidence of a particular market event.
It is an instrument showing volume three times its selected historical reference.
The research begins with that observation.
A reliable screening workflow makes the calculation reproducible, preserves the relevant assumptions and creates a path toward deeper investigation.
Screen the activity. Check the context. Verify the observation.
Research. Compare. Verify.
Technical reference: pandas documentation — GroupBy operations
All instruments and values in this tutorial are synthetic. This article is for educational and informational purposes only and does not constitute investment advice.
Top comments (0)