Deviation Bands
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © Electrified

//@version=5
//@description Plots the 1, 2 and 3 standard deviations from the mean as bands of color (hot and cold). Useful in identifying likely points of mean reversion.
indicator("Deviation Bands", overlay=true)
import Electrified/DataCleaner/7 as Data
import Electrified/MovingAverages/10 as MA
SMA = 'SMA', EMA = 'EMA', WMA = 'WMA', VWMA = 'VWMA', VAWMA = 'VAWMA'
import Electrified/Time/7
MINUTES = "Minutes", DAYS = "Days", BARS = "Bars"

hot = color.orange, hot_clear = color.new(hot, 100)
cold = color.blue, cold_clear = color.new(cold, 100)

SOURCE = "Source", LENGTH = "Length"
src = input.source(hlc3, SOURCE, inline = SOURCE)
var type = input.string(WMA, "", inline = SOURCE, options=[SMA,EMA,WMA,VWMA,VAWMA])
var len = Time.spanToIntLen(
  input.float(200, LENGTH, minval=0.5, step=0.5, inline=LENGTH),
  input.string(BARS, "", options = [BARS, MINUTES, DAYS], inline=LENGTH, tooltip = "The length of the source measurement."))
var useSource = input.bool(false, "Use source as mean", "With this selected, the source becomes the mean and the deviation is then calculated using HLC3.\nMode and Length will then have no effect.")

DEVIATION = "Deviation Calculation"
var mLen = Time.spanToIntLen(
  input.float(1200, LENGTH, minval=0.5, step=0.5, inline=LENGTH, group=DEVIATION),
  input.string(BARS, "", options = [BARS, MINUTES, DAYS], inline=LENGTH, group=DEVIATION, tooltip = "The length of the deviation measurement."))
var maxDev = input.float(4, "Maximum Outlier Level", group=DEVIATION, minval=0)

BIAS = "Deviation Bias"
// Bias causes the bands to deviate from the mean based upon previous deviations.
var bLen = Time.spanToIntLen(
  input.float(1200, LENGTH, minval=0.5, step=0.5, inline=LENGTH, group=BIAS),
  input.string(BARS, "", options = [BARS, MINUTES, DAYS], inline=LENGTH, group=BIAS, tooltip = "The number of bars to determine the bias.\nExample: if for the last (bias length) bars the values tended to be greater than the mean, the bias would tend to be positive."))  
var bMul = input.float(0.0, "Multiplier", group=BIAS, step = 0.1, tooltip = "Set this to zero if no bias is desired.")

ma = useSource ? src : MA.get(type, len, src)

// Derive a baseline +/- ratio away from the mean.
ma_d = ((useSource ? hlc3 : src) - ma)/ma
cleaned = Data.naOutliers(ma_d, mLen, maxDev)

// Is the deviation biased?
bias = bMul == 0.0 ? 0.0 : MA.wma(bLen, cleaned) * ma * bMul // MA.wma is used to fill in NA gaps.
biasedMa = ma + bias
// What is the standard deviation?
stdev = ta.stdev(cleaned, mLen) * ma

// Set levels.
upper1 = biasedMa + stdev
lower1 = biasedMa - stdev
upper2 = upper1 + stdev
lower2 = lower1 - stdev
upper3 = upper2 + stdev
lower3 = lower2 - stdev

// Plot levels from highest to lowest. Line-width=4 to make a larget hit area for the mouse to click on.
pUpper3 = plot(upper3, "+3", hot_clear, 4)
pUpper2 = plot(upper2, "+2", hot_clear, 4)
pUpper1 = plot(upper1, "+1", hot_clear, 4)
plot(ma, "Mean", color.new(color.silver, 75), 1, style=plot.style_circles)
pLower1 = plot(lower1, "-1", cold_clear, 4)
pLower2 = plot(lower2, "-2", cold_clear, 4)
pLower3 = plot(lower3, "-3", cold_clear, 4)

hot1A = color.new(hot, 90)
hot2A = color.new(hot, 75)
hot2B = color.new(hot, 85)
hot3B = color.new(hot, 100)

cold1A = color.new(cold, 90)
cold2A = color.new(cold, 75)
cold2B = color.new(cold, 85)
cold3B = color.new(cold, 100)

fill(pUpper1, pUpper2, upper1, upper2, hot1A, hot2A, "+1 to +2 Band")
fill(pUpper2, pUpper3, upper2, upper3, hot2B, hot3B, "+2 to +3 Band")

fill(pLower1, pLower2, lower1, lower2, cold1A, cold2A, "-1 to -2 Band")
fill(pLower2, pLower3, lower2, lower3, cold2B, cold3B, "-2 to -3 Band")