Opening Range Breakout with Price Targets
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © amitgandhinz
// © syntaxgeek
// Thanks to amitgandhinz for the work on this amazing indicator!
// Todo:
//    a) clean up all the functions, variables names and reorganize everything to ease maintenance
//    b) explore retest flags at orb low and mid
//    c) customizable fib, % price targets?

//@version=5
indicator(title="Opening Range Breakout", shorttitle="ORB", overlay=true)

// <inputs>
i_session = input.session("0930-0945", title="Opening Range Time", group="Session")
i_allowPre = input.bool(false, "Enable Premarket Support?", tooltip="Enables support for pre-market based opening range times.", group="Session")
i_timezone = input.string("America/New_York", title="Opening Range Timezone", group="Session")
i_marketType = input.string("24x5", "Market Type", options=["24x5", "24x7"], tooltip="For regular traded markets in nasdaq, s&p, etc use 24x5; for crypto and futures Sunday trading use 24x7")
i_showORBOnlyRTH = input.bool(true, "Show ORB During RTH Only?", group="Display Lines")
i_showORBLabels = input.string("default", "Show ORB labels?", options=["default", "short", "none"], group="Display Lines")
i_showPreviousDayORBs = input.bool(true, "Show ORB ranges on previous days", group="Display Lines")
i_showEntries = input.bool(true, "Show potential ORB Breakouts and Retests (BRB)", group="Display Lines")
i_showORBTargets = input.string("default", title="Price Target Type", options=["default", "fib", "none"], tooltip="Default Price Targets (50%, 100%, etc), Fib Targets (23.6%, 61.8%, 100%, etc) or none.", group="Display Lines")
i_orbTargetsLineWidth = input.int(1, title="Price target line width", minval=1, group="Display Lines")

i_boxBorderSize = input.int(2, title="Box border size", minval=0, group="ORB Box")
i_showShadedBox = input.bool(true, "Shade the ORB Range", group="ORB Box")
i_shadeColor = input.color(color.new(color.teal, 80), "Shaded ORB Range Color", group="ORB Box")

i_alertBreakoutsOnly = input.bool(false, "Alert only on ORB breakouts (not price ticks)", "Alerts")
// </inputs>

// <funcs>
f_tradingDays(_marketType) =>
    switch _marketType
        "24x5" => ":23456"
        "24x7" => ":1234567"
f_drawForSession() => not i_showORBOnlyRTH or (i_showORBOnlyRTH and session.ismarket)
f_timeInRange(_session, _marketType) => not na(time(timeframe.period, _session + f_tradingDays(_marketType), i_timezone)) ? 1 : 0
f_highest(_a, _b) => _a > _b ? _a : _b
f_lowest(_a, _b) => _a > _b ? _b : _a
f_range(_a, _b) => 
    v_high = f_highest(_a, _b)
    v_low = f_lowest(_a, _b)
    v_high - v_low
f_rangeMid(_price1, _price2) =>
    v_priceHigh = f_highest(_price1, _price2)
    v_priceHigh - (f_range(_price1, _price2) / 2)
// </funcs>

showPriceTargets = false
showFibTargets = false
showLabels = false
showShortLabels = false

if (i_showORBLabels == "default")
    showLabels := true
else if (i_showORBLabels == "short")
    showLabels := true
    showShortLabels := true

if (i_showORBTargets == "default")
    showPriceTargets := true
else if (i_showORBTargets == "fib")
    showFibTargets := true

in_session = f_timeInRange(i_session, i_marketType)

// Create variables
var orbTitle = "ORB"
var orbHighPrice = 0.0
var orbLowPrice  = 0.0
var box dailyBox = na
var inBreakout = false
     
bool isToday = false

if year(timenow, i_timezone) == year(time, i_timezone) and month(timenow, i_timezone) == month(time, i_timezone) and dayofmonth(timenow, i_timezone) == dayofmonth(time, i_timezone) and (session.ismarket or (i_allowPre and session.ispremarket))
    isToday := true

is_first = in_session and not in_session[1] and (session.ismarket or (i_allowPre and session.ispremarket))

if is_first
    orbHighPrice := high
    orbLowPrice := low
    inBreakout := false
else
    orbHighPrice := orbHighPrice[1]
    orbLowPrice := orbLowPrice[1]
if high > orbHighPrice and in_session
    orbHighPrice := high
if low < orbLowPrice and in_session
    orbLowPrice := low

bool drawOrbs = i_showPreviousDayORBs or (not i_showPreviousDayORBs and isToday)
drawMidOrb = false
// When a new day start, create a new box for that day.
// Else, during the day, update that day's box.
if (drawOrbs)
    if (i_showShadedBox)
        if is_first
            dailyBox := box.new(left=bar_index, top=orbHighPrice,
                 right=bar_index + 1, bottom=orbLowPrice,
                 border_width=i_boxBorderSize)
        
            // If we don't want the boxes to join, the previous box shouldn't
            // end on the same bar as the new box starts.
            if (not i_showORBOnlyRTH)
                box.set_right(dailyBox[1], bar_index[1])
           
        else if (f_drawForSession())
            box.set_top(dailyBox, orbHighPrice)
            box.set_rightbottom(dailyBox, right=bar_index + 1, bottom=orbLowPrice)
            drawMidOrb := true
    
        box.set_bgcolor(dailyBox, i_shadeColor)
        box.set_border_color(dailyBox, color.teal)

orbRange = f_range(orbHighPrice, orbLowPrice)
orbMidPrice = f_rangeMid(orbHighPrice, orbLowPrice)
orbHighMidPrice = f_rangeMid(orbHighPrice, orbMidPrice)
orbLowMidPrice = f_rangeMid(orbMidPrice, orbLowPrice)

box.set_text(dailyBox, "Range: " + str.tostring(orbRange))
box.set_text_valign(dailyBox, text.align_bottom)
box.set_text_halign(dailyBox, text.align_right)
box.set_text_size(dailyBox, size.normal)

plot(f_drawForSession() and not in_session and drawOrbs ? orbHighPrice : na, style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.green, title="ORB High", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and drawOrbs ? orbLowPrice : na , style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.red, title="ORB Low", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and drawOrbs ? orbHighMidPrice : na, style=plot.style_cross, color=color.gray, title="ORB Mid High", linewidth = 1)
plot(f_drawForSession() and not in_session and drawOrbs ? orbMidPrice : na, style=plot.style_linebr, color=color.white, title="ORB Mid", linewidth = 1)
plot(f_drawForSession() and not in_session and drawOrbs ? orbLowMidPrice : na, style=plot.style_cross, color=color.gray, title="ORB Mid Low", linewidth = 1)

// plot PT for ORB - 50% retracement
plot(f_drawForSession() and not in_session and showPriceTargets and drawOrbs ? orbHighPrice + (orbRange * 1.5): na ,     style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.lime, title="ORB High PT 3", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showPriceTargets and drawOrbs ? orbHighPrice + (orbRange * 1.0): na ,     style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.blue, title="ORB High PT 2", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showPriceTargets and drawOrbs ? orbHighPrice + (orbRange * 0.5) : na, style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.purple, title="ORB High PT 1", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showPriceTargets and drawOrbs ? orbLowPrice + (orbRange * -0.5): na ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.purple, title="ORB Low PT 1", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showPriceTargets and drawOrbs ? orbLowPrice + (orbRange * -1.0): na     ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.blue, title="ORB Low PT 2", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showPriceTargets and drawOrbs ? orbLowPrice + (orbRange * -1.5): na     ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.lime, title="ORB Low PT 3", linewidth=i_orbTargetsLineWidth)

// fib levels
plot(f_drawForSession() and not in_session and showFibTargets ? orbHighPrice + (orbRange * 0.236): na     ,  style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.aqua, title="FIB 23.6%", linewidth=i_orbTargetsLineWidth)
// plot(f_drawForSession() and not in_session and showFibTargets ? orbHighPrice + (orbRange * 0.382): na     ,  style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.blue, title="FIB 38.2%", linewidth=i_orbTargetsLineWidth)
// plot(f_drawForSession() and not in_session and showFibTargets ? orbHighPrice + (orbRange * 0.50): na      ,  style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.lime, title="FIB 50%", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showFibTargets ? orbHighPrice + (orbRange * 0.618): na     ,  style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.yellow, title="FIB 61.8%", linewidth=i_orbTargetsLineWidth)
// plot(f_drawForSession() and not in_session and showFibTargets ? orbHighPrice + (orbRange * 0.786): na     ,  style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.orange, title="FIB 78.6%", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showFibTargets ? orbHighPrice + (orbRange * 1): na         ,  style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.red, title="FIB 100%", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showFibTargets ? orbHighPrice + (orbRange * 1.382): na         ,  style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.fuchsia, title="FIB 138.2%", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showFibTargets ? orbHighPrice + (orbRange * 1.618): na         ,  style=plot.style_linebr, color=orbHighPrice[1] != orbHighPrice ? na : color.purple, title="FIB 161.8%", linewidth=i_orbTargetsLineWidth)

plot(f_drawForSession() and not in_session and showFibTargets ? orbLowPrice + (orbRange * -0.236): na     ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.aqua, title="FIB -23.6%", linewidth=i_orbTargetsLineWidth)
// plot(f_drawForSession() and not in_session and showFibTargets ? orbLowPrice + (orbRange * -0.382): na     ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.blue, title="FIB -38.2%", linewidth=i_orbTargetsLineWidth)
// plot(f_drawForSession() and not in_session and showFibTargets ? orbLowPrice + (orbRange * -0.50): na      ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.lime, title="FIB -50%", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showFibTargets ? orbLowPrice + (orbRange * -0.618): na     ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.yellow, title="FIB -61.8%", linewidth=i_orbTargetsLineWidth)
// plot(f_drawForSession() and not in_session and showFibTargets ? orbLowPrice + (orbRange * -0.786): na     ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.orange, title="FIB -78.6%", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showFibTargets ? orbLowPrice + (orbRange * -1): na         ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.red, title="FIB -100%", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showFibTargets ? orbLowPrice + (orbRange * -1.382): na         ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.fuchsia, title="FIB -138.2%", linewidth=i_orbTargetsLineWidth)
plot(f_drawForSession() and not in_session and showFibTargets ? orbLowPrice + (orbRange * -1.618): na     ,  style=plot.style_linebr, color=orbLowPrice[1] != orbLowPrice ? na : color.purple, title="FIB -161.8%", linewidth=i_orbTargetsLineWidth)

drawPriceTargetLabel(fromPrice, level, name, col) => 
    if showLabels
        finalTitle = not showShortLabels ? orbTitle + " " + name : name
        var pt = label.new(bar_index, fromPrice + (orbRange * level), style=label.style_label_lower_left, text=finalTitle, color=col, textcolor=color.white)

        if (f_drawForSession())
            label.set_xy(pt, bar_index + 2, fromPrice + (orbRange * level))

drawPriceTargetLabel(orbHighPrice, 0, "HIGH", color.green)
drawPriceTargetLabel(orbLowPrice, 0, "LOW", color.red)
drawPriceTargetLabel(orbMidPrice, 0, "MID", color.orange)

if (not in_session and showPriceTargets and isToday and session.ismarket and drawOrbs )
    drawPriceTargetLabel(orbHighPrice, 1.5, "PT 150%", color.lime)
    drawPriceTargetLabel(orbHighPrice, 1.0, "PT 100%", color.blue)
    drawPriceTargetLabel(orbHighPrice, 0.5, "PT 50%", color.purple)
    drawPriceTargetLabel(orbLowPrice, -0.5, "PT 50%", color.purple)
    drawPriceTargetLabel(orbLowPrice, -1.0, "PT 100%", color.blue)
    drawPriceTargetLabel(orbLowPrice, -1.5, "PT 150%", color.lime)

if (not in_session and showFibTargets and isToday and session.ismarket and drawOrbs )
    drawPriceTargetLabel(orbHighPrice, 0.236, "FIB 23.6%", color.aqua)
    // drawPriceTargetLabel(orbHighPrice, 0.382, "FIB 38.2%", color.blue)
    // drawPriceTargetLabel(orbHighPrice, 0.50, "FIB 50%", color.lime)
    drawPriceTargetLabel(orbHighPrice, 0.618, "FIB 61.8%", color.yellow)
    // drawPriceTargetLabel(orbHighPrice, 0.786, "FIB 78.6%", color.orange)
    drawPriceTargetLabel(orbHighPrice, 1, "FIB 100%", color.red)
    drawPriceTargetLabel(orbHighPrice, 1.382, "FIB 138.2%", color.fuchsia)
    drawPriceTargetLabel(orbHighPrice, 1.618, "FIB 161.8%", color.purple)
    
    drawPriceTargetLabel(orbLowPrice, -0.236, "FIB -23.6%", color.aqua)
    // drawPriceTargetLabel(orbLowPrice, -0.382, "FIB -38.2%", color.blue)
    // drawPriceTargetLabel(orbLowPrice, -0.50, "FIB -50%", color.lime)
    drawPriceTargetLabel(orbLowPrice, -0.618, "FIB -61.8%", color.yellow)
    // drawPriceTargetLabel(orbLowPrice, -0.786, "FIB -78.6%", color.orange)
    drawPriceTargetLabel(orbLowPrice, -1, "FIB -100%", color.red)
    drawPriceTargetLabel(orbLowPrice, -1.382, "FIB -138.2%", color.fuchsia)
    drawPriceTargetLabel(orbLowPrice, -1.618, "FIB -161.8%", color.purple)

// candle crossed orb level, next candle stayed above it, current candle also stayed above it, and had volume in there
//volumeSMA = ta.sma(volume, 20)
//bool volumeSpiked = volume[2] > volumeSMA[2] or volume[1] > volumeSMA[1] or volume > volumeSMA
bool highCrossBO = (low[2] < orbHighPrice and close[2] > orbHighPrice and low[1] > orbHighPrice and close[1] > orbHighPrice and close > low[1] and  low > orbHighPrice)  and session.ismarket
bool lowCrossBO =  (high[2] > orbLowPrice and close[2] < orbLowPrice and high[1] < orbLowPrice and close[1] < orbLowPrice   and close < high[1] and high < orbLowPrice)  and session.ismarket
bool highCross = (not i_alertBreakoutsOnly and ta.cross(close, orbHighPrice)) or (i_alertBreakoutsOnly and highCrossBO) 
bool lowCross =  (not i_alertBreakoutsOnly and ta.cross(close, orbLowPrice)) or (i_alertBreakoutsOnly and lowCrossBO) 
bool isRetestOrbHigh = close[1] > orbHighPrice and low <= orbHighPrice and close >= orbHighPrice
bool isRetestOrbLow  = close[1] < orbLowPrice and high >= orbLowPrice and close <= orbLowPrice
bool failedRetest = inBreakout and ((close[1] > orbHighPrice and close < orbHighPrice) or (close[1] < orbLowPrice and close > orbLowPrice))

// show entries
if (i_showEntries and session.ismarket)
    if (highCrossBO)
        lbl = label.new(bar_index, na)
        label.set_color(lbl, color.green)
        label.set_textcolor(lbl, color.green)
        label.set_text(lbl, "Breakout\n Wait for Retest")
        label.set_yloc( lbl,yloc.abovebar)
        label.set_style(lbl, label.style_triangledown)
        label.set_size(lbl, size.tiny)
        
        inBreakout := true
        
    if (lowCrossBO)
        lbl = label.new(bar_index, na)
        label.set_color(lbl, color.green)
        label.set_textcolor(lbl, color.green)
        label.set_text(lbl, "Breakout,\n Wait for Retest")
        label.set_yloc( lbl,yloc.belowbar)
        label.set_style(lbl, label.style_triangleup)
        label.set_size(lbl, size.tiny)
        
        inBreakout := true
        
    if inBreakout and (isRetestOrbHigh or isRetestOrbLow)
        // we have our breakout and retest
        lbl = label.new(bar_index, na)
        label.set_color(lbl, color.green)
        label.set_textcolor(lbl, color.white)
        label.set_text(lbl, "Retest")
        label.set_yloc( lbl,yloc.abovebar)
        label.set_style(lbl, label.style_label_down)
        label.set_size(lbl, size.tiny)
        
        inBreakout := false
        
    if inBreakout and failedRetest
        // we have failed the retest
        lbl = label.new(bar_index, na)
        label.set_color(lbl, color.red)
        label.set_textcolor(lbl, color.white)
        label.set_text(lbl, "Failed Retest")
        label.set_yloc( lbl,yloc.abovebar)
        label.set_style(lbl, label.style_label_down)
        label.set_size(lbl, size.tiny)
        
        inBreakout := false
        

// show alerts

alertcondition(not in_session and (session.ismarket or (i_allowPre and session.ispremarket)) and (highCross or lowCross), title="ORB Level Cross", message="Price crossing ORB Level")
alertcondition(not in_session and (session.ismarket or (i_allowPre and session.ispremarket)) and i_alertBreakoutsOnly and (highCrossBO or lowCrossBO), title="ORB Breakout", message="Price Breaking out of ORB Level, Look for Retest")

if (not in_session and isToday and (session.ismarket or (i_allowPre and session.ispremarket)))
    if (not i_alertBreakoutsOnly)
        if highCross
            alert("Price crossing ORB High Level", alert.freq_once_per_bar)
            
        if lowCross
            alert("Price crossing ORB Low Level", alert.freq_once_per_bar)
    
    if (i_alertBreakoutsOnly)
        if highCrossBO
            alert("Price breaking out of ORB High Level, Look for Retest", alert.freq_once_per_bar)
            
        if lowCrossBO
            alert("Price breaking out of ORB Low Level, Look for Retest", alert.freq_once_per_bar)