Slope Normalized (SN)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © peacefulLizard50262

//@version=5
indicator("Slope Normalized", explicit_plot_zorder = true)

diff(float src, int n) =>
    float derivative = 0.0
    if n == 1
        derivative := (src - src[1])
    if n == 2
        derivative := (src - src[2]) / 2
        derivative
    else if n == 3
        derivative := (src + src[1] - src[2] - src[3]) / 4
        derivative
    else if n == 4
        derivative := (src + 2 * src[1] - 2 * src[3] - src[4]) / 8
        derivative
    else if n == 5
        derivative := (src + 3 * src[1] + 2 * src[2] - 2 * src[3] - 3 * src[4] - src[5]) / 16
        derivative
    else if n == 6
        derivative := (src + 4 * src[1] + 5 * src[2] - 5 * src[4] - 4 * src[5] - src[6]) / 32
        derivative
    else if n == 7
        derivative := (src + 5 * src[1] + 9 * src[2] + 5 * src[3] - 5 * src[4] - 9 * src[5] - 5 * src[6] - src[7]) / 64
        derivative
    else if n == 8
        derivative := (src + 6 * src[1] + 14 * src[2] + 14 * src[3] - 14 * src[5] - 14 * src[6] - 6 * src[7] - src[8]) / 128
        derivative
    else if n == 9
        derivative := (src + 7 * src[1] + 20 * src[2] + 28 * src[3] + 14 * src[4] - 14 * src[5] - 28 * src[6] - 20 * src[7] - 7 * src[8] - src[9]) / 256
        derivative
    else if n == 10
        derivative := (src + 8 * src[1] + 27 * src[2] + 48 * src[3] + 42 * src[4] - 42 * src[6] - 48 * src[7] - 27 * src[8] - 8 * src[9] - src[10]) / 512
        derivative
    derivative

source         = input.source(close, "Source", inline = "Source", group = "Source")
length         = input.int(20, "Length", 1, inline = "Source", group = "Source")
pre_smoothing  = input.float(0.7, "Pre Smoothing", 0.05, 1, 0.05, inline = "Smooting", group = "Source")
post_smoothing = input.float(0.7, "Post Smoothing", 0.05, 1, 0.05, inline = "Smooting", group = "Source")
bounds         = input.bool(false, "", inline = "Bounds", group = "Bounds")
bound_level    = input.float(1, "Bound Level", 0.05, 1, 0.05, inline = "Bounds", group = "Bounds")
clip           = input.bool(false, "Hard Clipping", inline = "Bounds", group = "Bounds")
outlier_level  = input.float(2, "Outlier Level", 0, 100, 0.25, inline = "Outlier", group = "Outlier")
dev_lookback   = input.int(20, "Outlier Length", 2, inline = "Outlier", group = "Outlier")
enable_diff    = input.bool(false, "Enable Delta", group = "Delta")
diff           = input.int(2, "Delta Pre Smoothing", 1, 10, inline = "Diff", group = "Delta")
diff_alpha     = input.float(0.7, "Delta Post Smoothing", 0.05, 1, 0.05, inline = "Diff", group = "Delta")
center_line    = input.bool(true, "Center Line", inline = "Center", group = "Center")
center_center  = input.bool(true, "Mean Center", inline = "Center", group = "Center")
col_grow_below = input.color(color.new(#FFCDD2, 30), "Low Color", inline = "low", group = "Color")
col_fall_below = input.color(color.new(#FF5252, 30), "", inline = "low", group = "Color")
col_fall_above = input.color(color.new(#B2DFDB, 30), "High Color", inline = "high", group = "Color")
col_grow_above = input.color(color.new(#26A69A, 30), "", inline = "high", group = "Color")

bes(source, alpha)=>
    var float smoothed = na
    smoothed := na(smoothed) ? source : alpha * source + (1 - alpha) * nz(smoothed[1])

max(source, outlier_level, dev_lookback)=>
    var float max = na
    src = array.new<float>()
    stdev = math.abs((source - bes(source, 0.1))/ta.stdev(source, dev_lookback))
    array.push(src, stdev < outlier_level ? source : -1.7976931348623157e+308)
    max := math.max(nz(max[1]), array.get(src, 0))

min(source, outlier_level, dev_lookback)=>
    var float min = na
    src = array.new<float>()
    stdev = math.abs((source - bes(source, 0.1))/ta.stdev(source, dev_lookback))
    array.push(src, stdev < outlier_level ? source : 1.7976931348623157e+308)
    min := math.min(nz(min[1]), array.get(src, 0))

min_max(src, outlier_level, dev_lookback)=>
    ((src - min(src, outlier_level, dev_lookback))/(max(src, outlier_level, dev_lookback) - min(src, outlier_level, dev_lookback)) - 0.5) * 2

rms_slope(source, length, pre_smoothing, post_smoothing, outlier_level, dev_lookback)=>
    src = bes(source, pre_smoothing)
    avg = array.new<float>(length, na)
    for i = 1 to length
        array.push(avg, (src - src[i]))
    slope = min_max(bes(array.avg(avg), post_smoothing), outlier_level, dev_lookback)

slope  = rms_slope(source, length, pre_smoothing, post_smoothing, outlier_level, dev_lookback)
center = bes(slope, 0.001)
hist   = bes(diff(slope, diff) * 2, diff_alpha)
hist_col = hist >= 0 ? (hist[1] < hist ? col_grow_above : col_fall_above) : (hist[1] < hist ? col_grow_below : col_fall_below)
slope_out = center_center ? slope - center : slope

hline(bounds ? bound_level : na, linestyle = hline.style_solid)
hline(bounds ? -bound_level : na, linestyle = hline.style_solid)
plot(center_line ? (center_center ? 0 : center) : na, "Center Line", color.silver)
plot(clip ? (slope_out > bound_level ? bound_level : slope_out < -bound_level ? -bound_level : slope_out) : slope_out, "Normalized Slope", color.orange)
plot(enable_diff ? hist : na, "Delta", hist_col, style = plot.style_columns)
