Support & Resistance Analyzer
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © mks17

//@version=5
indicator("Support & Resistance Analyzer", overlay=true)

f_avgRet(float _src, simple int bar_start) =>
    sum = 0.0, var counter = 0, perc = 0.0
    sum := nz(sum[1])
    returns = 0.0
    if bar_index >= bar_start
        returns := (_src - _src[1]) * 100 / _src[1]
        if not(na(_src))
            counter := nz(counter[1]) + 1
            perc := math.abs(returns)
        sum := perc + sum
    avgRet = sum / (counter + 1)
    [returns, avgRet]

f_round(float _val) =>
    // Rounds _val to _increases/decreases of around 1%
    _decimals = math.round(3 - math.log10(_val))
    _p = math.pow(10, _decimals)
    math.round(math.abs(_val) * _p) / _p

//--------------------Average Returns---------------------------------

Bar = bar_index + 1

[returns, avgRet] = f_avgRet(close, 0)
abRet = math.abs(returns)
avgRet100 = avgRet / 100
avgRetClose100 = close * avgRet100

//-----------Inputs--------

Usewhat = input.string(defval='Analyze a MA', title='Level Type', options=['Analyze a MA','Analyze a Level'])
checkDirectionally = input.bool(true, "Check S&R Directionally")
upmargin = input.float(0.5, "Max Up Margin for In Zone detection", step=0.025) * avgRet100
dnmargin = input.float(0.5, "Max Down Margin for In Zone detection", step=0.025) * avgRet100
mult = input.float(0.5, "Max Multiple for compression detection", step=0.1, group="Detection Parameters")


//-------MA's analysis----------
//Put here your MA to be analyzed and assignt it to signal

signal = 0.0

source = input(title='Price Source', defval=ohlc4, group="MA Parameters")
length = input.int(20, title='Length', step=1, group="MA Parameters")

//EMA

// signal := ta.ema(source, length)

//Linear Regression

signal := ta.linreg(source, length, 0)


//------------Level's analysis -----------
//Put here your levels script and assign the array of levels to Array and the values to sr

varip Array = array.new_float(1, close), var sr = close
src0 = math.avg(open, close)

mult2 = input.float(0.5, "Max Multiple for Level Detection", step=0.1, group="Level Parameters") 

//Market Structure

srLoopback = input.int(title='Market Structure (MS) Loopback', defval=25, step=5, minval=1, maxval=300, group="Level Parameters")  //how many candles back will look to find a max or min    
srsource = input.string(title='MS Peaks/Valleys Source', defval='close', options=['close', 'high/low', 'highwick/lowwick', 'ohlc4'], group="Level Parameters")
highLowBoth = input.string(title='MS Peaks/Valleys Selection', defval='Both', options=['Both', 'highs', 'lows'], group="Level Parameters")

var srArray1 = array.new_float(1, close), var srArray2 = array.new_float(1, close), var lastSR = 0
highback = float(na), lowback = float(na)

highwick = ((close >= open ? close : open) + high) / 2
lowwick = ((close <= open ? close : open) + low) / 2
srsrc1 = srsource == 'ohlc4' ? ohlc4 : srsource == 'close' ? close : srsource == 'high/low' ? high : highwick
srsrc2 = srsource == 'ohlc4' ? ohlc4 : srsource == 'close' ? close : srsource == 'high/low' ? low : lowwick

highback := ta.highest(srsrc1[4], srLoopback)
lowback := ta.lowest(srsrc2[4], srLoopback)

if highback > srsrc1 and highback > srsrc1[1] and highback > srsrc1[2] and highback > srsrc1[3] and highback[1] < highback and (highLowBoth == 'Both' or highLowBoth == 'highs')
    sr := f_round(srsrc1[4])
    array.insert(srArray1, 0, sr)
    array.insert(Array, 0, sr)

if lowback < srsrc2 and lowback < srsrc2[1] and lowback < srsrc2[2] and lowback < srsrc2[3] and lowback[1] > lowback and (highLowBoth == 'Both' or highLowBoth == 'lows')
    sr := f_round(srsrc2[4])
    array.insert(srArray2, 0, sr)
    array.insert(Array, 0, sr)

//Speed and Acceleration

// plen = input.int(3, title='Price Smooth Length', step=1)
// salen = input.int(1, title='Speed Acceleration Length', step=1)

// [speed, acceleration, jerk] = lib.speedAccJerk(ta.sma(src0, plen), salen, avgRet) //

// if (speed < speed[1] and acceleration > acceleration[1] and speed > 0 or speed > speed[1] and acceleration < acceleration[1] and speed < 0) and abRet < avgRet * mult2
//     sr := lib.f_round(src0)
//     array.insert(Array, 0, sr)


//Flat Zones of MAs

// minSlope = input.float(title="Min MA Slope for Level detection",defval=0.2, step=0.01, group="Level Parameters")

// if (math.abs((signal - signal[1]) / signal[1]) < minSlope * avgRet / 100) and abRet < avgRet * mult2
//     sr := lib.f_round(src0)
//     array.insert(Array, 0, sr)


//-----------Calcs-------------------

var inZone = 0
var reversion = 0
var compression = 0

if Usewhat =='Analyze a MA'

    if checkDirectionally

        if close >= close[1]
            if close < signal and close > signal * (1 - dnmargin)
                inZone := inZone + 1
                if close[2] > close[1] and close[1] <= close
                    reversion := reversion + 1
                if math.abs(returns) < mult * avgRet
                    compression := compression + 1
        else
            if close < signal * (1 + upmargin) and close > signal
                inZone := inZone + 1
                if close[2] < close[1] and close[1] > close
                    reversion := reversion + 1
                if math.abs(returns) < mult * avgRet
                    compression := compression + 1

    else 
        
        if close < signal * (1 + upmargin) and close > signal * (1 - dnmargin)
            inZone := inZone + 1
            if close[2] > close[1] and close[1] < close or close[2] < close[1] and close[1] > close
                reversion := reversion + 1
            if math.abs(returns) < mult * avgRet
                compression := compression + 1

else   

    if checkDirectionally

        for i in Array
            //if price will have a resistance soon I dont open and wait till I surpass it
            if close >= close[1]
                if close < i and close > i * (1 - dnmargin)
                    inZone := inZone + 1
                    if close[2] > close[1] and close[1] <= close
                        reversion := reversion + 1
                    if math.abs(returns) < mult * avgRet
                        compression := compression + 1
                    break
            else
                if close[1] < i * (1 + upmargin) and close[1] > i 
                    inZone := inZone + 1
                    if close[2] < close[1] and close[1] > close
                        reversion := reversion + 1
                    if math.abs(returns) < mult * avgRet
                        compression := compression + 1
                    break

    else
        for i in Array
            //if price will have a resistance soon I dont open and wait till I surpass it
            if close < i * (1 + upmargin) and close > i * (1 - dnmargin)
                inZone := inZone + 1
                if close[2] > close[1] and close[1] < close or close[2] < close[1] and close[1] > close
                    reversion := reversion + 1
                if math.abs(returns) < mult * avgRet
                    compression := compression + 1
                break

var Oreversion = 0
var Ocompression = 0

if close[2] > close[1] and close[1] < close or close[2] < close[1] and close[1] > close
    Oreversion := Oreversion + 1
if math.abs(returns) < mult * avgRet
    Ocompression := Ocompression + 1

ratioC = compression / Ocompression / inZone * Bar
ratioR = reversion / Oreversion / inZone * Bar

//Plots

plot(Usewhat =='Analyze a MA' ? signal : na, "Moving Average")
plot(Usewhat =='Analyze a MA' ? signal * (1 + upmargin) : na , "Up Margin")
plot(Usewhat =='Analyze a MA' ? signal * (1 - dnmargin) : na , "Down Margin")
plot(sr == sr[20] or Usewhat =='Analyze a MA' ? na : sr, title='Support & Resistance', color=color.new(color.blue, 0), linewidth=1, style=plot.style_circles, join=false)
plot(sr == sr[20] or Usewhat =='Analyze a MA' ? na : sr * (1 + upmargin), title='Up Margin', color=color.new(color.blue, 0), linewidth=1, style=plot.style_circles, join=false)
plot(sr == sr[20] or Usewhat =='Analyze a MA' ? na : sr * (1 - dnmargin), title='Down Margin', color=color.new(color.blue, 0), linewidth=1, style=plot.style_circles, join=false)
//Results

plot((ratioR + ratioC - 2) * 100, title="S&R Overperformance/Underperformance", display=display.data_window, color=color.white)
plot(inZone * 100 / Bar, title="Percentage of Contacts", display=display.data_window, color=color.orange)
plot(ratioR, title="Ratio of Reversions", display=display.data_window, color=color.white)
plot(ratioC, title="Ratio of compressions", display=display.data_window, color=color.white)

plot(avgRet, title="Avg Abs Returns", display=display.data_window, color=color.orange)
plot(Bar, title="Number of bars", display=display.data_window, color=color.orange)
plot(inZone, title="Number of Contacts", display=display.data_window, color=color.orange)

plot(Oreversion * 100 / Bar, title="Percentage of Reversions out of total bars", display=display.data_window, color=color.white)
plot(reversion * 100 / inZone, title="Percentage of Reversions out of total contacts", display=display.data_window, color=color.white)
plot(Ocompression * 100 / Bar, title="Percentage of compressions out of total bars", display=display.data_window, color=color.white)
plot(compression * 100 / inZone, title="Percentage of compressions out of total contacts", display=display.data_window, color=color.white)

plot(Usewhat =='Analyze a level' ? array.size(Array) : na, title="Number of S&R Levels", display=display.data_window, color=color.white)

plotchar(inZone > inZone[1] ? 1 : na, title="Number of Contacts", char="•", location=location.bottom, size=size.tiny, color=color.orange)
