Moving Average Zones
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © HoanGhetti

//@version=5
indicator("Moving Average Zones [HG]", overlay = true)

// There won't be an input option for these settings to keep it simple.
// Feel free to change the values here although it's recommended to keep them like this.
// These are offset multipliers from the base moving average. All of these will offset off the script's default Simple Moving Average for example.
type levelMultiplier
    float one   = 12.0
    float two   = 8.0
    float three = 6.0
    float four  = 4.5
    float five  = 3.5
    float six   = 2.5
    float seven = 0.3

allBoxes  = box.all
allLabels = label.all
allLines  = line.all
noDisplay = display.all - display.status_line

color_input     = input.color(defval = color.new(#5e5a55, 80), title = 'Zone Color', inline = 'zonetouch')
bb_input        = input.int(defval = 100, title = 'Lookback Range', minval = 1, maxval = 4999, step = 10, inline = 'zonetouch', tooltip = 'Allows the script to check how many touches have occured between zones within the specified bars back period')
ma_input        = input.string(defval = 'SMA', title = 'MA Type ', inline = 'maconv', options = ['EMA', 'SMA', 'RMA', 'HMA', 'WMA', 'VWMA', 'ALMA', 'SWMA'])
len_input       = input.int(defval = 50, title = 'MA Length', minval = 1, inline = 'lensrc')
src_input       = input.source(defval = close, title = 'MA Source', inline = 'lensrc')  
offset_input    = input.float(defval = 0.85, title = 'MA Offset ', minval = 0, step = 0.05, inline = 'offsig')
sigma_input     = input.float(defval = 6, title = 'MA Sigma ', minval = 0, step = 0.05, inline = 'offsig', tooltip = 'Offset and sigma are used for ALMA')
aoc_input       = input.bool(defval = false, title = 'Areas of Confluence', group = 'AOC', tooltip = 'Allows the user to visually see which zones are currently active in terms of how many touches have occured within the given lookback period.')
zero_input      = input.bool(defval = false, title = 'Display Zero-Touch Zones', group = 'AOC', tooltip = 'If using AOC, allows zones to still be slightly displayed even if inactive.')
extend_input    = input.bool(defval = false, title = 'Extend Plot Levels', group = 'AOC', tooltip = 'Extends zones to the right.')
touch_input     = input.bool(defval = true, title = 'Display Touch Count', group = 'AOC', tooltip = 'Creates labels on the right side of the zones displaying how many touches have occured for that specific zone.')

data_input      = input.bool(defval = false, title = 'Display Data Window', group = 'Data Window')

// Determine which MA Input is selected, and then assign maType to it's specified moving average.
maType = switch ma_input
    'EMA'  => ta.ema(src_input, len_input)
    'SMA'  => ta.sma(src_input, len_input)
    'RMA'  => ta.rma(src_input, len_input)
    'HMA'  => ta.hma(src_input, len_input)
    'WMA'  => ta.wma(src_input, len_input)
    'VWMA' => ta.vwma(src_input, len_input)
    'SWMA' => ta.swma(src_input)
    'ALMA' => ta.alma(src_input, len_input, offset_input, sigma_input)

// Offsets a moving average both upward and downward of it's base moving average.
offsetMA(float movingAverage, float multiplier, int atrLength) =>
	atr = ta.atr(atrLength)
	multi = atr * multiplier
	ma_up = movingAverage + multi
	ma_dn = movingAverage - multi
	[ma_up, ma_dn]

// A neat way of fetching all right decimals universally, since other variants out there wouldn't work on futures.
getDecimals = str.length(str.tostring(syminfo.mintick)) - 2

// Used for touch detection.
hitDetection(up, dn, hi = high, lo = low) => hi > dn and hi < up or lo < up and lo > dn

// Create an instance for the properties made above
l = levelMultiplier.new()

// Utilize our offsetMA function here. ATR will always be MA length * 2
[maUpOne, maDnOne] = offsetMA(maType, l.one, len_input * 2)
[maUpTwo, maDnTwo] = offsetMA(maType, l.two, len_input * 2)
[maUpThree, maDnThree] = offsetMA(maType, l.three, len_input * 2)
[maUpFour, maDnFour] = offsetMA(maType, l.four, len_input * 2)
[maUpFive, maDnFive] = offsetMA(maType, l.five, len_input * 2)
[maUpSix, maDnSix] = offsetMA(maType, l.six, len_input * 2)
[maUpSeven, maDnSeven] = offsetMA(maType, l.seven, len_input * 2)

// Creating each individual variable of the hitDetection event, and counting how many hits have occured between each individual zone. This is used for color transparency
hit_one     = math.sum(hitDetection(maUpOne, maUpTwo) ? 1 : 0, bb_input)
hit_two     = math.sum(hitDetection(maUpThree, maUpFour) ? 1 : 0, bb_input)
hit_three   = math.sum(hitDetection(maUpFive, maUpSix) ? 1 : 0, bb_input)
hit_four    = math.sum(hitDetection(maUpSeven, maDnSeven) ? 1 : 0, bb_input)
hit_five    = math.sum(hitDetection(maDnSix, maDnFive) ? 1 : 0, bb_input)
hit_six     = math.sum(hitDetection(maDnFour, maDnThree) ? 1 : 0, bb_input)
hit_seven   = math.sum(hitDetection(maDnTwo, maDnOne) ? 1 : 0, bb_input)

// Separate data into multiple arrays/matrix to easily determine if price is between each zone.
m = matrix.new<float>(7, 2)
upPlots = array.from(maUpOne, maUpThree, maUpFive, maUpSeven, maDnSix, maDnFour, maDnTwo)
dnPlots = array.from(maUpTwo, maUpFour, maUpSix, maDnSeven, maDnFive, maDnThree, maDnOne)
maxHits = array.from(hit_one, hit_two, hit_three, hit_four, hit_five, hit_six, hit_seven)
alpha   = array.new<float>()
transp = zero_input ? 98 : 100

// Color Transparency
for i = 0 to array.size(maxHits) - 1
    array.push(alpha, math.abs(transp - (array.get(maxHits, i) / array.sum(maxHits)) * 100))

// Delete past objects
delObj(arr, inputOption, condition) =>
    if array.size(arr) > 0 and inputOption
        for i = 0 to array.size(arr) - 1
            if condition == 'lbl'
                label.delete(array.get(allLabels, i))
            if condition == 'box'
                box.delete(array.get(allBoxes, i))

delObj(allLabels, touch_input, 'lbl')
delObj(allBoxes, extend_input, 'box')

// Create two separate arrays that will track rejection count. See the bottom of script for visual guide.
var bearRevCount = array.new<int>()
var bullRevCount = array.new<int>()

// Here we loop through the arrays that we listed above so we can then create the plot extensions and touch count labels.
for i = 0 to array.size(upPlots) - 1
    matrix.set(m, i, 0, math.round(array.get(upPlots, i), getDecimals))
    matrix.set(m, i, 1, math.round(array.get(dnPlots, i), getDecimals))
    median = (matrix.get(m, i, 0) + matrix.get(m, i, 1)) / 2
    if touch_input
        label.new(bar_index + 5, median, str.tostring(array.get(maxHits, i)), style = label.style_none, textcolor = color.new(color_input, 0))
    if extend_input
        box.new(bar_index, matrix.get(m, i, 0), bar_index, matrix.get(m, i, 1), border_color = na, extend = extend.right, bgcolor = aoc_input ? color.new(color_input, array.get(alpha, i)) : color_input)

// Fill and plot the zones.
fill(plot(maUpOne, color = na, display = noDisplay, editable = false), plot(maUpTwo, color = na, display = noDisplay, editable = false), color = aoc_input ? color.new(color_input, array.get(alpha, 0)) : color_input)
fill(plot(maUpThree, color = na, display = noDisplay, editable = false), plot(maUpFour, color = na, display = noDisplay, editable = false), color = aoc_input ? color.new(color_input, array.get(alpha, 1)) : color_input)
fill(plot(maUpFive, color = na, display = noDisplay, editable = false), plot(maUpSix, color = na, display = noDisplay, editable = false), color = aoc_input ? color.new(color_input, array.get(alpha, 2)) : color_input)
fill(plot(maUpSeven, color = na, display = noDisplay, editable = false), plot(maDnSeven, color = na, display = noDisplay, editable = false), color = aoc_input ? color.new(color_input, array.get(alpha, 3)) : color_input)
fill(plot(maDnSix, color = na, display = noDisplay, editable = false), plot(maDnFive, color = na, display = noDisplay, editable = false), color = aoc_input ? color.new(color_input, array.get(alpha, 4)) : color_input)
fill(plot(maDnFour, color = na, display = noDisplay, editable = false), plot(maDnThree, color = na, display = noDisplay, editable = false), color = aoc_input ? color.new(color_input, array.get(alpha, 5)) : color_input)
fill(plot(maDnTwo, color = na, display = noDisplay, editable = false), plot(maDnOne, color = na, display = noDisplay, editable = false), color = aoc_input ? color.new(color_input, array.get(alpha, 6)) : color_input)

// Data Window (Displaying collected data)
if data_input
    data = table.new(position.top_center, 20, 20, bgcolor = color.new(color_input, 40), border_color = color_input, border_width = 2, frame_color = color_input, frame_width = 2)
    totalHits = array.sum(maxHits)
    table.cell(data, 0, 0, 'Type', text_color = color.white)
    table.cell(data, 0, 1, str.tostring(ma_input), text_color = color.white)
    table.cell(data, 1, 0, 'Length', text_color = color.white)
    table.cell(data, 1, 1, str.tostring(len_input), text_color = color.white)
    table.cell(data, 2, 0, 'Touch LBR', text_color = color.white)
    table.cell(data, 2, 1, str.tostring(bb_input), text_color = color.white)
    table.cell(data, 3, 0, 'Total Touches', text_color = color.white)
    table.cell(data, 3, 1, str.tostring(array.sum(maxHits)), text_color = color.white)