// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.
0 at
https://mozilla.org/MPL/2.0/
// © fluxchart
//@version=5
const bool DEBUG = false
const bool OBSettings = false
const int maxBoxesCount = 500
const int maxRowSize = 50
const int priceBoxSize = 5
const int v2Gap = 1
const string v2TextSize = size.small
const int edgeOffset = 25
const int minDistance = 125
const float overlapThresholdPercentage = 0
const int maxDistanceToLastBar = 1750 // Affects Running Time
const int maxOrderBlocks = 30
indicator("Weighted Volume Profile Pivot Points | Flux Charts", shorttitle = "WVPPP | Flux Charts",
overlay = true, max_boxes_count = maxBoxesCount, max_labels_count = maxBoxesCount,
max_lines_count = maxBoxesCount, max_bars_back = 1005)
showPreviousSessions = input.bool(true, "Show Previous Sessions", group = "General Configuration",
display = display.none)
dbgMode = input.string("Order Blocks", "Mode", options = ["Pivots", "Order Blocks"], group = "General
Configuration")
analyzeBars = input.int(200, "Analyze Bars", minval = 1, maxval = 1000, group = "General Configuration")
rowCount = input.int(30, "Row Count", minval = 1, maxval = 1000, group = "General Configuration")
volumeWeight = input.string("Normal", "Volume Weighting", ["Recent", "Normal", "Past"], group =
"General Configuration", display = display.none)
swingLen = input.int(10, "Swing Length", minval = 1, group = "General Configuration")
keepOldPOC = input.bool(true, "Keep Past POC Lines", group = "General Configuration")
intersectionBody = DEBUG ? input.bool(false, "[DBG] Intersection With Body", group = "General
Configuration") : false
// OB Settings
showInvalidated = OBSettings ? input.bool(true, "Show Historic Zones", group = "Order Blocks", display =
display.none) : true
OBRendering = OBSettings ? input.bool(true, "Show Order Blocks", group = "Order Blocks", display =
display.none) : false
orderBlockVolumetricInfo = OBSettings ? input.bool(true, "Volumetric Info", group = "Order Blocks",
inline="EV", display = display.none) : true
obEndMethod = OBSettings ? input.string("Wick", "Zone Invalidation", options = ["Wick", "Close"],
group = "Order Blocks", display = display.none) : "Wick"
combineOBs = DEBUG ? input.bool(true, "Combine Zones", group = "Order Blocks", display =
display.none) : true
maxATRMult = DEBUG ? input.float(3.5,"Max Atr Multiplier", group = "Order Blocks") : 3.5
zoneCount = OBSettings ? input.string("Low", 'Zone Count', options = ["High", "Medium", "Low", "One"],
tooltip = "Number of Order Block Zones to be rendered. Higher options will result in older Order Blocks
shown.", group = "Order Blocks", display = display.none) : "Low"
bullOrderBlockColor = OBSettings ? input(#08998180, 'Bullish', inline = 'obColor', group = 'Order Blocks',
display = display.none) : #08998180
bearOrderBlockColor = OBSettings ? input(#f2364680, 'Bearish', inline = 'obColor', group = 'Order
Blocks', display = display.none) : #f2364680
bullishOrderBlocks = zoneCount == "One" ? 1 : zoneCount == "Low" ? 3 : zoneCount == "Medium" ? 5 :
10
bearishOrderBlocks = zoneCount == "One" ? 1 : zoneCount == "Low" ? 3 : zoneCount == "Medium" ? 5 :
10
timeframe1Enabled = true
timeframe1 = ""
highlightSessions = input.bool(true, "Highlight Sessions", group = "Style", display = display.none)
dbgStyling = DEBUG ? input.string("v2", "[DBG] Styling", ["v1", "v2"], group = "Style", display =
display.none) : "v2"
alignSetting = input.string("Left", "Align To", options = ["Left", "Right"], group = "Style", display =
display.none)
POCEnabled = input.bool(true, "POC Line ", inline = "POC", group = "Style", display = display.none)
POCLineStyle = input.string("____", " ", options = ["____", "----", "...."], group = "Style", inline = "POC",
display = display.none)
POCLineWidth = input.int(2, "", options = [1,2,3], group = "Style", inline = "POC", display = display.none)
POCLineColor = input.color(color.yellow, "", group = "Style", inline = "POC", display = display.none)
bullishColor = input.color(#089981, "Bullish", inline = "color", group = "Style", display = display.none)
bearishColor = input.color(#f23646, "Bearish", inline = "color", group = "Style", display = display.none)
textColor = input.color(#ffffff80, "Text Color", group = "Style")
extendZonesBy = DEBUG ? input.int(15, "Extend Zones", group = "Style", minval = 1, maxval = 30, inline =
"ExtendZones") : 15
extendZonesDynamic = DEBUG ? input.bool(true, "Dynamic", group = "Style", inline = "ExtendZones") :
true
combinedText = DEBUG ? input.bool(false, "Combined Text", group = "Style", inline =
"CombinedColor") : false
volumeBarsPlace = DEBUG ? input.string("Left", "Show Volume Bars At", options = ["Left", "Right"],
group = "Style", inline = "volumebars") : "Left"
mirrorVolumeBars = DEBUG ? input.bool(true, "Mirror Volume Bars", group = "Style", inline =
"volumebars") : true
volumeBarsLeftSide = (volumeBarsPlace == "Left")
extendZonesByTime = extendZonesBy * timeframe.in_seconds(timeframe.period) * 1000
atr = ta.atr(10)
type orderBlockInfo
float top
float bottom
float obVolume
string obType
int startTime
float bbVolume
float obLowVolume
float obHighVolume
bool breaker
int breakTime
string timeframeStr
bool disabled = false
string combinedTimeframesStr = na
bool combined = false
type orderBlock
orderBlockInfo info
bool isRendered = false
box orderBox = na
box breakerBox = na
line orderBoxLineTop = na
line orderBoxLineBottom = na
line breakerBoxLineTop = na
line breakerBoxLineBottom = na
//
box orderBoxText = na
box orderBoxPositive = na
box orderBoxNegative = na
line orderSeperator = na
line orderTextSeperator = na
createOrderBlock (orderBlockInfo orderBlockInfoF) =>
orderBlock newOrderBlock = orderBlock.new(orderBlockInfoF)
newOrderBlock
safeDeleteOrderBlock (orderBlock orderBlockF) =>
orderBlockF.isRendered := false
box.delete(orderBlockF.orderBox)
box.delete(orderBlockF.breakerBox)
box.delete(orderBlockF.orderBoxText)
box.delete(orderBlockF.orderBoxPositive)
box.delete(orderBlockF.orderBoxNegative)
line.delete(orderBlockF.orderBoxLineTop)
line.delete(orderBlockF.orderBoxLineBottom)
line.delete(orderBlockF.breakerBoxLineTop)
line.delete(orderBlockF.breakerBoxLineBottom)
line.delete(orderBlockF.orderSeperator)
line.delete(orderBlockF.orderTextSeperator)
type timeframeInfo
int index = na
string timeframeStr = na
bool isEnabled = false
orderBlockInfo[] bullishOrderBlocksList = na
orderBlockInfo[] bearishOrderBlocksList = na
newTimeframeInfo (index, timeframeStr, isEnabled) =>
newTFInfo = timeframeInfo.new()
newTFInfo.index := index
newTFInfo.isEnabled := isEnabled
newTFInfo.timeframeStr := timeframeStr
newTFInfo
type obSwing
int x = na
float y = na
float swingVolume = na
bool crossed = false
// ____ TYPES END ____
var timeframeInfo[] timeframeInfos = array.from(newTimeframeInfo(1, timeframe1,
timeframe1Enabled))
var bullishOrderBlocksList = array.new<orderBlockInfo>(0)
var bearishOrderBlocksList = array.new<orderBlockInfo>(0)
var allOrderBlocksList = array.new<orderBlock>(0)
moveLine(_line, _x, _y, _x2) =>
line.set_xy1(_line, _x, _y)
line.set_xy2(_line, _x2, _y)
moveBox (_box, _topLeftX, _topLeftY, _bottomRightX, _bottomRightY) =>
box.set_lefttop(_box, _topLeftX, _topLeftY)
box.set_rightbottom(_box, _bottomRightX, _bottomRightY)
isTimeframeLower (timeframe1F, timeframe2F) =>
timeframe.in_seconds(timeframe1F) < timeframe.in_seconds(timeframe2F)
getMinTimeframe (timeframe1F, timeframe2F) =>
if isTimeframeLower(timeframe1F, timeframe2F)
timeframe1F
else
timeframe2F
getMaxTimeframe (timeframe1F, timeframe2F) =>
if isTimeframeLower(timeframe1F, timeframe2F)
timeframe2F
else
timeframe1F
formatTimeframeString (formatTimeframe) =>
timeframeF = formatTimeframe == "" ? timeframe.period : formatTimeframe
if str.contains(timeframeF, "D") or str.contains(timeframeF, "W") or str.contains(timeframeF, "S") or
str.contains(timeframeF, "M")
timeframeF
else
seconds = timeframe.in_seconds(timeframeF)
if seconds >= 3600
hourCount = int(seconds / 3600)
str.tostring(hourCount) + " Hour" + (hourCount > 1 ? "s" : "")
else
timeframeF + " Min"
betterCross(s1, s2) =>
string ret = na
if s1 >= s2 and s1[1] < s2
ret := "Bull"
if s1 < s2 and s1[1] >= s2
ret := "Bear"
ret
colorWithTransparency (colorF, transparencyX) =>
color.new(colorF, color.t(colorF) * transparencyX)
createOBBox (boxColor, transparencyX = 1.0, xlocType = xloc.bar_time) =>
box.new(na, na, na, na, text_size = size.normal, xloc = xlocType, extend = extend.none, bgcolor =
colorWithTransparency(boxColor, transparencyX), text_color = textColor, text_halign =
text.align_center, border_color = #00000000)
renderOrderBlock (orderBlock ob) =>
orderBlockInfo info = ob.info
ob.isRendered := true
orderColor = ob.info.obType == "Bull" ? bullOrderBlockColor : bearOrderBlockColor
if OBRendering and (not false or not (false and info.breaker)) and not (not showInvalidated and
info.breaker)
ob.orderBox := createOBBox(orderColor, 1.5)
if ob.info.combined
ob.orderBox.set_bgcolor(colorWithTransparency(orderColor, 1.1))
ob.orderBoxText := createOBBox(color.new(color.white, 100))
if orderBlockVolumetricInfo
ob.orderBoxPositive := createOBBox(bullOrderBlockColor)
ob.orderBoxNegative := createOBBox(bearOrderBlockColor)
ob.orderSeperator :=
line.new(na,na,na,na,xloc.bar_time,extend.none,textColor,line.style_dashed,1)
ob.orderTextSeperator :=
line.new(na,na,na,na,xloc.bar_time,extend.none,textColor,line.style_solid,1)
zoneSize = extendZonesDynamic ? na(info.breakTime) ? extendZonesByTime : (info.breakTime -
info.startTime) : extendZonesByTime
if na(info.breakTime)
zoneSize := (time + 1) - info.startTime
startX = volumeBarsLeftSide ? info.startTime : info.startTime + zoneSize - zoneSize / 3
maxEndX = volumeBarsLeftSide ? info.startTime + zoneSize / 3 : info.startTime + zoneSize
moveBox(ob.orderBox, info.startTime, info.top, info.startTime + zoneSize, info.bottom)
moveBox(ob.orderBoxText, volumeBarsLeftSide ? maxEndX : info.startTime, info.top,
volumeBarsLeftSide ? info.startTime + zoneSize : startX, info.bottom)
percentage = int((math.min(info.obHighVolume, info.obLowVolume) /
math.max(info.obHighVolume, info.obLowVolume)) * 100.0)
OBText = (na(ob.info.combinedTimeframesStr) ? formatTimeframeString(ob.info.timeframeStr) :
ob.info.combinedTimeframesStr) + " OB"
box.set_text(ob.orderBoxText, (orderBlockVolumetricInfo ? str.tostring(ob.info.obVolume,
format.volume) + " (" + str.tostring(percentage) + "%)\n" : "") + (combinedText and ob.info.combined ?
"[Combined]\n" : "") + OBText)
if orderBlockVolumetricInfo
showHighLowBoxText = false
curEndXHigh = int(math.ceil((info.obHighVolume / info.obVolume) * (maxEndX - startX) + startX))
curEndXLow = int(math.ceil((info.obLowVolume / info.obVolume) * (maxEndX - startX) + startX))
moveBox(ob.orderBoxPositive, mirrorVolumeBars ? startX : curEndXLow, info.top,
mirrorVolumeBars ? curEndXHigh : maxEndX, (info.bottom + info.top) / 2)
box.set_text(ob.orderBoxPositive, showHighLowBoxText ? str.tostring(info.obHighVolume,
format.volume) : "")
moveBox(ob.orderBoxNegative, mirrorVolumeBars ? startX : curEndXHigh, info.bottom,
mirrorVolumeBars ? curEndXLow : maxEndX, (info.bottom + info.top) / 2)
box.set_text(ob.orderBoxNegative, showHighLowBoxText ? str.tostring(info.obLowVolume,
format.volume) : "")
moveLine(ob.orderSeperator, volumeBarsLeftSide ? startX : maxEndX, (info.bottom + info.top) /
2, volumeBarsLeftSide ? maxEndX : startX)
line.set_xy1(ob.orderTextSeperator, volumeBarsLeftSide ? maxEndX : startX, info.top)
line.set_xy2(ob.orderTextSeperator, volumeBarsLeftSide ? maxEndX : startX, info.bottom)
findOBSwings(len) =>
var swingType = 0
var obSwing top = obSwing.new(na, na)
var obSwing bottom = obSwing.new(na, na)
upper = ta.highest(len)
lower = ta.lowest(len)
swingType := high[len] > upper ? 0 : low[len] < lower ? 1 : swingType
if swingType == 0 and swingType[1] != 0
top := obSwing.new(bar_index[len], high[len], volume[len])
if swingType == 1 and swingType[1] != 1
bottom := obSwing.new(bar_index[len], low[len], volume[len])
[top, bottom]
findOrderBlocks () =>
if bar_index > last_bar_index - maxDistanceToLastBar and barstate.isconfirmed
[top, btm] = findOBSwings(swingLen)
useBody = false
max = useBody ? math.max(close, open) : high
min = useBody ? math.min(close, open) : low
// Bullish Order Block
bullishBreaked = 0
if bullishOrderBlocksList.size() > 0
for i = bullishOrderBlocksList.size() - 1 to 0
currentOB = bullishOrderBlocksList.get(i)
if not currentOB.breaker
if (obEndMethod == "Wick" ? low : math.min(open, close)) < currentOB.bottom
currentOB.breaker := true
currentOB.breakTime := time
currentOB.bbVolume := volume
else
if high > currentOB.top
bullishOrderBlocksList.remove(i)
else if i < bullishOrderBlocks and top.y < currentOB.top and top.y > currentOB.bottom
bullishBreaked := 1
if close > top.y and not top.crossed
top.crossed := true
boxBtm = max[1]
boxTop = min[1]
boxLoc = time[1]
for i = 1 to (bar_index - top.x) - 1
boxBtm := math.min(min[i], boxBtm)
boxTop := boxBtm == min[i] ? max[i] : boxTop
boxLoc := boxBtm == min[i] ? time[i] : boxLoc
newOrderBlockInfo = orderBlockInfo.new(boxTop, boxBtm, volume + volume[1] + volume[2],
"Bull", boxLoc)
newOrderBlockInfo.obLowVolume := volume[2]
newOrderBlockInfo.obHighVolume := volume + volume[1]
obSize = math.abs(newOrderBlockInfo.top - newOrderBlockInfo.bottom)
if obSize <= atr * maxATRMult
bullishOrderBlocksList.unshift(newOrderBlockInfo)
if bullishOrderBlocksList.size() > maxOrderBlocks
bullishOrderBlocksList.pop()
// Bearish Order Block
bearishBreaked = 0
if bearishOrderBlocksList.size() > 0
for i = bearishOrderBlocksList.size() - 1 to 0
currentOB = bearishOrderBlocksList.get(i)
if not currentOB.breaker
if (obEndMethod == "Wick" ? high : math.max(open, close)) > currentOB.top
currentOB.breaker := true
currentOB.breakTime := time
currentOB.bbVolume := volume
else
if low < currentOB.bottom
bearishOrderBlocksList.remove(i)
else if i < bearishOrderBlocks and btm.y > currentOB.bottom and btm.y < currentOB.top
bearishBreaked := 1
if close < btm.y and not btm.crossed
btm.crossed := true
boxBtm = min[1]
boxTop = max[1]
boxLoc = time[1]
for i = 1 to (bar_index - btm.x) - 1
boxTop := math.max(max[i], boxTop)
boxBtm := boxTop == max[i] ? min[i] : boxBtm
boxLoc := boxTop == max[i] ? time[i] : boxLoc
newOrderBlockInfo = orderBlockInfo.new(boxTop, boxBtm, volume + volume[1] + volume[2],
"Bear", boxLoc)
newOrderBlockInfo.obLowVolume := volume + volume[1]
newOrderBlockInfo.obHighVolume := volume[2]
obSize = math.abs(newOrderBlockInfo.top - newOrderBlockInfo.bottom)
if obSize <= atr * maxATRMult
bearishOrderBlocksList.unshift(newOrderBlockInfo)
if bearishOrderBlocksList.size() > maxOrderBlocks
bearishOrderBlocksList.pop()
true
areaOfOB (orderBlockInfo OBInfoF) =>
float XA1 = OBInfoF.startTime
float XA2 = na(OBInfoF.breakTime) ? time + 1 : OBInfoF.breakTime
float YA1 = OBInfoF.top
float YA2 = OBInfoF.bottom
float edge1 = math.sqrt((XA2 - XA1) * (XA2 - XA1) + (YA2 - YA2) * (YA2 - YA2))
float edge2 = math.sqrt((XA2 - XA2) * (XA2 - XA2) + (YA2 - YA1) * (YA2 - YA1))
float totalArea = edge1 * edge2
totalArea
doOBsTouch (orderBlockInfo OBInfo1, orderBlockInfo OBInfo2) =>
float XA1 = OBInfo1.startTime
float XA2 = na(OBInfo1.breakTime) ? time + 1 : OBInfo1.breakTime
float YA1 = OBInfo1.top
float YA2 = OBInfo1.bottom
float XB1 = OBInfo2.startTime
float XB2 = na(OBInfo2.breakTime) ? time + 1 : OBInfo2.breakTime
float YB1 = OBInfo2.top
float YB2 = OBInfo2.bottom
float intersectionArea = math.max(0, math.min(XA2, XB2) - math.max(XA1, XB1)) * math.max(0,
math.min(YA1, YB1) - math.max(YA2, YB2))
float unionArea = areaOfOB(OBInfo1) + areaOfOB(OBInfo2) - intersectionArea
float overlapPercentage = (intersectionArea / unionArea) * 100.0
if overlapPercentage > overlapThresholdPercentage
true
else
false
isOBValid (orderBlockInfo OBInfo) =>
valid = true
if OBInfo.disabled
valid := false
valid
combineOBsFunc () =>
if allOrderBlocksList.size() > 0
lastCombinations = 999
while lastCombinations > 0
lastCombinations := 0
for i = 0 to allOrderBlocksList.size() - 1
curOB1 = allOrderBlocksList.get(i)
for j = 0 to allOrderBlocksList.size() - 1
curOB2 = allOrderBlocksList.get(j)
if i == j
continue
if not isOBValid(curOB1.info) or not isOBValid(curOB2.info)
continue
if curOB1.info.obType != curOB2.info.obType
continue
if doOBsTouch(curOB1.info, curOB2.info)
curOB1.info.disabled := true
curOB2.info.disabled := true
orderBlock newOB = createOrderBlock(orderBlockInfo.new(math.max(curOB1.info.top,
curOB2.info.top), math.min(curOB1.info.bottom, curOB2.info.bottom), curOB1.info.obVolume +
curOB2.info.obVolume, curOB1.info.obType))
newOB.info.startTime := math.min(curOB1.info.startTime, curOB2.info.startTime)
newOB.info.breakTime := math.max(nz(curOB1.info.breakTime),
nz(curOB2.info.breakTime))
newOB.info.breakTime := newOB.info.breakTime == 0 ? na : newOB.info.breakTime
newOB.info.timeframeStr := curOB1.info.timeframeStr
newOB.info.obVolume := curOB1.info.obVolume + curOB2.info.obVolume
newOB.info.obLowVolume := curOB1.info.obLowVolume + curOB2.info.obLowVolume
newOB.info.obHighVolume := curOB1.info.obHighVolume + curOB2.info.obHighVolume
newOB.info.bbVolume := nz(curOB1.info.bbVolume, 0) + nz(curOB2.info.bbVolume, 0)
newOB.info.breaker := curOB1.info.breaker or curOB2.info.breaker
newOB.info.combined := true
if timeframe.in_seconds(curOB1.info.timeframeStr) !=
timeframe.in_seconds(curOB2.info.timeframeStr)
newOB.info.combinedTimeframesStr := (na(curOB1.info.combinedTimeframesStr) ?
formatTimeframeString(curOB1.info.timeframeStr) : curOB1.info.combinedTimeframesStr) + " & " +
(na(curOB2.info.combinedTimeframesStr) ? formatTimeframeString(curOB2.info.timeframeStr) :
curOB2.info.combinedTimeframesStr)
allOrderBlocksList.unshift(newOB)
lastCombinations += 1
reqSeq (timeframeStr) =>
[bullishOrderBlocksListF, bearishOrderBlocksListF] = request.security(syminfo.tickerid, timeframeStr,
[bullishOrderBlocksList, bearishOrderBlocksList])
[bullishOrderBlocksListF, bearishOrderBlocksListF]
getTFData (timeframeInfo timeframeInfoF, timeframeStr) =>
if not isTimeframeLower(timeframeInfoF.timeframeStr, timeframe.period) and
timeframeInfoF.isEnabled
[bullishOrderBlocksListF, bearishOrderBlocksListF] = reqSeq(timeframeStr)
[bullishOrderBlocksListF, bearishOrderBlocksListF]
else
[na, na]
handleTimeframeInfo (timeframeInfo timeframeInfoF, bullishOrderBlocksListF, bearishOrderBlocksListF)
=>
if not isTimeframeLower(timeframeInfoF.timeframeStr, timeframe.period) and
timeframeInfoF.isEnabled
timeframeInfoF.bullishOrderBlocksList := bullishOrderBlocksListF
timeframeInfoF.bearishOrderBlocksList := bearishOrderBlocksListF
handleOrderBlocksFinal () =>
if DEBUG
log.info("Bullish OB Count " + str.tostring(bullishOrderBlocksList.size()))
log.info("Bearish OB Count " + str.tostring(bearishOrderBlocksList.size()))
if allOrderBlocksList.size () > 0
for i = 0 to allOrderBlocksList.size() - 1
safeDeleteOrderBlock(allOrderBlocksList.get(i))
allOrderBlocksList.clear()
for i = 0 to timeframeInfos.size() - 1
curTimeframe = timeframeInfos.get(i)
if not curTimeframe.isEnabled
continue
if curTimeframe.bullishOrderBlocksList.size() > 0
for j = 0 to math.min(curTimeframe.bullishOrderBlocksList.size() - 1, bullishOrderBlocks - 1)
orderBlockInfoF = curTimeframe.bullishOrderBlocksList.get(j)
orderBlockInfoF.timeframeStr := curTimeframe.timeframeStr
allOrderBlocksList.unshift(createOrderBlock(orderBlockInfo.copy(orderBlockInfoF)))
if curTimeframe.bearishOrderBlocksList.size() > 0
for j = 0 to math.min(curTimeframe.bearishOrderBlocksList.size() - 1, bearishOrderBlocks - 1)
orderBlockInfoF = curTimeframe.bearishOrderBlocksList.get(j)
orderBlockInfoF.timeframeStr := curTimeframe.timeframeStr
allOrderBlocksList.unshift(createOrderBlock(orderBlockInfo.copy(orderBlockInfoF)))
if combineOBs
combineOBsFunc()
if allOrderBlocksList.size() > 0
for i = 0 to allOrderBlocksList.size() - 1
curOB = allOrderBlocksList.get(i)
if isOBValid(curOB.info)
renderOrderBlock(curOB)
findOrderBlocks()
[bullishOrderBlocksListTimeframe1, bearishOrderBlocksListTimeframe1] =
getTFData(timeframeInfos.get(0), timeframe1)
if barstate.isconfirmed
handleTimeframeInfo(timeframeInfos.get(0), bullishOrderBlocksListTimeframe1,
bearishOrderBlocksListTimeframe1)
handleOrderBlocksFinal()
type rowType
float top
float bottom
int startIndex
int endIndex
float totalVolume = 0
float bullVolume = 0
float bearVolume = 0
box bullBox = na
box priceBox = na
box bearBox = na
type volumeProfile
array<rowType> rows
line POCLine = na
box highlightBox = na
int edgeIndex = na
int startIndex = na
float vpTop = na
float vpBottom = na
int vpStart = na
int vpEnd = na
type barType
float o = open
float c = close
float h = high
float l = low
float v = volume
var float validPivotHigh = na
var float validPivotLow = na
var int distanceToLastVP = 999
var volumeProfile latestVolumeProfile = na
if not na(latestVolumeProfile)
distanceToLastVP := bar_index - latestVolumeProfile.vpStart
pvHigh = ta.pivothigh(swingLen, swingLen)
pvLow = ta.pivotlow(swingLen, swingLen)
var float vph = na
var float vpl = na
vph := nz(pvHigh, vph)
vpl := nz(pvLow, vpl)
if dbgMode == "Pivots"
if (alignSetting == "Left" and distanceToLastVP > minDistance) or (alignSetting == "Right" and
distanceToLastVP > minDistance)
validPivotHigh := vph
validPivotLow := vpl
else if dbgMode == "Order Blocks"
if (alignSetting == "Left" and distanceToLastVP > minDistance) or (alignSetting == "Right" and
distanceToLastVP > minDistance)
if bullishOrderBlocksList.size() > 0
curOB = bullishOrderBlocksList.get(0)
validPivotHigh := curOB.bottom
if bearishOrderBlocksList.size() > 0
curOB = bearishOrderBlocksList.get(0)
validPivotLow := curOB.top
pivotsChanged = false
if ((validPivotHigh != validPivotHigh[1]) or (validPivotLow != validPivotLow[1])) and (not
na(validPivotHigh)) and (not na(validPivotLow)) and math.abs(validPivotHigh - validPivotLow) > atr * 5
pivotsChanged := true
float top = validPivotHigh
float bottom = validPivotLow
float step = (top - bottom) / rowCount
float gap = rowCount < 100 ? (step / 3.0) : 0
var line oldLine = na
var box oldBox = na
var int latestEdgeIndex = na
if pivotsChanged
if not na(latestVolumeProfile)
latestVolumeProfile.vpEnd := bar_index
oldBox := latestVolumeProfile.highlightBox
if not keepOldPOC
line.delete(latestVolumeProfile.POCLine)
latestEdgeIndex := latestVolumeProfile.edgeIndex
oldLine := latestVolumeProfile.POCLine
latestVolumeProfile := volumeProfile.new()
latestVolumeProfile.vpStart := bar_index
latestVolumeProfile.vpTop := top
latestVolumeProfile.vpBottom := bottom
latestVolumeProfile.rows := array.new<rowType>(rowCount)
for x = 0 to rowCount - 1
rowTop = top - step * x
latestVolumeProfile.rows.set(x, rowType.new(rowTop, rowTop - step))
doIntersect (candleTop, candleBottom, rowType row) =>
if (candleBottom > row.top)
false
else if (candleTop < row.bottom)
false
else
true
var float maxVolumeSize = 0
var float minVolumeSize = 0
f_lin_interpolate(x0, y0, x1, y1, x) =>
y0 + (x - x0) * (y1 - y0) / (x1 - x0)
renderRow (startIndex, flip, rowType row) =>
totalSize = f_lin_interpolate(minVolumeSize, 1, maxVolumeSize, maxRowSize, row.totalVolume)
bullSize = nz(math.round((row.bullVolume / row.totalVolume) * totalSize), 0)
bearSize = nz(math.round((row.bearVolume / row.totalVolume) * totalSize), 0)
bullPercent = str.tostring(bullSize * 100.0 / totalSize, "#.##") + "%"
bearPercent = str.tostring(100.0 - str.tonumber(str.replace(bullPercent,"%","")), "#.##") + "%"
rowPrice = (row.top + row.bottom) / 2.0
if dbgStyling == "v2"
row.startIndex := startIndex - bullSize
if flip
row.startIndex := startIndex + bullSize
else
row.startIndex := startIndex
row.endIndex := row.startIndex
if dbgStyling == "v1"
if bullSize + bearSize > 0
if not flip
row.endIndex := row.startIndex + bullSize + bearSize
row.bullBox := box.new(row.startIndex, row.top - gap / 2.0, row.startIndex + bullSize,
row.bottom + gap / 2.0, bgcolor = bullishColor, border_color = color.black, border_width = 0)
if bearSize > 0
row.bearBox := box.new(row.startIndex + bullSize, row.top - gap / 2.0, row.startIndex +
bullSize + bearSize, row.bottom + gap / 2.0, bgcolor = bearishColor, border_color = color.black,
border_width = 0)
else
row.endIndex := row.startIndex - bullSize - bearSize
row.bullBox := box.new(row.startIndex, row.top - gap / 2.0, row.startIndex - bullSize,
row.bottom + gap / 2.0, bgcolor = bullishColor, border_color = color.black, border_width = 0)
if bearSize > 0
row.bearBox := box.new(row.startIndex - bullSize, row.top - gap / 2.0, row.startIndex -
bullSize - bearSize, row.bottom + gap / 2.0, bgcolor = bearishColor, border_color = color.black,
border_width = 0)
else if dbgStyling == "v2"
if not flip
row.endIndex := row.startIndex + bullSize + v2Gap + priceBoxSize + v2Gap + bearSize
if bullSize > 0
row.bullBox := box.new(row.startIndex, row.top - gap / 2.0, row.startIndex + bullSize,
row.bottom + gap / 2.0, text = bullPercent, text_size = v2TextSize, text_color = textColor, bgcolor =
color.new(bullishColor, 50), border_color = bullishColor, border_width = 1)
row.priceBox := box.new(row.startIndex + bullSize + v2Gap, row.top - gap / 2.0, row.startIndex +
bullSize + v2Gap + priceBoxSize, row.bottom + gap / 2.0, text = str.tostring(rowPrice, format.mintick),
text_color = textColor, text_size = v2TextSize, bgcolor = color.new(color.blue, 50), border_width = 1)
if bearSize > 0
row.bearBox := box.new(row.startIndex + bullSize + v2Gap + priceBoxSize + v2Gap, row.top -
gap / 2.0, row.startIndex + bullSize + v2Gap + priceBoxSize + v2Gap + bearSize, row.bottom + gap / 2.0,
text = bearPercent, text_size = v2TextSize, text_color = textColor, bgcolor = color.new(bearishColor, 50),
border_color = bearishColor, border_width = 1)
else
row.endIndex := row.startIndex - bullSize - v2Gap - priceBoxSize - v2Gap - bearSize
if bullSize > 0
row.bullBox := box.new(row.startIndex, row.top - gap / 2.0, row.startIndex - bullSize,
row.bottom + gap / 2.0, text = bullPercent, text_size = v2TextSize, text_color = textColor, bgcolor =
color.new(bullishColor, 50), border_color = bullishColor, border_width = 1)
row.priceBox := box.new(row.startIndex - bullSize - v2Gap, row.top - gap / 2.0, row.startIndex -
bullSize - v2Gap - priceBoxSize, row.bottom + gap / 2.0, text = str.tostring(rowPrice, format.mintick),
text_color = textColor, text_size = v2TextSize, bgcolor = color.new(color.blue, 50), border_width = 1)
if bearSize > 0
row.bearBox := box.new(row.startIndex - bullSize - v2Gap - priceBoxSize - v2Gap, row.top -
gap / 2.0, row.startIndex - bullSize - v2Gap - priceBoxSize - v2Gap - bearSize, row.bottom + gap / 2.0,
text = bearPercent, text_size = v2TextSize, text_color = textColor, bgcolor = color.new(bearishColor, 50),
border_color = bearishColor, border_width = 1)
curIndex = bar_index
const float weightImpact = 0.85
if ((bar_index > last_bar_index - maxDistanceToLastBar and barstate.isconfirmed and
showPreviousSessions) or (barstate.islast and not showPreviousSessions)) and (not
na(latestVolumeProfile))
for i = 0 to analyzeBars
for j = 0 to latestVolumeProfile.rows.size() - 1
curRow = latestVolumeProfile.rows.get(j)
vol = volume[i]
if volumeWeight == "Recent"
vol := ((vol * weightImpact) / (i + 1)) + (vol * (1.0 - weightImpact))
if volumeWeight == "Past"
vol := ((vol * weightImpact) * ((i + 1) / analyzeBars)) + (vol * (1.0 - weightImpact))
if doIntersect(intersectionBody ? math.max(open[i], close[i]) : high[i], intersectionBody ?
math.min(open[i], close[i]) : low[i], curRow)
if close[i] > open[i]
curRow.bullVolume += vol
else
curRow.bearVolume += vol
curRow.totalVolume += vol
//
maxVolumeSize := 0
minVolumeSize := latestVolumeProfile.rows.get(0).totalVolume
rowType maxVolRow = na
for i = 0 to latestVolumeProfile.rows.size() - 1
curRow = latestVolumeProfile.rows.get(i)
maxVolumeSize := math.max(maxVolumeSize, curRow.totalVolume)
if curRow.totalVolume == maxVolumeSize
maxVolRow := curRow
minVolumeSize := math.min(minVolumeSize, curRow.totalVolume)
int startIndex = na
if alignSetting == "Left"
startIndex := (curIndex - analyzeBars + 1)
if not na(latestEdgeIndex)
startIndex := math.max(startIndex, latestEdgeIndex + edgeOffset)
else
startIndex := (curIndex + 75)
if not na(latestEdgeIndex)
startIndex := math.max(startIndex, latestEdgeIndex + 75)
latestVolumeProfile.startIndex := startIndex
for i = 0 to latestVolumeProfile.rows.size() - 1
curRow = latestVolumeProfile.rows.get(i)
box.delete(curRow.bearBox)
box.delete(curRow.priceBox)
box.delete(curRow.bullBox)
renderRow(startIndex, alignSetting == "Left" ? false : true, curRow)
if alignSetting == "Left"
latestVolumeProfile.edgeIndex := math.max(nz(latestVolumeProfile.edgeIndex, 0),
curRow.endIndex)
else
latestVolumeProfile.edgeIndex := math.max(nz(latestVolumeProfile.edgeIndex, 0),
curRow.startIndex)
if not na(oldLine)
oldLine.set_extend(extend.none)
if alignSetting == "Left"
oldLine.set_xy2(startIndex, oldLine.get_y2())
else
oldLine.set_xy1(latestEdgeIndex, oldLine.get_y2())
oldLine.set_xy2(latestVolumeProfile.edgeIndex, oldLine.get_y2())
if not na(oldBox)
oldBox.set_right(startIndex)
line.delete(latestVolumeProfile.POCLine)
box.delete(latestVolumeProfile.highlightBox)
if highlightSessions and alignSetting == "Left"
latestVolumeProfile.highlightBox := box.new(startIndex, latestVolumeProfile.vpTop,
nz(latestVolumeProfile.vpEnd, bar_index), latestVolumeProfile.vpBottom, bgcolor =
color.new(color.blue, 80), border_width = 0)
maxRowMid = (maxVolRow.top + maxVolRow.bottom) / 2.0
if POCEnabled and maxVolRow.totalVolume > 0
if alignSetting == "Left"
latestVolumeProfile.POCLine := line.new(maxVolRow.endIndex, maxRowMid, bar_index,
maxRowMid, extend = extend.right, width = POCLineWidth, color = POCLineColor, style = POCLineStyle
== "____" ? line.style_solid : POCLineStyle == "----" ? line.style_dashed : line.style_dotted)
else
latestVolumeProfile.POCLine := line.new(bar_index, maxRowMid, maxVolRow.endIndex,
maxRowMid, extend = extend.left, width = POCLineWidth, color = POCLineColor, style = POCLineStyle ==
"____" ? line.style_solid : POCLineStyle == "----" ? line.style_dashed : line.style_dotted)