Relative Volatility Index (Donald Dorsey, 1993) — rsi's form
applied to the standard deviation of the price instead of to the price
change: of the recent volatility, how much of it happened on up bars?
Appends one column, bounded 0..100. Dorsey built it as a confirming
indicator rather than a standalone one: momentum studies and volatility
studies fail on different kinds of market, so a breakout that RSI likes and
this one does not is a breakout on falling volatility. The conventional
thresholds are RSI's — above 50 (or 60) for confirmation of a long, below
for a short.
The name — read this before charting it beside rvi
"RVI" is used in the wild for both this and the Relative Vigor Index,
which is a completely different study (see relativeVigorIndex: a
ratio of smoothed candle bodies to smoothed ranges, not bounded, not built
on σ). So this study's default output is relVol, not rvi — the
shorter name stays with the Vigor Index, which claimed it first and whose
option is a prefix shared with a signal column. The two can therefore
both be appended to one series with no output juggling, which is the
point of not reusing it.
Which Relative Volatility Index — the corpus flags this F-AMBIG
Three real forks, all shipped by someone:
Dorsey's revision (1995), which is what ships: σ over 10 bars,
Wilder-smoothed over 14, on the close. His 1993 original ran the
whole thing on a 9-bar σ with a 14-bar smoothing; the revision is what
every current write-up states, and both lengths are options here so the
original is { period: 14, stdevPeriod: 9 }.
The high/low variant, also Dorsey's: compute the whole index
separately on the highs and on the lows and average the two readings.
That is a second study composed of two of these, and it is deliberately
not an option — a caller who wants it runs this twice with
column: 'high' and column: 'low' and averages, which is three lines
and is visible.
The EMA-smoothed variant (TradingView's built-in rvi smooths the
two legs with an EMA rather than Wilder's recursion). This ships
Wilder's, because Dorsey defined it as "RSI's calculation with σ
substituted for the price change" and RSI's smoothing is Wilder's — the
rsi kernel's own argument, that a recursive average is defined by
its seed as much as by its rate. The two differ materially, not
cosmetically; the measured separation on the oracle input is recorded in
the generator.
TA-Lib has no RVI of either kind, so the oracle is a pandas
replication with the analytic first-valid bar asserted and a measured
separation from the EMA-smoothed fork.
Conventions
Population σ (ddof = 0) — the package convention
(bollinger, rollingStdev, zScore,
historicalVolatility, and TA-Lib's own STDDEV). It comes from
rollingValues' stdev, so it is rollingStdev's number
bit-for-bit rather than a second implementation. A sample σ (ddof = 1)
is a constant factor larger and cancels between the two legs, so —
unusually — the choice does not move this reading at all; it is stated
for consistency with the rest of the package rather than because it
matters here.
An unchanged close counts as a down bar. Dorsey's rule is "up if
the close rose", so everything else — including a flat bar — is the other
leg. This is a deliberate asymmetry against rsi, whose
upDownLegValues split gives a flat bar 0 on both legs; there,
a flat bar contributes nothing, here it contributes its σ to the
downside. Both are their authors' definitions and neither is being
reconciled — the fork is stated per study, which is the package's rule
for exactly this.
Warm-up
σ first lands on bar stdevPeriod − 1; the legs need a direction as well,
so from bar 1; the Wilder seed then steps over the leading gap and lands
period − 1 bars later. The column first appears on
stdevPeriod + period − 2 — bar 22 at the defaults. Length-preserving.
Over another study's output the σ starts earlier than that rule
suggests, because it reads the column door: its window counts rows
and computes over the finite cells it holds, so a window with one value
reports σ = 0 rather than nothing (rollingStdev's contract, the
one historicalVolatility flags in its own edges). Measured:
relativeVolatilityIndex({ column: 'sma', period: 3, stdevPeriod: 3 })
over an sma(3) first lands on bar 5, one earlier than the
2 + 3 + 3 − 2 the rule alone would give. Pinned by a test.
Edges
Scale-invariant, and shift-invariant. σ is homogeneous of degree one
in price (so scaling cancels between the two legs) and unchanged by
adding a constant, and the up/down test is a comparison of two prices, so
neither transformation moves the reading. Both are pinned by property
tests.
Bounded 0..100, both ends attainable: a window whose every
direction was up reads 100, and its mirror reads 0. Pinned.
A flat window is 0/0 and needs no guard.U + D = 0 means every σ
in the smoother's memory was zero, which forces U = 0 with it — so the
ratio is JavaScript's own NaN and the study reports undefined on a
halted instrument with no branch (the commodityChannelIndex
finding: a guard no input can distinguish is dead code). σ is never
negative, so there is no crossing-columns case here either; this is a
genuinely guard-free study, unlike ulcerIndex and
choppinessIndex in the same batch.
A stdevPeriod of 1 makes every σ zero, so the whole column is
undefined — honest, and the reason the option is validated as a period
rather than special-cased.
A misnamed column reads all-missing rather than throwing, and that
is a reducer-level split rather than a study-level one worth knowing
about: rollingValues' stdev (and avg) take the range-exact
kernel, which reads a column that is not there as all-NaN, while its
max / min fall through to core's sweep, which rejects the name. So
this study answers empty where ulcerIndex and
verticalHorizontalFilter — on the same door, one reducer along —
throw. Both are pinned by tests; neither is being reconciled here,
because the fix belongs in the kernel.
A leading gap shifts the start rather than emptying the study — the
Wilder kernel steps its seed over one, so running this over another
study's output begins late and is otherwise unaffected. An interior
gap propagates to the end of the series: a recursion has no state to
carry across a hole. That is rsi's and atr's behaviour
for the same structural reason, and it is the sharpest difference between
this study and the other six in its batch, all of which are windows and
recover. Fill before smoothing if you need continuity.
Relative Volatility Index (Donald Dorsey, 1993) — rsi's form applied to the standard deviation of the price instead of to the price change: of the recent volatility, how much of it happened on up bars?
Appends one column, bounded
0..100. Dorsey built it as a confirming indicator rather than a standalone one: momentum studies and volatility studies fail on different kinds of market, so a breakout that RSI likes and this one does not is a breakout on falling volatility. The conventional thresholds are RSI's — above 50 (or 60) for confirmation of a long, below for a short.The name — read this before charting it beside
rvi"RVI" is used in the wild for both this and the Relative Vigor Index, which is a completely different study (see relativeVigorIndex: a ratio of smoothed candle bodies to smoothed ranges, not bounded, not built on σ). So this study's default output is
relVol, notrvi— the shorter name stays with the Vigor Index, which claimed it first and whose option is aprefixshared with a signal column. The two can therefore both be appended to one series with nooutputjuggling, which is the point of not reusing it.Which Relative Volatility Index — the corpus flags this F-AMBIG
Three real forks, all shipped by someone:
{ period: 14, stdevPeriod: 9 }.column: 'high'andcolumn: 'low'and averages, which is three lines and is visible.rvismooths the two legs with an EMA rather than Wilder's recursion). This ships Wilder's, because Dorsey defined it as "RSI's calculation with σ substituted for the price change" and RSI's smoothing is Wilder's — the rsi kernel's own argument, that a recursive average is defined by its seed as much as by its rate. The two differ materially, not cosmetically; the measured separation on the oracle input is recorded in the generator.TA-Lib has no RVI of either kind, so the oracle is a pandas replication with the analytic first-valid bar asserted and a measured separation from the EMA-smoothed fork.
Conventions
ddof = 0) — the package convention (bollinger, rollingStdev, zScore, historicalVolatility, and TA-Lib's ownSTDDEV). It comes from rollingValues'stdev, so it isrollingStdev's number bit-for-bit rather than a second implementation. A sample σ (ddof = 1) is a constant factor larger and cancels between the two legs, so — unusually — the choice does not move this reading at all; it is stated for consistency with the rest of the package rather than because it matters here.0on both legs; there, a flat bar contributes nothing, here it contributes its σ to the downside. Both are their authors' definitions and neither is being reconciled — the fork is stated per study, which is the package's rule for exactly this.Warm-up
σ first lands on bar
stdevPeriod − 1; the legs need a direction as well, so from bar 1; the Wilder seed then steps over the leading gap and landsperiod − 1bars later. The column first appears onstdevPeriod + period − 2— bar 22 at the defaults. Length-preserving.Over another study's output the σ starts earlier than that rule suggests, because it reads the column door: its window counts rows and computes over the finite cells it holds, so a window with one value reports
σ = 0rather than nothing (rollingStdev's contract, the one historicalVolatility flags in its own edges). Measured:relativeVolatilityIndex({ column: 'sma', period: 3, stdevPeriod: 3 })over ansma(3)first lands on bar 5, one earlier than the2 + 3 + 3 − 2the rule alone would give. Pinned by a test.Edges
0..100, both ends attainable: a window whose every direction was up reads100, and its mirror reads0. Pinned.0/0and needs no guard.U + D = 0means every σ in the smoother's memory was zero, which forcesU = 0with it — so the ratio is JavaScript's ownNaNand the study reportsundefinedon a halted instrument with no branch (the commodityChannelIndex finding: a guard no input can distinguish is dead code). σ is never negative, so there is no crossing-columns case here either; this is a genuinely guard-free study, unlike ulcerIndex and choppinessIndex in the same batch.stdevPeriodof 1 makes every σ zero, so the whole column isundefined— honest, and the reason the option is validated as a period rather than special-cased.columnreads all-missing rather than throwing, and that is a reducer-level split rather than a study-level one worth knowing about: rollingValues'stdev(andavg) take the range-exact kernel, which reads a column that is not there as all-NaN, while itsmax/minfall through to core's sweep, which rejects the name. So this study answers empty where ulcerIndex and verticalHorizontalFilter — on the same door, one reducer along — throw. Both are pinned by tests; neither is being reconciled here, because the fix belongs in the kernel.