PulseHub
OPEN-SOURCE SCRIPT

Combined Breaker Blocks with Imbalance Filter

381
//version=6
indicator("Combined Breaker Blocks with Imbalance Filter", shorttitle = "Combined Breakers + Imbalance", overlay = true, behind_chart = false, max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500, max_bars_back = 5000)

import PulseWire/ta/12

// ============================================================================
// BỘ LỌC IMBALANCE (FVG)
// ============================================================================
grp_imb = "Bộ Lọc Imbalance (FVG)"
useImbalanceFilter = input.bool(true, title = "Chỉ giữ OB có Imbalance (FVG)", group = grp_imb, tooltip = "Lọc bỏ những vùng OB/Breaker không có khoảng trống giá Imbalance xung quanh.")

// Hàm kiểm tra Imbalance
f_has_imbalance_bull() =>
// Bullish Imbalance (FVG Tăng): Giá cao nhất cây 2 thanh trước thấp hơn giá thấp nhất cây hiện tại
(high[2] < low) or (high[3] < low[1]) or (high[1] < low)

f_has_imbalance_bear() =>
// Bearish Imbalance (FVG Giảm): Giá thấp nhất cây 2 thanh trước cao hơn giá cao nhất cây hiện tại
(low[2] > high) or (low[3] > high[1]) or (low[1] > high)

// ============================================================================
// SECTION 1: ALGOALPHA BREAKER BLOCKS
// ============================================================================

// --- Inputs (AlgoAlpha) ---
preventOverlap = input.bool(true, title = "Prevent Overlap", group = "AlgoAlpha - Calculation", tooltip = "When enabled, new orderblocks will not be created if their price range overlaps any existing active orderblock.")
zLen = input.int(100, title = "Z-Score Window (bars)", minval = 1, group = "AlgoAlpha - Calculation", tooltip = "Lookback window used to normalize the impulse distance into a z-score.")
maxAge = input.int(500, title = "Max Box Age (bars)", minval = 1, group = "AlgoAlpha - Calculation", tooltip = "Maximum lifetime (in bars) of an orderblock that has not been mitigated.")
bullCol = input.color(#00ffbb, title = "Bullish Colour", group = "AlgoAlpha - Appearance", tooltip = "Color used for bullish breaker zones and bullish signals.")
bearCol = input.color(#ff1100, title = "Bearish Colour", group = "AlgoAlpha - Appearance", tooltip = "Color used for bearish breaker zones and bearish signals.")

// --- Calculations (AlgoAlpha) ---
var updist = 0.0
var downdist = 0.0

updist := close > open ? nz(updist[1]) + (close - open) : 0.0
downdist := close < open ? nz(downdist[1]) + (open - close) : 0.0

upMean = ta.sma(updist, zLen)
upStdev = ta.stdev(updist, zLen)
dnMean = ta.sma(downdist, zLen)
dnStdev = ta.stdev(downdist, zLen)

zUp = (updist - upMean) / (upStdev == 0.0 ? na : upStdev)
zDn = (downdist - dnMean) / (dnStdev == 0.0 ? na : dnStdev)

// Bổ sung lọc Imbalance cho tín hiệu AlgoAlpha
bullish = ta.crossover(zUp, 4) and nz(zUp[1]) != 0 and (not useImbalanceFilter or f_has_imbalance_bull())
bearish = ta.crossunder(-zDn, -4) and nz((-zDn)[1]) != 0 and (not useImbalanceFilter or f_has_imbalance_bear())

var box[] bullBoxes = array.new<box>()
var box[] bearBoxes = array.new<box>()
var int[] bullStarts = array.new_int()
var int[] bearStarts = array.new_int()
var line[] bullMidLines = array.new<line>()
var line[] bearMidLines = array.new<line>()
var box[] breakerBullBoxes = array.new<box>()
var box[] breakerBearBoxes = array.new<box>()
var int[] breakerBullStarts = array.new_int()
var int[] breakerBearStarts = array.new_int()
var line[] breakerBullMidLines = array.new<line>()
var line[] breakerBearMidLines = array.new<line>()

breakerBullPlot = float(na)
breakerBearPlot = float(na)
rejectBullPlot = float(na)
rejectBearPlot = float(na)

f_can_create(float tNew, float bNew) =>
bool ok = true
if bullBoxes.size() > 0
for j = 0 to array.size(bullBoxes) - 1
exB = array.get(bullBoxes, j)
float exTop = box.get_top(exB)
float exBot = box.get_bottom(exB)
if (tNew > exBot) and (bNew < exTop)
ok := false
break
if ok and bearBoxes.size() > 0
for j = 0 to array.size(bearBoxes) - 1
exS = array.get(bearBoxes, j)
float exTop2 = box.get_top(exS)
float exBot2 = box.get_bottom(exS)
if (tNew > exBot2) and (bNew < exTop2)
ok := false
break
if ok and breakerBullBoxes.size() > 0
for j = 0 to array.size(breakerBullBoxes) - 1
exB = array.get(breakerBullBoxes, j)
float exTop = box.get_top(exB)
float exBot = box.get_bottom(exB)
if (tNew > exBot) and (bNew < exTop)
ok := false
break
if ok and breakerBearBoxes.size() > 0
for j = 0 to array.size(breakerBearBoxes) - 1
exS = array.get(breakerBearBoxes, j)
float exTop2 = box.get_top(exS)
float exBot2 = box.get_bottom(exS)
if (tNew > exBot2) and (bNew < exTop2)
ok := false
break
ok

f_can_create_breaker(float tNew, float bNew) =>
f_can_create(tNew, bNew)

lastDownIdx = ta.valuewhen(close < open, bar_index, 0)
lastDownHigh = ta.valuewhen(close < open, high, 0)
lastDownLow = ta.valuewhen(close < open, low, 0)

breakerBullPlot := na
breakerBearPlot := na
rejectBullPlot := na
rejectBearPlot := na

if bullish and not na(lastDownIdx) and not na(lastDownHigh) and not na(lastDownLow)
if not preventOverlap or f_can_create(lastDownHigh, lastDownLow)
float midYB = (lastDownHigh + lastDownLow) / 2.0
bx = box.new(lastDownIdx, lastDownHigh, lastDownIdx + 1, lastDownLow, border_color = color.new(color.gray, 40), bgcolor = color.new(color.gray, 85))
ln = line.new(x1 = lastDownIdx, y1 = midYB, x2 = lastDownIdx + 1, y2 = midYB, xloc = xloc.bar_index, extend = extend.none, color = color.new(color.gray, 40), style = line.style_dashed, width = 1)
array.unshift(bullBoxes, bx)
array.unshift(bullStarts, lastDownIdx)
array.unshift(bullMidLines, ln)

lastUpIdx = ta.valuewhen(close > open, bar_index, 0)
lastUpHigh = ta.valuewhen(close > open, high, 0)
lastUpLow = ta.valuewhen(close > open, low, 0)

if bearish and not na(lastUpIdx) and not na(lastUpHigh) and not na(lastUpLow)
if not preventOverlap or f_can_create(lastUpHigh, lastUpLow)
float midYS = (lastUpHigh + lastUpLow) / 2.0
bx2 = box.new(lastUpIdx, lastUpHigh, lastUpIdx + 1, lastUpLow, border_color = color.new(color.gray, 40), bgcolor = color.new(color.gray, 85))
ln2 = line.new(x1 = lastUpIdx, y1 = midYS, x2 = lastUpIdx + 1, y2 = midYS, xloc = xloc.bar_index, extend = extend.none, color = color.new(color.gray, 40), style = line.style_dashed, width = 1)
array.unshift(bearBoxes, bx2)
array.unshift(bearStarts, lastUpIdx)
array.unshift(bearMidLines, ln2)

if bullBoxes.size() > 0
for i = array.size(bullBoxes) - 1 to 0
if array.size(bullBoxes) <= i or array.size(bullStarts) <= i
continue
b = array.get(bullBoxes, i)
box.set_right(b, bar_index)
if array.size(bullMidLines) > i
ln = array.get(bullMidLines, i)
float topB = box.get_top(b)
float botB = box.get_bottom(b)
float midY = (topB + botB) / 2.0
int leftX = box.get_left(b)
line.set_x1(ln, leftX)
line.set_x2(ln, bar_index)
line.set_y1(ln, midY)
line.set_y2(ln, midY)

float topB = box.get_top(b)
float botB = box.get_bottom(b)
bool mitigatedB = close < botB and close[1] < botB
int startB = array.get(bullStarts, i)
bool expiredB = (bar_index - startB) >= maxAge
if mitigatedB or expiredB
bool makeBreaker = mitigatedB
array.remove(bullBoxes, i)
array.remove(bullStarts, i)
if array.size(bullMidLines) > i
array.remove(bullMidLines, i)
if makeBreaker and (not preventOverlap or f_can_create_breaker(topB, botB))
float midYB = (topB + botB) / 2.0
int leftX = bar_index
bxBr = box.new(leftX, topB, leftX + 1, botB, border_color = color.new(bearCol, 40), bgcolor = color.new(bearCol, 85))
lnBr = line.new(x1 = leftX, y1 = midYB, x2 = leftX + 1, y2 = midYB, xloc = xloc.bar_index, extend = extend.none, color = color.new(bearCol, 40), style = line.style_dashed, width = 1)
array.unshift(breakerBearBoxes, bxBr)
array.unshift(breakerBearStarts, leftX)
array.unshift(breakerBearMidLines, lnBr)
breakerBearPlot := topB

if bearBoxes.size() > 0
for i = array.size(bearBoxes) - 1 to 0
if array.size(bearBoxes) <= i or array.size(bearStarts) <= i
continue
b = array.get(bearBoxes, i)
box.set_right(b, bar_index)
if array.size(bearMidLines) > i
ln = array.get(bearMidLines, i)
float topS = box.get_top(b)
float botS = box.get_bottom(b)
float midY = (topS + botS) / 2.0
int leftX = box.get_left(b)
line.set_x1(ln, leftX)
line.set_x2(ln, bar_index)
line.set_y1(ln, midY)
line.set_y2(ln, midY)

float topS = box.get_top(b)
float botS = box.get_bottom(b)
bool mitigatedS = close > topS and close[1] > topS
int startS = array.get(bearStarts, i)
bool expiredS = (bar_index - startS) >= maxAge
if mitigatedS or expiredS
bool makeBreaker = mitigatedS
array.remove(bearBoxes, i)
array.remove(bearStarts, i)
if array.size(bearMidLines) > i
array.remove(bearMidLines, i)
if makeBreaker and (not preventOverlap or f_can_create_breaker(topS, botS))
float midYS = (topS + botS) / 2.0
int leftX = bar_index
bxBr = box.new(leftX, topS, leftX + 1, botS, border_color = color.new(bullCol, 40), bgcolor = color.new(bullCol, 85))
lnBr = line.new(x1 = leftX, y1 = midYS, x2 = leftX + 1, y2 = midYS, xloc = xloc.bar_index, extend = extend.none, color = color.new(bullCol, 40), style = line.style_dashed, width = 1)
array.unshift(breakerBullBoxes, bxBr)
array.unshift(breakerBullStarts, leftX)
array.unshift(breakerBullMidLines, lnBr)
breakerBullPlot := botS

if breakerBullBoxes.size() > 0
for i = array.size(breakerBullBoxes) - 1 to 0
if array.size(breakerBullBoxes) <= i or array.size(breakerBullStarts) <= i
continue
b = array.get(breakerBullBoxes, i)
box.set_right(b, bar_index)
if array.size(breakerBullMidLines) > i
ln = array.get(breakerBullMidLines, i)
float topB = box.get_top(b)
float botB = box.get_bottom(b)
float midY = (topB + botB) / 2.0
int leftX = box.get_left(b)
line.set_x1(ln, leftX)
line.set_x2(ln, bar_index)
line.set_y1(ln, midY)
line.set_y2(ln, midY)

float topB = box.get_top(b)
float botB = box.get_bottom(b)
bool mitigatedB = close < botB and close[1] < botB
int startB = array.get(breakerBullStarts, i)
bool expiredB = (bar_index - startB) >= maxAge
if not mitigatedB and not expiredB and high > botB and low < topB and close > topB
float pad = math.max(math.abs(topB - botB) * 0.1, syminfo.mintick * 2)
rejectBullPlot := botB - pad
if mitigatedB or expiredB
array.remove(breakerBullBoxes, i)
array.remove(breakerBullStarts, i)
if array.size(breakerBullMidLines) > i
array.remove(breakerBullMidLines, i)

if breakerBearBoxes.size() > 0
for i = array.size(breakerBearBoxes) - 1 to 0
if array.size(breakerBearBoxes) <= i or array.size(breakerBearStarts) <= i
continue
b = array.get(breakerBearBoxes, i)
box.set_right(b, bar_index)
if array.size(breakerBearMidLines) > i
ln = array.get(breakerBearMidLines, i)
float topS = box.get_top(b)
float botS = box.get_bottom(b)
float midY = (topS + botS) / 2.0
int leftX = box.get_left(b)
line.set_x1(ln, leftX)
line.set_x2(ln, bar_index)
line.set_y1(ln, midY)
line.set_y2(ln, midY)

float topS = box.get_top(b)
float botS = box.get_bottom(b)
bool mitigatedS = close > topS and close[1] > topS
int startS = array.get(breakerBearStarts, i)
bool expiredS = (bar_index - startS) >= maxAge
if not mitigatedS and not expiredS and high > botS and low < topS and close < botS
float pad = math.max(math.abs(topS - botS) * 0.1, syminfo.mintick * 2)
rejectBearPlot := topS + pad
if mitigatedS or expiredS
array.remove(breakerBearBoxes, i)
array.remove(breakerBearStarts, i)
if array.size(breakerBearMidLines) > i
array.remove(breakerBearMidLines, i)

// --- Plots (AlgoAlpha) ---
breakerBullFormed = not na(breakerBullPlot)
breakerBearFormed = not na(breakerBearPlot)
rejectBull = not na(rejectBullPlot)
rejectBear = not na(rejectBearPlot)

plotshape(breakerBullPlot, "Bullish Breaker [AlgoAlpha]", shape.labelup, location.absolute, bullCol, size = size.tiny, text = "▲", textcolor = chart.fg_color)
plotshape(breakerBearPlot, "Bearish Breaker [AlgoAlpha]", shape.labeldown, location.absolute, bearCol, size = size.tiny, text = "▼", textcolor = chart.fg_color)
plotchar(rejectBullPlot, title = "Bullish Rejection [AlgoAlpha]", char = "▲", location = location.absolute, color = bullCol, size = size.tiny)
plotchar(rejectBearPlot, title = "Bearish Rejection [AlgoAlpha]", char = "▼", location = location.absolute, color = bearCol, size = size.tiny)


// ============================================================================
// SECTION 2: VOLUMIZED BREAKER BLOCKS [FLUX CHARTS]
// ============================================================================

const bool DEBUG = false
const int maxBoxesCount = 500
const float overlapThresholdPercentage = 0
const int maxDistanceToLastBar = 1750
const int maxOrderBlocks = 60
var bool initRun = true

// --- Inputs (Flux Charts) ---
showInvalidated = input.bool(true, "Show Historic Zones", group = "Flux Charts - Configuration", display = display.none)
OBsEnabled = true
breakBlockVolumetricInfo = input.bool(false, "Volumetric Info", group = "Flux Charts - Configuration", inline="EV", display = display.none)
obEndMethod = input.string("Close", "Order Block Invalidation", options = ["Wick", "Close"], group = "Flux Charts - Configuration", display = display.none)
bbEndMethod = input.string("Close", "Breaker Block Invalidation", options = ["Wick", "Close"], group = "Flux Charts - Configuration", display = display.none)
combineOBs = DEBUG ? input.bool(true, "Combine Zones", group = "Flux Charts - Configuration", display = display.none) : true
maxATRMult = DEBUG ? input.float(3.5,"Max Atr Multiplier", group = "Flux Charts - Configuration") : 3.5
swingLength = input.int(10, 'Swing Length', minval = 3, tooltip="Swing length is used when finding breaker block formations.", group = "Flux Charts - Configuration", display = display.none)
zoneCount = input.string("Low", 'Zone Count', options = ["High", "Medium", "Low"], tooltip = "Number of Breaker Block Zones to be rendered.", group = "Flux Charts - Configuration", display = display.none)
bullishBreakerBlockColor = input(color.new(#2962ff, 75), "Bullish Breaker", inline = 'breakerColor', group = 'Flux Charts - Configuration', display = display.none)
bearishBreakerBlockColor = input(color.new(#ffeb3b, 75), "Bearish Breaker", inline = 'breakerColor', group = 'Flux Charts - 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 = "Flux Charts - Style", display = display.none) : true
textColor = input.color(#ffffff80, "Text Color", group = "Flux Charts - Style")
combinedText = DEBUG ? input.bool(false, "Combined Text", group = "Flux Charts - Style", inline = "CombinedColor") : false

atr = ta.atr(10)

// --- Types (Flux Charts) ---
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

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)

// --- Helper Functions (Flux Charts) ---
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) ? (ob.info.timeframeStr == "" ? timeframe.period : 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, color.new(breakerBlockColor, 0), line.style_dashed)
ob.breakerBoxLineBottom := line.new(startTime, info.bottom, time + 1, info.bottom, xloc.bar_time, extend.none, color.new(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 (có bổ sung kiểm tra Imbalance)
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)

if close > top.y and not top.crossed and (not useImbalanceFilter or f_has_imbalance_bull())
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, boxBtm)
boxTop := boxBtm == min ? max : boxTop
boxLoc := boxBtm == min ? time : 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 (có bổ sung kiểm tra Imbalance)
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)

if close < btm.y and not btm.crossed and (not useImbalanceFilter or f_has_imbalance_bear())
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, boxTop)
boxBtm := boxTop == max ? min : boxBtm
boxLoc := boxTop == max ? time : 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))
edge1 * edge2

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
overlapPercentage > overlapThresholdPercentage

isOBValid(orderBlockInfo OBInfo) =>
not OBInfo.disabled

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 or not isOBValid(curOB1.info) or not isOBValid(curOB2.info) or 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
allOrderBlocksList.unshift(newOB)
lastCombinations += 1

reqSeq(timeframeStr) =>
request.security(syminfo.tickerid, timeframeStr, [bullishOrderBlocksList, bearishOrderBlocksList])

getTFData(timeframeInfo timeframeInfoF, timeframeStr) =>
if timeframeInfoF.isEnabled
reqSeq(timeframeStr)
else
[na, na]

handleTimeframeInfo(timeframeInfo timeframeInfoF, bullishOrderBlocksListF, bearishOrderBlocksListF) =>
if 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

handleOrderBlocksFinal() =>
newBullishOBAlert = false
newBearishOBAlert = false
newBullishBBAlert = false
newBearishBBAlert = false
alertTimeOB = ""
alertTimeBB = ""

orderBlock[] orderBlocksToAdd = array.new<orderBlock>(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.bearishOrderBlocksList)
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)))

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

if allOrderBlocksList.size() > 0
for i = 0 to allOrderBlocksList.size() - 1
safeDeleteOrderBlock(allOrderBlocksList.get(i))
allOrderBlocksList.clear()

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

// --- Alerts (Flux Charts) ---
alertcondition((newBullishBBTick or newBearishBBTick) and not initRun, "Breaker Block Formation [Flux]", "A new Breaker Block has formed.")
alertcondition(newBullishBBTick and not initRun, "Bullish Breaker Block Formation [Flux]", "A new Bullish Breaker Block has formed.")
alertcondition(newBearishBBTick and not initRun, "Bearish Breaker Block Formation [Flux]", "A new Bearish Breaker Block has formed.")

if barstate.isconfirmed
initRun := false

Disclaimer

The information and publications are not meant to be, and do not constitute, financial, investment, trading, or other types of advice or recommendations supplied or endorsed by PulseWire. Read more in the Terms of Use.