AltSession
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © KryptoGostek aka MrCode X AltSignals.io Enjoy =)

// This script is a modification of boitoki's session script. Include some fixes, new features and options.

//@version=5

indicator('AltSessions', overlay=true, max_boxes_count=220, max_labels_count=400, max_lines_count=400, max_bars_back=550)

///////////////
// Groups
///////////////
g0  = 'SETTINGS'
g1  = '♯1 SESSION'
g2  = '♯2 SESSION'
g3  = '♯3 SESSION'
g4  = 'SESSION BOX STYLE'
g5  = 'DATA LABELS SHOW & STYLE'
g6  = 'INFOBOX SHOW & STYLE'
g7  = 'MIN/MAX SHOW & STYLE'
g81 = 'SESSION #1'
g82 = 'SESSION #2'
g83 = 'SESSION #3'
g9  = 'NY MIDNIGHT OPEN'

///////////////
// Defined
///////////////
show = true
pips = syminfo.mintick * 10
max_bars = 500

option_yes = 'Yes'
option_no = 'No'

fmt_price = '{0,number,#.#####}'
fmt_pips  = '{0,number,#.#}'

icon_separator = ' • '
empty_character = ' ' // U+2003

c_none = color.new(color.black, 100)

valLab_offset = 3 // number of bars added to x2 as an offset for value label
label_NYMO_offset = 15 // number of bars added to x as an offset for NYMO label

var float mo = na
mo := ta.valuewhen(ta.change(time("D","0000-0000","GMT-5")), open, 0) // NY Midnight Open

///////////////
// Functions
///////////////
f_get_started (_session) => na(_session[1]) and _session // previous bar wasn't in session, current one is in session time

f_get_ended (_session) => na(_session) and _session[1]   // previous bar was in session time, current one isn't in session time

f_inSession (_session, _timezone) => na(time(timeframe.period, _session + ":1234567", _timezone)) == false // function for detecting if current bar falls within specified custom time period (session)

f_get_period (_session, _start, _lookback) =>  // check i bars back if they are in session or not, if the first bar of session is found - break, the result is session duration in bars
    result = math.max(_start, 1)
    for i = result to _lookback
        if f_get_started(_session)
            result := 1
            break
        else if na(_session[i+1]) and _session[i]
            result := i+1
            break
    result

f_get_label_position (_y) =>  // set label position according to inputs data
    switch _y
        'top'    => label.style_label_lower_left
        'bottom' => label.style_label_upper_left

f_get_day (n) =>  // set day name
    switch n
        1 => 'Sun'
        2 => 'Mon'
        3 => 'Tue'
        4 => 'Wed'
        5 => 'Thu'
        6 => 'Fri'
        7 => 'Sat'

///////////////
// Inputs
///////////////

// Timezone & history switch
i_tz = input.string(title="🕑 Timezone", defval="GMT", options=["GMT-10","GMT-9","GMT-8","GMT-7","GMT-6","GMT-5","GMT-4","GMT-3","GMT-2","GMT-1","GMT","GMT+1","GMT+2","GMT+3","GMT+3:30","GMT+4","GMT+5","GMT+5:30","GMT+6","GMT+7","GMT+8","GMT+9","GMT+10","GMT+10:30","GMT+11","GMT+12","GMT+13","GMT+13:45"], group=g0)
i_show_history = input.string(option_no, 'Show history', options=[option_yes, option_no], group=g0) == option_yes
i_encapsulate = input.bool(true, 'Encapsulate the last session candle', group=g0)
sess_offset = i_encapsulate ? 0 : 1 // (should be 1 or 0) subtract number of candles from end of session to encapsulate last session candle or not 
i_lookback = 12 * 60 // 720 lookback period for calculation of session bars number - max session duration 12h on 1m chart, 6h on 30sec etc.

// Sessions
i_show_sess1   = input.bool(true, title="Session", inline="asia", group=g81)
i_sess1_label  = input.string('Tokyo',':', inline="asia", group=g81)
i_sess1        = input.session(title=" ", defval="0100-0400", inline="asia", group=g81)
i_sess1_bgCol  = input.color(color.new(color.yellow,0), title="color", inline="asia", group=g81)
i_sess1_minCol = input.color(color.rgb(207, 205, 66), title="Min", inline="asia", group=g81)
i_sess1_maxCol = input.color(color.rgb(207, 205, 66), title="Max", inline="asia", group=g81)
i_sess1_minmax = input.bool(true, title="Show Min/Max lines", inline="asia_mm", group=g81)
i_sess1_extend = input.bool(false, 'Extend Min/Max lines', inline="asia_mm", group=g81)

i_show_sess2   = input.bool(true, title="Session", inline="euro", group=g82)
i_sess2_label  = input.string('London',':', inline="euro", group=g82)
i_sess2        = input.session(title=" ", defval="0700-1100", inline="euro", group=g82)
i_sess2_bgCol  = input.color(color.new(color.blue,0), title="color", inline="euro", group=g82)
i_sess2_minCol = input.color(color.rgb(42, 129, 215), title="Min", inline="euro", group=g82)
i_sess2_maxCol = input.color(color.rgb(42, 129, 215), title="Max", inline="euro", group=g82)
i_sess2_minmax = input.bool(true, title="Show Min/Max lines", inline="euro_mm", group=g82)
i_sess2_extend = input.bool(false, 'Extend Min/Max lines', inline="euro_mm", group=g82)

i_show_sess3   = input.bool(true, title="Session", inline="america", group=g83)
i_sess3_label  = input.string('New York',':', inline="america", group=g83)
i_sess3        = input.session(title=" ", defval="1430-2000", inline="america", group=g83)
i_sess3_bgCol  = input.color(color.new(color.red,0), title="color", inline="america", group=g83)
i_sess3_minCol = input.color(color.rgb(200, 75, 75), title="Min", inline="america", group=g83)
i_sess3_maxCol = input.color(color.rgb(200, 75, 75), title="Max", inline="america", group=g83)
i_sess3_minmax = input.bool(true, title="Show Min/Max lines", inline="america_mm", group=g83)
i_sess3_extend = input.bool(false, 'Extend Min/Max lines', inline="america_mm", group=g83)

// Session Box Show & Styles
i_sess_border_style = input.string(line.style_dashed, 'Border Style', options=[line.style_solid, line.style_dotted, line.style_dashed], inline='sess_box_style', group=g4)
i_sess_border_width = input.int(1, 'width', minval=0, inline='sess_box_style', group=g4)
i_sess_bgopacity = input.int(94, 'Background Opacity (100 = no color)', minval=0, maxval=100, step=1, group=g4)

// Session MinMax Show & Styles
i_minmax_lineStyle = input.string(line.style_solid, title="Line Style", options=[line.style_solid, line.style_dotted, line.style_dashed], inline="line_style", group=g7)
i_minmax_lineWidth = input.int(2, title="width", inline="line_style", group=g7)
i_show_minmax_lines = input.bool(true, title="Toggle showing min/max lines for all sessions", group=g7)
i_show_minmax_val = input.bool(true, title="Toggle showing min/max values for all sessions", group=g7)

// Data Label
i_label_show = input.bool(true, 'Show labels', inline='label_show', group=g5) and show
i_label_size = str.lower(input.string('Normal', '', options=['Auto', 'Tiny', 'Small', 'Normal', 'Large', 'Huge'], inline='label_show', group=g5))
i_label_position_y = str.lower(input.string('Top', '', options=['Top', 'Bottom'], inline='label_show', group=g5))
i_label_position = f_get_label_position(i_label_position_y)
i_label_format_name = input.bool(true, 'Name', inline='label_format', group=g5)
i_label_format_day = input.bool(false, 'Day', inline='label_format', group=g5)
i_label_format_price = input.bool(false, 'Price range', inline='label_format', group=g5)
i_label_format_pips = input.bool(false, 'Pips range', inline='label_format', group=g5)

// NY Midnight Open
i_mo = input.bool(true, 'Midnight Open', inline="mo", group=g9)
i_moCol = input.color(color.rgb(216, 68, 226), title="color", inline="mo", group=g9)
i_mo_lineStyle = input.string(line.style_dotted, 'Line Style', options=[line.style_solid, line.style_dotted, line.style_dashed], inline='mo_style', group=g9)
i_mo_lineWidth = input.int(2, title="width", inline="mo_style", group=g9)
i_mo_label = input.bool(true, 'Show price label', inline="mo_extend", group=g9)
i_mo_extend = input.bool(false, 'Extend line', inline="mo_extend", group=g9)

// InfoBox table
i_table_InfoBox_show = input.bool(true, 'Show InfoBox', group=g6)
i_tableCol = input.color(color.new(color.blue, 0), title="Background", inline="sessionBox", group=g6)
i_tableCell_txtCol = input.color(color.new(color.white,0), title="Text", inline="sessionBox", group=g6)

// ------------------------
// Drawing labels
// ------------------------
f_render_dataLabel (_show, _session, _is_started, _color, _top, _bottom, _text, _delete_history) =>
    var label my_label = na
    var int start_time = na

    v_position_y = (i_label_position_y == 'top') ? _top : _bottom
    v_label = array.new_string()
    v_chg = _top - _bottom  // price range
    
    if _is_started
        start_time := time
    
    if i_label_format_name and not na(_text) // session name
        array.push(v_label, _text)

    if i_label_format_day
        array.push(v_label, f_get_day(dayofweek(start_time, i_tz))) // day name

    if i_label_format_price
        array.push(v_label, str.format(fmt_price, v_chg)) // price range
    
    if i_label_format_pips
        array.push(v_label, str.format(fmt_pips, v_chg / pips) + ' pips') // pips range

    if _show
        if _is_started // check if bar is the firts bar of session
            my_label := label.new(time, v_position_y, array.join(v_label, icon_separator), textcolor=_color, color=c_none, size=i_label_size, style=i_label_position, xloc=xloc.bar_time, text_font_family=font.family_monospace) // create label
            
            if _delete_history  // delete history label according to input
                label.delete(my_label[1])

        if _session  // if in session,change y parameter and update text
            label.set_y(my_label, v_position_y)
            label.set_text(my_label, array.join(v_label, icon_separator))

f_drawValLabel (_show, _session, _is_started, _is_ended, _x, _y, _textColor, _delete_history) => // Function to draw labels with min/max values
    var label level_label = na
    
    if _show
        if _is_started
            level_label := label.new(_x+valLab_offset, _y, '    ' + str.tostring(_y), xloc.bar_index, yloc.price, #00000000, label.style_none, _textColor, textalign=text.align_left, text_font_family=font.family_monospace)
        
            if _delete_history  // delete history label according to input
                label.delete(level_label[1])
        
        else if _session
            label.set_x(level_label, _x+valLab_offset)
            label.set_y(level_label, _y)
            label.set_text(level_label, str.tostring(_y))
        
        else if _is_ended
            label.set_x(level_label, bar_index-sess_offset)

// ------------------------
// Drawing session boxes
// ------------------------
f_render_session (_show, _session, _is_started, _is_ended, _color, _top, _bottom, _extend, _delete_history) =>
    var box my_box = na

    x0_1 = ta.valuewhen(na(_session[1]) and _session, bar_index, 1) // barindex on the previous session start
    x0_2 = ta.valuewhen(na(_session) and _session[1], bar_index, 0) // barindex on the last session end
    var x1 = 0
    var x2 = 0
    var session_open  = 0.0
    var session_high  = 0.0
    var session_low   = 0.0

    if _show
        if _is_started
            diff = math.abs(x0_2 - x0_1)-sess_offset // previous session duration in bars
            x1 := bar_index
            x2 := bar_index + (math.min(diff, max_bars))
            my_box := box.new(x1, _top, x2, _bottom, _color, i_sess_border_width, i_sess_border_style, bgcolor=color.new(_color, i_sess_bgopacity))

            session_open := open
            session_high := _top
            session_low  := _bottom

            // if _extend //_is_extend
            //     box.set_extend(my_box, extend.right)

            if _delete_history
                box.delete(my_box[1])

        else if _session
            box.set_top(my_box, _top)
            box.set_bottom(my_box, _bottom)

            session_high := _top
            session_low  := _bottom
        
        else if _is_ended
            session_open := na
            box.set_right(my_box, bar_index-sess_offset)

    [x1, x2, session_open, session_high, session_low]

// ------------------------
// Drawing lines
// ------------------------
f_drawLine (_show ,_session, _is_started, _is_ended, _x1, _x2, _y1, _color, _extend, _delete_history) =>
    var line lineMinMax = na
    
    if _show
        if _is_started
            lineMinMax := line.new(_x1, _y1, _x2, _y1, xloc = xloc.bar_index, extend=_delete_history and _extend ?extend.right:extend.none, color=_color, style=i_minmax_lineStyle, width=i_minmax_lineWidth)
        
            if _delete_history  // delete history label according to input
                line.delete(lineMinMax[1])
        
        else if _session
            line.set_y1(lineMinMax, _y1)
            line.set_y2(lineMinMax, _y1)

        else if _is_ended
            line.set_x2(lineMinMax, bar_index-sess_offset)

// ------------------------
// Drawing NY midnigh open
// ------------------------

f_drawNYMO() =>
    var line line_NYMO = na
    var label label_NYMO = na
    var int x1 = bar_index
    var int x2 = bar_index
    float y1 = mo

    delete_history = (not i_show_history) or i_mo_extend

    if mo[1] != mo
        x1 := bar_index
        x2 := bar_index
        line_NYMO := line.new(x1, mo, x2, mo, xloc = xloc.bar_index, extend=delete_history and i_mo_extend ?extend.right:extend.none, color=i_moCol, style=i_mo_lineStyle, width=i_mo_lineWidth)

        if i_mo_label
            label_NYMO := label.new(x2+label_NYMO_offset, mo, str.tostring(mo) + ' NY Midnight Open', xloc.bar_index, yloc.price, #FFFFFF, label.style_none, i_moCol, textalign=text.align_left, text_font_family=font.family_monospace)
        
        if delete_history  // delete history line according to input
            line.delete(line_NYMO[1])
            label.delete(label_NYMO[1])
    
    else if mo == mo[1]
        line.set_x2(line_NYMO, bar_index)
        label.set_x(label_NYMO, bar_index+label_NYMO_offset)

// ------------------------
// Drawing
// ------------------------
draw (_show, _session, _bgcolor, _show_minmax, _maxcol, _mincol, _label, _extend, _lookback) =>
    
    is_started = f_get_started(_session) // check if bar is the first bar of session
    is_ended = f_get_ended(_session) // check if bar is the first bar after the session
    
    max = f_get_period(_session, 1, _lookback) // get session duration in bars  // is_started ? 1 : is_started[1] ? 2 : 
    top = ta.highest(high, max) // get top of the session
    bottom = ta.lowest(low, max) // get bottom of the session
    
    delete_history = (not i_show_history) or _extend // check if history drawings and labels should be deleted, showing only the last ones 

    [x1, x2, _open, _high, _low] = f_render_session(_show, _session, is_started, is_ended, _bgcolor, top, bottom, _extend, delete_history) // draw a session box

    if i_label_show
        f_render_dataLabel(_show, _session, is_started, _bgcolor, top, bottom, _label, delete_history) // draw a session label if needed

    if i_show_minmax_lines and _show_minmax
        f_drawLine(_show, _session, is_started, is_ended, x1, x2, _high, _maxcol, _extend, delete_history)
        f_drawLine(_show, _session, is_started, is_ended, x1, x2, _low, _mincol, _extend, delete_history)

    if i_show_minmax_val
        f_drawValLabel(_show, _session, is_started, is_ended, x2, _high, _maxcol, delete_history)
        f_drawValLabel(_show, _session, is_started, is_ended, x2, _low, _mincol, delete_history)

    [_session, _open, _high, _low, max] // return session, open, top, low

///////////////////
// Calculating
///////////////////
int sess1 = time(timeframe.period, i_sess1, i_tz) // convert session to timezone from input
int sess2 = time(timeframe.period, i_sess2, i_tz)
int sess3 = time(timeframe.period, i_sess3, i_tz)

///////////////////
// Plotting
///////////////////
[is_sess1, sess1_open, sess1_high, sess1_low, sess1_max] = draw(i_show_sess1, sess1, i_sess1_bgCol, i_sess1_minmax, i_sess1_maxCol, i_sess1_minCol, i_sess1_label, i_sess1_extend, i_lookback) // draw session box and label
[is_sess2, sess2_open, sess2_high, sess2_low, sess2_max] = draw(i_show_sess2, sess2, i_sess2_bgCol, i_sess2_minmax, i_sess2_maxCol, i_sess2_minCol, i_sess2_label, i_sess2_extend, i_lookback)
[is_sess3, sess3_open, sess3_high, sess3_low, sess3_max] = draw(i_show_sess3, sess3, i_sess3_bgCol, i_sess3_minmax, i_sess3_maxCol, i_sess3_minCol, i_sess3_label, i_sess3_extend, i_lookback)

// Create table - InfoBox
var table infoBoxTable = table.new(position.top_right, 1, 1, border_width = 3)

is_asia_sess = f_inSession(i_sess1, i_tz) and i_show_sess1
is_euro_sess = f_inSession(i_sess2, i_tz) and i_show_sess2
is_america_sess = f_inSession(i_sess3, i_tz) and i_show_sess3

cell_text = is_america_sess and is_asia_sess and is_euro_sess ? i_sess1_label + "\n" + i_sess2_label + "\n" + i_sess3_label :
             is_america_sess and is_asia_sess ? i_sess3_label + "\n" + i_sess1_label : 
             is_asia_sess and is_euro_sess ? i_sess1_label + "\n" + i_sess2_label :
             is_euro_sess and is_america_sess ? i_sess2_label + "\n" + i_sess3_label : 
             is_america_sess ? i_sess3_label : is_asia_sess ? i_sess1_label : is_euro_sess ? i_sess2_label : "None"

f_fillCell(_table, _column, _row, _text) =>
    table.cell(_table, _column, _row, _text, bgcolor = i_tableCol, text_color = i_tableCell_txtCol, width = 6)

if barstate.islast and i_table_InfoBox_show
    f_fillCell(infoBoxTable, 0, 0, cell_text)

// Draw NY Midnight Open line
if i_mo
    f_drawNYMO()