One major feature of this indicator is its ability to signal potential trend reversals. For example, a shift in the direction at the lower end of the heatmap can indicate a weakening of the current trend, suggesting a possible reversal. On the other hand, when the heatmap is fully saturated at all levels, it may indicate a strong trend that could be nearing a reversal point.
Another important and unique aspect of the Osmosis indicator is its automatic highlighting feature. This feature emphasizes regions within the heatmap that score exceptionally high or low, drawing attention to significant market movements or potential anomalies.
All of the indicators are normalized using min/max scaling driven by the highest highs and lows. The period of this scaling is adjustable by changing the "Lookback" parameter under settings. Delta length changes the lookback for "MA Delta" and "Volume Delta". A longer period corresponds to a smoother output. Fast Mode scales back the range of the indicator, literally halving the increment.
//@version=5
indicator("Osmosis [ChartPrime]", precision=0, max_labels_count=500)
heatmapColor(series float source, simple string colorScheme, simple bool invert, simple array customColors) =>
    float value = invert ? source : 100.0 - source
    if colorScheme == "Prime"
        switch
            value <   0.0 => #000000
            value <  14.3 => color.from_gradient(value,  0.0,  14.3, #6600FF, #0000FF)
            value <  28.6 => color.from_gradient(value, 14.3,  28.6, #0000FF, #00FFFF)
            value <  42.9 => color.from_gradient(value, 28.6,  42.9, #00FFFF, #00FF00)
            value <  57.1 => color.from_gradient(value, 42.9,  57.1, #00FF00, #FFFF00)
            value <  71.4 => color.from_gradient(value, 57.1,  71.4, #FFFF00, #FF8000)
            value <  85.7 => color.from_gradient(value, 71.4,  85.7, #FF8000, #FF0000)
            value <=100.0 => color.from_gradient(value, 85.7, 100.0, #FF0000, #FF00FF)
            =>               #FFFFFF
    else if colorScheme == "Standard"
        switch
            value <   0.0  => #000000
            value <   20.0 => color.from_gradient(value,  0.0,  20.0, #008000, #00FF00)
            value <   40.0 => color.from_gradient(value, 20.0,  40.0, #00FF00, #FFFF00)
            value <   60.0 => color.from_gradient(value, 40.0,  60.0, #FFFF00, #FF8000)
            value <   80.0 => color.from_gradient(value, 60.0,  80.0, #FF8000, #FF0000)
            value <= 100.0 => color.from_gradient(value, 80.0, 100.0, #FF0000, #800000)
            =>                #FFFFFF
    else // colorScheme == "User Defined"
        switch
            value <    0.0 => #000000
            value <   25.0 => color.from_gradient(value,  0.0,  25.0, customColors.get(0), customColors.get(1))
            value <   50.0 => color.from_gradient(value, 25.0,  50.0, customColors.get(1), customColors.get(2))
            value <   75.0 => color.from_gradient(value, 50.0,  75.0, customColors.get(2), customColors.get(3))
            value <= 100.0 => color.from_gradient(value, 75.0, 100.0, customColors.get(3), customColors.get(4))
            =>                #FFFFFF
max(series float source, simple int outlier_level, simple int dev_lookback) =>
    var float maxima = na
    array src = array.new()
    float      stdev = math.abs((source - ta.ema(source, dev_lookback)) / ta.stdev(source, dev_lookback))
    src.push(stdev < outlier_level ? source : -1.7976931348623157e+308)
    maxima := math.max(nz(maxima[1]), src.get(0))
min(series float source, simple int outlier_level, simple int dev_lookback) =>
    var float minima = na
    array src = array.new()
    float      stdev = math.abs((source - ta.ema(source, dev_lookback)) / ta.stdev(source, dev_lookback))
    src.push(stdev < outlier_level ? source : 1.7976931348623157e+308)
    minima := math.min(nz(minima[1]), src.get(0))
min_max(series float source, simple int outlier_level, simple int dev_lookback) =>
    float out = (source - min(source, outlier_level, dev_lookback)) / (max(source, outlier_level, dev_lookback) - min(source, outlier_level, dev_lookback)) * 100.0
    math.max(0.0, math.min(100.0, out))
diff(series float source, simple int length) =>
    switch length
        1  =>  source -     source[1]
        2  => (source -     source[2]) / 2
        3  => (source +     source[1] -      source[2] - source[3]) / 4
        4  => (source + 2 * source[1] -  2 * source[3] - source[4]) / 8
        5  => (source + 3 * source[1] +  2 * source[2] -  2 * source[3] -  3 * source[4] -      source[5]) / 16
        6  => (source + 4 * source[1] +  5 * source[2] -  5 * source[4] -  4 * source[5] -      source[6]) / 32
        7  => (source + 5 * source[1] +  9 * source[2] +  5 * source[3] -  5 * source[4] -  9 * source[5] -  5 * source[6] -      source[7]) / 64
        8  => (source + 6 * source[1] + 14 * source[2] + 14 * source[3] - 14 * source[5] - 14 * source[6] -  6 * source[7] -      source[8]) / 128
        9  => (source + 7 * source[1] + 20 * source[2] + 28 * source[3] + 14 * source[4] - 14 * source[5] - 28 * source[6] - 20 * source[7] - 7 * source[8] - source[ 9]) / 256
        =>    (source + 8 * source[1] + 27 * source[2] + 48 * source[3] + 42 * source[4] - 42 * source[6] - 48 * source[7] - 27 * source[8] - 8 * source[9] - source[10]) / 512 // 10
average_true_range(series float source, simple int length) =>
    float true_range = high - low
    float     _range = math.abs(open - close)
    float    a_range = (true_range * 2.0 + _range) / 3.0
    ta.rma(a_range, length)
tr_score(series float source, simple int length) =>
    (source - ta.sma(source, length)) / average_true_range(source, length)
tr_algo(series float source, simple int length, smooth) =>
    min_max(ta.sma(tr_score(source, length), smooth), 1, length)
ema(series float source, simple float length) =>
    var float alpha = 2.0 / (length + 1)
    var float  beta = 1.0 - alpha
    float smoothed = na
    smoothed := alpha * source + beta * nz(smoothed[1], source)
preFilter(series float source, simple int period) =>
    var float halfPeriod = 0.5 * period
    var int  periodPlus1 =   1 + period
    if period > 1
        float E = 0.0
        float W = 0.0
        for int i=1 to period
            float w = if i < halfPeriod
                i + 1
            else
                periodPlus1 - i
            E += w * source[i - 1]
            W += w
        E / W
    else
        source
method find_streak(series array  self,
                   simple float    threshold,
                   simple bool   bullishMode,
                   simple int streakDuration,
                   simple bool        enable) =>
    var int sizeOf = self.size() - 1
    int streak_start_index     = na
    int max_streak_start_index = na
    int max_streak_end_index   = na
    int streak_maxima          = 0
    int streak_count           = 0
    if enable
        int i = 0
        while i < sizeOf
            if streak_maxima > sizeOf
                break
            float current = self.get(i)
            if(not bullishMode and current <= threshold)
               or (bullishMode and current >= threshold)
                streak_count := 1
                int j = i + 1
                while j < sizeOf and ((bullishMode and self.get(j) >= threshold)
                               or (not bullishMode and self.get(j) <= threshold))
                    streak_count += 1
                    j            += 1
                if streak_count > streak_maxima
                    streak_maxima          := streak_count
                    max_streak_start_index := i
                    max_streak_end_index   := j + 1
                i := j
            else
                i += 1
    if streak_maxima >= streakDuration
        [max_streak_end_index + 1, max_streak_start_index + 1]
    else
        [na, na]
normalize(series float source, simple int length) =>
    float lowestLow = ta.lowest(source, length)
    (source - lowestLow) / (ta.highest(source, length) - lowestLow) * 100.0
stdev(series float source, simple int length) =>
    math.sqrt(math.sum(math.pow(source - ta.ema(source, length), 2), length) * 0.5) * 0.5
fastMode(simple int length, simple bool fastMode) =>
    fastMode ? int(length / 2) : length
rsi(series float source, simple int length) =>
    float rsi = ta.rsi(source, length)
    float   f =  -math.pow(math.abs(math.abs(rsi - 50.0) - 50.0), 1.0 + math.pow(length, 0.707) - 1.0) /      // 0.618 14 ###############################
                                                         math.pow(50.0, math.pow(length, 0.707) - 1.0) + 50.0 // 0.618 14 ###############################
    if rsi > 50.0
        50.0 + f
    else
        50.0 - f
trend_angle(series float source, simple int length, simple int preFilterLength) =>
    float degrees = preFilter((math.todegrees(math.atan((source - source[length]) / ta.atr(length))) + 90.0) / 180.0 * 100.0, preFilterLength)
string   sources = "Alternative Input Sourcing"
float  altsource = input.source(close,     " Source:", group=sources, inline=sources)
float highsource = input.source( high,       " High:", group=sources, inline=sources)
float  lowsource = input.source(  low,        " Low:", group=sources, inline=sources)
int    preFilter = input.int   (    1, "PreSmoothing", group=sources, minval=1)
string  settings = "Algorithm Settings"
string algorithm = input.string("Normalized MACD", "Select an Algorithm", group=settings,
   options=["MACD Histogram", "Normalized MACD", "Slow MACD", "MACD Percent Rank", "MA Delta *",
  "BB Width", "BB Width Percentile", "RSI", "Stochastic", "True Range Algo", "Normalized Volume",
  "Volume Delta *", "True Range", "Rate of Change", "OBV", "MFI", "Trend Angle"])
int normLength = input.int (   90, "Normalization Lookback", group=settings, minval=1, tooltip="???????????????????????????????")
int  madLength = input.int (    4,         "Delta Length *", group=settings, minval=1, maxval=10, tooltip="???????????????????????????????")
bool  fastMode = input.bool(false,       "Enable Fast Mode", group=settings, tooltip="???????????????????????????????")
string  colors = "Heatmap Color Options"
bool    invert = input.bool  (   false,   "Invert Colors     ", group=colors, inline=colors)
string  scheme = input.string( "Standard",       "Color Schemes:", group=colors, inline=colors, options=["Standard","Prime","User Defined"])
color color000 = input.color(#0000FF, "User Defined Colors:", group=colors, inline=colors)
color color025 = input.color(#00FFFF,                     "", group=colors, inline=colors)
color color050 = input.color(#00Ff00,                     "", group=colors, inline=colors)
color color075 = input.color(#FFFF00,                     "", group=colors, inline=colors)
color color100 = input.color(#FF0000,                     "", group=colors, inline=colors)
string     regions = "Trend Streak Options"
string   emphasize = input.string(   "Both",         "Emphasize Trends", group=regions, options=["Both","Bullish","Bearish","Disabled"])
int upperThreshold = input.int   (       70,      "Thresholds - Upper:", group=regions, inline="thresh", minval=50, maxval=100, step=5)
int lowerThreshold = input.int   (       30,                   "Lower:", group=regions, inline="thresh", minval= 0, maxval= 50, step=5, tooltip="Upper threshold range: 50 to 100\nLower threshold range:  0 to 50")
int    maxDuration = input.int   (        5,   "   Duration - Bullish:", group=regions, inline="widths", minval= 0, maxval= 30, step=5)
int    minDuration = input.int   (        5,                 "Bearish:", group=regions, inline="widths", minval= 0, maxval= 30, step=5, tooltip="???????????????????????????????")
string   bullOnset = input.string( "Bottom", "Onset Markers - Bullish:", group=regions, inline="onsets", options=["Both","Top","Bottom","None"])
string   bearOnset = input.string(    "Top",                 "Bearish:", group=regions, inline="onsets", options=["Both","Top","Bottom","None"], tooltip="???????????????????????????????")
int  lineThickness = input.int   (        3,              "Line Widths", group=regions, minval=2, maxval=4)
color bearishColor = input.color (#008000,  "Trend Colors - Bearish:", group=regions, inline="colors")
color bullishColor = input.color (#800000,                 "Bullish:", group=regions, inline="colors")
indicator_picker(length) =>
    float alt_src   = preFilter( altsource, preFilter)
    float alt_high  = preFilter(highsource, preFilter)
    float alt_low   = preFilter( lowsource, preFilter)
    int fastLength2 = fastMode(2, fastMode)
    int fastLength3 = fastMode(3, fastMode)
    int fastLength4 = fastMode(4, fastMode)
    int fastLength5 = fastMode(5, fastMode)
    int fastLength6 = fastMode(6, fastMode)
    int tempLength5 = 5 + length * fastLength5
    switch algorithm
        "Normalized MACD"     =>  (length == 1 ? normalize(ta.ema(alt_src, fastLength3) - ta.ema(alt_src,              fastLength5), normLength) :
                                                 normalize(ta.ema(alt_src, fastLength5) - ta.ema(alt_src, 1 + length * fastLength6), normLength))
        "MACD Histogram"      => normalize(( ema(    alt_src, fastLength2 * length * 0.5 + 2.0)  -
                                             ema(    alt_src, fastLength4 * length * 0.5 + 2.0)) -
                                             ema(ema(alt_src, fastLength2 * length * 0.5 + 2.0)  -
                                             ema(    alt_src, fastLength4 * length * 0.5 + 2.0), 9), normLength) 
        "MACD Percent Rank"   => ta.percentrank(ta.ema(alt_src, fastLength5) - ta.ema(alt_src, 1 + fastLength6 * length) , normLength)
        "Slow MACD"           => normalize(ta.ema(alt_src, fastMode(15, fastMode)) - ta.ema(alt_src, fastMode(20, fastMode) + length * fastLength5), normLength)
        "RSI"                 => rsi(preFilter(alt_src, preFilter), length)
        "True Range Algo"     => tr_algo(alt_src, fastLength2 * length, 3)
        "Normalized Volume"   => normalize(preFilter(     ta.wma(volume, 1 + (fastLength3 * (length - 1))),           preFilter), normLength)
        "Volume Delta *"      => normalize(preFilter(diff(ta.wma(volume, 1 + (fastLength3 * (length - 1))), madLength), preFilter), normLength)
        "MA Delta *"          => normalize(diff(ta.ema(alt_src, fastLength6 * length), madLength), normLength)
        "True Range"          => normalize(ta.rma(high - low,   fastLength2 * length),           normLength)
        "Rate of Change"      => normalize(ta.roc(alt_src,      fastLength3 * length),           normLength)
        "OBV"                 => normalize(preFilter(ta.obv - ta.wma(ta.obv, 2 + fastLength2 * length), preFilter), normLength)
        "MFI"                 => normalize(ta.mfi(preFilter(hlc3, preFilter),    fastLength3 + length), normLength)
        "BB Width"            => normalize((( ta.sma(alt_src, tempLength5) + ta.stdev(alt_src, tempLength5))  -
                                             (ta.sma(alt_src, tempLength5) - ta.stdev(alt_src, tempLength5))) /
                                              ta.sma(alt_src, tempLength5), normLength)
        "BB Width Percentile" => ta.percentrank(((ta.sma(alt_src, tempLength5) + ta.stdev(alt_src, tempLength5))  -
                                                 (ta.sma(alt_src, tempLength5) - ta.stdev(alt_src, tempLength5))) /
                                                  ta.sma(alt_src, tempLength5), normLength)
        "Stochastic"          => ta.sma(ta.stoch(alt_src, alt_high, alt_low, fastLength2 * length), 3)
        =>                       trend_angle(close, fastLength2 * length, preFilter) // "Trend Angle"
float algo02 = indicator_picker( 2)
float algo03 = indicator_picker( 3)
float algo04 = indicator_picker( 4)
float algo05 = indicator_picker( 5)
float algo06 = indicator_picker( 6)
float algo07 = indicator_picker( 7)
float algo08 = indicator_picker( 8)
float algo09 = indicator_picker( 9)
float algo10 = indicator_picker(10)
float algo11 = indicator_picker(11)
float algo12 = indicator_picker(12)
float algo13 = indicator_picker(13)
float algo14 = indicator_picker(14)
float algo15 = indicator_picker(15)
float algo16 = indicator_picker(16)
float algo17 = indicator_picker(17)
float algo18 = indicator_picker(18)
float algo19 = indicator_picker(19)
float algo20 = indicator_picker(20)
float algo21 = indicator_picker(21)
float algo22 = indicator_picker(22)
float algo23 = indicator_picker(23)
float algo24 = indicator_picker(24)
float algo25 = indicator_picker(25)
float algo26 = indicator_picker(26)
float algo27 = indicator_picker(27)
float algo28 = indicator_picker(28)
float algo29 = indicator_picker(29)
var array customColors = array.from(color000, color025, color050, color075, color100)
plot( 2,  "2", heatmapColor(algo02, scheme, invert, customColors), style=plot.style_area, histbase= 1, editable=false, display=display.pane)
plot( 3,  "3", heatmapColor(algo03, scheme, invert, customColors), style=plot.style_area, histbase= 2, editable=false, display=display.pane)
plot( 4,  "4", heatmapColor(algo04, scheme, invert, customColors), style=plot.style_area, histbase= 3, editable=false, display=display.pane)
plot( 5,  "5", heatmapColor(algo05, scheme, invert, customColors), style=plot.style_area, histbase= 4, editable=false, display=display.pane)
plot( 6,  "6", heatmapColor(algo06, scheme, invert, customColors), style=plot.style_area, histbase= 5, editable=false, display=display.pane)
plot( 7,  "7", heatmapColor(algo07, scheme, invert, customColors), style=plot.style_area, histbase= 6, editable=false, display=display.pane)
plot( 8,  "8", heatmapColor(algo08, scheme, invert, customColors), style=plot.style_area, histbase= 7, editable=false, display=display.pane)
plot( 9,  "9", heatmapColor(algo09, scheme, invert, customColors), style=plot.style_area, histbase= 8, editable=false, display=display.pane)
plot(10, "10", heatmapColor(algo10, scheme, invert, customColors), style=plot.style_area, histbase= 9, editable=false, display=display.pane)
plot(11, "11", heatmapColor(algo11, scheme, invert, customColors), style=plot.style_area, histbase=10, editable=false, display=display.pane)
plot(12, "12", heatmapColor(algo12, scheme, invert, customColors), style=plot.style_area, histbase=11, editable=false, display=display.pane)
plot(13, "13", heatmapColor(algo13, scheme, invert, customColors), style=plot.style_area, histbase=12, editable=false, display=display.pane)
plot(14, "14", heatmapColor(algo14, scheme, invert, customColors), style=plot.style_area, histbase=13, editable=false, display=display.pane)
plot(15, "15", heatmapColor(algo15, scheme, invert, customColors), style=plot.style_area, histbase=14, editable=false, display=display.pane)
plot(16, "16", heatmapColor(algo16, scheme, invert, customColors), style=plot.style_area, histbase=15, editable=false, display=display.pane)
plot(17, "17", heatmapColor(algo17, scheme, invert, customColors), style=plot.style_area, histbase=16, editable=false, display=display.pane)
plot(18, "18", heatmapColor(algo18, scheme, invert, customColors), style=plot.style_area, histbase=17, editable=false, display=display.pane)
plot(19, "19", heatmapColor(algo19, scheme, invert, customColors), style=plot.style_area, histbase=18, editable=false, display=display.pane)
plot(20, "20", heatmapColor(algo20, scheme, invert, customColors), style=plot.style_area, histbase=19, editable=false, display=display.pane)
plot(21, "21", heatmapColor(algo21, scheme, invert, customColors), style=plot.style_area, histbase=20, editable=false, display=display.pane)
plot(22, "22", heatmapColor(algo22, scheme, invert, customColors), style=plot.style_area, histbase=21, editable=false, display=display.pane)
plot(23, "23", heatmapColor(algo23, scheme, invert, customColors), style=plot.style_area, histbase=22, editable=false, display=display.pane)
plot(24, "24", heatmapColor(algo24, scheme, invert, customColors), style=plot.style_area, histbase=23, editable=false, display=display.pane)
plot(25, "25", heatmapColor(algo25, scheme, invert, customColors), style=plot.style_area, histbase=24, editable=false, display=display.pane)
plot(26, "26", heatmapColor(algo26, scheme, invert, customColors), style=plot.style_area, histbase=25, editable=false, display=display.pane)
plot(27, "27", heatmapColor(algo27, scheme, invert, customColors), style=plot.style_area, histbase=26, editable=false, display=display.pane)
plot(28, "28", heatmapColor(algo28, scheme, invert, customColors), style=plot.style_area, histbase=27, editable=false, display=display.pane)
plot(29, "29", heatmapColor(algo29, scheme, invert, customColors), style=plot.style_area, histbase=28, editable=false, display=display.pane)
array algoArray = array.from( algo02, algo03, algo04, algo05, algo06, algo07, algo08, algo09, algo10, algo11, algo12, algo13, algo14, algo15,
                                     algo16, algo17, algo18, algo19, algo20, algo21, algo22, algo23, algo24, algo25, algo26, algo27, algo28, algo29)
[upperBullishStreak, lowerBullishStreak] = algoArray.find_streak(upperThreshold,  true, maxDuration, emphasize=="Both" or emphasize=="Bullish")
[upperBearishStreak, lowerBearishStreak] = algoArray.find_streak(lowerThreshold, false, minDuration, emphasize=="Both" or emphasize=="Bearish")
plot(upperBullishStreak, "Bullish Region Top",    bearishColor, lineThickness-1, plot.style_circles, display=display.pane)
plot(lowerBullishStreak, "Bullish Region Bottom", bearishColor, lineThickness,   plot.style_linebr,  display=display.pane)
plot(upperBearishStreak, "Bearish Region Top",    bullishColor, lineThickness,   plot.style_linebr,  display=display.pane)
plot(lowerBearishStreak, "Bearish Region Bottom", bullishColor, lineThickness-1, plot.style_circles, display=display.pane)
if not na(lowerBullishStreak) and na(lowerBullishStreak[1])
    if bullOnset=="Top"    or bullOnset=="Both"
        label.new(bar_index, upperBullishStreak, "⬤", style=label.style_text_outline, textcolor=bearishColor, color=#00000000, size=size.normal)
    if bullOnset=="Bottom" or bullOnset=="Both"
        label.new(bar_index, lowerBullishStreak, "⬤", style=label.style_text_outline, textcolor=bearishColor, color=#00000000, size=size.normal)
if not na(lowerBearishStreak) and na(lowerBearishStreak[1])
    if bearOnset=="Top"    or bearOnset=="Both"
        label.new(bar_index, upperBearishStreak, "⬤", style=label.style_text_outline, textcolor=bullishColor, color=#00000000, size=size.normal)
    if bearOnset=="Bottom" or bearOnset=="Both"
        label.new(bar_index, lowerBearishStreak, "⬤", style=label.style_text_outline, textcolor=bullishColor, color=#00000000, size=size.normal)