Rain Flow Candles
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © KioseffTrading
// 
//@version=5
indicator("Rain Flow Candles", overlay = true, max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500)

auto       = input.bool      (defval = true, title = "Auto Calculate Everything (Deselect Before Changing Settings)", group = "Auto")
trad       = input.bool      (defval = false, title = "Traditional Look", group = "Traditional Look")
tim        = input.timeframe (defval = "1D", title = "Higher Timeframe", group = "Settings")
strx       = input.string    (defval = "1", title = "Lower Timeframe", group = "Settings")
size       = input.int       (defval = 25, minval = 5,group = "Settings",title = "Columns")
profWidthx = input.int       (minval = 2, title = "Profile Width", defval = 6,group = "Settings"),                 var        profWidth = profWidthx,
showTxt    = input.bool      (defval = true, title = "Show Volume Values",group = "Settings"),                     var int [] timeArr   = array.new_int()            
upcol      = input.color     (defval = #14D990, title = "VWAP Higher Color", inline = "1",group = "Settings"),   var int    timeTrack = 0
dncol      = input.color     (defval = #F24968, title = "VWAP Lower Color" , inline = "1",group = "Settings")
lbcol      = input.color     (defval = #6929F2, title = "Last Bar Color",  group = "Settings")
vwapS      = input.bool      (defval = true,      title = "Show VWAP Lines", group = "Settings")
div        = input.bool      (defval = true, title = "Segment Higher Timeframe", group = "Settings")

import RicardoSantos/MathOperator/2      
import HeWhoMustNotBeNamed/arraymethods/1

method float (int id) => 

    float(id)

method determine (bool id, a, b) =>

    switch id 

        true => a 
        =>      b

str  = switch auto 

    false => str.tostring(math.max(1, math.round(timeframe.in_seconds(timeframe.period) / 60 / 100)))
    =>       strx

if barstate.isfirst 
    
    if not auto
        
        if timeframe.in_seconds(str).float().over_equal(timeframe.in_seconds(timeframe.period))
            runtime.error("Please Select A Lower Timeframe Less Than What's On Your Chart")
        
        if timeframe.in_seconds(tim).float().under_equal(timeframe.in_seconds(timeframe.period))
            runtime.error("Please Select A Higher Timeframe Greater Than What's On Your Chart")

[hx, lx, vw1x, vw2x] = request.security_lower_tf(syminfo.tickerid, str, [high, low, hlc3 * volume, volume])

type values 

    array <float> h
    array <float> l
    array <float> vw1
    array <float> vw2
    line          rainLineL 
    box           rainBoxL
    line          rainLineR
    box           rainBoxR

var val   = values.new(array.new_float(), array.new_float(), 
                       array.new_float(), array.new_float()), var Half = 0


method locate (matrix <float> id, array <float> id2) => 
    
    for x = 0 to val.vw1.size() - 1

        up = id2.binary_search_leftmost (val.h.get(x))
        dn = id2.binary_search_rightmost(val.l.get(x))
    
        for i = dn to up 
        
            switch x.float().under(Half + 1)
            
                true =>  id.set(0, i, id.get(0, i) + (val.vw2.get(x) / (math.abs(up - dn) + 1)))
                =>       id.set(1, i, id.get(1, i) + (val.vw2.get(x) / (math.abs(up - dn) + 1)))


method rankCalc (matrix <float> id, int end, matrix <float> id2) => 
    
    for i = 0 to end

        num = math.floor(100 / (end + 1))

        id.add_col(id.columns(), 
                         
                         array.from(
                              
                                  id2.row(0).percentile_nearest_rank(num * (i + 1)), 
                                  id2.row(1).percentile_nearest_rank(num * (i + 1)))) 

    id.add_col(id.columns(), array.from(
                              
                              id2.row(0).max(),
                              id2.row(1).max()))

method appendLevels (matrix <float> id, increments) => 
    
    for i = 0 to size

        id.set(2, i, val.l.min() + increments * i)

method widthCalc (array <float> id, matrix <float> id2, matrix <float> id3, int i) => 

    index = 1, index2 = 1
    
    for x = 0 to id3.columns() - 1
        if id2.get(0, i).under_equal(id3.get(0, x))
            
            index := math.max(x - 1, 1)
            break 

    if id2.row(1).sum().over(0)
        for x = 0 to id3.columns() - 1
            if id2.get(1, i).under_equal(id3.get(1, x))
                
                index2 := math.max(x - 1, 1)
                break 

    id.set(0, index), id.set(1, index2)

cond = auto.determine(bar_index % 30 == 0, timeframe.change(tim)), timeArr.push(time)



if cond and val.h.size().float().over(0) and last_bar_index - bar_index <= 10000

    drips     = matrix.new<values>(4, 0), var barCount = 0, var barCount2 = 0
    levelsVol = matrix.new<float>(3, size + 1, 0), width = array.new_int(2) 

    
    Half      := math.round(val.h.size() / 2)
    barCount2 := barCount
    barCount  := auto.determine(timeArr.get(timeArr.size() - 16), int(math.avg(time, timeTrack))) 
    ind       = auto.determine(timeArr.size() - 16, timeArr.binary_search_leftmost(barCount))

    increments = (val.h.max() - val.l.min()) / size

    firstHalf = val.vw1.slice(0, Half + 1).sum().divide(val.vw2.slice(0, Half + 1).sum())
    secndHalf = val.vw1.slice(Half + 1, val.vw1.size()).sum().divide(val.vw2.slice(Half + 1, val.vw2.size()).sum())

    levelsVol.appendLevels(increments)
    levelsVol.locate(levelsVol.row(2))

    txtcol = showTxt.determine(color.white, color(na))

    if barCount2 != 0 
        
        if timeArr.get(ind - profWidth).float().under_equal(timeArr.get(
                                 math.min(timeArr.indexof(barCount2) + profWidth, timeArr.size() - 1)))
            
            for i = profWidth to 0 
                if timeArr.get(ind - i).float().over(      timeArr.get(
                                 math.min(timeArr.indexof(barCount2) - i, timeArr.size() - 1)))
                    
                    profWidth := math.max(i - 1, 0)
                    break

    percRank = matrix.new<float>(2, 0), levels = levelsVol.row(2)

    percRank.rankCalc(profWidth, levelsVol)


    col = switch 

        firstHalf.under(secndHalf)   =>  upcol 
        firstHalf.over (secndHalf)   =>  dncol 
        firstHalf.equal(secndHalf)   =>  color.blue


    if vwapS

        for i = 0 to 2 

            [x1, y1, x2, y2] = switch 

                i.float().equal(0) => [barCount, val.h.max(), barCount, val.l.min()]
                i.float().equal(1) => [timeArr.get(ind - profWidth), firstHalf, barCount , firstHalf]
                =>                    [timeArr.get(math.min(ind + profWidth, timeArr.size() - 1)), secndHalf, barCount , secndHalf]

            line.new(x1, y1, x2, y2, color = col, xloc = xloc.bar_time)
            
    lineOnly = matrix.new<line>(2, 0)

    switch trad 
        
        true =>  lineOnly.add_col(0, array.from( line.new(barCount, levels.first(), barCount, levels.first(), color = col, xloc = xloc.bar_time),
                                                 line.new(barCount, levels.first(), barCount, levels.first(), color = col, xloc = xloc.bar_time)))

        => 

                 drips.add_col(0, 
                          
             
             
                              array.from(
                                  
                                  values.new(rainBoxL = box.new(barCount, levels.first(), barCount, levels.get(1),xloc = xloc.bar_time,
                                                  text_color   = txtcol, 
                                                  text         = str.tostring(levelsVol.get(0, 0),format.volume), 
                                                  border_color = #00000000, 
                                                  text_halign  = text.align_right, 
                                                  bgcolor      = color.new(col, 75)
                                                  )),
             
                                  values.new(rainBoxR = box.new(barCount, levels.first(), barCount, levels.get(1), xloc = xloc.bar_time,
                                                  text_color   = txtcol, 
                                                  text         = str.tostring(levelsVol.get(1, 0),format.volume), 
                                                  border_color = #00000000, 
                                                  text_halign  = text.align_left , 
                                                  bgcolor      = color.new(col, 75)
                                                  )),
                                                   
                                  values.new(rainLineL = line.new(barCount, levels.first(), barCount, levels.get(1), 
                                                  xloc = xloc.bar_time, color = col)),
                                  
                                  
                                  values.new(rainLineR = line.new(barCount, levels.first(), barCount, levels.get(1), 
                                                  xloc = xloc.bar_time, color = col)
                                                  )))
             

    for i = 1 to levels.size() - 1
        
        width.widthCalc(levelsVol, percRank, i)   

        switch trad 

            true =>  lineOnly.add_col(lineOnly.columns(),

                     array.from(line.new(lineOnly.get(0, i - 1).get_x2(), levels.get(i - 1), timeArr.get(ind - width.first()), 
                                             levels.get(i), color = col, xloc = xloc.bar_time),
                     
                     line.new(lineOnly.get(1, i - 1).get_x2(), levels.get(i - 1), timeArr.get(math.min(ind + width.get(1), timeArr.size() - 1)), 
                                             levels.get(i), color = col, xloc = xloc.bar_time)))
            =>

                     drips.add_col(0, 
                                      array.from(
                                     
                                              values.new(rainBoxL =  box.new(barCount, levels.get(i - 1), timeArr.get(ind - width.first()) ,  levels.get(i),  
                                                                  xloc        = xloc.bar_time, 
                                                                  text        = str.tostring(levelsVol.get(0, i), format.volume), 
                                                                  text_color  = txtcol, border_color = #00000000, 
                                                                  bgcolor     = color.new(col, 75), 
                                                                  text_halign = text.align_right)), 
             
             
                                              values.new(rainBoxR = box.new(barCount, levels.get(i - 1), timeArr.get(math.min(ind + width.get(1), timeArr.size() - 1)), 
                                                                                 levels.get(i), 
                                                                  text         = str.tostring(levelsVol.get(1, i), format.volume), 
                                                                  text_color   = txtcol,  
                                                                  xloc         = xloc.bar_time, 
                                                                  border_color = #00000000, 
                                                                  bgcolor      = color.new(col, 75), 
                                                                  text_halign  = text.align_left))
                                                                  ,
             
                                              values.new(rainLineL = line.new(timeArr.get(ind - width.first()) , levels.get(i - 1), timeArr.get(ind - width.first()) , 
                                                         levels.get(i), xloc = xloc.bar_time, color =  col)), 
             
             
                                              values.new(rainLineR = line.new(timeArr.get(math.min(ind + width.get(1), timeArr.size()-1)), levels.get(i - 1), 
                                                       timeArr.get(math.min(ind + width.get(1), timeArr.size()-1)), levels.get(  i  ), 
                                                                  xloc = xloc.bar_time, color =  col
                                                                  ))))

        
        if not trad
            
            switch drips.get(2, 0).rainLineL.get_x2().float().equal(drips.get(0, 1).rainLineL.get_x2())

                true =>  drips.get(2, 0).rainLineL.set_y1(levels.get(i - 1)), drips.set(0, 1, values.new(rainLineL = line(na)))
                =>       line.new(drips.get(2, 0).rainLineL.get_x1(), levels.get(i - 1), drips.get(2, 1).rainLineL.get_x1(), levels.get(i - 1), 
                                                     xloc = xloc.bar_time, color = col)


            switch drips.get(3, 0).rainLineR.get_x2().float().equal(drips.get(3, 1).rainLineR.get_x2())

                true =>  drips.get(3, 0).rainLineR.set_y1(levels.get(i - 1)), drips.set(3, 1, values.new(rainLineR = line(na)))
                =>       line.new(drips.get(3, 0).rainLineR.get_x1(), levels.get(i - 1), drips.get(3, 1).rainLineR.get_x1(), levels.get(i - 1), 
                                                     xloc = xloc.bar_time, color = col)


        if i == levels.size() - 1

            switch trad

                false => line.new(timeArr.get(ind - width.first()), levels.get(i), timeArr.get(math.min(ind + width.get(1), timeArr.size() - 1)), 
                                                             levels.get(i), 
                                                             color = col, 
                                                             xloc  = xloc.bar_time
                                                             )

                =>       
                
                         lineOnly.row(0).last().set_x2(barCount), 
                         lineOnly.row(1).last().set_x2(barCount)



if barstate.islast 

    bo = box.all, li = line.all 

    if bo.size() > 0 
        for i = 0 to bo.size() - 1
            if bo.get(i).get_left().float().under(bar_index + 50)
                bo.get(i).delete()

    if li.size() > 0 
        for i = 0 to li.size() - 1
            if li.get(i).get_x2().float().under(bar_index + 50)
                li.get(i).delete()

    

    firstHalf = switch 
        
        val.vw1.size().float().over(0) =>    val.vw1.slice(0, math.min(val.vw1.size(), Half + 1)).sum().divide(val.vw2.slice(0, math.min(val.vw2.size(), Half + 1)).sum())
        =>                                   float(na)

    secondHalf = switch 

        val.vw1.size().float().over(Half) => val.vw1.slice(Half + 1, val.vw1.size()).sum().divide(val.vw2.slice(Half + 1, val.vw2.size()).sum())
        =>                                   float(na)


    if vwapS

        for i = 0 to 2 

            [x1, y1, x2, y2] = switch 

                i.float().equal(0) => [bar_index + 15, val.h.max(), bar_index + 15, val.l.min()]
                i.float().equal(1) => [bar_index + 10, firstHalf  , bar_index + 15, firstHalf  ]
                =>                    [bar_index + 15, secondHalf , bar_index + 20, secondHalf ]

            line.new(x1, y1, x2, y2, color = lbcol)

            
    levelsVol  = matrix.new<float>(3, size + 1, 0)  
    increments = (val.h.max() - val.l.min()) / size
    
    levelsVol.appendLevels(increments), levels = levelsVol.row(2)

    if val.vw1.size() > 0 

        dripsL   = matrix.new<values>(2, 0), dripsR = matrix.new<values>(2, 0)
        percRank = matrix.new<float> (2, 0), width  = array.new_int     (2, 0)

        levelsVol.locate(levels)
        percRank.rankCalc(5, levelsVol)

        lineOnlyL = array.new_line(), lineOnlyR = array.new_line()
        
        if not trad

            dripsL.add_col(0, 
                             array.from(

                                 values.new(rainBoxL  = box.new(bar_index + 15, levels.first(), bar_index+ 15, levels.get(1), 
                                             bgcolor  = color.new(lbcol, 75), 
                                             border_color = #00000000
                                             )),

                                 values.new(rainLineL = line.new(bar_index + 15, levels.first(), bar_index + 15, levels.get(1), 
                                                     color = lbcol))


                             ))


            if levelsVol.row(1).sum().over(0)

                dripsR.add_col(0, 

                             array.from(

                                     values.new(rainBoxR = 

                                             box.new(bar_index + 15, levels.first(), bar_index+ 15, levels.get(1), 
                                                         bgcolor = color.new(lbcol, 75), 
                                                         border_color = #00000000)),


                                     values.new(rainLineR = 
                                                 line.new(bar_index + 15, levels.first(), bar_index + 15, levels.get(1), color = lbcol))


                                 ))

        else 

            lineOnlyL.unshift(line.new(bar_index + 15, levels.first(), bar_index + 15, levels.first(), color = lbcol))
            lineOnlyR.unshift(line.new(bar_index + 15, levels.first(), bar_index + 15, levels.first(), color = lbcol))

        for i = 1 to levels.size() - 1

            width.widthCalc(levelsVol, percRank, i)   

            if not trad

                dripsL.add_col(0, 

                             array.from(

                                     values.new(rainBoxL = box.new(bar_index + 15, levels.get(i - 1), bar_index + 15 - width.first(), levels.get(i),
                                                                   text        = str.tostring(levelsVol.get(0, i), format.volume), 
                                                                   text_color  = showTxt.determine(color.white, color(na)), border_color = #00000000, 
                                                                   bgcolor     = color.new(lbcol, 75), 
                                                                   text_halign = text.align_right)), 

                                     values.new(rainLineL = line.new(bar_index + 15 - width.first(), levels.get(i - 1), bar_index + 15 - width.first(), levels.get(i), 
                                                                 color = lbcol))

                 ))



                switch dripsL.get(1, 0).rainLineL.get_x2().float().equal(dripsL.get(1, 1).rainLineL.get_x2())

                    true =>  dripsL.get(1, 0).rainLineL.set_y1(levels.get(i - 1)), dripsL.set(1, 1, values.new(rainLineL = line(na)))
                    =>       line.new(bar_index + 15 - width.first(), levels.get(i - 1), dripsL.get(1, 1).rainLineL.get_x1(), levels.get(i - 1), 
                                                         color = lbcol)

            else 

                lineOnlyL.push(line.new(lineOnlyL.last().get_x2(), levels.get(i - 1), bar_index + 15 - width.first(), levels.get(i), color = lbcol))

            if levelsVol.row(1).sum().over(0)

                if not trad

                    dripsR.add_col(0, 

                                 array.from(

                                         values.new(rainBoxR = box.new(bar_index + 15, levels.get(i - 1), bar_index + 15 + width.get(1), levels.get(i),
                                                                      text        = str.tostring(levelsVol.get(1, i), format.volume), 
                                                                      text_color  = showTxt ? color.white : na, border_color = #00000000, 
                                                                      bgcolor     = color.new(lbcol, 75), 
                                                                      text_halign = text.align_left)),

                                         values.new(rainLineR = line.new(bar_index + 15 + width.get(1), levels.get(i - 1), bar_index + 15 + width.get(1), levels.get(i), 
                                                                                 color = lbcol))


                             ))



                    switch dripsR.get(1, 0).rainLineR.get_x2().float().equal(dripsR.get(1, 1).rainLineR.get_x2())

                        true =>  dripsR.get(1, 0).rainLineR.set_y1(levels.get(i - 1)), dripsR.set(1, 1, values.new(rainLineR = line(na)))
                        =>       line.new(dripsR.get(1, 0).rainLineR.get_x1(), levels.get(i - 1), dripsR.get(1, 1).rainLineR.get_x1(), levels.get(i - 1), 
                                                             color = lbcol)

                else 

                    lineOnlyR.push(line.new(lineOnlyR.last().get_x2(), levels.get(i - 1), bar_index + 15 + width.get(1), levels.get(i), color = lbcol))


            if i.float().equal(levels.size() - 1)

                if not trad 
            
                    x2 = switch levelsVol.row(1).sum().over(0)

                        false => bar_index + 15 
                        =>       bar_index + 15 + width.get(1)

                    line.new(bar_index + 15 - width.first(), levels.get(i), x2,
                                         levels.get(i), 
                                         color = lbcol 
                                         )
                    
                else 

                    lineOnlyL.last().set_x2(bar_index + 15)

                    if levelsVol.row(1).sum().over(0)

                        lineOnlyR.last().set_x2(bar_index + 15)

                    

if cond 

    timeTrack := time
    val.h  .clear(), val  .l.clear()  
    val.vw1.clear(), val.vw2.clear()

if hx.size().float().over(0)

    for i = 0 to hx.size() - 1

        val.h.push  (hx.get  (i)), val.l.push  (lx  .get(i))
        val.vw1.push(vw1x.get(i)), val.vw2.push(vw2x.get(i))

bgcolor(cond and div ? color.new(color.white, 80) : color(na))

