// 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 int maxBoxesCount = 500 const float overlapThresholdPercentage = 0 const int maxDistanceToLastBar = 1750 // Affects Running Time const int maxOrderBlocks = 60 var bool initRun = true indicator(title = 'Volumized Breaker Blocks | Flux Charts', overlay = true, max_boxes_count = maxBoxesCount, max_labels_count = maxBoxesCount, max_lines_count = maxBoxesCount, max_bars_back = 5000) showInvalidated = input.bool(true, "Show Historic Zones", group = "General Configuration", display = display.none) OBsEnabled = true breakBlockVolumetricInfo = input.bool(false, "Volumetric Info", group = "General Configuration", inline="EV", display = display.none) obEndMethod = input.string("Close", "Order Block Invalidation", options = ["Wick", "Close"], group = "General Configuration", display = display.none) bbEndMethod = input.string("Close", "Breaker Block Invalidation", options = ["Wick", "Close"], group = "General Configuration", display = display.none) combineOBs = DEBUG ? input.bool(true, "Combine Zones", group = "General Configuration", display = display.none) : true maxATRMult = DEBUG ? input.float(3.5,"Max Atr Multiplier", group = "General Configuration") : 3.5 swingLength = input.int(10, 'Swing Length', minval = 3, tooltip="Swing length is used when finding breaker block formations. Smaller values will result in finding smaller breaker blocks.", group = "General Configuration", display = display.none) zoneCount = input.string("Low", 'Zone Count', options = ["High", "Medium", "Low"], tooltip = "Number of Breaker Block Zones to be rendered. Higher options will result in older Breaker Blocks shown.", group = "General Configuration", display = display.none) bullishBreakerBlockColor = input(color.new(#2962ff, 75), "Bullish Breaker", inline = 'breakerColor', group = 'General Configuration', display = display.none) bearishBreakerBlockColor = input(color.new(#ffeb3b, 75), "Bearish Breaker", inline = 'breakerColor', group = 'General Configuration', display = display.none) bullishBreakerBlocks = zoneCount == "Low" ? 3 : zoneCount == "Medium" ? 5 : 10 bearishBreakerBlocks = zoneCount == "Low" ? 3 : zoneCount == "Medium" ? 5 : 10 bullishOrderBlocks = zoneCount == "Low" ? 15 : zoneCount == "Medium" ? 30 : 60 bearishOrderBlocks = zoneCount == "Low" ? 15 : zoneCount == "Medium" ? 30 : 60 BBsEnabled = true timeframe1Enabled = true timeframe1 = "" breakersFull = DEBUG ? input.bool(true, "Breakers Full", group = "Style", display = display.none) : true textColor = input.color(#ffffff80, "Text Color", group = "Style") combinedText = DEBUG ? input.bool(false, "Combined Text", group = "Style", inline = "CombinedColor") : false 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(0) var bearishOrderBlocksList = array.new(0) var allOrderBlocksList = array.new(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 breakerBlockColor = ob.info.obType == "Bull" ? bearishBreakerBlockColor : bullishBreakerBlockColor if info.breaker and BBsEnabled startTime = (OBsEnabled and not breakersFull) ? info.breakTime : info.startTime ob.breakerBox := box.new(startTime, info.top, time + 1, info.bottom, na, bgcolor = breakerBlockColor, extend = extend.none, xloc = xloc.bar_time, text_color = textColor, text_size = size.normal) BBText = (na(ob.info.combinedTimeframesStr) ? formatTimeframeString(ob.info.timeframeStr) : ob.info.combinedTimeframesStr) + " BB" box.set_text(ob.breakerBox, (breakBlockVolumetricInfo ? str.tostring(ob.info.bbVolume, format.volume) + "\n" : "") + (combinedText and ob.info.combined ? "[Combined]\n" : "") + BBText) ob.breakerBoxLineTop := line.new(startTime, info.top, time + 1, info.top, xloc.bar_time, extend.none, colorWithTransparency(breakerBlockColor, 0), line.style_dashed) ob.breakerBoxLineBottom := line.new(startTime, info.bottom, time + 1, info.bottom, xloc.bar_time, extend.none, colorWithTransparency(breakerBlockColor, 0), line.style_dashed) 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 [top, btm] = findOBSwings(swingLength) 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 : close) < currentOB.bottom) currentOB.breaker := true currentOB.breakTime := time currentOB.bbVolume := volume else if (bbEndMethod == "Wick" ? high : close) > 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 : close) > currentOB.top currentOB.breaker := true currentOB.breakTime := time currentOB.bbVolume := volume else if (bbEndMethod == "Wick" ? low : close) < 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 arrHasOB (orderBlock[] arr, orderBlock obF) => hasOB = false if arr.size() > 0 for i = 0 to arr.size() - 1 orderBlock ob1 = arr.get(i) if isOBValid(ob1.info) and isOBValid(obF.info) and (ob1.info.breaker == obF.info.breaker) and doOBsTouch(ob1.info, obF.info) hasOB := true break hasOB bool newBullishBBTick = false bool newBearishBBTick = false string BBAlertTime = "" handleOrderBlocksFinal () => if DEBUG log.info("Bullish OB Count " + str.tostring(bullishOrderBlocksList.size())) log.info("Bearish OB Count " + str.tostring(bearishOrderBlocksList.size())) newBullishOBAlert = false newBearishOBAlert = false newBullishBBAlert = false newBearishBBAlert = false alertTimeOB = "" alertTimeBB = "" orderBlock[] orderBlocksToAdd = array.new(0) for i = 0 to timeframeInfos.size() - 1 curTimeframe = timeframeInfos.get(i) if not curTimeframe.isEnabled continue if not na(curTimeframe.bullishOrderBlocksList) 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 orderBlocksToAdd.unshift(createOrderBlock(orderBlockInfo.copy(orderBlockInfoF))) if not na(curTimeframe.bullishOrderBlocksList) 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 orderBlocksToAdd.unshift(createOrderBlock(orderBlockInfo.copy(orderBlockInfoF))) // Check New Order & Breaker Blocks if orderBlocksToAdd.size () > 0 for i = 0 to orderBlocksToAdd.size() - 1 obToTest = orderBlocksToAdd.get(i) if obToTest.info.breaker == false if not arrHasOB(allOrderBlocksList, obToTest) alertTimeOB := obToTest.info.timeframeStr if obToTest.info.obType == "Bull" newBullishOBAlert := true else newBearishOBAlert := true else if not arrHasOB(allOrderBlocksList, obToTest) alertTimeBB := obToTest.info.timeframeStr if obToTest.info.obType == "Bull" newBearishBBAlert := true else newBullishBBAlert := true // Delete Old Order Blocks if allOrderBlocksList.size () > 0 for i = 0 to allOrderBlocksList.size() - 1 safeDeleteOrderBlock(allOrderBlocksList.get(i)) allOrderBlocksList.clear() // Add New Order Blocks if orderBlocksToAdd.size () > 0 for i = 0 to orderBlocksToAdd.size() - 1 allOrderBlocksList.unshift(orderBlocksToAdd.get(i)) if combineOBs combineOBsFunc() if allOrderBlocksList.size() > 0 for i = 0 to allOrderBlocksList.size() - 1 curOB = allOrderBlocksList.get(i) if isOBValid(curOB.info) renderOrderBlock(curOB) [alertTimeOB, alertTimeBB, newBullishOBAlert, newBearishOBAlert, newBullishBBAlert, newBearishBBAlert] findOrderBlocks() [bullishOrderBlocksListTimeframe1, bearishOrderBlocksListTimeframe1] = getTFData(timeframeInfos.get(0), timeframe1) if barstate.isconfirmed and (bar_index > last_bar_index - maxDistanceToLastBar) handleTimeframeInfo(timeframeInfos.get(0), bullishOrderBlocksListTimeframe1, bearishOrderBlocksListTimeframe1) [alertTimeOB, alertTimeBB, newBullishOBAlert, newBearishOBAlert, newBullishBBAlert, newBearishBBAlert] = handleOrderBlocksFinal() if newBullishBBAlert newBullishBBTick := true if newBearishBBAlert newBearishBBTick := true alertcondition((newBullishBBTick or newBearishBBTick) and not initRun, "Breaker Block Formation", "A new Breaker Block has formed.") alertcondition(newBullishBBTick and not initRun, "Bullish Breaker Block Formation", "A new Bullish Breaker Block has formed.") alertcondition(newBearishBBTick and not initRun, "Bearish Breaker Block Formation", "A new Bearish Breaker Block has formed.") if barstate.isconfirmed initRun := false