Range Oscillator (Zeiierman) is a dynamic market oscillator designed to visualize how far the price is trading relative to its equilibrium range.
This is a conversion where I added a histogram to indicate the candle's strength or weakness. Over or under 200, are extreme levels.
Instead of relying on traditional overbought/oversold thresholds, it uses adaptive range detection and heatmap coloring to reveal where price is trading within a volatility-adjusted band.
The oscillator maps market movement as a heat zone, highlighting when the price approaches the upper or lower range boundaries and signaling potential breakout or mean-reversion conditions.
Highlights
- Adaptive range detection based on ATR and weighted price movement.
- Heatmap-driven coloring that visualizes volatility pressure and directional bias.
- Clear transition zones for detecting trend shifts and equilibrium points.
The Range Oscillator source code can be found here. https://www.tradingview.com/script/mlL8CpJq-Range-Oscillator-Zeiierman/Range Detection
The indicator identifies a dynamic price range using two main parameters:
This approach ensures that the oscillator automatically adapts to both trending and ranging markets without manual recalibration.
- Minimum Range Length: The number of bars required to confirm that a valid range exists.
- Range Width Multiplier: Expands or contracts the detected range proportionally to the ATR (Average True Range).
Weighted Mean Calculation
Instead of a simple moving average, the script calculates a weighted equilibrium mean based on the size of consecutive candle movements:
- Larger price changes are given greater weight, emphasizing recent volatility.
Code:
# Range Oscillator (Zeiierman) - ThinkScript Conversion
# Assembled by Chewie
declare lower;
#---- Inputs ----#
input length = 50;
input mult = 2.0;
input atrLength = 200;
input fillOpacity = 60; # 0–100 visual strength of shaded fill
input backgroundIsDark = yes; # set to 'no' for white/light chart backgrounds
# Base RGB color settings
input strongBull_R = 0;
input strongBull_G = 180;
input strongBull_B = 0;
input strongBear_R = 180;
input strongBear_G = 0;
input strongBear_B = 0;
input weakBull_R = 0;
input weakBull_G = 128;
input weakBull_B = 0;
input weakBear_R = 128;
input weakBear_G = 0;
input weakBear_B = 0;
input transition_R = 0;
input transition_G = 0;
input transition_B = 255;
#---- Core Calculations ----#
def ATR = MovingAverage(AverageType.WILDERS, TrueRange(high, close, low), atrLength);
def rangeATR = ATR * mult;
# Weighted moving average using GetValue()
def sumWeightedClose = fold idx1 = 0 to length - 1
with acc = 0
do acc + (AbsValue(GetValue(close, idx1) - GetValue(close, idx1 + 1)) / GetValue(close, idx1 + 1)) * GetValue(close, idx1);
def sumWeights = fold idx2 = 0 to length - 1
with acc2 = 0
do acc2 + AbsValue(GetValue(close, idx2) - GetValue(close, idx2 + 1)) / GetValue(close, idx2 + 1);
def ma = if sumWeights != 0 then sumWeightedClose / sumWeights else Double.NaN;
# Max distance over the lookback
def maxDist = fold idx3 = 0 to length - 1
with m = 0
do Max(m, AbsValue(GetValue(close, idx3) - ma));
def inRange = maxDist <= rangeATR;
# Trend direction
def trendDir = if close > ma then 1 else if close < ma then -1 else trendDir[1];
# Oscillator
def osc1 = if rangeATR != 0 then 100 * (close - ma) / rangeATR else Double.NaN;
def breakUp = close > ma + rangeATR;
def breakDn = close < ma - rangeATR;
#---- Simulated Opacity Blend ----#
def w = Max(0, Min(1, fillOpacity / 100));
def bgR = if backgroundIsDark then 0 else 255;
def bgG = if backgroundIsDark then 0 else 255;
def bgB = if backgroundIsDark then 0 else 255;
def bullFillR = Round(strongBull_R * w + bgR * (1 - w), 0);
def bullFillG = Round(strongBull_G * w + bgG * (1 - w), 0);
def bullFillB = Round(strongBull_B * w + bgB * (1 - w), 0);
def bearFillR = Round(strongBear_R * w + bgR * (1 - w), 0);
def bearFillG = Round(strongBear_G * w + bgG * (1 - w), 0);
def bearFillB = Round(strongBear_B * w + bgB * (1 - w), 0);
#---- Plot and Color Logic ----#
plot Osc = osc1;
Osc.SetLineWeight(2);
Osc.AssignValueColor(
if breakUp then CreateColor(strongBull_R, strongBull_G, strongBull_B)
else if breakDn then CreateColor(strongBear_R, strongBear_G, strongBear_B)
else if trendDir > 0 then CreateColor(weakBull_R, weakBull_G, weakBull_B)
else if trendDir < 0 then CreateColor(weakBear_R, weakBear_G, weakBear_B)
else CreateColor(transition_R, transition_G, transition_B)
);
plot OSC2 = osc1;
osc2.SetPaintingStrategy( PaintingStrategy.HISTOGRAM ); #else if osc2 > 0 and osc2 < 100 then color.dark_green else if osc2 < -100 then color.red else if osc2 > -100 and < 0 then color.dark_red else color.current
osc2.assignvalueColor(if osc2 > 200 then color.cyan else if osc2 < -200 then color.magenta else if osc2 > 100 and osc2 < 200 then color.green else if osc2 > 0 and osc2 < 100 then color.dark_green else if osc2 < -100 and osc2 > -200 then color.red else if osc2 > -100 and osc2 < 0 then color.dark_red else color.current);
# Reference Lines
plot Upper = 100;
plot Lower = -100;
plot ZeroLine = 0;
Upper.SetDefaultColor(Color.GRAY);
Lower.SetDefaultColor(Color.GRAY);
ZeroLine.SetDefaultColor(Color.gray);
#---- Shaded Fills ----#
# Bullish fill above zero
AddCloud(
if osc >= 0 then osc else Double.NaN,
0,
CreateColor(bullFillR, bullFillG, bullFillB),
CreateColor(bullFillR, bullFillG, bullFillB)
);
# Bearish fill below zero
AddCloud(
if osc <= 0 then osc else Double.NaN,
0,
CreateColor(bearFillR, bearFillG, bearFillB),
CreateColor(bearFillR, bearFillG, bearFillB)
);
# Optional highlight clouds for extreme zones
AddCloud(
if osc > 100 then osc else Double.NaN,
100,
CreateColor(strongBull_R, strongBull_G, strongBull_B),
CreateColor(strongBull_R, strongBull_G, strongBull_B)
);
AddCloud(
if osc < -100 then osc else Double.NaN,
-100,
CreateColor(strongBear_R, strongBear_G, strongBear_B),
CreateColor(strongBear_R, strongBear_G, strongBear_B)
);
Last edited: