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.
| Parameter | Default | Description |
|---|---|---|
| Length | 14 | Matches many default RSI settings for easy side-by-side comparison, though you should tune per market. |
| Overbought / oversold | -20 / -80 | Common horizontal guides. In strong trends, %R can remain pinned in one chamber for extended runs. |
| Scale | 0 to -100 | TradingView plots the negative convention; some legacy platforms invert or rescale—mind translation. |
| Confirmation | Structure or trend filter | Use 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.
//@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.
| Feature | Manual Pine Script | Pineify Visual Editor |
|---|---|---|
| Formula complexity | Hand-write highest/lowest window math with divide-by-zero guards. | Call `ta.wpr` directly inside generated modules. |
| RSI comparison | Maintain two oscillators with separate scaling and docs. | Template educational overlays with clear labeling. |
| Alert wiring | Duplicate threshold logic between plots and alertcondition. | Single source of truth for OB/OS crossings. |
| Chamber styling | Tune bgcolor alpha and hline colors manually. | Reuse style presets for consistent branding. |
| Iteration | Slow 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
Create oscillator indicator
Use `overlay=false` so %R occupies its own pane.
Insert ta.wpr
Bind length to an input defaulting to 14.
Add OB/OS guides
Plot hlines at -20 and -80 (or your optimized levels).
Highlight chambers
Optional bgcolor fills when %R sits beyond thresholds.
Crossing alerts
Alert on re-entries from extreme zones for clearer timing.
Compare with RSI
If educational, document scale differences explicitly.
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.