Re: Already Converted TradingView Indicators to MT4 Indicators

621
Came across this one - https://www.tradingview.com/script/qZRH ... nd-Filter/ - and seemed interesting. Used an Mladen's version of Sylvain Vervoort Bollinger Band indie as a basis ("sve bollinger band_main_mtf + alerts.mq4").

It basically counts how many times the price crosses the upper channel or the lower channel. The original version resets when the price crosses the opposite channel. I did add the option to reset the counter to 0 when the middle band is crossed, which could potentially indicate a ranging market. First impression is that this option is better set to FALSE when using "SVE BB" channel.

BB Channel with and without reset when middle band is crossed:


Re: Already Converted TradingView Indicators to MT4 Indicators

624
Hello and regards to everyone,

This LuxAlgo indicator is very useful — especially the way it automatically identifies peak points and draws support and resistance levels based on volume peaks and valleys.

I would really appreciate it if this indicator could be converted to MQL4 so that it can be used in MetaTrader 4.

Also, it would be great if the indicator could be anchored manually, similar to Anchored Volume Profile — allowing the user to move and resize the range between lines, rather than relying on a fixed number of bars.

If you need any additional information to help with the conversion, I am more than happy to provide it.

Thank you very much in advance for making this available for MT4!

https://www.luxalgo.com/library/indicat ... detection/

Main code:

Code: Select all

// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © LuxAlgo
//@version=5

indicator("Volume Profile with Node Detection [LuxAlgo]", "LuxAlgo - Volume Profile with Node Detection", overlay = true, max_boxes_count = 500, max_bars_back = 5000)

//---------------------------------------------------------------------------------------------------------------------
// Settings
//---------------------------------------------------------------------------------------------------------------------{

display = display.all - display.status_line

vn_volumeNodesGroup = 'Volume Nodes'

vn_peakTTip = 'A volume peak node is recognized when the volume profile nodes for the N preceding and N succeeding nodes are lower than that of the evaluated one, where N is determined by the \'Node Detection Percent %\' option'
vn_peaksShow = input.string('Peaks', 'Volume Peaks', options = ['Peaks', 'Clusters', 'None'], inline = 'vnP', tooltip = vn_peakTTip, group = vn_volumeNodesGroup, display = display)
vn_peakVolumeColor = input.color(color.new(color.blue, 50), '', inline = 'vnP', group = vn_volumeNodesGroup)
vn_peaksNumberOfNodes = input.int(9, '  Node Detection Percent %', minval = 0, maxval = 100, group = vn_volumeNodesGroup, display = display) / 100
vn_peaksShow := vn_peaksNumberOfNodes == 0 ? 'None' : vn_peaksShow

vn_troughsTTip  = 'A volume trough node is recognized when the volume profile nodes for the N preceding and N succeeding nodes exceed that of the evaluated one, where N is determined by the \'Node Detection Percent %\' option'
vn_troughsShow = input.string('None', 'Volume Troughs', options = ['Troughs', 'Clusters', 'None'], inline = 'vnT', tooltip = vn_troughsTTip, group = vn_volumeNodesGroup, display = display)
vn_troughVolumeColor = input.color(color.new(color.gray, 50), '', inline = 'vnT', group = vn_volumeNodesGroup)
vn_troughsNumberOfNodes = input.int(7, '  Node Detection Percent %', minval = 0, maxval = 100, group = vn_volumeNodesGroup, display = display) / 100
vn_troughsShow := vn_troughsNumberOfNodes == 0 ? 'None' : vn_troughsShow

vn_thresholdTTip = 'A threshold value specified as a percentage is utilized to detect peak/trough volume nodes. If a value is set, the detection will disregard volume node values lower than the specified threshold.'
vn_VolumeNodeThreshold = input.int(1, 'Volume Node Threshold %', minval = 0, maxval = 100, tooltip = vn_thresholdTTip, group = vn_volumeNodesGroup, display = display) / 100

vn_highestNVolumeNodes = input.int(0, 'Highest Volume Nodes', minval = 0, maxval = 31, inline = 'vnL', group = vn_volumeNodesGroup, display = display)
vn_highestVolumeColor = input.color(color.new(color.orange, 25), '', inline = 'vnL', group = vn_volumeNodesGroup)

vn_lowestNVolumeNodes = input.int(0, 'Lowest Volume Nodes', minval = 0, maxval = 31, inline = 'vnH', group = vn_volumeNodesGroup, display = display)
vn_lowestVolumeColor = input.color(color.new(color.navy, 25), '', inline = 'vnH', group = vn_volumeNodesGroup)

vp_componentsGroup = 'Volume Profile - Components'

vp_profileShow = input.bool(true, 'Volume Profile', inline = 'vp', group = vp_componentsGroup)
vp_profileGradientColors = input.string('Gradient Colors', '', options = ['Gradient Colors', 'Classic Colors' ], inline = 'vp', group = vp_componentsGroup)
vp_valueAreaUpColor = input.color(color.new(#2962ff, 30), '  Value Area Up / Down', inline = 'VA', group = vp_componentsGroup)
vp_valueAreaDwonColor = input.color(color.new(#fbc02d, 30), '/', inline = 'VA', group = vp_componentsGroup)
vp_profileUpVolumeColor = input.color(color.new(#5d606b, 50), '  Profile Up / Down Volume', inline = 'VP', group = vp_componentsGroup)
vp_profileDownVolumeColor = input.color(color.new(#d1d4dc, 50), '/', inline = 'VP', group = vp_componentsGroup)

vp_pocShow = input.string('None', 'Point of Control', options = ['Developing', 'Regular', 'None'], inline = 'poc', group = vp_componentsGroup, display = display)
vp_pocColor = input.color(#fbc02d, '', inline = 'poc', group = vp_componentsGroup)
vp_pocWidth = input.int(2, 'Width', inline = 'poc', group = vp_componentsGroup, display = display)

vp_vahShow = input.bool(false, 'Value Area High (VAH)', inline = 'vah', group = vp_componentsGroup)
vp_vahColor = input.color(#2962ff, '', inline = 'vah', group = vp_componentsGroup)

vp_valShow = input.bool(false, 'Value Area Low (VAL)', inline = 'val', group = vp_componentsGroup)
vp_valColor = input.color(#2962ff, '', inline = 'val', group = vp_componentsGroup)

vp_profileLevels = input.string('Small', "Profile Price Labels", options=['Tiny', 'Small', 'Normal', 'None'], group = vp_componentsGroup, display = display)

vp_displayGroup = 'Volume Profile - Display Settings'

vp_profileLength = input.int(360, 'Profile Lookback Length', minval = 10, maxval = 5000, step = 10, group = vp_displayGroup, display = display)
vp_profileLength:= last_bar_index < vp_profileLength ? last_bar_index : vp_profileLength - 1

vp_valueAreaThreshold = input.float(70, 'Value Area (%)', minval = 0, maxval = 100, group = vp_displayGroup, display = display) / 100

vp_profilePlracment = input.string('Right', 'Profile Placement', options = ['Right', 'Left'], group = vp_displayGroup, display = display), profilePlacementRight = vp_profilePlracment == 'Right' 
vp_profileNumberOfRows = input.int(100, 'Profile Number of Rows' , minval = 30, maxval = 130 , step = 10, group = vp_displayGroup, display = display)
vp_profileWidth = input.float(31, 'Profile Width', minval = 0, maxval = 250, group = vp_displayGroup, display = display) / 100
vp_profileHorizontalOffset = input.int(13, 'Profile Horizontal Offset', maxval = 50, group = vp_displayGroup, display = display)

vp_valueAreaBackground = input.bool(false, 'Value Area Background  ', inline = 'vBG', group = vp_displayGroup)
vp_valueAreaBackgroundColor = input.color(color.new(#2962ff, 89), '', inline = 'vBG', group = vp_displayGroup)

vp_profileBackground = input.bool(false, 'Profile Range Background ', inline = 'pBG', group = vp_displayGroup)
vp_profileBackgroundColor  = input.color(color.new(#2962ff, 95), '', inline = 'pBG', group = vp_displayGroup)

//---------------------------------------------------------------------------------------------------------------------}
// User Defined Types
//---------------------------------------------------------------------------------------------------------------------{

type BAR
    float open   = open
    float high   = high
    float low    = low
    float close  = close
    float volume = volume
    int   index  = bar_index

type barData
    float [] barHigh
    float [] barLow
    float [] barVolume
    bool  [] barPolarity
    int   [] barCount

type volumeData
    float [] totalVolume
    float [] bullishVolume
    float [] bearishVolume
    int   [] endProfileIndex
    bool  [] peakVolume
    bool  [] troughVolume

type volumeProfile
    box         []  boxes
    chart.point []  pocPoints
    polyline        pocPolyline
    int             pocLevel
    int             vahLevel
    int             valLevel
    int             startIndex

//---------------------------------------------------------------------------------------------------------------------}
// Variables
//---------------------------------------------------------------------------------------------------------------------{

BAR bar = BAR.new()
BAR [] ltfBarData = array.new<BAR> (1, BAR.new())

var barData barDataArray = barData.new(
     array.new <float> (na), 
     array.new <float> (na), 
     array.new <float> (na), 
     array.new <bool>  (na), 
     array.new <int>   (na)
 )

volumeData volumeDataArray = volumeData.new(
     array.new <float> (vp_profileNumberOfRows, 0.), 
     array.new <float> (vp_profileNumberOfRows, 0.), 
     array.new <float> (vp_profileNumberOfRows, 0.),
     array.new <int>   (vp_profileNumberOfRows, 0 ),
     array.new <bool>  (vp_profileNumberOfRows, 0.),
     array.new <bool>  (vp_profileNumberOfRows, 0.)
 )

var volumeProfile VP = volumeProfile.new(
     array.new<box>         (na),
     array.new<chart.point> (na),
     polyline.new           (na), na, na, na, na
 )

var float highestPrice = na
var float lowestPrice = na

//---------------------------------------------------------------------------------------------------------------------}
// Functions / Methods
//---------------------------------------------------------------------------------------------------------------------{

renderLine(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width) =>
    var id = line.new(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width)
    line.set_xy1(id, _x1, _y1)
    line.set_xy2(id, _x2, _y2)
    line.set_color(id, _color)

renderLabel(_x, _y, _text, _color, _style, _textcolor, _size, _tooltip) =>
    var lb = label.new(_x, _y, _text, xloc.bar_index, yloc.price, _color, _style, _textcolor, _size, text.align_left, _tooltip)
    lb.set_xy(_x, _y)
    lb.set_text(_text)
    lb.set_tooltip(_tooltip)
    lb.set_textcolor(_textcolor)

requestBarData(_lowerTimeframe) => request.security_lower_tf(syminfo.tickerid, _lowerTimeframe, BAR.new(), ignore_invalid_timeframe = true)

calculateTimeframe(_depth) => 
    int tfInMs = timeframe.in_seconds(timeframe.period)
    int  mInMS = 60

    if _depth == 2
        switch
            tfInMs <                 30  =>  '1S'
            tfInMs <          1 * mInMS  =>  '5S'

            tfInMs <=        15 * mInMS  =>   '1'
            tfInMs <=        60 * mInMS  =>   '5'
            tfInMs <=       240 * mInMS  =>  '15'
            tfInMs <=      1440 * mInMS  =>  '60'
            => 'D'

    else if _depth == 1
        switch
            tfInMs <                 15  =>  '1S'
            tfInMs <                 30  =>  '5S'
            tfInMs <          1 * mInMS  => '15S'

            tfInMs <=         5 * mInMS  =>   '1'
            tfInMs <=        15 * mInMS  =>   '5'
            tfInMs <=        60 * mInMS  =>  '15'
            tfInMs <=       240 * mInMS  =>  '60'
            tfInMs <=      1440 * mInMS  => '240'
            => 'D'

getTextSize(_text) =>
    if _text != 'None'
        switch _text
            'Tiny'   => size.tiny
            'Small'  => size.small 
            'Normal' => size.normal
            => size.auto

//---------------------------------------------------------------------------------------------------------------------}
// Calculations - Volume Profile
//---------------------------------------------------------------------------------------------------------------------{

profileLevesSize  = getTextSize(vp_profileLevels)

if bar.index == last_bar_index - vp_profileLength
    VP.startIndex := bar.index
    lowestPrice := bar.low 
    highestPrice := bar.high
else if bar.index > last_bar_index - vp_profileLength
    lowestPrice := math.min(bar.low, lowestPrice)
    highestPrice := math.max(bar.high, highestPrice)

//if vp_profileLength <= 200
//    ltfBarData := requestBarData(calculateTimeframe(2))  
//else 
if vp_profileLength <= 700
    ltfBarData := requestBarData(calculateTimeframe(2)) 
else
    ltfBarData := array.new<BAR> (1, BAR.new(bar.open, bar.high, bar.low, bar.close, bar.volume))

if barstate.ishistory and (bar.index >= last_bar_index - vp_profileLength) and bar.index < last_bar_index and ltfBarData.size() > 0

    log.info("yaz_kizim {0} {1}", ltfBarData.get(0).volume, na(nz(ltfBarData.get(0).volume)) )

    if ltfBarData.size() > 0 and not na(nz(ltfBarData.get(0).volume))
        for currentLtfBar = 0 to ltfBarData.size() - 1
            barDataArray.barHigh.push(ltfBarData.get(currentLtfBar).high)
            barDataArray.barLow.push(ltfBarData.get(currentLtfBar).low)
            barDataArray.barVolume.push(ltfBarData.get(currentLtfBar).volume)
            barDataArray.barPolarity.push(ltfBarData.get(currentLtfBar).close > ltfBarData.get(currentLtfBar).open)

        barDataArray.barCount.push(ltfBarData.size())

priceStep = (highestPrice - lowestPrice) / vp_profileNumberOfRows

if barstate.islast and ltfBarData.size() > 0 //  barDataArray.barVolume.size() > 0 //

    if VP.boxes.size() > 0
        for boxIndex = 0 to VP.boxes.size() - 1
            box.delete(VP.boxes.shift())

    if barDataArray.barCount.size() > vp_profileLength
        barCount = barDataArray.barCount.shift()
        for barCountIndex = 0 to barCount - 1
            barDataArray.barHigh.shift()
            barDataArray.barLow.shift()
            barDataArray.barVolume.shift()
            barDataArray.barPolarity.shift()

    VP.pocPoints.clear()
    VP.pocPolyline.delete()

    if ltfBarData.size() > 0 and not na(nz(ltfBarData.get(0).volume))
        for currentLtfBar = 0 to ltfBarData.size() - 1
            barDataArray.barHigh.push(ltfBarData.get(currentLtfBar).high)
            barDataArray.barLow.push(ltfBarData.get(currentLtfBar).low)
            barDataArray.barVolume.push(ltfBarData.get(currentLtfBar).volume)
            barDataArray.barPolarity.push(ltfBarData.get(currentLtfBar).close > ltfBarData.get(currentLtfBar).open)

        barDataArray.barCount.push(ltfBarData.size())

    barIndex = vp_profileLength
    numberOfBars = 0
    arraySize = barDataArray.barVolume.size()

    for arrayIndex = 0 to arraySize - 1

        levelHigh = barDataArray.barHigh.get(arrayIndex)
        levelLow = barDataArray.barLow.get(arrayIndex)
        levelVolume = barDataArray.barVolume.get(arrayIndex)

        // Shoutout to @tkarolak for contributing to the code's optimization! Much appreciated.
        
        int startSlotIndex = math.max(math.floor((levelLow - lowestPrice) / priceStep), 0)
        int endSlotIndex = math.min(math.floor((levelHigh - lowestPrice) / priceStep), vp_profileNumberOfRows - 1)
        
        for priceLevelIndex = startSlotIndex to endSlotIndex

            float priceLevel = lowestPrice + priceLevelIndex * priceStep

            volumeProportion = switch
                levelLow >= priceLevel and levelHigh > priceLevel + priceStep => (priceLevel + priceStep - levelLow) / (levelHigh - levelLow)
                levelHigh <= priceLevel + priceStep and levelLow < priceLevel => (levelHigh - priceLevel) / (levelHigh - levelLow)
                levelLow >= priceLevel and levelHigh <= priceLevel + priceStep => 1
                => priceStep / (levelHigh - levelLow)

            volumeDataArray.totalVolume.set(priceLevelIndex, volumeDataArray.totalVolume.get(priceLevelIndex) + levelVolume * volumeProportion)

            if barDataArray.barPolarity.get(arrayIndex)
                volumeDataArray.bullishVolume.set(priceLevelIndex, volumeDataArray.bullishVolume.get(priceLevelIndex) + levelVolume * volumeProportion)

//        priceLevelIndex = 0
//        for priceLevel = lowestPrice to highestPrice - priceStep by priceStep
//
//            if levelHigh >= priceLevel and levelLow < priceLevel + priceStep
//
//                volumeProportion = if levelLow >= priceLevel and levelHigh > priceLevel + priceStep
//                    (priceLevel + priceStep - levelLow) / (levelHigh - levelLow)
//                else if levelHigh <= priceLevel + priceStep and levelLow < priceLevel
//                    (levelHigh - priceLevel) / (levelHigh - levelLow)
//                else if levelLow >= priceLevel and levelHigh <= priceLevel + priceStep
//                    1
//                else
//                    priceStep / (levelHigh - levelLow)
//
//                volumeDataArray.totalVolume.set(priceLevelIndex, volumeDataArray.totalVolume.get(priceLevelIndex) + levelVolume * volumeProportion)
//
//                if barDataArray.barPolarity.get(arrayIndex)
//                    volumeDataArray.bullishVolume.set(priceLevelIndex, volumeDataArray.bullishVolume.get(priceLevelIndex) + levelVolume * volumeProportion)
//            priceLevelIndex += 1

        if vp_pocShow == 'Developing'
            if arrayIndex == barDataArray.barCount.get(vp_profileLength - barIndex)
                VP.pocPoints.push(chart.point.from_index(bar.index[barIndex], math.avg(bar.high[barIndex], bar.low[barIndex])))
                VP.pocPoints.push(chart.point.from_index(bar.index[barIndex] + 1, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max()) + .5) * priceStep))
                numberOfBars += barDataArray.barCount.get(vp_profileLength - barIndex)
                barIndex  -= 1
            else if arrayIndex == (numberOfBars + barDataArray.barCount.get(vp_profileLength - barIndex)) and numberOfBars != 0
                VP.pocPoints.push(chart.point.from_index(bar.index[barIndex] + 1, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max()) + .5) * priceStep))
                numberOfBars += barDataArray.barCount.get(vp_profileLength - barIndex)
                barIndex  -= 1
            else if barIndex == 0
                VP.pocPoints.push(chart.point.from_index(bar.index[barIndex] + 1, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max()) + .5) * priceStep))
                numberOfBars += barDataArray.barCount.get(vp_profileLength - barIndex)

    VP.pocPolyline := polyline.new(VP.pocPoints, false, false, xloc.bar_index, vp_pocColor, color(na), line.style_solid, vp_pocWidth)

    for volumeIndex = 0 to vp_profileNumberOfRows - 1
        bearishVolume = 2 * volumeDataArray.bullishVolume.get(volumeIndex) - volumeDataArray.totalVolume.get(volumeIndex)
        volumeDataArray.bearishVolume.set(volumeIndex, volumeDataArray.bearishVolume.get(volumeIndex) + bearishVolume * (bearishVolume > 0 ? 1 : -1) )

    VP.pocLevel := volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max())
    totalTradedVolume = volumeDataArray.totalVolume.sum() * vp_valueAreaThreshold
    valueAreaVolume = VP.pocLevel != -1 ? volumeDataArray.totalVolume.get(VP.pocLevel) : 0
    VP.vahLevel := VP.pocLevel
    VP.valLevel := VP.pocLevel
    
    while valueAreaVolume < totalTradedVolume
        if VP.valLevel == 0 and VP.vahLevel == vp_profileNumberOfRows - 1
            break

        volumeAbovePOC = 0.
        if VP.vahLevel < vp_profileNumberOfRows - 1 
            volumeAbovePOC := volumeDataArray.totalVolume.get(VP.vahLevel + 1)

        volumeBelowPOC = 0.
        if VP.valLevel > 0
            volumeBelowPOC := volumeDataArray.totalVolume.get(VP.valLevel - 1)
        
        if volumeBelowPOC == 0 and volumeAbovePOC == 0
            break

        if volumeAbovePOC >= volumeBelowPOC
            valueAreaVolume  += volumeAbovePOC
            VP.vahLevel += 1
        else
            valueAreaVolume  += volumeBelowPOC
            VP.valLevel -= 1

    vahPrice = lowestPrice + (VP.vahLevel + 1.) * priceStep
    pocPrice = lowestPrice + (VP.pocLevel + .5) * priceStep
    valPrice = lowestPrice + (VP.valLevel + .0) * priceStep

    profilePlottingLength = vp_profileLength > 360 ? 360 : vp_profileLength
    profileWidth = profilePlottingLength * vp_profileWidth
    profileHorizontalOffset = int(profileWidth + vp_profileHorizontalOffset)

    if vp_profileShow and profilePlacementRight and vp_pocShow == 'Developing'
        renderLine(last_bar_index, pocPrice, profileHorizontalOffset + int(last_bar_index - profileWidth + 1), pocPrice, xloc.bar_index, extend.none, vp_pocColor, line.style_solid, vp_pocWidth)

    if vp_vahShow
        renderLine(VP.startIndex, vahPrice, 
                   profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                   vahPrice, xloc.bar_index, extend.none, vp_vahColor, line.style_solid, 1)
    
    if vp_pocShow == 'Regular'
        renderLine(VP.startIndex, pocPrice, profilePlacementRight ? vp_profileShow ? profileHorizontalOffset + int(last_bar_index - profileWidth + 1) : last_bar_index : last_bar_index, pocPrice, xloc.bar_index, extend.none, vp_pocColor, line.style_solid, vp_pocWidth)

    if vp_valShow
        renderLine(VP.startIndex, valPrice, 
                   profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                   valPrice, xloc.bar_index, extend.none, vp_valColor, line.style_solid, 1)

    if vp_valueAreaBackground
        VP.boxes.push(box.new(VP.startIndex, valPrice, last_bar_index, vahPrice, vp_valueAreaBackgroundColor, 1, line.style_dotted, bgcolor = vp_valueAreaBackgroundColor))

    if vp_profileBackground
        VP.boxes.push(box.new(VP.startIndex, lowestPrice, last_bar_index, highestPrice, vp_profileBackgroundColor, 1, line.style_dotted, bgcolor = vp_profileBackgroundColor))

    if vp_profileLevels != 'None' and VP.pocLevel != -1 
        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : vp_profileShow ? VP.startIndex : last_bar_index, 
                     highestPrice, str.tostring(highestPrice, format.mintick), color.new(chart.fg_color, 89), label.style_label_down, chart.fg_color, profileLevesSize, 'Profile High')

        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                     vahPrice, str.tostring(vahPrice, format.mintick), color.new(vp_vahColor, 89), label.style_label_left, vp_vahColor, profileLevesSize, 'Value Area High')

        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                     pocPrice, str.tostring(pocPrice, format.mintick), color.new(vp_pocColor, 89), label.style_label_left, vp_pocColor, profileLevesSize, 'Point of Control')
 
        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                     valPrice, str.tostring(valPrice, format.mintick), color.new(vp_valColor, 89), label.style_label_left, vp_valColor, profileLevesSize, 'Value Area Low')

        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : vp_profileShow ? VP.startIndex : last_bar_index, 
                     lowestPrice, str.tostring(lowestPrice, format.mintick), color.new(chart.fg_color, 89), label.style_label_up, chart.fg_color, profileLevesSize, 'Profile Low')

    for volumeNodeLevel = 0 to vp_profileNumberOfRows - 1
    
        if vp_profileShow
            if vp_profileGradientColors == 'Gradient Colors'
                vp_valueAreaUpColor       := color.from_gradient(volumeDataArray.totalVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max(), 0, 1, color.new(vp_valueAreaUpColor      , 95), color.new(vp_valueAreaUpColor      , 0))  
                vp_valueAreaDwonColor     := color.from_gradient(volumeDataArray.totalVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max(), 0, 1, color.new(vp_valueAreaDwonColor    , 95), color.new(vp_valueAreaDwonColor    , 0))  
                vp_profileUpVolumeColor   := color.from_gradient(volumeDataArray.totalVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max(), 0, 1, color.new(vp_profileUpVolumeColor  , 95), color.new(vp_profileUpVolumeColor  , 0))  
                vp_profileDownVolumeColor := color.from_gradient(volumeDataArray.totalVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max(), 0, 1, color.new(vp_profileDownVolumeColor, 95), color.new(vp_profileDownVolumeColor, 0))  
 
            startProfileIndex = profilePlacementRight ? 
                                 profileHorizontalOffset + int(last_bar_index - volumeDataArray.bullishVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max() * profileWidth) :
                                 VP.startIndex
            endProfileIndex   = profilePlacementRight ? 
                                 profileHorizontalOffset + last_bar_index : 
                                 int(startProfileIndex + volumeDataArray.bullishVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max() * profileWidth)

            VP.boxes.push(box.new(startProfileIndex, lowestPrice + (volumeNodeLevel + .1) * priceStep, endProfileIndex, lowestPrice + (volumeNodeLevel + .9) * priceStep, 
                                   color(na), bgcolor = volumeNodeLevel >= VP.valLevel and volumeNodeLevel <= VP.vahLevel ? vp_valueAreaUpColor : vp_profileUpVolumeColor))

            startProfileIndex := profilePlacementRight ? startProfileIndex : endProfileIndex
            endProfileIndex   := profilePlacementRight ? 
                                 startProfileIndex - int( (volumeDataArray.totalVolume.get(volumeNodeLevel) - volumeDataArray.bullishVolume.get(volumeNodeLevel)) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                 startProfileIndex + int( (volumeDataArray.totalVolume.get(volumeNodeLevel) - volumeDataArray.bullishVolume.get(volumeNodeLevel)) / volumeDataArray.totalVolume.max() * profileWidth)

            VP.boxes.push(box.new(startProfileIndex, lowestPrice + (volumeNodeLevel + .1) * priceStep, endProfileIndex, lowestPrice + (volumeNodeLevel + .9) * priceStep, 
                                   color(na), bgcolor = volumeNodeLevel >= VP.valLevel and volumeNodeLevel <= VP.vahLevel ? vp_valueAreaDwonColor : vp_profileDownVolumeColor))
            volumeDataArray.endProfileIndex.set(volumeNodeLevel, endProfileIndex)

    if  vn_peaksShow != 'None' or  vn_troughsShow != 'None'
        var int startVolumeNodeIndex = na, var int endVolumeNodeIndex = na
        var bool peakUpperNth = na, var bool peakLowerNth = na

        peaksNumberOfNodes = int(vp_profileNumberOfRows * vn_peaksNumberOfNodes)

        tempPeakTotalVolume = volumeDataArray.totalVolume.copy()

        for index = 1 to peaksNumberOfNodes
            tempPeakTotalVolume.unshift(0.)
            tempPeakTotalVolume.push(0.)

        for volumeNodeLevel = 0 to vp_profileNumberOfRows - 1 + 2 * peaksNumberOfNodes 

            if vn_peaksShow != 'None' and volumeNodeLevel >= 2 * peaksNumberOfNodes 

                for currentVolumeNode = volumeNodeLevel - 2 * peaksNumberOfNodes to volumeNodeLevel - peaksNumberOfNodes - 1
                    if tempPeakTotalVolume.get(volumeNodeLevel - peaksNumberOfNodes) <= tempPeakTotalVolume.get(currentVolumeNode)
                        peakUpperNth := false
                        break
                    else
                        peakUpperNth := true

                for currentVolumeNode = volumeNodeLevel - peaksNumberOfNodes + 1 to volumeNodeLevel
                    if tempPeakTotalVolume.get(volumeNodeLevel - peaksNumberOfNodes) <= tempPeakTotalVolume.get(currentVolumeNode)
                        peakLowerNth := false
                        break
                    else
                        peakLowerNth := true

                if peakUpperNth and peakLowerNth and tempPeakTotalVolume.get(volumeNodeLevel - peaksNumberOfNodes) / tempPeakTotalVolume.max() > vn_VolumeNodeThreshold

                    startVolumeNodeIndex := vp_profileShow ? profilePlacementRight ? 
                                                             VP.startIndex : 
                                                             volumeDataArray.endProfileIndex.get(volumeNodeLevel - 2 * peaksNumberOfNodes) : //VP.startIndex + int(volumeDataArray.totalVolume.get(volumeNodeLevel - vn_peaksNumberOfNodes) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                             VP.startIndex

                    endVolumeNodeIndex   := vp_profileShow ? profilePlacementRight ? 
                                                             volumeDataArray.endProfileIndex.get(volumeNodeLevel - 2 * peaksNumberOfNodes) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(volumeNodeLevel - vn_peaksNumberOfNodes) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                             last_bar_index : 
                                             last_bar_index

                    vn_peakVolumeColor := vn_peaksShow == 'Peaks' ? vn_peakVolumeColor : color.from_gradient(tempPeakTotalVolume.get(volumeNodeLevel - peaksNumberOfNodes) / tempPeakTotalVolume.max(), 0, 1, color.new(vn_peakVolumeColor, 95), color.new(vn_peakVolumeColor, 65))  

                    VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (volumeNodeLevel - 2 * peaksNumberOfNodes + .1) * priceStep, endVolumeNodeIndex, lowestPrice + (volumeNodeLevel - 2 * peaksNumberOfNodes + .9) * priceStep, 
                                          color(na), bgcolor = vn_peakVolumeColor))

                    if vn_peaksShow == 'Clusters'

                        for currentVolumeNode = volumeNodeLevel - 2 * peaksNumberOfNodes to volumeNodeLevel

                            if currentVolumeNode >= peaksNumberOfNodes and currentVolumeNode <= vp_profileNumberOfRows - 1 + peaksNumberOfNodes
                                if not volumeDataArray.peakVolume.get(currentVolumeNode - peaksNumberOfNodes)

                                    startVolumeNodeIndex := vp_profileShow ? profilePlacementRight ? 
                                                                         VP.startIndex : 
                                                                         volumeDataArray.endProfileIndex.get(currentVolumeNode - peaksNumberOfNodes) : //VP.startIndex + int(volumeDataArray.totalVolume.get(currentVolumeNode) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                         VP.startIndex

                                    endVolumeNodeIndex   := vp_profileShow ? profilePlacementRight ? 
                                                                         volumeDataArray.endProfileIndex.get(currentVolumeNode - peaksNumberOfNodes) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(currentVolumeNode) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                                         last_bar_index : 
                                                         last_bar_index

                                    VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (currentVolumeNode - peaksNumberOfNodes + .0) * priceStep, endVolumeNodeIndex, lowestPrice + (currentVolumeNode - peaksNumberOfNodes + 1.) * priceStep, 
                                                       color(na), bgcolor = vn_peakVolumeColor))
                                    volumeDataArray.peakVolume.set(currentVolumeNode - peaksNumberOfNodes, true)

        tempPeakTotalVolume.clear()

        var bool troughUpperNth = na, var bool troughLowerNth = na
        troughsNumberOfNodes = int(vp_profileNumberOfRows * vn_troughsNumberOfNodes)

        tempTroughTotalVolume = volumeDataArray.totalVolume.copy()

        for index = 1 to troughsNumberOfNodes
            tempTroughTotalVolume.unshift(volumeDataArray.totalVolume.max())
            tempTroughTotalVolume.push(volumeDataArray.totalVolume.max())
            
        for volumeNodeLevel = 0 to vp_profileNumberOfRows - 1 + 2 * troughsNumberOfNodes 

            if vn_troughsShow != 'None' and volumeNodeLevel >= 2 * troughsNumberOfNodes 

                for currentVolumeNode = volumeNodeLevel - 2 * troughsNumberOfNodes to volumeNodeLevel - troughsNumberOfNodes - 1
                    if tempTroughTotalVolume.get(volumeNodeLevel - troughsNumberOfNodes) >= tempTroughTotalVolume.get(currentVolumeNode)
                        troughUpperNth := false
                        break
                    else
                        troughUpperNth := true

                for currentVolumeNode = volumeNodeLevel - troughsNumberOfNodes + 1 to volumeNodeLevel
                    if tempTroughTotalVolume.get(volumeNodeLevel - troughsNumberOfNodes) >= tempTroughTotalVolume.get(currentVolumeNode)
                        troughLowerNth := false
                        break
                    else
                        troughLowerNth := true

                if troughUpperNth and troughLowerNth and tempTroughTotalVolume.get(volumeNodeLevel - troughsNumberOfNodes) / tempTroughTotalVolume.max() > vn_VolumeNodeThreshold

                    startVolumeNodeIndex := vp_profileShow ? profilePlacementRight ? 
                                                             VP.startIndex : 
                                                             volumeDataArray.endProfileIndex.get(volumeNodeLevel - 2 * troughsNumberOfNodes) : //VP.startIndex + int(volumeDataArray.totalVolume.get(volumeNodeLevel - vn_troughsNumberOfNodes) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                             VP.startIndex

                    endVolumeNodeIndex   := vp_profileShow ? profilePlacementRight ? 
                                                             volumeDataArray.endProfileIndex.get(volumeNodeLevel - 2 * troughsNumberOfNodes) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(volumeNodeLevel - vn_troughsNumberOfNodes) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                             last_bar_index : 
                                             last_bar_index

                    vn_troughVolumeColor := vn_troughsShow == 'Troughs' ? vn_troughVolumeColor : color.from_gradient(tempTroughTotalVolume.get(volumeNodeLevel - troughsNumberOfNodes) / tempTroughTotalVolume.max(), 0, 1, color.new(vn_troughVolumeColor, 95), color.new(vn_troughVolumeColor, 31))  

                    VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (volumeNodeLevel - 2 * troughsNumberOfNodes + .1) * priceStep, endVolumeNodeIndex, lowestPrice + (volumeNodeLevel - 2 * troughsNumberOfNodes + .9) * priceStep, 
                                          color(na), bgcolor = vn_troughVolumeColor))


                    if vn_troughsShow == 'Clusters'

                        for currentVolumeNode = volumeNodeLevel - 2 * troughsNumberOfNodes to volumeNodeLevel

                            if currentVolumeNode >= troughsNumberOfNodes and currentVolumeNode <= vp_profileNumberOfRows - 1 + troughsNumberOfNodes

                                if not volumeDataArray.troughVolume.get(currentVolumeNode - troughsNumberOfNodes)
                                    startVolumeNodeIndex := vp_profileShow ? profilePlacementRight ? 
                                                                         VP.startIndex : 
                                                                         volumeDataArray.endProfileIndex.get(currentVolumeNode - troughsNumberOfNodes) : //VP.startIndex + int(volumeDataArray.totalVolume.get(currentVolumeNode) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                         VP.startIndex

                                    endVolumeNodeIndex   := vp_profileShow ? profilePlacementRight ? 
                                                                         volumeDataArray.endProfileIndex.get(currentVolumeNode - troughsNumberOfNodes) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(currentVolumeNode) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                                         last_bar_index : 
                                                         last_bar_index

                                    VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (currentVolumeNode - troughsNumberOfNodes + .0) * priceStep, endVolumeNodeIndex, lowestPrice + (currentVolumeNode - troughsNumberOfNodes + 1.) * priceStep, 
                                                      color(na), bgcolor = vn_troughVolumeColor))
                                    volumeDataArray.troughVolume.set(currentVolumeNode - troughsNumberOfNodes, true)

        tempTroughTotalVolume.clear()

    if vn_highestNVolumeNodes > 0
        for highestNode = 0 to vn_highestNVolumeNodes - 1
            startVolumeNodeIndex = vp_profileShow ? profilePlacementRight ? 
                                                     VP.startIndex : 
                                                     volumeDataArray.endProfileIndex.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode))) : //VP.startIndex + int(volumeDataArray.totalVolume.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode))) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                     VP.startIndex
            endVolumeNodeIndex   = vp_profileShow ? profilePlacementRight ? 
                                                     volumeDataArray.endProfileIndex.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode))) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode))) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                     last_bar_index : 
                                     last_bar_index

            VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode)) + .1) * priceStep, endVolumeNodeIndex, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode)) + .9) * priceStep, color(na), bgcolor = vn_highestVolumeColor))

    if vn_lowestNVolumeNodes > 0

        lowestNVolumeNodeCount = 0
        lowestNVolumeNodeIndex = 0//volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min())
        lowestNVolumeNodeValue = 0.

        while lowestNVolumeNodeCount < vn_lowestNVolumeNodes

            if lowestNVolumeNodeIndex == vp_profileNumberOfRows
                break

            if volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex) != lowestNVolumeNodeValue
                lowestNVolumeNodeValue := volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex)

                startVolumeNodeIndex = vp_profileShow ? profilePlacementRight ? 
                                                     VP.startIndex : 
                                                     volumeDataArray.endProfileIndex.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex))) : //VP.startIndex + int(volumeDataArray.totalVolume.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex))) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                     VP.startIndex

                endVolumeNodeIndex   = vp_profileShow ? profilePlacementRight ? 
                                                     volumeDataArray.endProfileIndex.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex))) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex))) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                     last_bar_index : 
                                     last_bar_index

                VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex)) + .1) * priceStep, endVolumeNodeIndex, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex)) + .9) * priceStep, color(na), bgcolor = vn_lowestVolumeColor))
                lowestNVolumeNodeCount += 1
            lowestNVolumeNodeIndex += 1

    log.info("yaz_kizim {0} {1}", VP.boxes.size(), volumeDataArray.totalVolume.size() )

//---------------------------------------------------------------------------------------------------------------------}
These users thanked the author iardavan for the post:
RodrigoRT7
When your strategy is based on hope, it's a HOPELESS strategy!

Re: Already Converted TradingView Indicators to MT4 Indicators

625
iardavan wrote: Wed Jun 11, 2025 10:34 am Hello and regards to everyone,

This LuxAlgo indicator is very useful — especially the way it automatically identifies peak points and draws support and resistance levels based on volume peaks and valleys.

I would really appreciate it if this indicator could be converted to MQL4 so that it can be used in MetaTrader 4.

Also, it would be great if the indicator could be anchored manually, similar to Anchored Volume Profile — allowing the user to move and resize the range between lines, rather than relying on a fixed number of bars.

If you need any additional information to help with the conversion, I am more than happy to provide it.

Thank you very much in advance for making this available for MT4!

https://www.luxalgo.com/library/indicat ... detection/

Main code:

Code: Select all

// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © LuxAlgo
//@version=5

indicator("Volume Profile with Node Detection [LuxAlgo]", "LuxAlgo - Volume Profile with Node Detection", overlay = true, max_boxes_count = 500, max_bars_back = 5000)

//---------------------------------------------------------------------------------------------------------------------
// Settings
//---------------------------------------------------------------------------------------------------------------------{

display = display.all - display.status_line

vn_volumeNodesGroup = 'Volume Nodes'

vn_peakTTip = 'A volume peak node is recognized when the volume profile nodes for the N preceding and N succeeding nodes are lower than that of the evaluated one, where N is determined by the \'Node Detection Percent %\' option'
vn_peaksShow = input.string('Peaks', 'Volume Peaks', options = ['Peaks', 'Clusters', 'None'], inline = 'vnP', tooltip = vn_peakTTip, group = vn_volumeNodesGroup, display = display)
vn_peakVolumeColor = input.color(color.new(color.blue, 50), '', inline = 'vnP', group = vn_volumeNodesGroup)
vn_peaksNumberOfNodes = input.int(9, '  Node Detection Percent %', minval = 0, maxval = 100, group = vn_volumeNodesGroup, display = display) / 100
vn_peaksShow := vn_peaksNumberOfNodes == 0 ? 'None' : vn_peaksShow

vn_troughsTTip  = 'A volume trough node is recognized when the volume profile nodes for the N preceding and N succeeding nodes exceed that of the evaluated one, where N is determined by the \'Node Detection Percent %\' option'
vn_troughsShow = input.string('None', 'Volume Troughs', options = ['Troughs', 'Clusters', 'None'], inline = 'vnT', tooltip = vn_troughsTTip, group = vn_volumeNodesGroup, display = display)
vn_troughVolumeColor = input.color(color.new(color.gray, 50), '', inline = 'vnT', group = vn_volumeNodesGroup)
vn_troughsNumberOfNodes = input.int(7, '  Node Detection Percent %', minval = 0, maxval = 100, group = vn_volumeNodesGroup, display = display) / 100
vn_troughsShow := vn_troughsNumberOfNodes == 0 ? 'None' : vn_troughsShow

vn_thresholdTTip = 'A threshold value specified as a percentage is utilized to detect peak/trough volume nodes. If a value is set, the detection will disregard volume node values lower than the specified threshold.'
vn_VolumeNodeThreshold = input.int(1, 'Volume Node Threshold %', minval = 0, maxval = 100, tooltip = vn_thresholdTTip, group = vn_volumeNodesGroup, display = display) / 100

vn_highestNVolumeNodes = input.int(0, 'Highest Volume Nodes', minval = 0, maxval = 31, inline = 'vnL', group = vn_volumeNodesGroup, display = display)
vn_highestVolumeColor = input.color(color.new(color.orange, 25), '', inline = 'vnL', group = vn_volumeNodesGroup)

vn_lowestNVolumeNodes = input.int(0, 'Lowest Volume Nodes', minval = 0, maxval = 31, inline = 'vnH', group = vn_volumeNodesGroup, display = display)
vn_lowestVolumeColor = input.color(color.new(color.navy, 25), '', inline = 'vnH', group = vn_volumeNodesGroup)

vp_componentsGroup = 'Volume Profile - Components'

vp_profileShow = input.bool(true, 'Volume Profile', inline = 'vp', group = vp_componentsGroup)
vp_profileGradientColors = input.string('Gradient Colors', '', options = ['Gradient Colors', 'Classic Colors' ], inline = 'vp', group = vp_componentsGroup)
vp_valueAreaUpColor = input.color(color.new(#2962ff, 30), '  Value Area Up / Down', inline = 'VA', group = vp_componentsGroup)
vp_valueAreaDwonColor = input.color(color.new(#fbc02d, 30), '/', inline = 'VA', group = vp_componentsGroup)
vp_profileUpVolumeColor = input.color(color.new(#5d606b, 50), '  Profile Up / Down Volume', inline = 'VP', group = vp_componentsGroup)
vp_profileDownVolumeColor = input.color(color.new(#d1d4dc, 50), '/', inline = 'VP', group = vp_componentsGroup)

vp_pocShow = input.string('None', 'Point of Control', options = ['Developing', 'Regular', 'None'], inline = 'poc', group = vp_componentsGroup, display = display)
vp_pocColor = input.color(#fbc02d, '', inline = 'poc', group = vp_componentsGroup)
vp_pocWidth = input.int(2, 'Width', inline = 'poc', group = vp_componentsGroup, display = display)

vp_vahShow = input.bool(false, 'Value Area High (VAH)', inline = 'vah', group = vp_componentsGroup)
vp_vahColor = input.color(#2962ff, '', inline = 'vah', group = vp_componentsGroup)

vp_valShow = input.bool(false, 'Value Area Low (VAL)', inline = 'val', group = vp_componentsGroup)
vp_valColor = input.color(#2962ff, '', inline = 'val', group = vp_componentsGroup)

vp_profileLevels = input.string('Small', "Profile Price Labels", options=['Tiny', 'Small', 'Normal', 'None'], group = vp_componentsGroup, display = display)

vp_displayGroup = 'Volume Profile - Display Settings'

vp_profileLength = input.int(360, 'Profile Lookback Length', minval = 10, maxval = 5000, step = 10, group = vp_displayGroup, display = display)
vp_profileLength:= last_bar_index < vp_profileLength ? last_bar_index : vp_profileLength - 1

vp_valueAreaThreshold = input.float(70, 'Value Area (%)', minval = 0, maxval = 100, group = vp_displayGroup, display = display) / 100

vp_profilePlracment = input.string('Right', 'Profile Placement', options = ['Right', 'Left'], group = vp_displayGroup, display = display), profilePlacementRight = vp_profilePlracment == 'Right' 
vp_profileNumberOfRows = input.int(100, 'Profile Number of Rows' , minval = 30, maxval = 130 , step = 10, group = vp_displayGroup, display = display)
vp_profileWidth = input.float(31, 'Profile Width', minval = 0, maxval = 250, group = vp_displayGroup, display = display) / 100
vp_profileHorizontalOffset = input.int(13, 'Profile Horizontal Offset', maxval = 50, group = vp_displayGroup, display = display)

vp_valueAreaBackground = input.bool(false, 'Value Area Background  ', inline = 'vBG', group = vp_displayGroup)
vp_valueAreaBackgroundColor = input.color(color.new(#2962ff, 89), '', inline = 'vBG', group = vp_displayGroup)

vp_profileBackground = input.bool(false, 'Profile Range Background ', inline = 'pBG', group = vp_displayGroup)
vp_profileBackgroundColor  = input.color(color.new(#2962ff, 95), '', inline = 'pBG', group = vp_displayGroup)

//---------------------------------------------------------------------------------------------------------------------}
// User Defined Types
//---------------------------------------------------------------------------------------------------------------------{

type BAR
    float open   = open
    float high   = high
    float low    = low
    float close  = close
    float volume = volume
    int   index  = bar_index

type barData
    float [] barHigh
    float [] barLow
    float [] barVolume
    bool  [] barPolarity
    int   [] barCount

type volumeData
    float [] totalVolume
    float [] bullishVolume
    float [] bearishVolume
    int   [] endProfileIndex
    bool  [] peakVolume
    bool  [] troughVolume

type volumeProfile
    box         []  boxes
    chart.point []  pocPoints
    polyline        pocPolyline
    int             pocLevel
    int             vahLevel
    int             valLevel
    int             startIndex

//---------------------------------------------------------------------------------------------------------------------}
// Variables
//---------------------------------------------------------------------------------------------------------------------{

BAR bar = BAR.new()
BAR [] ltfBarData = array.new<BAR> (1, BAR.new())

var barData barDataArray = barData.new(
     array.new <float> (na), 
     array.new <float> (na), 
     array.new <float> (na), 
     array.new <bool>  (na), 
     array.new <int>   (na)
 )

volumeData volumeDataArray = volumeData.new(
     array.new <float> (vp_profileNumberOfRows, 0.), 
     array.new <float> (vp_profileNumberOfRows, 0.), 
     array.new <float> (vp_profileNumberOfRows, 0.),
     array.new <int>   (vp_profileNumberOfRows, 0 ),
     array.new <bool>  (vp_profileNumberOfRows, 0.),
     array.new <bool>  (vp_profileNumberOfRows, 0.)
 )

var volumeProfile VP = volumeProfile.new(
     array.new<box>         (na),
     array.new<chart.point> (na),
     polyline.new           (na), na, na, na, na
 )

var float highestPrice = na
var float lowestPrice = na

//---------------------------------------------------------------------------------------------------------------------}
// Functions / Methods
//---------------------------------------------------------------------------------------------------------------------{

renderLine(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width) =>
    var id = line.new(_x1, _y1, _x2, _y2, _xloc, _extend, _color, _style, _width)
    line.set_xy1(id, _x1, _y1)
    line.set_xy2(id, _x2, _y2)
    line.set_color(id, _color)

renderLabel(_x, _y, _text, _color, _style, _textcolor, _size, _tooltip) =>
    var lb = label.new(_x, _y, _text, xloc.bar_index, yloc.price, _color, _style, _textcolor, _size, text.align_left, _tooltip)
    lb.set_xy(_x, _y)
    lb.set_text(_text)
    lb.set_tooltip(_tooltip)
    lb.set_textcolor(_textcolor)

requestBarData(_lowerTimeframe) => request.security_lower_tf(syminfo.tickerid, _lowerTimeframe, BAR.new(), ignore_invalid_timeframe = true)

calculateTimeframe(_depth) => 
    int tfInMs = timeframe.in_seconds(timeframe.period)
    int  mInMS = 60

    if _depth == 2
        switch
            tfInMs <                 30  =>  '1S'
            tfInMs <          1 * mInMS  =>  '5S'

            tfInMs <=        15 * mInMS  =>   '1'
            tfInMs <=        60 * mInMS  =>   '5'
            tfInMs <=       240 * mInMS  =>  '15'
            tfInMs <=      1440 * mInMS  =>  '60'
            => 'D'

    else if _depth == 1
        switch
            tfInMs <                 15  =>  '1S'
            tfInMs <                 30  =>  '5S'
            tfInMs <          1 * mInMS  => '15S'

            tfInMs <=         5 * mInMS  =>   '1'
            tfInMs <=        15 * mInMS  =>   '5'
            tfInMs <=        60 * mInMS  =>  '15'
            tfInMs <=       240 * mInMS  =>  '60'
            tfInMs <=      1440 * mInMS  => '240'
            => 'D'

getTextSize(_text) =>
    if _text != 'None'
        switch _text
            'Tiny'   => size.tiny
            'Small'  => size.small 
            'Normal' => size.normal
            => size.auto

//---------------------------------------------------------------------------------------------------------------------}
// Calculations - Volume Profile
//---------------------------------------------------------------------------------------------------------------------{

profileLevesSize  = getTextSize(vp_profileLevels)

if bar.index == last_bar_index - vp_profileLength
    VP.startIndex := bar.index
    lowestPrice := bar.low 
    highestPrice := bar.high
else if bar.index > last_bar_index - vp_profileLength
    lowestPrice := math.min(bar.low, lowestPrice)
    highestPrice := math.max(bar.high, highestPrice)

//if vp_profileLength <= 200
//    ltfBarData := requestBarData(calculateTimeframe(2))  
//else 
if vp_profileLength <= 700
    ltfBarData := requestBarData(calculateTimeframe(2)) 
else
    ltfBarData := array.new<BAR> (1, BAR.new(bar.open, bar.high, bar.low, bar.close, bar.volume))

if barstate.ishistory and (bar.index >= last_bar_index - vp_profileLength) and bar.index < last_bar_index and ltfBarData.size() > 0

    log.info("yaz_kizim {0} {1}", ltfBarData.get(0).volume, na(nz(ltfBarData.get(0).volume)) )

    if ltfBarData.size() > 0 and not na(nz(ltfBarData.get(0).volume))
        for currentLtfBar = 0 to ltfBarData.size() - 1
            barDataArray.barHigh.push(ltfBarData.get(currentLtfBar).high)
            barDataArray.barLow.push(ltfBarData.get(currentLtfBar).low)
            barDataArray.barVolume.push(ltfBarData.get(currentLtfBar).volume)
            barDataArray.barPolarity.push(ltfBarData.get(currentLtfBar).close > ltfBarData.get(currentLtfBar).open)

        barDataArray.barCount.push(ltfBarData.size())

priceStep = (highestPrice - lowestPrice) / vp_profileNumberOfRows

if barstate.islast and ltfBarData.size() > 0 //  barDataArray.barVolume.size() > 0 //

    if VP.boxes.size() > 0
        for boxIndex = 0 to VP.boxes.size() - 1
            box.delete(VP.boxes.shift())

    if barDataArray.barCount.size() > vp_profileLength
        barCount = barDataArray.barCount.shift()
        for barCountIndex = 0 to barCount - 1
            barDataArray.barHigh.shift()
            barDataArray.barLow.shift()
            barDataArray.barVolume.shift()
            barDataArray.barPolarity.shift()

    VP.pocPoints.clear()
    VP.pocPolyline.delete()

    if ltfBarData.size() > 0 and not na(nz(ltfBarData.get(0).volume))
        for currentLtfBar = 0 to ltfBarData.size() - 1
            barDataArray.barHigh.push(ltfBarData.get(currentLtfBar).high)
            barDataArray.barLow.push(ltfBarData.get(currentLtfBar).low)
            barDataArray.barVolume.push(ltfBarData.get(currentLtfBar).volume)
            barDataArray.barPolarity.push(ltfBarData.get(currentLtfBar).close > ltfBarData.get(currentLtfBar).open)

        barDataArray.barCount.push(ltfBarData.size())

    barIndex = vp_profileLength
    numberOfBars = 0
    arraySize = barDataArray.barVolume.size()

    for arrayIndex = 0 to arraySize - 1

        levelHigh = barDataArray.barHigh.get(arrayIndex)
        levelLow = barDataArray.barLow.get(arrayIndex)
        levelVolume = barDataArray.barVolume.get(arrayIndex)

        // Shoutout to @tkarolak for contributing to the code's optimization! Much appreciated.
        
        int startSlotIndex = math.max(math.floor((levelLow - lowestPrice) / priceStep), 0)
        int endSlotIndex = math.min(math.floor((levelHigh - lowestPrice) / priceStep), vp_profileNumberOfRows - 1)
        
        for priceLevelIndex = startSlotIndex to endSlotIndex

            float priceLevel = lowestPrice + priceLevelIndex * priceStep

            volumeProportion = switch
                levelLow >= priceLevel and levelHigh > priceLevel + priceStep => (priceLevel + priceStep - levelLow) / (levelHigh - levelLow)
                levelHigh <= priceLevel + priceStep and levelLow < priceLevel => (levelHigh - priceLevel) / (levelHigh - levelLow)
                levelLow >= priceLevel and levelHigh <= priceLevel + priceStep => 1
                => priceStep / (levelHigh - levelLow)

            volumeDataArray.totalVolume.set(priceLevelIndex, volumeDataArray.totalVolume.get(priceLevelIndex) + levelVolume * volumeProportion)

            if barDataArray.barPolarity.get(arrayIndex)
                volumeDataArray.bullishVolume.set(priceLevelIndex, volumeDataArray.bullishVolume.get(priceLevelIndex) + levelVolume * volumeProportion)

//        priceLevelIndex = 0
//        for priceLevel = lowestPrice to highestPrice - priceStep by priceStep
//
//            if levelHigh >= priceLevel and levelLow < priceLevel + priceStep
//
//                volumeProportion = if levelLow >= priceLevel and levelHigh > priceLevel + priceStep
//                    (priceLevel + priceStep - levelLow) / (levelHigh - levelLow)
//                else if levelHigh <= priceLevel + priceStep and levelLow < priceLevel
//                    (levelHigh - priceLevel) / (levelHigh - levelLow)
//                else if levelLow >= priceLevel and levelHigh <= priceLevel + priceStep
//                    1
//                else
//                    priceStep / (levelHigh - levelLow)
//
//                volumeDataArray.totalVolume.set(priceLevelIndex, volumeDataArray.totalVolume.get(priceLevelIndex) + levelVolume * volumeProportion)
//
//                if barDataArray.barPolarity.get(arrayIndex)
//                    volumeDataArray.bullishVolume.set(priceLevelIndex, volumeDataArray.bullishVolume.get(priceLevelIndex) + levelVolume * volumeProportion)
//            priceLevelIndex += 1

        if vp_pocShow == 'Developing'
            if arrayIndex == barDataArray.barCount.get(vp_profileLength - barIndex)
                VP.pocPoints.push(chart.point.from_index(bar.index[barIndex], math.avg(bar.high[barIndex], bar.low[barIndex])))
                VP.pocPoints.push(chart.point.from_index(bar.index[barIndex] + 1, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max()) + .5) * priceStep))
                numberOfBars += barDataArray.barCount.get(vp_profileLength - barIndex)
                barIndex  -= 1
            else if arrayIndex == (numberOfBars + barDataArray.barCount.get(vp_profileLength - barIndex)) and numberOfBars != 0
                VP.pocPoints.push(chart.point.from_index(bar.index[barIndex] + 1, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max()) + .5) * priceStep))
                numberOfBars += barDataArray.barCount.get(vp_profileLength - barIndex)
                barIndex  -= 1
            else if barIndex == 0
                VP.pocPoints.push(chart.point.from_index(bar.index[barIndex] + 1, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max()) + .5) * priceStep))
                numberOfBars += barDataArray.barCount.get(vp_profileLength - barIndex)

    VP.pocPolyline := polyline.new(VP.pocPoints, false, false, xloc.bar_index, vp_pocColor, color(na), line.style_solid, vp_pocWidth)

    for volumeIndex = 0 to vp_profileNumberOfRows - 1
        bearishVolume = 2 * volumeDataArray.bullishVolume.get(volumeIndex) - volumeDataArray.totalVolume.get(volumeIndex)
        volumeDataArray.bearishVolume.set(volumeIndex, volumeDataArray.bearishVolume.get(volumeIndex) + bearishVolume * (bearishVolume > 0 ? 1 : -1) )

    VP.pocLevel := volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max())
    totalTradedVolume = volumeDataArray.totalVolume.sum() * vp_valueAreaThreshold
    valueAreaVolume = VP.pocLevel != -1 ? volumeDataArray.totalVolume.get(VP.pocLevel) : 0
    VP.vahLevel := VP.pocLevel
    VP.valLevel := VP.pocLevel
    
    while valueAreaVolume < totalTradedVolume
        if VP.valLevel == 0 and VP.vahLevel == vp_profileNumberOfRows - 1
            break

        volumeAbovePOC = 0.
        if VP.vahLevel < vp_profileNumberOfRows - 1 
            volumeAbovePOC := volumeDataArray.totalVolume.get(VP.vahLevel + 1)

        volumeBelowPOC = 0.
        if VP.valLevel > 0
            volumeBelowPOC := volumeDataArray.totalVolume.get(VP.valLevel - 1)
        
        if volumeBelowPOC == 0 and volumeAbovePOC == 0
            break

        if volumeAbovePOC >= volumeBelowPOC
            valueAreaVolume  += volumeAbovePOC
            VP.vahLevel += 1
        else
            valueAreaVolume  += volumeBelowPOC
            VP.valLevel -= 1

    vahPrice = lowestPrice + (VP.vahLevel + 1.) * priceStep
    pocPrice = lowestPrice + (VP.pocLevel + .5) * priceStep
    valPrice = lowestPrice + (VP.valLevel + .0) * priceStep

    profilePlottingLength = vp_profileLength > 360 ? 360 : vp_profileLength
    profileWidth = profilePlottingLength * vp_profileWidth
    profileHorizontalOffset = int(profileWidth + vp_profileHorizontalOffset)

    if vp_profileShow and profilePlacementRight and vp_pocShow == 'Developing'
        renderLine(last_bar_index, pocPrice, profileHorizontalOffset + int(last_bar_index - profileWidth + 1), pocPrice, xloc.bar_index, extend.none, vp_pocColor, line.style_solid, vp_pocWidth)

    if vp_vahShow
        renderLine(VP.startIndex, vahPrice, 
                   profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                   vahPrice, xloc.bar_index, extend.none, vp_vahColor, line.style_solid, 1)
    
    if vp_pocShow == 'Regular'
        renderLine(VP.startIndex, pocPrice, profilePlacementRight ? vp_profileShow ? profileHorizontalOffset + int(last_bar_index - profileWidth + 1) : last_bar_index : last_bar_index, pocPrice, xloc.bar_index, extend.none, vp_pocColor, line.style_solid, vp_pocWidth)

    if vp_valShow
        renderLine(VP.startIndex, valPrice, 
                   profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                   valPrice, xloc.bar_index, extend.none, vp_valColor, line.style_solid, 1)

    if vp_valueAreaBackground
        VP.boxes.push(box.new(VP.startIndex, valPrice, last_bar_index, vahPrice, vp_valueAreaBackgroundColor, 1, line.style_dotted, bgcolor = vp_valueAreaBackgroundColor))

    if vp_profileBackground
        VP.boxes.push(box.new(VP.startIndex, lowestPrice, last_bar_index, highestPrice, vp_profileBackgroundColor, 1, line.style_dotted, bgcolor = vp_profileBackgroundColor))

    if vp_profileLevels != 'None' and VP.pocLevel != -1 
        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : vp_profileShow ? VP.startIndex : last_bar_index, 
                     highestPrice, str.tostring(highestPrice, format.mintick), color.new(chart.fg_color, 89), label.style_label_down, chart.fg_color, profileLevesSize, 'Profile High')

        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                     vahPrice, str.tostring(vahPrice, format.mintick), color.new(vp_vahColor, 89), label.style_label_left, vp_vahColor, profileLevesSize, 'Value Area High')

        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                     pocPrice, str.tostring(pocPrice, format.mintick), color.new(vp_pocColor, 89), label.style_label_left, vp_pocColor, profileLevesSize, 'Point of Control')
 
        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : last_bar_index, 
                     valPrice, str.tostring(valPrice, format.mintick), color.new(vp_valColor, 89), label.style_label_left, vp_valColor, profileLevesSize, 'Value Area Low')

        renderLabel(profilePlacementRight ? (vp_profileShow ? profileHorizontalOffset : 0) + last_bar_index : vp_profileShow ? VP.startIndex : last_bar_index, 
                     lowestPrice, str.tostring(lowestPrice, format.mintick), color.new(chart.fg_color, 89), label.style_label_up, chart.fg_color, profileLevesSize, 'Profile Low')

    for volumeNodeLevel = 0 to vp_profileNumberOfRows - 1
    
        if vp_profileShow
            if vp_profileGradientColors == 'Gradient Colors'
                vp_valueAreaUpColor       := color.from_gradient(volumeDataArray.totalVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max(), 0, 1, color.new(vp_valueAreaUpColor      , 95), color.new(vp_valueAreaUpColor      , 0))  
                vp_valueAreaDwonColor     := color.from_gradient(volumeDataArray.totalVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max(), 0, 1, color.new(vp_valueAreaDwonColor    , 95), color.new(vp_valueAreaDwonColor    , 0))  
                vp_profileUpVolumeColor   := color.from_gradient(volumeDataArray.totalVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max(), 0, 1, color.new(vp_profileUpVolumeColor  , 95), color.new(vp_profileUpVolumeColor  , 0))  
                vp_profileDownVolumeColor := color.from_gradient(volumeDataArray.totalVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max(), 0, 1, color.new(vp_profileDownVolumeColor, 95), color.new(vp_profileDownVolumeColor, 0))  
 
            startProfileIndex = profilePlacementRight ? 
                                 profileHorizontalOffset + int(last_bar_index - volumeDataArray.bullishVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max() * profileWidth) :
                                 VP.startIndex
            endProfileIndex   = profilePlacementRight ? 
                                 profileHorizontalOffset + last_bar_index : 
                                 int(startProfileIndex + volumeDataArray.bullishVolume.get(volumeNodeLevel) / volumeDataArray.totalVolume.max() * profileWidth)

            VP.boxes.push(box.new(startProfileIndex, lowestPrice + (volumeNodeLevel + .1) * priceStep, endProfileIndex, lowestPrice + (volumeNodeLevel + .9) * priceStep, 
                                   color(na), bgcolor = volumeNodeLevel >= VP.valLevel and volumeNodeLevel <= VP.vahLevel ? vp_valueAreaUpColor : vp_profileUpVolumeColor))

            startProfileIndex := profilePlacementRight ? startProfileIndex : endProfileIndex
            endProfileIndex   := profilePlacementRight ? 
                                 startProfileIndex - int( (volumeDataArray.totalVolume.get(volumeNodeLevel) - volumeDataArray.bullishVolume.get(volumeNodeLevel)) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                 startProfileIndex + int( (volumeDataArray.totalVolume.get(volumeNodeLevel) - volumeDataArray.bullishVolume.get(volumeNodeLevel)) / volumeDataArray.totalVolume.max() * profileWidth)

            VP.boxes.push(box.new(startProfileIndex, lowestPrice + (volumeNodeLevel + .1) * priceStep, endProfileIndex, lowestPrice + (volumeNodeLevel + .9) * priceStep, 
                                   color(na), bgcolor = volumeNodeLevel >= VP.valLevel and volumeNodeLevel <= VP.vahLevel ? vp_valueAreaDwonColor : vp_profileDownVolumeColor))
            volumeDataArray.endProfileIndex.set(volumeNodeLevel, endProfileIndex)

    if  vn_peaksShow != 'None' or  vn_troughsShow != 'None'
        var int startVolumeNodeIndex = na, var int endVolumeNodeIndex = na
        var bool peakUpperNth = na, var bool peakLowerNth = na

        peaksNumberOfNodes = int(vp_profileNumberOfRows * vn_peaksNumberOfNodes)

        tempPeakTotalVolume = volumeDataArray.totalVolume.copy()

        for index = 1 to peaksNumberOfNodes
            tempPeakTotalVolume.unshift(0.)
            tempPeakTotalVolume.push(0.)

        for volumeNodeLevel = 0 to vp_profileNumberOfRows - 1 + 2 * peaksNumberOfNodes 

            if vn_peaksShow != 'None' and volumeNodeLevel >= 2 * peaksNumberOfNodes 

                for currentVolumeNode = volumeNodeLevel - 2 * peaksNumberOfNodes to volumeNodeLevel - peaksNumberOfNodes - 1
                    if tempPeakTotalVolume.get(volumeNodeLevel - peaksNumberOfNodes) <= tempPeakTotalVolume.get(currentVolumeNode)
                        peakUpperNth := false
                        break
                    else
                        peakUpperNth := true

                for currentVolumeNode = volumeNodeLevel - peaksNumberOfNodes + 1 to volumeNodeLevel
                    if tempPeakTotalVolume.get(volumeNodeLevel - peaksNumberOfNodes) <= tempPeakTotalVolume.get(currentVolumeNode)
                        peakLowerNth := false
                        break
                    else
                        peakLowerNth := true

                if peakUpperNth and peakLowerNth and tempPeakTotalVolume.get(volumeNodeLevel - peaksNumberOfNodes) / tempPeakTotalVolume.max() > vn_VolumeNodeThreshold

                    startVolumeNodeIndex := vp_profileShow ? profilePlacementRight ? 
                                                             VP.startIndex : 
                                                             volumeDataArray.endProfileIndex.get(volumeNodeLevel - 2 * peaksNumberOfNodes) : //VP.startIndex + int(volumeDataArray.totalVolume.get(volumeNodeLevel - vn_peaksNumberOfNodes) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                             VP.startIndex

                    endVolumeNodeIndex   := vp_profileShow ? profilePlacementRight ? 
                                                             volumeDataArray.endProfileIndex.get(volumeNodeLevel - 2 * peaksNumberOfNodes) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(volumeNodeLevel - vn_peaksNumberOfNodes) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                             last_bar_index : 
                                             last_bar_index

                    vn_peakVolumeColor := vn_peaksShow == 'Peaks' ? vn_peakVolumeColor : color.from_gradient(tempPeakTotalVolume.get(volumeNodeLevel - peaksNumberOfNodes) / tempPeakTotalVolume.max(), 0, 1, color.new(vn_peakVolumeColor, 95), color.new(vn_peakVolumeColor, 65))  

                    VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (volumeNodeLevel - 2 * peaksNumberOfNodes + .1) * priceStep, endVolumeNodeIndex, lowestPrice + (volumeNodeLevel - 2 * peaksNumberOfNodes + .9) * priceStep, 
                                          color(na), bgcolor = vn_peakVolumeColor))

                    if vn_peaksShow == 'Clusters'

                        for currentVolumeNode = volumeNodeLevel - 2 * peaksNumberOfNodes to volumeNodeLevel

                            if currentVolumeNode >= peaksNumberOfNodes and currentVolumeNode <= vp_profileNumberOfRows - 1 + peaksNumberOfNodes
                                if not volumeDataArray.peakVolume.get(currentVolumeNode - peaksNumberOfNodes)

                                    startVolumeNodeIndex := vp_profileShow ? profilePlacementRight ? 
                                                                         VP.startIndex : 
                                                                         volumeDataArray.endProfileIndex.get(currentVolumeNode - peaksNumberOfNodes) : //VP.startIndex + int(volumeDataArray.totalVolume.get(currentVolumeNode) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                         VP.startIndex

                                    endVolumeNodeIndex   := vp_profileShow ? profilePlacementRight ? 
                                                                         volumeDataArray.endProfileIndex.get(currentVolumeNode - peaksNumberOfNodes) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(currentVolumeNode) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                                         last_bar_index : 
                                                         last_bar_index

                                    VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (currentVolumeNode - peaksNumberOfNodes + .0) * priceStep, endVolumeNodeIndex, lowestPrice + (currentVolumeNode - peaksNumberOfNodes + 1.) * priceStep, 
                                                       color(na), bgcolor = vn_peakVolumeColor))
                                    volumeDataArray.peakVolume.set(currentVolumeNode - peaksNumberOfNodes, true)

        tempPeakTotalVolume.clear()

        var bool troughUpperNth = na, var bool troughLowerNth = na
        troughsNumberOfNodes = int(vp_profileNumberOfRows * vn_troughsNumberOfNodes)

        tempTroughTotalVolume = volumeDataArray.totalVolume.copy()

        for index = 1 to troughsNumberOfNodes
            tempTroughTotalVolume.unshift(volumeDataArray.totalVolume.max())
            tempTroughTotalVolume.push(volumeDataArray.totalVolume.max())
            
        for volumeNodeLevel = 0 to vp_profileNumberOfRows - 1 + 2 * troughsNumberOfNodes 

            if vn_troughsShow != 'None' and volumeNodeLevel >= 2 * troughsNumberOfNodes 

                for currentVolumeNode = volumeNodeLevel - 2 * troughsNumberOfNodes to volumeNodeLevel - troughsNumberOfNodes - 1
                    if tempTroughTotalVolume.get(volumeNodeLevel - troughsNumberOfNodes) >= tempTroughTotalVolume.get(currentVolumeNode)
                        troughUpperNth := false
                        break
                    else
                        troughUpperNth := true

                for currentVolumeNode = volumeNodeLevel - troughsNumberOfNodes + 1 to volumeNodeLevel
                    if tempTroughTotalVolume.get(volumeNodeLevel - troughsNumberOfNodes) >= tempTroughTotalVolume.get(currentVolumeNode)
                        troughLowerNth := false
                        break
                    else
                        troughLowerNth := true

                if troughUpperNth and troughLowerNth and tempTroughTotalVolume.get(volumeNodeLevel - troughsNumberOfNodes) / tempTroughTotalVolume.max() > vn_VolumeNodeThreshold

                    startVolumeNodeIndex := vp_profileShow ? profilePlacementRight ? 
                                                             VP.startIndex : 
                                                             volumeDataArray.endProfileIndex.get(volumeNodeLevel - 2 * troughsNumberOfNodes) : //VP.startIndex + int(volumeDataArray.totalVolume.get(volumeNodeLevel - vn_troughsNumberOfNodes) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                             VP.startIndex

                    endVolumeNodeIndex   := vp_profileShow ? profilePlacementRight ? 
                                                             volumeDataArray.endProfileIndex.get(volumeNodeLevel - 2 * troughsNumberOfNodes) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(volumeNodeLevel - vn_troughsNumberOfNodes) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                             last_bar_index : 
                                             last_bar_index

                    vn_troughVolumeColor := vn_troughsShow == 'Troughs' ? vn_troughVolumeColor : color.from_gradient(tempTroughTotalVolume.get(volumeNodeLevel - troughsNumberOfNodes) / tempTroughTotalVolume.max(), 0, 1, color.new(vn_troughVolumeColor, 95), color.new(vn_troughVolumeColor, 31))  

                    VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (volumeNodeLevel - 2 * troughsNumberOfNodes + .1) * priceStep, endVolumeNodeIndex, lowestPrice + (volumeNodeLevel - 2 * troughsNumberOfNodes + .9) * priceStep, 
                                          color(na), bgcolor = vn_troughVolumeColor))


                    if vn_troughsShow == 'Clusters'

                        for currentVolumeNode = volumeNodeLevel - 2 * troughsNumberOfNodes to volumeNodeLevel

                            if currentVolumeNode >= troughsNumberOfNodes and currentVolumeNode <= vp_profileNumberOfRows - 1 + troughsNumberOfNodes

                                if not volumeDataArray.troughVolume.get(currentVolumeNode - troughsNumberOfNodes)
                                    startVolumeNodeIndex := vp_profileShow ? profilePlacementRight ? 
                                                                         VP.startIndex : 
                                                                         volumeDataArray.endProfileIndex.get(currentVolumeNode - troughsNumberOfNodes) : //VP.startIndex + int(volumeDataArray.totalVolume.get(currentVolumeNode) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                         VP.startIndex

                                    endVolumeNodeIndex   := vp_profileShow ? profilePlacementRight ? 
                                                                         volumeDataArray.endProfileIndex.get(currentVolumeNode - troughsNumberOfNodes) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(currentVolumeNode) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                                         last_bar_index : 
                                                         last_bar_index

                                    VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (currentVolumeNode - troughsNumberOfNodes + .0) * priceStep, endVolumeNodeIndex, lowestPrice + (currentVolumeNode - troughsNumberOfNodes + 1.) * priceStep, 
                                                      color(na), bgcolor = vn_troughVolumeColor))
                                    volumeDataArray.troughVolume.set(currentVolumeNode - troughsNumberOfNodes, true)

        tempTroughTotalVolume.clear()

    if vn_highestNVolumeNodes > 0
        for highestNode = 0 to vn_highestNVolumeNodes - 1
            startVolumeNodeIndex = vp_profileShow ? profilePlacementRight ? 
                                                     VP.startIndex : 
                                                     volumeDataArray.endProfileIndex.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode))) : //VP.startIndex + int(volumeDataArray.totalVolume.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode))) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                     VP.startIndex
            endVolumeNodeIndex   = vp_profileShow ? profilePlacementRight ? 
                                                     volumeDataArray.endProfileIndex.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode))) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode))) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                     last_bar_index : 
                                     last_bar_index

            VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode)) + .1) * priceStep, endVolumeNodeIndex, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.max(highestNode)) + .9) * priceStep, color(na), bgcolor = vn_highestVolumeColor))

    if vn_lowestNVolumeNodes > 0

        lowestNVolumeNodeCount = 0
        lowestNVolumeNodeIndex = 0//volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min())
        lowestNVolumeNodeValue = 0.

        while lowestNVolumeNodeCount < vn_lowestNVolumeNodes

            if lowestNVolumeNodeIndex == vp_profileNumberOfRows
                break

            if volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex) != lowestNVolumeNodeValue
                lowestNVolumeNodeValue := volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex)

                startVolumeNodeIndex = vp_profileShow ? profilePlacementRight ? 
                                                     VP.startIndex : 
                                                     volumeDataArray.endProfileIndex.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex))) : //VP.startIndex + int(volumeDataArray.totalVolume.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex))) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                     VP.startIndex

                endVolumeNodeIndex   = vp_profileShow ? profilePlacementRight ? 
                                                     volumeDataArray.endProfileIndex.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex))) : //profileHorizontalOffset + int(last_bar_index - volumeDataArray.totalVolume.get(volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex))) / volumeDataArray.totalVolume.max() * profileWidth) : 
                                                     last_bar_index : 
                                     last_bar_index

                VP.boxes.push(box.new(startVolumeNodeIndex, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex)) + .1) * priceStep, endVolumeNodeIndex, lowestPrice + (volumeDataArray.totalVolume.indexof(volumeDataArray.totalVolume.min(lowestNVolumeNodeIndex)) + .9) * priceStep, color(na), bgcolor = vn_lowestVolumeColor))
                lowestNVolumeNodeCount += 1
            lowestNVolumeNodeIndex += 1

    log.info("yaz_kizim {0} {1}", VP.boxes.size(), volumeDataArray.totalVolume.size() )

//---------------------------------------------------------------------------------------------------------------------}
Very interesting to mark points of interest of the market profile :D