Unrecovered Imbalanced Zone with PVRSA
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// Credit to frozon, infernixx, Traders Reality and creengrack for ideas towards how to put this indicator togther
//@version=5

indicator(title='Unrecovered Imbalanced Zone with PVSRA', shorttitle="Imbalanced PVSRA", overlay=true , max_boxes_count=500, max_lines_count=500)


// Release Notes 10/Aug/2022
// 1. Added the ability to mark the entire body of the candle with an imbalance. 
// 2. Added an Option to also include the wicks of the imbalanced candles. 
// 3. Increased the default opacity of the midline colour to be more visible
// 4. Fixed issue with repainting candles when overriding the colume profile with another symbol.
// 5. Introduced more grannualr alert for PVSRA candles with imbalances
// 6. Code improvements for stability

// Release Notes 29/Dec/2022
// 1. Renamed settings to be more intuitive (Penetration Percentage to Invalidation Percentage)
// 2. Improved Symbol Override Functionality to Automatically Override with the BINANCE Exchange Equivalent - This helps when switching between currencies
// 3. Updated logic to display Imbalances on Previous Candle in real time. 
// 4. Fixed Bug surrounding all alerts. These are now working both in realtime and on candle closes. 
// 5. Introduced Flag to only show Confirmed Imbalances (i.e. on Candle Close)

// Release Notes 29/Dec/2022 Part 2
// Introduced functionality to reduce the size of the Imbalanced zones if it is partially recovered. (Only displaying the unrecovered poortion of the candle/imbalanced zone)

// Release Notes 12/Jan/2023 
// 1. Update/Bug Fix for Calculation of the PVSRA. 


// ###########################################################
// #####
// #####         Indicator Settings
// #####
// ###########################################################
var showImbalancedZones = input.bool(defval=true, title="Show Imbalanced Zones", group="Imbalanced Zone Settings")
var showOnHigherTimeFrames = input.bool(defval=true, title="Higher Timeframes Only" , tooltip="Only Show on 1hr timeframe or above", group="Imbalanced Zone Settings")
var showNormalVolImbalance = input.bool(title='Show Imbalances with Normal Volume', defval=false, group="Imbalanced Zone Settings")
var markEntireCandle = input.bool(title='Mark Candle Body', defval=false, group="Imbalanced Zone Settings", tooltip="If checked the candle body with an imbalance is marked, If unchecked only the area of imbalance is displayed.")
var includeWicks = input.bool(title=' - Include Wicks when Marking Candle Body', defval=false, group="Imbalanced Zone Settings", tooltip="If checked the wicks are also included when marking the entire candle. Note: Must also have the 'Mark Entire Candle Body' option checked.")
var extendBoxes = input.bool(defval=false, title="Extend Zones", group="Imbalanced Zone Settings")
var onlyDisplayUnrecoveredPortion = input.bool(defval=false, title="Only Display the Unrecovered Portion of the Candle", group="Imbalanced Zone Settings", tooltip="When set will only show the unrecovered zone of the candle.")

var confirmedImbalancesOnly = input.bool(defval=false, title="Only Show Confirmed Imbalances (ie Candle Close)", group="Imbalanced Zone Settings")
var invalidationRatioInput = input.int(defval=75, minval=0, maxval=100, step=1, title="Invalidation Percentage", tooltip="The percentage of the candle, that price needs recover, which invalidates the zone", group="Imbalanced Zone Settings")
var invalidationRatio = invalidationRatioInput/100
var overideCandleColours = input.bool(title='Override Candles with PVSRA Colour', defval=true, tooltip="Indicator must be dragged to the top of the Object Tree to display correctly", group="Candle Colours")

var Bull200CandleColor = input.color(color.new(color.lime, 0), title="200% Volume", group="Candle Colours", inline="1")  
var Bear200CandleColor = input.color(color.new(color.red, 0), title="", group="Candle Colours", inline = "1") 

var Bull150CandleColor = input.color(color.new(color.blue, 0), title="150% Volume", group="Candle Colours", inline="2")   
var Bear150CandleColor = input.color(color.new(color.fuchsia, 0), title="", group="Candle Colours", inline="2")  

var BullNormCandleColor = input.color(color.new(color.white, 0), title="Norm Volume", group="Candle Colours", inline="3")
var BearNormCandleColor = input.color(color.new(color.gray, 0), title="", group="Candle Colours", inline="3")

var bool override_imnt = input.bool(defval=false, title="Overide Symbol", group="Alternate Symbol Overide Settings", inline="0")
var string pvsra_sym = input.symbol(title="", defval="BINANCE:BTCUSDTPERP", group="Alternate Symbol Overide Settings", inline="0", tooltip="Note the selected symbol will be ignored if the 'Automatically Derive' option below is selected")

var bool automaticallyDetermineOverideTicker = input.bool(defval=true, title="Automatically Derive the equivalent BINANCE PERP pair to overide volume", group="Alternate Symbol Overide Settings", tooltip="Must have the 'Overide Symbol' option above checked. Note This may introduce an error in the study if it cant find the pair on Binance.")

var Bull200ImbalanceColor = input.color(color.new(color.lime, 85), title="200% Volume", group="Imbalance Background Colours", inline="1")  
var Bear200ImbalanceColor = input.color(color.new(color.red, 85), title="", group="Imbalance Background Colours", inline = "1")  

var Bull150ImbalanceColor = input.color(color.new(color.blue, 85), title="150% Volume", group="Imbalance Background Colours", inline="2")
var Bear150ImbalanceColor = input.color(color.new(color.fuchsia, 85), title="", group="Imbalance Background Colours", inline="2")

var BullNormImbalanceColor = input.color(color.new(color.white, 85), title="Norm Volume", group="Imbalance Background Colours", inline="3")
var BearNormImbalanceColor = input.color(color.new(color.gray, 85), title="", group="Imbalance Background Colours", inline="3")

var bullNormMidLineColor = input.color(color.new(color.green,30) , title="Mid Line Color", group="Imbalance Background Colours", inline="4")
var bearNormMidLineColor = input.color(color.new(color.red,30) , title="", group="Imbalance Background Colours", inline="4")

var useOriginalCalc = input.bool(defval=false, title="Use Original PVSRA Calc", group="Miscellaneous", tooltip="Original Calc had a bug, This option will be phased out in the near future") 

var color candleColor = na
var color imbalanceColor = na
var color imbalancedLineColor = na

var color NO_COLOR = na


var bool chartIs60MinOrMore = false 

if showOnHigherTimeFrames
    if (timeframe.isintraday and timeframe.multiplier >=60 ) or timeframe.isdaily or timeframe.ismonthly or timeframe.isweekly
        chartIs60MinOrMore := true
else 
    chartIs60MinOrMore := true        


// ###########################################################
// #####
// #####         Functions/Utils
// #####
// ###########################################################

f_print(_text) =>
    var table _t = table.new(position.bottom_right, 1, 1)
    table.cell(_t, 0, 0, _text, bgcolor = color.yellow) 

// ###########################################################
// #####
// #####         Core Logic
// #####
// ###########################################################

// Logic to reference another Instruments Volume Profile
var string binancePerpSymbol = na
binancePerpSymbol := "BINANCE:"+syminfo.basecurrency + syminfo.currency+"PERP"

pvsra_imnt(sresolution,sseries) => request.security(override_imnt ? automaticallyDetermineOverideTicker? binancePerpSymbol :pvsra_sym : syminfo.tickerid ,sresolution,sseries, barmerge.gaps_off,barmerge.lookahead_off)
volume_imnt = override_imnt == true? pvsra_imnt("",volume): volume
high_imnt = override_imnt == true? pvsra_imnt("",high): high
low_imnt = override_imnt == true? pvsra_imnt("",low): low
close_imnt = override_imnt == true? pvsra_imnt("",close): close
open_imnt = override_imnt == true? pvsra_imnt("",open): open

var float highest10_hl_weightedVolume = na
var float hl_weightedVolume = na 
var float av = na 

//Update to Code to reflect Last 10 Candles 

if useOriginalCalc
    av := ta.sma(volume_imnt, 10)//sum_2 = math.sum(volume, 10)
    hl_weightedVolume := volume_imnt * (high_imnt - low_imnt) 
    highest10_hl_weightedVolume := ta.highest(hl_weightedVolume, 10)
else 
    av := ta.sma(volume_imnt[1], 10)//sum_2 = math.sum(volume, 10)
    hl_weightedVolume := volume_imnt * (high_imnt - low_imnt) 
    highest10_hl_weightedVolume := ta.highest(hl_weightedVolume[1], 10)

imnt_override_pvsra_calc_part2 = volume_imnt >= av * 1.5 ? 2 : 0
va = volume_imnt >= av * 2 or hl_weightedVolume >= highest10_hl_weightedVolume ? 1 : imnt_override_pvsra_calc_part2


// Bull or bear Candle Colors
isBull =  close_imnt > open_imnt


var bool is200Bull = na
var bool is150Bull = na
var bool is100Bull = na
var bool is200Bear = na
var bool is150Bear = na
var bool is100Bear = na

is200Bull := false
is150Bull := false
is100Bull := false
is200Bear := false
is150Bear := false
is100Bear := false

if isBull
    if va == 1
        candleColor := Bull200CandleColor
        imbalanceColor := Bull200ImbalanceColor
        imbalancedLineColor := bullNormMidLineColor
        is200Bull := true
    else 
        if va == 2 
            candleColor := Bull150CandleColor
            imbalanceColor := Bull150ImbalanceColor
            imbalancedLineColor := bullNormMidLineColor
            is150Bull := true
        else 
            is100Bull := true
            if showNormalVolImbalance
                candleColor := BullNormCandleColor
                imbalanceColor := BullNormImbalanceColor
                imbalancedLineColor := bullNormMidLineColor
            else 
                candleColor := BullNormCandleColor
                imbalanceColor := na
                imbalancedLineColor := na
else
    if va == 1
        candleColor := Bear200CandleColor 
        imbalanceColor := Bear200ImbalanceColor
        imbalancedLineColor := bearNormMidLineColor
        is200Bear := true
    else
        if va == 2
            candleColor := Bear150CandleColor
            imbalanceColor := Bear150ImbalanceColor
            imbalancedLineColor := bearNormMidLineColor
            is150Bear := true
        else 
            is100Bear := true
            if showNormalVolImbalance
                candleColor := BearNormCandleColor
                imbalanceColor := BearNormImbalanceColor
                imbalancedLineColor := bearNormMidLineColor
            else 
                candleColor := BearNormCandleColor
                imbalanceColor := na
                imbalancedLineColor := na
 
barcolor(overideCandleColours ? candleColor : NO_COLOR)
plotcandle(open, high, low, close, color=(overideCandleColours ? candleColor : NO_COLOR), wickcolor=(overideCandleColours ? candleColor : NO_COLOR), bordercolor=(overideCandleColours ? candleColor : NO_COLOR), display = display.all)


var box[] imbalancedDownBoxes = array.new_box()
var box[] imbalancedUpBoxes = array.new_box()
var line[] imbalancedDownMidLines = array.new_line()
var line[] imbalancedUpMidLines = array.new_line()


extension = extend.none
if extendBoxes
    extension := extend.right

var float midpoint = na

if high[3] < low[1] and showImbalancedZones and chartIs60MinOrMore and confirmedImbalancesOnly
    if markEntireCandle
        if includeWicks
            array.push(imbalancedDownBoxes, box.new(left=bar_index - 2, bottom=math.min(high[2],low[2]), right=bar_index + 20, top=math.max(high[2],low[2]), bgcolor=imbalanceColor[2], border_color=imbalanceColor[2], extend=extension))
            midPoint = (math.max(high[2],low[2])-math.min(high[2],low[2]))/2+math.min(low[2],low[2])
            array.push(imbalancedDownMidLines, line.new(bar_index - 2, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[2]))
        else 
            array.push(imbalancedDownBoxes, box.new(left=bar_index - 2, bottom=math.min(open[2],close[2]), right=bar_index + 20, top=math.max(open[2],close[2]), bgcolor=imbalanceColor[2], border_color=imbalanceColor[2], extend=extension))
            midPoint = (math.max(open[2],close[2])-math.min(open[2],close[2]))/2+math.min(open[2],close[2])
            array.push(imbalancedDownMidLines, line.new(bar_index - 2, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[2]))
    else
        array.push(imbalancedDownBoxes, box.new(left=bar_index - 2, bottom=high[3], right=bar_index + 20, top=low[1], bgcolor=imbalanceColor[2], border_color=imbalanceColor[2], extend=extension))
        midPoint = (high[3]-low[1])/2+low[1]
        array.push(imbalancedDownMidLines, line.new(bar_index - 2, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[2]))




if high[2] < low and showImbalancedZones and chartIs60MinOrMore and not confirmedImbalancesOnly
    if markEntireCandle
        if includeWicks
            array.push(imbalancedDownBoxes, box.new(left=bar_index - 1, bottom=math.min(high[1],low[1]), right=bar_index + 20, top=math.max(high[1],low[1]), bgcolor=imbalanceColor[1], border_color=imbalanceColor[1], extend=extension))
            midPoint = (math.max(high[1],low[1])-math.min(high[1],low[1]))/2+math.min(low[1],low[1])
            array.push(imbalancedDownMidLines, line.new(bar_index - 1, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[1]))
        else 
            array.push(imbalancedDownBoxes, box.new(left=bar_index - 1, bottom=math.min(open[1],close[1]), right=bar_index + 20, top=math.max(open[1],close[1]), bgcolor=imbalanceColor[1], border_color=imbalanceColor[1], extend=extension))
            midPoint = (math.max(open[1],close[1])-math.min(open[1],close[1]))/2+math.min(open[1],close[1])
            array.push(imbalancedDownMidLines, line.new(bar_index - 1, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[1]))
    else
        array.push(imbalancedDownBoxes, box.new(left=bar_index - 1, bottom=high[2], right=bar_index + 20, top=low, bgcolor=imbalanceColor[1], border_color=imbalanceColor[1], extend=extension))
        midPoint = (high[2]-low)/2+low
        array.push(imbalancedDownMidLines, line.new(bar_index - 1, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[1]))



if low[3] > high[1] and showImbalancedZones and chartIs60MinOrMore and confirmedImbalancesOnly
    if markEntireCandle
        if includeWicks
            array.push(imbalancedUpBoxes, box.new(left=bar_index - 2, top=math.max(high[2],low[2]), right=bar_index + 20, bottom=math.min(high[2],low[2]), bgcolor=imbalanceColor[2], border_color=imbalanceColor[2], extend=extension))
            midPoint = (math.max(high[2],low[2])-math.min(high[2],low[2]))/2+math.min(low[2],low[2])
            array.push(imbalancedUpMidLines, line.new(bar_index - 2, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[2]))
        else 
            array.push(imbalancedUpBoxes, box.new(left=bar_index - 2, top=math.max(open[2],close[2]), right=bar_index + 20, bottom=math.min(open[2],close[2]), bgcolor=imbalanceColor[2], border_color=imbalanceColor[2], extend=extension))
            midPoint = (math.max(open[2],close[2])-math.min(open[2],close[2]))/2+math.min(open[2],close[2])
            array.push(imbalancedUpMidLines, line.new(bar_index - 2, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[2]))
    else
        array.push(imbalancedUpBoxes, box.new(left=bar_index - 2, top=low[3], right=bar_index + 20, bottom=high[1], bgcolor=imbalanceColor[2], border_color=imbalanceColor[2], extend=extension))
        midPoint = (high[1]-low[3])/2+low[3]    
        array.push(imbalancedUpMidLines, line.new(bar_index - 2, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[2]))

if low[2] > high and showImbalancedZones and chartIs60MinOrMore and not confirmedImbalancesOnly
    if markEntireCandle
        if includeWicks
            array.push(imbalancedUpBoxes, box.new(left=bar_index - 1, top=math.max(high[1],low[1]), right=bar_index + 20, bottom=math.min(high[1],low[1]), bgcolor=imbalanceColor[1], border_color=imbalanceColor[1], extend=extension))
            midPoint = (math.max(high[1],low[1])-math.min(high[1],low[1]))/2+math.min(low[1],low[1])
            array.push(imbalancedUpMidLines, line.new(bar_index - 1, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[1]))
        else 
            array.push(imbalancedUpBoxes, box.new(left=bar_index - 1, top=math.max(open[1],close[1]), right=bar_index + 20, bottom=math.min(open[1],close[1]), bgcolor=imbalanceColor[1], border_color=imbalanceColor[1], extend=extension))
            midPoint = (math.max(open[1],close[1])-math.min(open[1],close[1]))/2+math.min(open[1],close[1])
            array.push(imbalancedUpMidLines, line.new(bar_index - 1, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[1]))
    else
        array.push(imbalancedUpBoxes, box.new(left=bar_index - 1, top=low[2], right=bar_index + 20, bottom=high, bgcolor=imbalanceColor[1], border_color=imbalanceColor[1], extend=extension)) 
        midPoint = (high-low[2])/2+low[2]    
        array.push(imbalancedUpMidLines, line.new(bar_index - 1, midPoint, bar_index+20, midPoint, style=line.style_dotted, extend=extension, color=imbalancedLineColor[1]))



if array.size(imbalancedUpBoxes) > 0
    for i = array.size(imbalancedUpBoxes) - 1 to 0 by 1
        imbalancedBox = array.get(imbalancedUpBoxes, i)
        top = box.get_top(imbalancedBox)
        bottom = box.get_bottom(imbalancedBox)
        invalidationLimit = (top - bottom) * invalidationRatio
        box.set_right(imbalancedBox, bar_index + 30)

        midLine = array.get(imbalancedUpMidLines, i)
        line.set_x2(midLine, bar_index + 30)


        if high >= bottom + invalidationLimit
            box.delete(imbalancedBox)
            array.remove(imbalancedUpBoxes, i)
            line.delete(midLine)
            array.remove(imbalancedUpMidLines, i)

        if high > box.get_bottom(imbalancedBox) and high < box.get_top(imbalancedBox) and onlyDisplayUnrecoveredPortion
            box.set_bottom(imbalancedBox, high)

if array.size(imbalancedDownBoxes) > 0
    for i = array.size(imbalancedDownBoxes) - 1 to 0 by 1
        imbalancedBox = array.get(imbalancedDownBoxes, i)
        top = box.get_top(imbalancedBox)
        bottom = box.get_bottom(imbalancedBox)
        invalidationLimit = (top - bottom) * invalidationRatio
        box.set_right(imbalancedBox, bar_index + 30)

        midLine = array.get(imbalancedDownMidLines, i)
        line.set_x2(midLine, bar_index + 30)

        if low <= top - invalidationLimit
            box.delete(imbalancedBox)
            array.remove(imbalancedDownBoxes, i)
            line.delete(midLine)
            array.remove(imbalancedDownMidLines, i)

        if low < box.get_top(imbalancedBox) and low > box.get_bottom(imbalancedBox) and onlyDisplayUnrecoveredPortion
            box.set_top(imbalancedBox, low)


// ####################################################################
// #####
// #####         Alert  Logic
// #####         Note: Alerts are in real time. To set an alert for a 
// #####         Confirmed Imbalance you must set it for Candle Close
// #####
// ####################################################################

var bool isImbalanced = na 

// isImbalanced := (low[3] > high[1]) or (high[3] < low[1])
isImbalanced := (low[2] > high) or (high[2] < low)

alertcondition(isImbalanced, title="Imbalanced Candle (Any)", message="New Imbalanced Candle")
alertcondition(isImbalanced and (is200Bull[1] or is150Bull[1]) , title="Imbalanced PVSRA Bullish Candle", message="New Imbalanced PVSRA Bullish Candle")
alertcondition(isImbalanced and is200Bull[1] , title="Imbalanced PVSRA 200% Bullish Candle", message="New Imbalanced PVSRA 200% Bullish Candle")
alertcondition(isImbalanced and is150Bull[1] , title="Imbalanced PVSRA 150% Bullish Candle", message="New Imbalanced PVSRA 150% Bullish Candle")
alertcondition(isImbalanced and is100Bull[1] , title="Imbalanced Bullish Candle (Avg Vol)", message="New Imbalanced Bullish Candle (Avg Vol)")
alertcondition(isImbalanced and (is200Bear[1] or is150Bear[1]) , title="Imbalanced PVSRA Bearish Candle", message="New Imbalanced PVSRA Bearish Candle")
alertcondition(isImbalanced and is200Bear[1] , title="Imbalanced PVSRA 200% Bearish Candle", message="New Imbalanced PVSRA 200% Bearish Candle")
alertcondition(isImbalanced and is150Bear[1] , title="Imbalanced PVSRA 150% Bearish Candle", message="New Imbalanced PVSRA 150% Bearish Candle")
alertcondition(isImbalanced and is100Bear[1] , title="Imbalanced Bearish Candle (Avg Vol)", message="New Imbalanced Bearish Candle (Avg Vol)")



//f_print("isImbalanced = " + str.tostring(isImbalanced) + " : " +  "is200Bull[1] = " + str.tostring(is200Bull[1])+ " : " +  "is150Bull[1] = " + str.tostring(is150Bull[1]) + " : " +  "is100Bull[1] = " + str.tostring(is100Bull[1]) ) 
//f_print("isImbalanced = " + str.tostring(isImbalanced) + " : " +  "is200Bull[2] = " + str.tostring(is200Bull[2])+ " : " +  "is150Bull[2] = " + str.tostring(is150Bull[2]) + " : " +  "is100Bull[2] = " + str.tostring(is100Bull[2])       + " : " +  "is200Bear[2] = " + str.tostring(is200Bear[2])+ " : " +  "is150Bear[2] = " + str.tostring(is150Bear[2]) + " : " +  "is100Bear[2] = " + str.tostring(is100Bear[2])) 
//f_print("isImbalanced = " + str.tostring(isImbalanced) + " : " +  "is200Bull[1] = " + str.tostring(is200Bull[1])+ " : " +  "is150Bull[1] = " + str.tostring(is150Bull[1]) + " : " +  "is100Bull[1] = " + str.tostring(is100Bull[1])       + " : " +  "is200Bear[1] = " + str.tostring(is200Bear[1])+ " : " +  "is150Bear[1] = " + str.tostring(is150Bear[1]) + " : " +  "is100Bear[1] = " + str.tostring(is100Bear[1])) 
//f_print(" (is200Bull[1] or is150Bull[1]) = " + str.tostring( (is200Bull[1] or is150Bull[1]) )) //
