ICT NWOG/NDOG [Source Code] (fadi)
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © fadizeidan

//@version=5
indicator(title="ICT NWOG/NDOG (fadi)", overlay=true, max_bars_back=5000, max_lines_count = 500)

// Helper type is a hack to group generic functions
type Helper
    string  name

type Settings
    bool    show
    color   color
    color   color_new
    color   ce_color
    color   ce_color_new
    string  extend
    bool    EH_show // Event Horizon
    color   EH_color
    string  EH_style
    int     EH_size
    int     max_count
    bool    label_show
    string  label_size
    color   label_color
    color   label_bgcolor
    

type Gap
    string  name
    float   open
    float   middle
    float   close
    int     open_time
    int     close_time
    box     box
    line    CE
    label       lbl_date
    bool    is_current = false

type OpenGap
    Gap[]       gaps
    Settings    settings
    line        EventHorizon        = na


var nwog_gaps                = array.new<Gap>()
var OpenGap NWOG             = OpenGap.new()
NWOG.gaps                   := nwog_gaps
NWOG.settings               := Settings.new()

var ndog_gaps               = array.new<Gap>()
var OpenGap NDOG             = OpenGap.new()
NDOG.gaps                   := ndog_gaps
NDOG.settings               := Settings.new()

var Helper helper            = Helper.new()

NWOG_Group                  = "New Week Open Gap (NWOG)"
NDOG_Group                  = "New Day Open Gap (NDOG)"
General_Group               = "General Settings"

NWOG.settings.show         := input.bool(true, "NWOG", group=NWOG_Group)
NWOG.settings.color_new    := input.color(color.new(color.purple,85), 'Range           ', group=NWOG_Group, inline='1')
NWOG.settings.color        := input.color(color.new(color.black,85), '', group=NWOG_Group, inline='1')
NWOG.settings.ce_color_new := input.color(color.new(color.purple,20), 'Middle           ', group=NWOG_Group, inline='3')
NWOG.settings.ce_color     := input.color(color.new(color.black,20), '', group=NWOG_Group, inline='3')
NWOG.settings.label_show    := input.bool(true, "Date        ", group=NWOG_Group, inline='2')
NWOG.settings.label_color   := input.color(color.black, "", group=NWOG_Group, inline='2')
NWOG.settings.label_bgcolor := input.color(color.new(color.orange, 100), "", group=NWOG_Group, inline='2')
NWOG.settings.label_size    := input.string(size.small, "", [size.auto, size.tiny, size.small, size.normal, size.large, size.huge], group=NWOG_Group, inline='2')
NWOG.settings.EH_show      := input.bool(true, "Event Horizon", group=NWOG_Group, inline="4", tooltip="Only applicable to Previous and Next, draws middle point Event Horizon.")
NWOG.settings.EH_color     := input.color(color.new(color.purple,20), "", group=NWOG_Group, inline='4')
NWOG.settings.EH_style     := input.string('----', '', options = ['⎯⎯⎯', '----', '····'], group=NWOG_Group, inline='4')
NWOG.settings.EH_size      := input.int(1, '', options = [1,2,3,4], group=NWOG_Group, inline='4')


NWOG.settings.max_count    := input.int(5, "Maximum number of NWOGs to use", 1, 50, group=NWOG_Group)
NWOG.settings.extend       := input.string("Always", "Extend Gaps", options = ['Always', 'Above and below only', 'Any that is near current price'], group=NWOG_Group)

NDOG.settings.show         := input.bool(true, "NDOG", group=NDOG_Group)
NDOG.settings.color_new    := input.color(color.new(color.green,85), 'Range           ', group=NDOG_Group, inline='1')
NDOG.settings.color        := input.color(color.new(color.blue,85), '', group=NDOG_Group, inline='1')
NDOG.settings.label_show    := input.bool(true, "Date        ", group=NDOG_Group, inline='2')
NDOG.settings.label_color   := input.color(color.black, "", group=NDOG_Group, inline='2')
NDOG.settings.label_bgcolor := input.color(color.new(color.orange, 100), "", group=NDOG_Group, inline='2')
NDOG.settings.label_size    := input.string(size.small, "", [size.auto, size.tiny, size.small, size.normal, size.large, size.huge], group=NDOG_Group, inline='2')

NDOG.settings.ce_color_new := input.color(color.new(color.green,20), 'Middle           ', group=NDOG_Group, inline='3')
NDOG.settings.ce_color     := input.color(color.new(color.blue,20), '', group=NDOG_Group, inline='3')

NDOG.settings.max_count    := input.int(1, "Maximum number of NDOGs to use", 1, 50, group=NDOG_Group)
NDOG.settings.extend       := input.string("Always", "Extend Gaps", options = ['Always', 'Above and below only', 'Any that is near current price'], group=NDOG_Group)

distance                    = input.int(200, "Number of candles to use in calculation", group="Other")
factor                      = input.float(20, "Factor multiplier for distance test", group="Other")
buffer                      = input.int(1, "Buffer - Number of candles to extend by", minval=0, group="Other")+1

atrf                        = ta.atr(distance) + (ta.stdev(high - low, distance)*factor)
dt                          = time - time[1]


//+------------------------------------------------------------------------------------------------------------+//
//+--- Functions                                                                                            ---+//
//+------------------------------------------------------------------------------------------------------------+//

f_get_line_style(style) =>
    out = switch style
        '⎯⎯⎯'  => line.style_solid
        '----' => line.style_dashed
        '····' => line.style_dotted

//+------------------------------------------------------------------------------------------------------------+//
//+--- Methods                                                                                              ---+//
//+------------------------------------------------------------------------------------------------------------+//

method AbovePrice(Gap[] gaps, price) =>
    float delta = -1
    size = gaps.size()
    Gap _return = na
    
    if size > 0
        for i= size-1 to 0
            Gap ng = gaps.get(i)
            float mark = math.max(ng.open, ng.close)
            if price < mark
                ndelta = math.abs(mark - price)
                delta := delta == -1 ? ndelta : delta
                if ndelta <= delta
                    delta := ndelta
                    _return := ng
    _return

method BelowPrice(Gap[] gaps, price) =>
    float delta = -1
    size = gaps.size()
    Gap _return = na
    if size > 0
        for i=0 to size-1
            Gap ng = gaps.get(i)
            float mark = math.min(ng.open, ng.close)
            if price > mark
                ndelta = math.abs(price - mark)
                delta := delta == -1 ? ndelta : delta
                if ndelta <= delta 
                    delta := ndelta
                    _return := ng
    _return

method GetExtendedValue(Helper helper, string extendby, Gap gap) =>
    helper.name := "GetExtendedValue" // Dummy setting to make Helper type work

    switch extendby
        'Always' =>
            time + (dt*buffer)
        'Above and below only' =>
            gap.close_time + (dt*buffer)
        'Any that is near current price' =>
            t = (math.min(gap.open, gap.close) - high)
            l = (low - math.max(gap.open, gap.close))
            if (t > 0 and t < atrf) or (l > 0 and l < atrf) or (t <= 0 and l <= 0)
                time + dt*buffer 
            else
                gap.close_time + (dt*buffer)
    
method RenderBox(Helper helper, box box, line line, label lbl, float top, float bottom, int left, int right, Settings settings, bool is_current) =>
    helper.name := "RenderBox" //Dummy setting

    if not na(box)
        box.set_lefttop(box, left, top)
        box.set_rightbottom(box, right, bottom)
        box.set_bgcolor(box, is_current ? settings.color_new : settings.color)
        box.set_border_color(box, is_current ? settings.color_new : settings.color)

    if not na(line)
        line.set_x2(line, right)
        line.set_color(line, is_current ? settings.ce_color_new : settings.ce_color)

    if not na(lbl) and settings.label_show
        label.set_x(lbl, right)

method Add(OpenGap GAP, string name) =>
    gap = Gap.new()
    gap.name       := name
    gap.open       := close[1]
    gap.close      := open
    gap.middle     := (open+close[1])/2
    gap.open_time  := time[1]
    gap.close_time := time

    gap.box := box.new(gap.open_time, gap.open, gap.close_time, gap.close, GAP.settings.color_new, 1, line.style_solid, extend.none, xloc.bar_time, GAP.settings.color_new)
    gap.CE := line.new(gap.open_time, gap.middle, gap.close_time, gap.middle, xloc.bar_time, extend.none, GAP.settings.ce_color_new, line.style_dotted, 1)
    if GAP.settings.label_show
        gap.lbl_date := label.new(gap.close_time, gap.middle, str.format("({0, date, d MMM yyyy}) - {1}", gap.close_time, gap.name), style=label.style_label_left, xloc=xloc.bar_time, size=GAP.settings.label_size, color=GAP.settings.label_bgcolor, textcolor=GAP.settings.label_color)
    GAP.gaps.unshift(gap)


    if GAP.gaps.size() > GAP.settings.max_count
        g = GAP.gaps.pop()
        box.delete(g.box)
        line.delete(g.CE)
        label.delete(g.lbl_date)
    
    GAP

method Redraw(OpenGap GAP) =>
    if GAP.gaps.size() > 0
        for i = 0 to GAP.gaps.size()-1
            g = GAP.gaps.get(i)
            g.is_current := i == 0
            e = helper.GetExtendedValue(GAP.settings.extend, g)
            helper.RenderBox(g.box, g.CE, g.lbl_date, math.max(g.open, g.close), math.min(g.open, g.close), g.open_time, e, GAP.settings, g.is_current)

        if GAP.settings.extend == 'Above and below only' or GAP.settings.EH_show
            prev = GAP.gaps.AbovePrice(close)
            next = GAP.gaps.BelowPrice(close)

            d = time + (time-time[1])*buffer

            if GAP.settings.extend == 'Above and below only'
                if not na(prev)
                    helper.RenderBox(prev.box, prev.CE, prev.lbl_date, math.max(prev.open, prev.close), math.min(prev.open, prev.close), prev.open_time, d, GAP.settings, prev.is_current)
                if not na(next)
                    helper.RenderBox(next.box, next.CE, next.lbl_date, math.max(next.open, next.close), math.min(next.open, next.close), next.open_time, d, GAP.settings, next.is_current)

            if GAP.settings.EH_show
                if not na(prev) and not na(next)
                    if math.min(prev.open, prev.close) > math.max(next.open, next.close)
                        middle = (math.min(prev.open, prev.close) + math.max(next.open, next.close))/2
                        if na(GAP.EventHorizon)
                            GAP.EventHorizon := line.new(math.max(prev.open_time, next.open_time), middle, time+(time-time[1])*buffer, middle, xloc=xloc.bar_time, color=GAP.settings.EH_color, width = GAP.settings.EH_size, style=f_get_line_style(GAP.settings.EH_style))
                        else
                            line.set_xy1(GAP.EventHorizon, math.max(prev.open_time, next.open_time), middle)
                            line.set_xy2(GAP.EventHorizon, time+(time-time[1])*buffer, middle)
                    else
                        line.delete(GAP.EventHorizon)
                else
                    line.delete(GAP.EventHorizon)
                    1
            else
                line.delete(GAP.EventHorizon)
                1
    GAP

//+------------------------------------------------------------------------------------------------------------+//
//+--- Main                                                                                                 ---+//
//+------------------------------------------------------------------------------------------------------------+//

if barstate.isconfirmed
    if NWOG.settings.show
        is_sunday = dayofweek == dayofweek.sunday
        if not is_sunday[1] and is_sunday //and open != close[1]
            NWOG.Add("NWOG")
        NWOG.Redraw()
    
    if NDOG.settings.show
        dailyBarTime = time('1D')
        isNewDay = ta.change(dailyBarTime)

        if isNewDay and open != close[1]
            NDOG.Add("NDOG")
        NDOG.Redraw()
