Indicator TutorialPine Script 5

How to Create Williams %R on TradingView

Plot bounded momentum, classic overbought and oversold bands, and compare signals with RSI thinking.

Williams %R is a fast-reacting momentum oscillator that measures where the latest close sits within the recent high-low window. Larry Williams popularized the tool for spotting overbought and oversold stretches, often with -20 and -80 guides. This tutorial breaks down the math, contrasts Williams %R with RSI, and ships a Pine Script v5 indicator using ta.wpr() with alerts, fills, and a compact cheat sheet table.

What is Williams %R?

Williams %R compares the closing price to the highest high of the lookback window, scaled to a 0 to -100 range (TradingView’s implementation follows the classic negative scale).

Values near 0 indicate closes are hugging the top of the range (strong recent buying pressure within the window). Values near -100 show closes near the bottom of the range.

Unlike RSI, which uses average gains and losses, Williams %R is entirely anchored to the raw high-low envelope, which makes it especially sensitive to sharp spikes and short-term reversals.

Why Traders Use This Indicator

  • Flag short-term overbought and oversold pockets relative to recent range extremes.
  • Confirm breakouts when %R holds in the “upper chamber” during momentum phases.
  • Diverge price versus %R for early warning of fading thrust—always confirm with structure.
  • Pair with slower filters so choppy ranges do not generate excessive false flips.
ParameterDefaultDescription
Length14Matches many default RSI settings for easy side-by-side comparison, though you should tune per market.
Overbought / oversold-20 / -80Common horizontal guides. In strong trends, %R can remain pinned in one chamber for extended runs.
Scale0 to -100TradingView plots the negative convention; some legacy platforms invert or rescale—mind translation.
ConfirmationStructure or trend filterUse higher timeframe bias or price action triggers so oscillator extremes become context, not blind entries.

How Williams %R is calculated

Let Hn be the highest high over the last n bars and Ln the lowest low. With close C:

%R = -100 × (Hn - C) / (Hn - Ln)

When C = Hn, the numerator is zero and %R is 0. When C = Ln, %R is -100. Division by zero is guarded internally when the range collapses.

Pine’s ta.wpr(length) returns this value consistently with the platform engine.

Signal Interpretation

Above -20: Often treated as overbought, but in momentum regimes price can stay “overbought” while grinding higher—think continuation, not automatic short.

Below -80: Mirrors oversold language; confirm with bullish structure for reversals.

Crosses of midline (-50): Some systems use the midpoint as a coarse bias toggle—less common than RSI 50-line plays but analogous in spirit.

Divergences: Bearish when price prints higher highs while %R makes lower highs; bullish for the inverse. Validate with volume or candlestick triggers.

Combining with Other Indicators

Blend Williams %R with a 200-period moving average or market structure rules so shorts at -20 only occur in downtrends and longs near -80 only in uptrends. Add ATR for stop spacing and MACD histogram for slower momentum agreement.

The Hard Way: Writing Pine Script Manually

Challenges of Manual Coding

Hand-code %R using ta.highest and ta.lowest and diff against ta.wpr().

Plot RSI in the same pane with normalized scaling notes for educators.

Require two closes beyond -20 before firing a “confirmed overbought” alert.

Build a histogram of time spent below -80 to study mean-reversion edge cases.

Pine Script 5
//@version=5
indicator("Williams %R Tutorial (ta.wpr)", shorttitle="W%R", overlay=false)

// -----------------------------------------------------------------------------
// Inputs
// -----------------------------------------------------------------------------
length = input.int(14, "Length", minval=1)
ob = input.float(-20.0, "Overbought (upper band)", maxval=0, step=1)
os = input.float(-80.0, "Oversold (lower band)", minval=-100, step=1)
showMid = input.bool(true, "Show -50 mid reference")
showFill = input.bool(true, "Fill OB/OS chambers")
showRSI = input.bool(false, "Plot RSI(14) secondary (scaled note)")

// -----------------------------------------------------------------------------
// Core series
// -----------------------------------------------------------------------------
w = ta.wpr(length)

// RSI for optional educational overlay (distinct scale—interpret carefully)
r = ta.rsi(close, 14)

// -----------------------------------------------------------------------------
// Chamber logic
// -----------------------------------------------------------------------------
inOB = w > ob
inOS = w < os
crossUpOS = ta.crossover(w, os)
crossDnOB = ta.crossunder(w, ob)

// -----------------------------------------------------------------------------
// Plots
// -----------------------------------------------------------------------------
plot(w, title="Williams %R", color=color.new(color.purple, 0), linewidth=2)
plot(showRSI ? r - 100.0 : na, title="RSI shifted for rough compare", color=color.new(color.teal, 65), linewidth=1)

hline(0, "0", color=color.new(color.gray, 70))
hline(-100, "-100", color=color.new(color.gray, 70))
hline(ob, "OB", color=color.new(color.red, 40))
hline(os, "OS", color=color.new(color.green, 40))
plot(showMid ? -50.0 : na, title="-50 mid reference", color=color.new(color.gray, 75), linewidth=1, style=plot.style_circles)

// -----------------------------------------------------------------------------
// Background fills
// -----------------------------------------------------------------------------
bgcolor(showFill and inOB ? color.new(color.red, 88) : na)
bgcolor(showFill and inOS ? color.new(color.green, 88) : na)

// -----------------------------------------------------------------------------
// Markers on chamber exits / entries (illustrative)
// -----------------------------------------------------------------------------
plotshape(crossUpOS, title="Lift off oversold", style=shape.triangleup, location=location.bottom, size=size.tiny, color=color.new(color.lime, 0))
plotshape(crossDnOB, title="Roll from overbought", style=shape.triangledown, location=location.top, size=size.tiny, color=color.new(color.red, 0))

// -----------------------------------------------------------------------------
// Alerts
// -----------------------------------------------------------------------------
alertcondition(crossUpOS, title="W%R left oversold zone", message="Williams %R crossed above oversold threshold")
alertcondition(crossDnOB, title="W%R left overbought zone", message="Williams %R crossed below overbought threshold")
alertcondition(ta.cross(w, -50), title="W%R crossed -50", message="Williams %R crossed midpoint")

// -----------------------------------------------------------------------------
// Stats table (last bar)
// -----------------------------------------------------------------------------
var table tb = table.new(position.top_right, 2, 7, border_width=1)
if barstate.islast
    table.cell(tb, 0, 0, "W%R(" + str.tostring(length) + ")", text_color=color.white, bgcolor=color.new(color.gray, 35))
    table.cell(tb, 1, 0, str.tostring(w, "#.##"), text_color=color.white)
    table.cell(tb, 0, 1, "Status", text_color=color.white, bgcolor=color.new(color.gray, 35))
    st = inOB ? "Overbought chamber" : inOS ? "Oversold chamber" : "Neutral"
    table.cell(tb, 1, 1, st, text_color=color.white)
    table.cell(tb, 0, 2, "Dist to OB", text_color=color.white, bgcolor=color.new(color.gray, 35))
    table.cell(tb, 1, 2, str.tostring(ob - w, "#.##"), text_color=color.white)
    table.cell(tb, 0, 3, "Dist to OS", text_color=color.white, bgcolor=color.new(color.gray, 35))
    table.cell(tb, 1, 3, str.tostring(w - os, "#.##"), text_color=color.white)
    table.cell(tb, 0, 4, "14-bar high", text_color=color.white, bgcolor=color.new(color.gray, 35))
    table.cell(tb, 1, 4, str.tostring(ta.highest(high, length), format.mintick), text_color=color.white)
    table.cell(tb, 0, 5, "14-bar low", text_color=color.white, bgcolor=color.new(color.gray, 35))
    table.cell(tb, 1, 5, str.tostring(ta.lowest(low, length), format.mintick), text_color=color.white)
    table.cell(tb, 0, 6, "Note", text_color=color.white, bgcolor=color.new(color.gray, 35))
    table.cell(tb, 1, 6, "RSI line shifted", text_color=color.white)

// -----------------------------------------------------------------------------
// Implementation notes
// -----------------------------------------------------------------------------
// 1) ta.wpr uses the canonical TradingView definition on 0..-100 scale.
// 2) RSI overlay is shifted by -100 for rough visual comparison only—not equivalent math.
// 3) For strategies, combine with trend filters to reduce range chop signals.

Maintenance Note: Remove or gate the shifted RSI plot before publishing to traders who might misread the overlay. If you port to strategies, prefer `ta.wpr` signals confirmed on bar close to reduce intrabar noise.

The Easy Way: Build with Pineify Visual Editor

What if you could create the same indicator without writing a single line of code? Pineify is a visual editor designed for TradingView users who want professional-grade indicators through an intuitive interface.

FeatureManual Pine ScriptPineify Visual Editor
Formula complexityHand-write highest/lowest window math with divide-by-zero guards.Call `ta.wpr` directly inside generated modules.
RSI comparisonMaintain two oscillators with separate scaling and docs.Template educational overlays with clear labeling.
Alert wiringDuplicate threshold logic between plots and alertcondition.Single source of truth for OB/OS crossings.
Chamber stylingTune bgcolor alpha and hline colors manually.Reuse style presets for consistent branding.
IterationSlow to test multiple lengths across watchlists.Faster sweeps before locking defaults.

Quickly clone Williams %R variants with shared alert scaffolding.

Reduce mistakes when mirroring RSI strategies onto %R logic.

Export readable Pine for education and production.

Step-by-Step Tutorial

1

Create oscillator indicator

Use `overlay=false` so %R occupies its own pane.

2

Insert ta.wpr

Bind length to an input defaulting to 14.

3

Add OB/OS guides

Plot hlines at -20 and -80 (or your optimized levels).

4

Highlight chambers

Optional bgcolor fills when %R sits beyond thresholds.

5

Crossing alerts

Alert on re-entries from extreme zones for clearer timing.

6

Compare with RSI

If educational, document scale differences explicitly.

7

Export Pine v5

Validate against TradingView’s built-in Williams %R.

Trading Strategies & Pro Tips

Trend-filtered mean reversion

Buy dips when %R lifts out of oversold while price holds above a rising long-term MA.

Pro Tip: Require a bullish candle close to avoid catching falling knives.

Momentum continuation

In strong uptrends, treat %R holding above -20 as evidence buyers keep paying up.

Dual confirmation with RSI

Demand RSI and %R align in extreme zones before scaling into counter-trend trades.

Failure swing style

Look for %R to fail at retesting a prior extreme while price makes a shallower wave—classic swing failure patterns.

Common Mistakes to Avoid

  • Shorting every touch of -20 in a parabolic uptrend.
  • Treating %R exactly like RSI despite different mathematics and sensitivity.
  • Using tiny lengths on noisy tick charts without additional smoothing or filters.
  • Ignoring gap risk when ranges compress and denominators shrink.
  • Enabling shifted RSI plots without explaining the non-equivalent scale.

Frequently Asked Questions

Generate Williams %R Pine Script with Pineify

  • Prototype OB/OS logic and alerts without manual formula boilerplate.
  • Keep RSI companion studies documented and consistent.
  • Export Pine Script v5 ready for TradingView.

Disclaimer: Trading involves significant risk. The indicators, code, and strategies provided are for educational purposes only and do not constitute financial advice. Past performance is not indicative of future results.