Next-Gen Engulfing Retracement
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © RickSimpson
//@version=5
indicator('Next-Gen Engulfing Retracement', 'Next-Gen Engulfing Retracement', overlay=true, max_bars_back=500, max_lines_count=500, max_labels_count=500)

/////////////////////////////////////
//  Next-Gen Engulfing Retracement //
/////////////////////////////////////

//Groups Setup

groupcolor       = "COLOR SETUP"
groupretracement = "RETRACEMENT LINES SETTINGS"
groupengulfing   = "ENGULFING SETTINGS"
groupalert       = "ALERTS SETUP"

//Customization Inputs

bearishengulfingcolor = input.color(color.red,   'Bearish Engulfing Color', group=groupcolor)
bullishengulfingcolor = input.color(color.green, 'Bullish engulfing Color', group=groupcolor)

//Inputs

showstrong       = input.bool(true,       'Show Strong Engulfing Retracements?',                      group=groupretracement)
show             = input.bool(true,       'Show Engulfing Retracements?',                             group=groupretracement)
maxlines         = input.int(10,          'Maximum Lines',     minval=1, maxval=500,                  group=groupretracement, inline='lines')
layout           = input.string('Wick',   'Lines Type',        options=['Wick', 'Zone'],              group=groupretracement, inline='lines')
linessize        = input.int(1,           'Lines Width     ',  minval=1, maxval=3,                    group=groupretracement, inline='lines style')
linestyle        = input.string('Dotted', 'Lines Style',       options=['Solid', 'Dotted', 'Dashed'], group=groupretracement, inline='lines style')
extend           = input.bool(false,      'Extend Lines?',                                            group=groupretracement, inline='lines style')
showengulfing    = input.bool(true,       'Strong Engulfing Labels?',                                 group=groupengulfing)
showallengulfing = input.bool(false,      'Engulfing Labels?',                                        group=groupengulfing)
engulfingalerts  = input.bool(true,       'Enable Engulfing Candles Alerts?',                         group=groupalert)
crossingalerts   = input.bool(true,       'Enable Retracement Lines Crossing Alerts?',                group=groupalert)

//Price Variables Declaration

open_      = open
high_      = high
low_       = low
close_     = close
bar_index_ = bar_index

//Color Call Function

fzonecolor(obcolor, _call) =>
    c1 = color.r(obcolor)
    c2 = color.g(obcolor)
    c3 = color.b(obcolor)
    color.rgb(c1, c2, c3, _call)

//Round Function

round_f(x) =>
    math.round(x / syminfo.mintick) * syminfo.mintick

//Truncated Percentile Function

percent_1(x, y) =>
    z = x * 100 / y
    z

//Lines Styles String Function

f_linestyle(_style) =>
    _style == 'Solid'  ? line.style_solid : _style == 'Dotted' ? line.style_dotted : line.style_dashed

//Directions Variables Declaration

beardir = close < open
bulldir = close > open

//Body Candle Size Calculation

body = math.abs(close - open)

//Momentum Calculation

math = math.max(high - low, math.abs(high - close[1]), math.abs(low - close[1]))

//Average True Range Built-in Function

atr = ta.atr(100)

//Engulfment Size Calculation

bodysizetop         = atr * 1.1
bodysizebottom      = atr / 3
smallbodysizetop    = atr / 3
smallbodysizebottom = atr / 8

//Engulfment Conditions

bodyengulfing      = body > bodysizebottom      and body < bodysizetop
smallbodyengulfing = body > smallbodysizebottom and body < smallbodysizetop

//Engulfing Calculation

bearengulfing = (smallbodyengulfing[1] or bodyengulfing[1]) and bulldir[1] and beardir and open[1] > close
bullengulfing = (smallbodyengulfing[1] or bodyengulfing[1]) and beardir[1] and bulldir and close > open[1]

//Boolean Conditions to Float

var float bearengulfingprice = na
if bearengulfing
    bearengulfingprice := high
    bearengulfingprice
var float bullengulfingprice = na
if bullengulfing
    bullengulfingprice := low
    bullengulfingprice

if showallengulfing
    l = ta.change(bearengulfingprice) ? label.new(bar_index_, bearengulfingprice[1] + 0.01, str.tostring(round_f(bearengulfingprice)), color=color.new(bearishengulfingcolor, 100), textcolor=bearishengulfingcolor, style=label.style_label_down, yloc=yloc.abovebar, size=size.small) : ta.change(bullengulfingprice) ? label.new(bar_index_, bullengulfingprice[1] - 0.01, str.tostring(round_f(bullengulfingprice)), color=color.new(bullishengulfingcolor, 100), textcolor=bullishengulfingcolor, style=label.style_label_up, yloc=yloc.belowbar, size=size.small) : na
    l

//Strong Engulfing Calculation

strongbearengulfing = beardir[2] and bullengulfing[1] and bearengulfing
strongbullengulfing = bulldir[2] and bearengulfing[1] and bullengulfing

//Boolean Conditions to Float

var float strongbearengulfingprice = na
if strongbearengulfing
    strongbearengulfingprice := high
    strongbearengulfingprice
var float strongbullengulfingprice = na
if strongbullengulfing
    strongbullengulfingprice := low
    strongbullengulfingprice

if showengulfing
    l = ta.change(strongbearengulfingprice) ? label.new(bar_index_, strongbearengulfingprice[1] + 0.01, str.tostring(round_f(strongbearengulfingprice)), color=color.new(bearishengulfingcolor, 100), textcolor=bearishengulfingcolor, style=label.style_label_down, yloc=yloc.abovebar, size=size.small) : ta.change(strongbullengulfingprice) ? label.new(bar_index_, strongbullengulfingprice[1] - 0.01, str.tostring(round_f(strongbullengulfingprice)), color=color.new(bullishengulfingcolor, 100), textcolor=bullishengulfingcolor, style=label.style_label_up, yloc=yloc.belowbar, size=size.small) : na
    l

//Plotting

plotshape(strongbearengulfing, title='Strong Bearish Engulfing', text=' ', color=bearishengulfingcolor, style=shape.triangledown, location=location.abovebar, size=size.small)
barcolor(beardir[2] and bullengulfing[1] and bearengulfing ? bearishengulfingcolor : na)
plotshape(strongbullengulfing, title='Strong Bullish Engulfing', text=' ', color=bullishengulfingcolor, style=shape.triangleup,   location=location.belowbar, size=size.small)
barcolor(bulldir[2] and bearengulfing[1] and bullengulfing ? bullishengulfingcolor : na)
plotshape(showallengulfing ? bearengulfing : na, title= "Bearish Engulfing", color=bearishengulfingcolor, text=' ', textcolor=bearishengulfingcolor, style=shape.arrowdown, location=location.abovebar, size=size.tiny)
plotshape(showallengulfing ? bullengulfing : na, title= "Bullish Engulfing", color=bullishengulfingcolor, text=' ', textcolor=bullishengulfingcolor, style=shape.arrowup,   location=location.belowbar, size=size.tiny)

//Alerts

if engulfingalerts and strongbearengulfing == 1
    alert('Strong Bearish Engulfing Detected! At ' + str.tostring(strongbearengulfingprice), alert.freq_once_per_bar)
if engulfingalerts and strongbullengulfing == 1
    alert('Strong Bullish Engulfing Detected! At ' + str.tostring(strongbullengulfingprice), alert.freq_once_per_bar)
if engulfingalerts and bearengulfing       == 1
    alert('Bearish Engulfing Detected! At '        + str.tostring(bearengulfingprice),       alert.freq_once_per_bar)
if engulfingalerts and bullengulfing       == 1
    alert('Bullish Engulfing Detected! At '        + str.tostring(bullengulfingprice),       alert.freq_once_per_bar)

//Retracement Lines Variables Declaration

var int     numberofline        = maxlines
var float   upperphzone         = na
var float   upperplzone         = na
var float   lowerphzone         = na
var float   lowerplzone         = na
var line[]  upperphzonearr      = array.new_line(0, na)
var line[]  upperplzonearr      = array.new_line(0, na)
var line[]  lowerphzonearr      = array.new_line(0, na)
var line[]  lowerplzonearr      = array.new_line(0, na)
var line    upperphzoneline     = na
var line    upperplzoneline     = na
var line    lowerphzoneline     = na
var line    lowerplzoneline     = na
var bool[]  upperzonetestedarr  = array.new_bool(0, false)
var bool[]  lowerzonetestedarr  = array.new_bool(0, false)
var bool    upperzonetested     = false
var bool    lowerzonetested     = false
var bool    nobool              = true
var bool    showprice           = true
var color   upperzonecolor      = bearishengulfingcolor
var color   lowerzonecolor      = bullishengulfingcolor
var label[] labelpharr          = array.new_label(0, na)
var label[] labelplarr          = array.new_label(0, na)
var label   labelph             = na
var label   labelpl             = na
var int     numberofline2       = maxlines
var float   upperphzone2        = na
var float   upperplzone2        = na
var float   lowerphzone2        = na
var float   lowerplzone2        = na
var line[]  upperphzonearr2     = array.new_line(0, na)
var line[]  upperplzonearr2     = array.new_line(0, na)
var line[]  lowerphzonearr2     = array.new_line(0, na)
var line[]  lowerplzonearr2     = array.new_line(0, na)
var line    upperphzoneline2    = na
var line    upperplzoneline2    = na
var line    lowerphzoneline2    = na
var line    lowerplzoneline2    = na
var bool[]  upperzonetestedarr2 = array.new_bool(0, false)
var bool[]  lowerzonetestedarr2 = array.new_bool(0, false)
var bool    upperzonetested2    = false
var bool    lowerzonetested2    = false
var bool    nobool2             = true
var color   upperzonecolor2     = bearishengulfingcolor
var color   lowerzonecolor2     = bullishengulfingcolor
var string  stronglinestyle     = 'Solid'
var label[] labelpharr2         = array.new_label(0, na)
var label[] labelplarr2         = array.new_label(0, na)
var label   labelph2            = na
var label   labelpl2            = na

//Strong Bearish Engulfing Retracement Lines Calculation

if showstrong and strongbearengulfing
    upperphzone     := high_
    upperplzone     := close_ < open_      ? close_ : open_
    upperplzoneline := layout == 'Zone'    ? line.new(bar_index_,  upperplzone, bar_index, upperplzone, width=linessize) : na
    upperphzoneline := nobool              ? line.new(bar_index_,  upperphzone, bar_index, upperphzone, width=linessize) : line.new(bar_index_, (upperphzone + upperplzone) / 2, bar_index, (upperphzone + upperplzone) / 2, width=linessize)
    labelph         := showprice           ? label.new(bar_index_, nobool ? upperphzone : (upperphzone + upperplzone) / 2, text=str.tostring(bar_index - bar_index_), textcolor=upperzonecolor, style=label.style_none) : na
    if array.size(upperphzonearr) > numberofline
        line.delete(array.shift(upperphzonearr))
        line.delete(array.shift(upperplzonearr))
        array.shift(upperzonetestedarr)
        label.delete(array.shift(labelpharr))
    array.push(upperphzonearr, upperphzoneline)
    array.push(upperplzonearr, upperplzoneline)
    array.push(upperzonetestedarr,       extend ? true : false)
    array.push(labelpharr,             labelph)
if array.size(upperplzonearr) > 0
    for i = 0 to array.size(upperplzonearr) - 1 by 1
        line  tempupperline  = array.get(upperphzonearr,     i)
        line  templowerline  = array.get(upperplzonearr,     i)
        label linepricelabel = array.get(labelpharr,         i)
        bool  tested         = array.get(upperzonetestedarr, i)
        line.set_style(tempupperline, f_linestyle(stronglinestyle))
        line.set_style(templowerline, f_linestyle(stronglinestyle))
        line.set_color(tempupperline, color.from_gradient(i,       1, numberofline, fzonecolor(upperzonecolor, 00), fzonecolor(upperzonecolor, 00)))
        line.set_color(templowerline, color.from_gradient(i,       1, numberofline, fzonecolor(upperzonecolor, 00), fzonecolor(upperzonecolor, 00)))
        label.set_textcolor(linepricelabel, color.from_gradient(i, 1, numberofline, fzonecolor(upperzonecolor, 00), upperzonecolor))
        label.set_text(linepricelabel, str.tostring(round_f(line.get_y1(tempupperline))))
        label.set_text(linepricelabel, '                                                    Bearish Engulfing - ' + str.tostring(round_f(line.get_y1(tempupperline))))
        label.set_x(linepricelabel, bar_index)
        crossed = high > line.get_y1(tempupperline)
        if crossed and not tested
            array.set(upperzonetestedarr, i, true)
            label.delete(linepricelabel)
        if crossingalerts and crossed and not tested
            array.set(upperzonetestedarr, i, true)
            label.delete(linepricelabel)
            alert('Strong Bearish Engulfing Line Has Been Crossed! At ' + str.tostring(close), alert.freq_once_per_bar)
        else if extend ? tested : not tested
            line.set_x2(tempupperline, bar_index)
            array.set(upperphzonearr, i, tempupperline)
            line.set_x2(templowerline, bar_index)
            array.set(upperplzonearr, i, templowerline)

//Strong Bullish Engulfing Retracement Lines Calculation

if showstrong and strongbullengulfing
    lowerplzone     := low_
    lowerphzone     := close_ < open_      ? open_ : close_
    lowerphzoneline := layout == 'Zone'    ? line.new(bar_index_,  lowerphzone, bar_index, lowerphzone, width=linessize) : na
    lowerplzoneline := nobool              ? line.new(bar_index_,  lowerplzone, bar_index, lowerplzone, width=linessize) : line.new(bar_index_, (lowerphzone + lowerplzone) / 2, bar_index, (lowerphzone + lowerplzone) / 2, width=linessize)
    labelpl         := showprice           ? label.new(bar_index_, nobool ? lowerplzone : (lowerphzone + lowerplzone) / 2, text=str.tostring(bar_index - bar_index_), textcolor=lowerzonecolor, style=label.style_none) : na
    if array.size(lowerphzonearr) > numberofline
        line.delete(array.shift(lowerphzonearr))
        line.delete(array.shift(lowerplzonearr))
        array.shift(lowerzonetestedarr)
        label.delete(array.shift(labelplarr))
    array.push(lowerphzonearr, lowerphzoneline)
    array.push(lowerplzonearr, lowerplzoneline)
    array.push(lowerzonetestedarr,       extend ? true : false)
    array.push(labelplarr,             labelpl)
if array.size(lowerplzonearr) > 0
    for i = 0 to array.size(lowerplzonearr) - 1 by 1
        line  tempupperline  = array.get(lowerphzonearr,     i)
        line  templowerline  = array.get(lowerplzonearr,     i)
        label linepricelabel = array.get(labelplarr,         i)
        bool  tested         = array.get(lowerzonetestedarr, i)
        line.set_style(tempupperline, f_linestyle(stronglinestyle))
        line.set_style(templowerline, f_linestyle(stronglinestyle))
        line.set_color(tempupperline, color.from_gradient(i,       1, numberofline, fzonecolor(lowerzonecolor, 00), fzonecolor(lowerzonecolor, 00)))
        line.set_color(templowerline, color.from_gradient(i,       1, numberofline, fzonecolor(lowerzonecolor, 00), fzonecolor(lowerzonecolor, 00)))
        label.set_textcolor(linepricelabel, color.from_gradient(i, 1, numberofline, fzonecolor(lowerzonecolor, 00), lowerzonecolor))
        label.set_text(linepricelabel, str.tostring(round_f(line.get_y1(templowerline))))
        label.set_text(linepricelabel, '                                                   Bullish Engulfing - ' + str.tostring(round_f(line.get_y1(templowerline))))
        label.set_x(linepricelabel, bar_index)
        crossed = low < line.get_y1(templowerline)
        if crossed and not tested
            array.set(lowerzonetestedarr, i, true)
            label.delete(linepricelabel)
        if crossingalerts and crossed and not tested
            array.set(lowerzonetestedarr, i, true)
            label.delete(linepricelabel)
            alert('Strong Bullish Engulfing Line Has Been Crossed! At ' + str.tostring(close), alert.freq_once_per_bar)
        else if extend ? tested : not tested
            line.set_x2(tempupperline, bar_index)
            array.set(lowerphzonearr, i, tempupperline)
            line.set_x2(templowerline, bar_index)
            array.set(lowerplzonearr, i, templowerline)

//Bearish Engulfing Retracement Lines Calculation

if show and bearengulfing
    upperphzone2     := high_
    upperplzone2     := close_ < open_      ? close_ : open_
    upperplzoneline2 := layout == 'Zone'    ? line.new(bar_index_,  upperplzone2, bar_index, upperplzone2, width=linessize) : na
    upperphzoneline2 := nobool              ? line.new(bar_index_,  upperphzone2, bar_index, upperphzone2, width=linessize) : line.new(bar_index_, (upperphzone2 + upperplzone2) / 2, bar_index, (upperphzone2 + upperplzone2) / 2, width=linessize)
    labelph2         := showprice           ? label.new(bar_index_, nobool ? upperphzone2 : (upperphzone2 + upperplzone2) / 2, text=str.tostring(bar_index - bar_index_), textcolor=upperzonecolor2, style=label.style_none) : na
    if array.size(upperphzonearr2) > numberofline2
        line.delete(array.shift(upperphzonearr2))
        line.delete(array.shift(upperplzonearr2))
        array.shift(upperzonetestedarr2)
        label.delete(array.shift(labelpharr2))
    array.push(upperphzonearr2, upperphzoneline2)
    array.push(upperplzonearr2, upperplzoneline2)
    array.push(upperzonetestedarr2,        extend ? true : false)
    array.push(labelpharr2,             labelph2)
if array.size(upperplzonearr2) > 0
    for i = 0 to array.size(upperplzonearr2) - 1 by 1
        line  tempupperline2  = array.get(upperphzonearr2,     i)
        line  templowerline2  = array.get(upperplzonearr2,     i)
        label linepricelabel2 = array.get(labelpharr2,         i)
        bool  tested2         = array.get(upperzonetestedarr2, i)
        line.set_style(tempupperline2, f_linestyle(linestyle))
        line.set_style(templowerline2, f_linestyle(linestyle))
        line.set_color(tempupperline2, color.from_gradient(i,       1, numberofline2, fzonecolor(upperzonecolor2, 00), fzonecolor(upperzonecolor2, 00)))
        line.set_color(templowerline2, color.from_gradient(i,       1, numberofline2, fzonecolor(upperzonecolor2, 00), fzonecolor(upperzonecolor2, 00)))
        label.set_textcolor(linepricelabel2, color.from_gradient(i, 1, numberofline2, fzonecolor(upperzonecolor2, 00), upperzonecolor2))
        label.set_text(linepricelabel2, str.tostring(round_f(line.get_y1(tempupperline2))))
        label.set_text(linepricelabel2, '                                                    Bearish Engulfing - ' + str.tostring(round_f(line.get_y1(tempupperline2))))
        label.set_x(linepricelabel2, bar_index)
        crossed = high > line.get_y1(tempupperline2)
        if crossed and not tested2
            array.set(upperzonetestedarr2, i, true)
            label.delete(linepricelabel2)
        if crossingalerts and crossed and not tested2
            array.set(upperzonetestedarr2, i, true)
            label.delete(linepricelabel2)
            alert('Bearish Engulfing Line Has Been Crossed! At ' + str.tostring(close), alert.freq_once_per_bar)
        else if extend ? tested2 : not tested2
            line.set_x2(tempupperline2, bar_index)
            array.set(upperphzonearr2, i, tempupperline2)
            line.set_x2(templowerline2, bar_index)
            array.set(upperplzonearr2, i, templowerline2)

//Bullish Engulfing Retracement Lines Calculation

if show and bullengulfing
    lowerplzone2     := low_
    lowerphzone2     := close_ < open_      ? open_ : close_
    lowerphzoneline2 := layout == 'Zone'    ? line.new(bar_index_,  lowerphzone2, bar_index, lowerphzone2, width=linessize) : na
    lowerplzoneline2 := nobool              ? line.new(bar_index_,  lowerplzone2, bar_index, lowerplzone2, width=linessize) : line.new(bar_index_, (lowerphzone2 + lowerplzone2) / 2, bar_index, (lowerphzone2 + lowerplzone2) / 2, width=linessize)
    labelpl2         := showprice           ? label.new(bar_index_, nobool ? lowerplzone2 : (lowerphzone2 + lowerplzone2) / 2, text=str.tostring(bar_index - bar_index_), textcolor=lowerzonecolor2, style=label.style_none) : na
    if array.size(lowerphzonearr2) > numberofline2
        line.delete(array.shift(lowerphzonearr2))
        line.delete(array.shift(lowerplzonearr2))
        array.shift(lowerzonetestedarr2)
        label.delete(array.shift(labelplarr2))
    array.push(lowerphzonearr2, lowerphzoneline2)
    array.push(lowerplzonearr2, lowerplzoneline2)
    array.push(lowerzonetestedarr2,        extend ? true : false)
    array.push(labelplarr2,             labelpl2)
if array.size(lowerplzonearr2) > 0
    for i = 0 to array.size(lowerplzonearr2) - 1 by 1
        line  tempupperline2  = array.get(lowerphzonearr2,     i)
        line  templowerline2  = array.get(lowerplzonearr2,     i)
        label linepricelabel2 = array.get(labelplarr2,         i)
        bool  tested2         = array.get(lowerzonetestedarr2, i)
        line.set_style(tempupperline2, f_linestyle(linestyle))
        line.set_style(templowerline2, f_linestyle(linestyle))
        line.set_color(tempupperline2, color.from_gradient(i,       1, numberofline2, fzonecolor(lowerzonecolor2, 00), fzonecolor(lowerzonecolor2, 00)))
        line.set_color(templowerline2, color.from_gradient(i,       1, numberofline2, fzonecolor(lowerzonecolor2, 00), fzonecolor(lowerzonecolor2, 00)))
        label.set_textcolor(linepricelabel2, color.from_gradient(i, 1, numberofline2, fzonecolor(lowerzonecolor2, 00), lowerzonecolor2))
        label.set_text(linepricelabel2, str.tostring(round_f(line.get_y1(templowerline2))))
        label.set_text(linepricelabel2, '                                                   Bullish Engulfing - ' + str.tostring(round_f(line.get_y1(templowerline2))))
        label.set_x(linepricelabel2, bar_index)
        crossed = low < line.get_y1(templowerline2)
        if crossed and not tested2
            array.set(lowerzonetestedarr2, i, true)
            label.delete(linepricelabel2)
        if crossingalerts and crossed and not tested2
            array.set(lowerzonetestedarr2, i, true)
            label.delete(linepricelabel2)
            alert('Bullish Engulfing Line Has Been Crossed! At ' + str.tostring(close), alert.freq_once_per_bar)
        else if extend ? tested2 : not tested2
            line.set_x2(tempupperline2, bar_index)
            array.set(lowerphzonearr2, i, tempupperline2)
            line.set_x2(templowerline2, bar_index)
            array.set(lowerplzonearr2, i, templowerline2)