Pre-market Highs & Lows on regular trading hours (RTH) chart
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// Added feature to choose only to extend most recent session lines/labels to last bar index
// Fix to work on SPY, Indices (e.g. ES1!) and Stocks (14th Oct'23)
// © twingall


//@version=5
indicator("Pre-market Highs & Lows on RTH chart", overlay = true, max_bars_back = 2000)

showHighs = input.bool(true, "show pre-market high", inline ='1')
showLows = input.bool(true, "show pre-market low", inline ='2')
maxDayLines = input.int(3, minval = 1, title = "Previous Sessions to show")
extendSessChoice = input.string("Previous Only", "Previous Session Lines Extend Choice", options = ["Previous Only", "All", "None"], tooltip = "Extend session lines to last bar index, or have them stop at end of non market hours+input bars forward line length\n\nPrevious Only: only the last completed overnight session lines extend to the last bar index\n\nAll: all previous session lines extend to last bar index\n\nNone: all previous session lines stop at end of overnight session + input bars forward")
rthLineLen = input.int(22, minval=0, title= "(Line length: bars forward from RTH open)", tooltip ="This is only really designed for when chart is toggled to RTH (regular trading hours); i.e. if it were set to 0 on RTH chart you wouldn't see the lines")
showLabels = input.bool(true, "labels", group = 'formatting', inline ='1')
textCol = input.color(color.blue, "|| Color:", group = 'formatting', inline ='1')
textSize = input.string(size.normal, "Size:", options =[size.auto, size.tiny, size.small, size.normal, size.large, size.huge], group = 'formatting', inline ='1')
showLines = input.bool(true, "lines", group = 'formatting', inline ='2')
lineColH = input.color(color.green, "|| High line color:", group = 'formatting', inline ='2')
lineColL = input.color(color.red, "Low line color:", group = 'formatting', inline ='2')
lineWidth = input.int(1, "width", group = 'formatting', inline ='3')
_lineStyle = input.string("dotted", "linestyle", options =["arrow left" ,"solid", "dotted", "dashed"], group = 'formatting', inline ='3', tooltip ="only applies when chart is toggles to ETH(electronic trading hours)")

showdayDate = input.bool(true, "(show day/date on labels)",group = 'formatting', inline = '9')

showNascent = input.bool(true, "Plot realtime pre-Mkt Highest/Lowest", group = 'Plot Currently forming preMkt Highs/Lows', inline = '1', tooltip = "Only applies outside of Regular trading hours\n\nShows currently forming, dynamic Highest/Lowest of preMkt as we progress through the overnight session\n\nNote that if toggled to RTH chart, these nascent levels will not automatically/dynamically shift, but only change on a 'refresh' event (timeframe change, page refresh, indicator reload)")
colNascentHi = input.color(color.blue, "High line color: ", group = 'Plot Currently forming preMkt Highs/Lows', inline = '2')
colNascentLo = input.color(color.fuchsia, "Low line color: ", group = 'Plot Currently forming preMkt Highs/Lows', inline = '2')
_lineStyle2 = input.string("dotted", "| Line style:", options =["arrow left" ,"solid", "dotted", "dashed"], group = 'Plot Currently forming preMkt Highs/Lows', inline ='2')
inpRegTradHrs =input.session('0930-1600', 'Regular Session', group = 'Plot Currently forming preMkt Highs/Lows')
inputNonMktHrs =input.session('1600-0930', 'Non-Market Hours', group = 'Plot Currently forming preMkt Highs/Lows')


string lineStyle = switch _lineStyle
    "arrow left" => line.style_arrow_left
    "solid"      => line.style_solid
    "dotted"     => line.style_dotted
    "dashed"     => line.style_dashed

colorNone = color.new(color.white, 100)

extendedTicker = ticker.modify(syminfo.ticker, session = session.extended)

    //condition to see if our chart is toggled to electronic trading hours (ETH) or regular trading hours (RTH)
_7_00= timestamp('America/New_York', year, month, dayofmonth, 07, 00, 00) //arbitrary pre-market time used for the 'is RTH or is ETH' condition
outOfHoursClose = ta.valuewhen(time==_7_00, close,0) 
isETH = not na(outOfHoursClose) 

inNonMktHrs= not na(time(timeframe.period, inputNonMktHrs, "America/New_York"))
inRegSess =not na(time(timeframe.period, inpRegTradHrs, 'America/New_York'))
/// ~~FUNCTIONS~~

high() => 
    var float result = na
    if inNonMktHrs and not inNonMktHrs[1]
        result:=high
    else if inNonMktHrs   
        result:=math.max(result, high)
    result

low() => 
    var float result = na
    if inNonMktHrs and not inNonMktHrs[1]
        result:=low
    else if inNonMktHrs   
        result:=math.min(result, low)
    result

openTime(_sess)=>
    var int  _openTime  = na
    if _sess and not _sess[1]
        _openTime   := time
    int result = _openTime

tickerExtHighest = request.security(extendedTicker, timeframe.period, high()[1], lookahead = barmerge.lookahead_on) // use [1], so it doesn't count 9:30 high as highest pre-market
tickerExtLowest  = request.security(extendedTicker, timeframe.period, low()[1], lookahead = barmerge.lookahead_on) // use [1], so it doesn't count 9:30 low as lowest pre-market
nonMktHrsStart  = request.security(extendedTicker, timeframe.period, openTime(inNonMktHrs))
MktHrsStart  = request.security(extendedTicker, timeframe.period, openTime(inRegSess))

var line hiLine = na
var label hiLineLab =na
var array<line> hiLineArr = array.new<line>(0)
var array<label> hiLineLabArr = array.new<label>(0)
var float lastPreMktHi = na
var line loLine = na
var label loLineLab =na
var array<line> loLineArr = array.new<line>(0)
var array<label> loLineLabArr = array.new<label>(0)
var float lastPreMktLo = na


timeFormatFxn(int _time)=>
    _dayNo = str.format_time(_time, "dd", "America/New_York")
    dayNo = str.startswith(_dayNo, "0")? str.substring(_dayNo,1):_dayNo
    var string superScript = na
    if str.length(dayNo) == 1
        superScript := str.endswith(dayNo, "1")?"ˢᵗ":str.endswith(dayNo, "2")?"ⁿᵈ":str.endswith(dayNo, "3")?"ʳᵈ":"ᵗʰ"
    if str.length(dayNo) == 2 and str.startswith(dayNo, "1")
        superScript :="ᵗʰ"
    if str.length(dayNo)==2 and str.startswith(dayNo, "2")
        superScript := str.endswith(dayNo, "1")?"ˢᵗ":str.endswith(dayNo, "2")?"ⁿᵈ":str.endswith(dayNo, "3")?"ʳᵈ":"ᵗʰ"
    if str.length(dayNo)==2 and str.startswith(dayNo, "3")
        superScript:= str.endswith(dayNo, "1")?"ˢᵗ":"ᵗʰ"
    result = dayNo+ superScript

getDayOfWeekLabel(_time) =>
    _dayofweek = dayofweek(_time, "America/New_York")
    switch _dayofweek
        1 => "Sun"
        2 => "Mon"
        3 => "Tue"
        4 => "Wed"
        5 => "Thu"
        6 => "Fri"
        7 => "Sat"
dayDateStr(_time)=>
    string result = " " +getDayOfWeekLabel(_time) +" " + timeFormatFxn(_time)

rthLnLenTime = rthLineLen * 1000 * timeframe.in_seconds(timeframe.period)
var int x2 = na
if syminfo.type == 'stock' or syminfo.type == 'fund'? session.islastbar_regular: inNonMktHrs[1] and not inNonMktHrs
    x2:=MktHrsStart[1]
if  syminfo.type == 'stock' or syminfo.type == 'fund'? (session.isfirstbar_regular and showHighs): (inNonMktHrs[1] and not inNonMktHrs and showHighs)

    lastPreMktHi:= tickerExtHighest
    hiLine:=isETH?line.new(nonMktHrsStart, tickerExtHighest, extendSessChoice=="All"?last_bar_time:time, tickerExtHighest, xloc=xloc.bar_time, style= lineStyle,color =showLines?lineColH:colorNone, width =lineWidth):
     line.new(time, tickerExtHighest, last_bar_time, tickerExtHighest, xloc=xloc.bar_time, style= line.style_dotted, color =showLines?lineColH:colorNone, width =lineWidth) 
    hiLineLab:=isETH?label.new(last_bar_time, tickerExtHighest, text=showLabels?str.tostring(tickerExtHighest)+ (showdayDate?dayDateStr(time):""):"", style = label.style_label_left,color = colorNone,textcolor =textCol, xloc=xloc.bar_time, size =textSize):
     label.new(last_bar_time,tickerExtHighest, text=showLabels?str.tostring(tickerExtHighest)+ (showdayDate?dayDateStr(time):""):" ", style = label.style_label_left,color = colorNone,textcolor =textCol, xloc=xloc.bar_time, size =textSize)
    array.push(hiLineArr, hiLine)
    array.push(hiLineLabArr,hiLineLab)

    if array.size(hiLineArr)>maxDayLines
        line.delete(array.shift(hiLineArr))
        label.delete(array.shift(hiLineLabArr))

    if extendSessChoice=="Previous Only" and hiLineArr.size()>0   
        line.set_x2(hiLineArr.last(), last_bar_time)  
        if hiLineArr.size()>1 
            _2ndLstHiLn = hiLineArr.get(hiLineArr.size()-2)          // getting 2nd last line & label, and setting line x2 to original line x1 + small line length; and setting label x position to the same
            line.set_x2(_2ndLstHiLn, x2+rthLnLenTime)
            _2ndLstHiLab = hiLineLabArr.get(hiLineLabArr.size()-2)
            label.set_x(_2ndLstHiLab, x2+rthLnLenTime)

    if extendSessChoice=="None" and hiLineArr.size()>0
        lastHiLn = hiLineArr.last()
        lastHiLnX1 = isETH?line.get_x2(lastHiLn):line.get_x1(lastHiLn)
        line.set_x2(lastHiLn, lastHiLnX1+rthLnLenTime)
        lastHiLab = hiLineLabArr.last()
        label.set_x(lastHiLab, lastHiLnX1+rthLnLenTime)

if  syminfo.type == 'stock' or syminfo.type == 'fund'? (session.isfirstbar_regular and showLows) : (inNonMktHrs[1] and not inNonMktHrs and showLows)
    lastPreMktLo:= tickerExtLowest
    loLine:=isETH?line.new(nonMktHrsStart, tickerExtLowest, extendSessChoice=="All"?last_bar_time:time, tickerExtLowest, xloc=xloc.bar_time, style= lineStyle,color =showLines?lineColL:colorNone, width =lineWidth):
     line.new(time, tickerExtLowest, last_bar_time, tickerExtLowest, xloc=xloc.bar_time, style= line.style_dotted, color =showLines?lineColL:colorNone, width =lineWidth) 
    loLineLab:=isETH?label.new(last_bar_time, tickerExtLowest, text=showLabels?str.tostring(tickerExtLowest)+ (showdayDate?dayDateStr(time):""):"", style = label.style_label_left, color = colorNone, textcolor =textCol, xloc=xloc.bar_time, size =textSize):
     label.new(last_bar_time,tickerExtLowest, text=showLabels?str.tostring(tickerExtLowest)+ (showdayDate?dayDateStr(time):""):" ", style = label.style_label_left, color = colorNone,textcolor =textCol, xloc=xloc.bar_time, size =textSize)
    array.push(loLineArr, loLine)
    array.push(loLineLabArr,loLineLab)

    if array.size(loLineArr)>maxDayLines
        line.delete(array.shift(loLineArr))
        label.delete(array.shift(loLineLabArr))

    if extendSessChoice=="Previous Only" and  loLineArr.size()>0   
        line.set_x2(loLineArr.last(), last_bar_time)  
        if loLineArr.size()>1
            _2ndLstLoLn = loLineArr.get(loLineArr.size()-2)           // getting 2nd last line & label, and setting line x2 to original line x1 + small line length; and setting label x position to the same
            line.set_x2(_2ndLstLoLn, x2+rthLnLenTime)
            _2ndLstLoLab = loLineLabArr.get(loLineLabArr.size()-2)
            label.set_x(_2ndLstLoLab, x2+rthLnLenTime)

    if extendSessChoice=="None" and loLineArr.size()>0
        lastLoLn = loLineArr.last()
        lastLoLnX1 = isETH?line.get_x2(lastLoLn):line.get_x1(lastLoLn)
        line.set_x2(lastLoLn, lastLoLnX1+rthLnLenTime)
        lastLoLab = loLineLabArr.last()
        label.set_x(lastLoLab, lastLoLnX1+rthLnLenTime)


//////////////// Nascent preMkt highs and lows : this will only show outside of regular trading hours, but should display on RTH chart dynamically through the overnight session


string lineStyle2 = switch _lineStyle2
    "arrow left" => line.style_arrow_left
    "solid"      => line.style_solid
    "dotted"     => line.style_dotted
    "dashed"     => line.style_dashed


barsSinceRTHcloseETH = ta.barssince(inRegSess) 
 //turns out can only retrieve ETH price info from RTH toggled chart by using request.security_lower_tf (request.security() won't work)
timeDiff = timenow-last_bar_time
extHighsArr = request.security_lower_tf(extendedTicker, timeframe.period, timeDiff>0?high:0)  
if timeDiff ==0
    array.clear(extHighsArr)
highestOf = array.max(extHighsArr)
extLowsArr = request.security_lower_tf(extendedTicker, timeframe.period, timeDiff>0?low:0)
if timeDiff ==0
    array.clear(extLowsArr)
lowestOf = array.min(extLowsArr)

_t_extHcurrRTH = request.security(extendedTicker, timeframe.period, ta.highest(high, barsSinceRTHcloseETH>0?barsSinceRTHcloseETH:1))
_t_extHcurrETH = ta.highest(high, barsSinceRTHcloseETH>0?barsSinceRTHcloseETH:1)
tickerExtHighestCurr = isETH? _t_extHcurrETH:math.max((timeDiff>0?highestOf:_t_extHcurrRTH),_t_extHcurrRTH) //math.max: account for the short period between 4pm 'close' and last bar of RTH 'close'

_t_extLcurrRTH = request.security(extendedTicker, timeframe.period, ta.lowest(low, barsSinceRTHcloseETH>0?barsSinceRTHcloseETH:1))
_t_extLcurrETH = ta.lowest(low, barsSinceRTHcloseETH>0?barsSinceRTHcloseETH:1)
tickerExtLowestCurr = isETH? _t_extLcurrETH: math.min((timeDiff>0?lowestOf:_t_extLcurrRTH),_t_extLcurrRTH) //math.min: account for the short period between 4pm 'close' and last bar of RTH 'close'

var line hiLineCurr = na, var label hiLineLabCurr =na
var line loLineCurr = na, var label loLineLabCurr =na 

if  isETH?barstate.islast and showNascent and not inRegSess: session.islastbar and showNascent     //this could be simpler; but I left it in for the 'partial' functionality of plotting out-of-hours levels on RTH chart. I say 'partial' because levels only refresh on static/out-of-hours RTH chart when a refresh event occurs (timeframe or asset change, page refresh, indicator reload)      
    hiLineCurr:=isETH?line.new(bar_index, tickerExtHighestCurr, bar_index -barsSinceRTHcloseETH, tickerExtHighestCurr, style= lineStyle2, extend =extend.none,color =colNascentHi, width =lineWidth):
     line.new(bar_index, tickerExtHighestCurr,bar_index+rthLineLen, tickerExtHighestCurr, style= line.style_dotted, extend =extend.none, color =color.blue, width =lineWidth) 
    loLineCurr:=isETH?line.new(bar_index, tickerExtLowestCurr, bar_index -barsSinceRTHcloseETH, tickerExtLowestCurr, style= lineStyle2, extend =extend.none,color =colNascentLo, width =lineWidth):
     line.new(bar_index, tickerExtLowestCurr, bar_index+rthLineLen, tickerExtLowestCurr, style= line.style_dotted, extend =extend.none,color =color.red, width =lineWidth) 
    hiLineLabCurr:=isETH?label.new(bar_index,tickerExtHighestCurr, text=showLabels?str.tostring(tickerExtHighestCurr):"", style = label.style_label_left,color = colorNone,textcolor =textCol, size =textSize):
     label.new(bar_index+rthLineLen,tickerExtHighestCurr, text=showLabels?str.tostring(tickerExtHighestCurr):"", style = label.style_label_left,color = colorNone,textcolor =textCol, size =textSize)
    loLineLabCurr:=isETH?label.new(bar_index, tickerExtLowestCurr, text=showLabels?str.tostring(tickerExtLowestCurr):"", style = label.style_label_left,color = colorNone,textcolor =textCol, size =textSize):
     label.new(bar_index+rthLineLen,tickerExtLowestCurr, text=showLabels?str.tostring(tickerExtLowestCurr):"", style = label.style_label_left,color = colorNone,textcolor =textCol, size =textSize)
    if tickerExtHighestCurr!=tickerExtLowestCurr[1] 
        line.delete(hiLineCurr[1])
        label.delete(hiLineLabCurr[1])
        line.delete(loLineCurr[1])
        label.delete(loLineLabCurr[1])

if isETH? showNascent and session.isfirstbar_regular: showNascent and session.isfirstbar
    line.delete(hiLineCurr)
    label.delete(hiLineLabCurr)
    line.delete(loLineCurr)
    label.delete(loLineLabCurr)

//Alerts:
alertcondition(ta.crossover(high, lastPreMktHi), "Cross OVER pre Mkt High", "price crossed above Pre market high")  // lastPreMktHi & lastPreMktLo will refresh each day at start of regular trading hours
alertcondition(ta.crossunder(low, lastPreMktLo), "cross UNDER pre Mkt Low", "price crossed below Pre market low")


///****************** debugging 

// var Table = table.new(position.top_right, columns = 14, rows = 5, bgcolor = color.orange, border_width = 1)
// if barstate.islast
//     table.cell(Table, 0, 0, "DEBUG TABLE", bgcolor=color.blue)
//     table.cell(Table, 1, 0, "tickerExtHighestCurr = "+str.tostring(tickerExtHighestCurr))
//     table.cell(Table, 1, 1, "inRegSess = " +str.tostring(inRegSess))
//     table.cell(Table, 0, 2, "lowestOf = " +str.tostring(lowestOf),bgcolor = color.yellow)
//     table.cell(Table, 2, 2, "session.islastbar = " +str.tostring(session.islastbar),bgcolor = color.green)
//     table.cell(Table, 0, 3, "isETH = " +str.tostring(isETH),bgcolor = color.green)//
//     table.cell(Table, 1, 2, "lastPreMktHi = " +str.tostring(lastPreMktHi, format.mintick))
//     table.cell(Table, 1, 3, "lastPreMktLo = " +str.tostring(lastPreMktLo, format.mintick))
//     table.cell(Table, 2, 3, "syminfo type:  " +str.tostring(syminfo.type))
//     table.cell(Table, 2, 4, "session type:  " +str.tostring(syminfo.session==session.extended?"extended":syminfo.session==session.regular?"regular":"other"))

