Volume Forecast
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © gigi_duru

//@version=5
indicator("VolumeForecast", overlay  = false, max_lines_count = 500, max_labels_count = 500, max_boxes_count = 500)

measureMode = input.string("High/low", options = ["Open/close", "High/low", "Push", "Volume"])
startDate = input.time(timestamp("2023-01-01 00:00+03:00"))
enableSession1 = input.bool(true, "", inline = "session1")
session1 = input.session("0200-0659", inline = "session1")
var sessionFrom1 = str.split(session1, "-").get(0)
var sessionTo1 = str.split(session1, "-").get(1)

enableSession2 = input.bool(true, "", inline = "session2")
session2 = input.session("0900-1159", inline = "session2")
var sessionFrom2 = str.split(session2, "-").get(0)
var sessionTo2 = str.split(session2, "-").get(1)

enableSession3 = input.bool(true, "", inline = "session3")
session3 = input.session("1400-1859", inline = "session3")
var sessionFrom3 = str.split(session3, "-").get(0)
var sessionTo3 = str.split(session3, "-").get(1)

enableSession4 = input.bool(true, "", inline = "session4")
session4 = input.session("2000-2259", inline = "session4")
var sessionFrom4 = str.split(session4, "-").get(0)
var sessionTo4 = str.split(session4, "-").get(1)

timezone = input.string("GMT+2")

forecastBars = input.int(100, minval = 0, inline = "forecast")
accountforDayOfWeek = input(false, inline = "forecast")
deviationLookback = input(100, inline = "deviation")
deviationMin = input(0.5, title = "Min", inline = "deviation")
deviationMax = input(2.0, title = "Max", inline = "deviation")
showMinValues = input.bool(false, inline = "display")
showMaxValues = input.bool(false, inline = "display")
showRealTimeVolume = input(false)
showAverageVolume = input(true)
measurementMultiplier = input(0.1)

statsGroupByFormat = accountforDayOfWeek ? "E-HH:mm" : "HH:mm"

var int timeframeMs = time_close - time
var tbl = table.new(position.bottom_right, 2, 10)
tbl.cell(0, 0, "Stats group size:", text_color = color.white)
tbl.cell(0, 1, "Deviation (last " + str.tostring(deviationLookback) + " bars):", text_color = color.white)

openInSession(sess) => 
    na(time(timeframe.period, sess + ":1234567", timezone)) == false

toHourString(timestamp) =>
    str.format_time(timestamp, "HHmm", timezone)

var int openTimeSession1 = na
var int openTimeSession2 = na
var int openTimeSession3 = na
var int openTimeSession4 = na

var int closeTimeSession1 = na
var int closeTimeSession2 = na
var int closeTimeSession3 = na
var int closeTimeSession4 = na

openInSession1 = openInSession(session1) and enableSession1
openInSession2 = openInSession(session2) and enableSession2
openInSession3 = openInSession(session3) and enableSession3
openInSession4 = openInSession(session4) and enableSession4

isOpenInSession = openInSession1 or openInSession2 or openInSession3 or openInSession4

type Volumes
    array<float> Volumes

var deviations = array.new_float()
while(deviations.size() > deviationLookback)
    deviations.remove(0)

avgDeviation = deviations.size() > 0 ? deviations.avg() : 1.0
tbl.cell(1, 1, str.tostring(avgDeviation, "0.00"), text_color = color.white)

var float sessionOpen1 = na
var float sessionOpen2 = na
var float sessionOpen3 = na
var float sessionOpen4 = na

var float push = na

if(openInSession1)
    if(str.tonumber(toHourString(time_close)) < str.tonumber(sessionTo1))
        if(na(sessionOpen1))
            sessionOpen1 := open
            openTimeSession1 := time
            closeTimeSession1 := na
    else
        push := close - sessionOpen1
        closeTimeSession1 := time_close
else
    sessionOpen1 := na
    openTimeSession1 := na
    closeTimeSession1 := na
    if(not isOpenInSession)
        push := na

if(openInSession2)
    if(str.tonumber(toHourString(time_close)) < str.tonumber(sessionTo2))
        if(na(sessionOpen2))
            sessionOpen2 := open
            openTimeSession2 := time
            closeTimeSession2 := na
    else
        push := close - sessionOpen2
        closeTimeSession2 := time_close
else
    sessionOpen2 := na
    openTimeSession2 := na
    closeTimeSession2 := na
    if(not isOpenInSession)
        push := na

if(openInSession3)
    if(str.tonumber(toHourString(time_close)) < str.tonumber(sessionTo3))
        if(na(sessionOpen3))
            sessionOpen3 := open
            openTimeSession3 := time
            closeTimeSession3 := na
    else
        push := close - sessionOpen3
        closeTimeSession3 := time_close
else
    sessionOpen3 := na
    openTimeSession3 := na
    closeTimeSession3 := na
    if(not isOpenInSession)
        push := na

if(openInSession4)
    if(str.tonumber(toHourString(time_close)) < str.tonumber(sessionTo4))
        if(na(sessionOpen4))
            sessionOpen4 := open
            openTimeSession4 := time
            closeTimeSession4 := na
    else
        push := close - sessionOpen4
        closeTimeSession4 := time_close
else
    sessionOpen4 := na
    openTimeSession4 := na
    closeTimeSession4 := na
    if(not isOpenInSession)
        push := na

push := math.abs(push) / syminfo.mintick

var float maxMeasurement = na

measurement = measureMode == "" ? na
 : measureMode == "High/low" ? (high - low) / syminfo.mintick
 : measureMode == "Open/close" ? math.abs(close - open) / syminfo.mintick
 : measureMode == "Volume" ? volume
 : measureMode == "Push" ? push
 : na

measurement := math.round(measurement * measurementMultiplier, 1)

if(openTimeSession1 and closeTimeSession1)
    box.new(openTimeSession1, measurement, closeTimeSession1, 0, bgcolor =  color.rgb(33, 149, 243, 81), xloc = xloc.bar_time, border_color = na)
if(openTimeSession2 and closeTimeSession2)
    box.new(openTimeSession2, measurement, closeTimeSession2, 0, bgcolor =  color.rgb(33, 249, 243, 82), xloc = xloc.bar_time, border_color = na)
if(openTimeSession3 and closeTimeSession3)
    box.new(openTimeSession3, measurement, closeTimeSession3, 0, bgcolor =  color.rgb(255, 33, 162, 83), xloc = xloc.bar_time, border_color = na)
if(openTimeSession4 and closeTimeSession4)
    box.new(openTimeSession4, measurement, closeTimeSession4, 0, bgcolor =  color.rgb(255, 233, 33, 84), xloc = xloc.bar_time, border_color = na)

var volumes = map.new<string, Volumes>()
currentTime = str.format_time(time, statsGroupByFormat)

var futureBars = array.new_box()
if(futureBars.size() == 0 and forecastBars > 0)
    for i = 1 to forecastBars
        futureBars.push(box.new(time, 0, time_close, 0, border_color = color.black, border_width = 1, bgcolor = color.rgb(255, 255, 255, 77), xloc = xloc.bar_time))

timeVolumes = volumes.get(currentTime)
avgVol = 0.0
var volumeLine = line.new(0, measurement, 0, measurement, extend = extend.right, color = open <= close ? color.green : color.red, xloc = xloc.bar_time, style = line.style_dotted)
volumeLine.set_y1(measurement)
volumeLine.set_y2(measurement)

if(time > startDate)
    if(isOpenInSession)
        if(showAverageVolume)
            if(na(timeVolumes))
                timeVolumes := Volumes.new(array.new_float())
                volumes.put(currentTime, timeVolumes)

            timeVolumes.Volumes.push(measurement)
            avgVol := timeVolumes.Volumes.avg()
            
            if(barstate.isconfirmed and deviationLookback > 0)
                deviations.push(math.max(deviationMin, math.min(deviationMax, avgVol / measurement)))
        
            tbl.cell(1, 0, str.tostring(timeVolumes.Volumes.size()), text_color = color.white)
            avgVol := avgVol / avgDeviation

        if(showRealTimeVolume and showAverageVolume)
            line.new(time, 0, time, measurement, xloc = xloc.bar_time, color = open <= close ? color.rgb(76, 175, 79, 60) : color.rgb(255, 82, 82, 52), width = 6)
            
plotcandle(isOpenInSession ? (showMinValues ? timeVolumes.Volumes.min() : 0) : 0, isOpenInSession ? (showMaxValues ? timeVolumes.Volumes.max() : (showAverageVolume ? avgVol : measurement)) : na, 0, isOpenInSession ? (showAverageVolume ? avgVol : measurement) : na, color = color.rgb(24, 86, 138), title = "Average Volume", wickcolor = color.rgb(24, 86, 138), bordercolor = color.rgb(24, 86, 138), display = display.pane)

if(forecastBars > 0)
    for i = 1 to forecastBars
        futureBar = futureBars.get(i - 1)
        futureBar.set_left(time + timeframeMs * i)
        futureBar.set_right(time_close + timeframeMs * i)
        
        futureTime = str.format_time(time + timeframeMs * i, statsGroupByFormat)
        futureTimeVolumes = volumes.get(futureTime)
        if(na(futureTimeVolumes))
            break
        else
            averageVol = futureTimeVolumes.Volumes.avg() / avgDeviation

            if(showMinValues)
                futureBar.set_bottom(futureTimeVolumes.Volumes.min())
            if(showMaxValues)
                line.new(time + timeframeMs * i, 0, time + timeframeMs * i, futureTimeVolumes.Volumes.max(), xloc = xloc.bar_time, color = color.rgb(255, 255, 255, 95), style = line.style_dotted, width = 1)

            futureBar.set_top(averageVol)
