1. Ranked Order Block Zones Description (Zeiierman)
An indicator that detects "Order Block" candles from simple 2-candle flip patterns, scores and ranks every zone, and shows only the highest-quality ones on the chart
1.1 Indicator Concept
An Order Block (OB) is a Smart Money Concepts (SMC) idea: the last opposite-colored candle right before a strong, decisive "flip" candle is believed to mark the exact spot where large players placed their orders just before pushing price hard in the other direction. Because a portion of those large orders is theorized to remain unfilled, price is expected to react again when it revisits that same candle's range later on.
- Simple 2-candle detection: unlike swing-based S/R indicators that need several bars to confirm a pivot, this indicator flags an Order Block the instant it sees a small reversal pattern: one candle of one color, immediately followed by a candle of the opposite color whose body is unusually large (measured in ATR). The first candle (the opposite-colored one) becomes the Order Block zone.
- Quality, not just presence: just like not every S/R pivot deserves attention, not every 2-candle flip is a meaningful Order Block. Each detected OB is scored on its size, the volume behind it, and whether price is currently trading in a direction that supports it (trend alignment).
- Ranking, not accumulation: every bar, all stored Order Blocks are re-sorted by quality score and only the Top-N are actually drawn - lower-quality ones stay in memory (in case they climb back up) but stay hidden.
- Consumed, not broken: an Order Block is not invalidated by a close beyond it the way a S/R zone is - it is gradually mitigated as price trades back into its range, and disappears entirely only once fully filled (100% mitigation), reflecting the idea that the block's orders have now been fully absorbed.
1.2 Indicator Features
-
Order Block Boxes:
- Green boxes (default) = bullish Order Blocks (support-like, expected to hold price up), red boxes (default) = bearish Order Blocks (resistance-like, expected to hold price down). Both colors are user-configurable.
- Each box spans the full high-low range of the Order Block candle and stretches across time while the block is still active.
- Only the top-ranked blocks (controlled by "Show Top Zones") are drawn in full color; the rest exist internally but are invisible.
-
Strength Bars:
- Inside every visible block, the upper half can show a red bar with a bear % label and the lower half a green bar with a bull % label. Unlike a live "confidence meter", these two percentages are fixed at the moment the block is created and never change afterward (see 1.4).
- The bar's length is proportional to its percentage.
- Zone Text: a simple "Bullish OB" or "Bearish OB" label is printed inside the block. Unlike some other zone-based indicators, this label does not change with quality or mitigation - it only states the direction.
- Direction Filter: the indicator can be set to show "All" blocks, "Bullish Only", or "Bearish Only".
- Alerts: 6 alert conditions - new bullish/bearish OB formed, a new top-ranked OB, price touching a bullish/bearish OB, and an OB being fully mitigated - each with its own on/off toggle in the Inputs.
1.3 How to Use the Indicator
- Trade the Top-Ranked Blocks First: because blocks are re-ranked every bar by quality, the ones currently drawn on your chart are the ones the indicator considers most significant right now - treat these as your primary reaction zones.
- Read the Strength % at Creation Time: since bull%/bear% are frozen the moment a block forms, they represent how strong the original flip candle was (size, volume, trend alignment, candle cleanliness) - not how the block is performing now. A freshly formed OB with a 90% reading was a very decisive flip; a 30% one was a much weaker reaction.
- Use "Bullish/Bearish OB Touch" Alerts as Early Warning: these fire the instant price starts trading back into a block's range - useful to get ready for a potential bounce (or a failure) at that level.
- Use "OB Fully Mitigated" to Know a Level Is Gone: once an Order Block is 100% filled, it is deleted outright - there is no partial "weak/mitigated" visual warning like some S/R-style indicators; you only find out it's used up when it vanishes (or via this alert).
- Use "New Top-Ranked OB" to Track the Most Relevant Level: fires whenever the #1 block by quality score changes.
- Tune to Your Market/Timeframe: raise "Min Flip Body ATR" on noisy/lower timeframes so only genuinely aggressive flip candles qualify; lower it to catch more (weaker) reversals. Turn off "Merge Overlapping OBs" if you'd rather see every individual flip as its own separate block instead of consolidated zones.
1.4 How the Indicator Works
Inputs & Roles
- maxZones ("Show Top Zones", Integer, default: 10, range 1-20): how many of the highest quality-scored Order Blocks are actually drawn on the chart. Example: if 40 blocks are currently stored and maxZones=10, only the 10 highest-scored ones get colored boxes; the rest stay invisible until they rank high enough. Increase → more blocks visible at once. Decrease → only the very best blocks shown.
- maxStored ("Max Stored OBs", Integer, default: 50, range 10-200): the maximum number of Order Blocks kept in memory (visible + hidden). Once exceeded, the lowest quality-scored block is deleted first. Increase → more historical blocks remembered. Decrease → less memory used, older low-quality blocks forgotten faster.
- volLength ("Volume MA Length", Integer, default: 20): lookback for the average-volume baseline (ta.sma(volume, volLength)) that a new block's OB-candle volume is compared against. Example: 20-bar average volume = 1,000, OB candle's volume = 1,800 → volume score = 1.8 (1.8× average), which raises the block's quality score and its volume-strength component. Increase → smoother, slower-changing baseline. Decrease → baseline reacts faster to recent volume spikes/dips.
- trendLength ("Trend EMA Length", Integer, default: 50): length of the EMA used to judge trend alignment. A bullish OB is "aligned" when the current close is above this EMA at the moment of creation; a bearish OB is aligned when close is below it. Aligned blocks get a flat trend-score bonus. Increase → trend filter reacts to a longer-term bias. Decrease → reacts to a shorter-term bias.
- minFlipBodyAtr ("Min Flip Body ATR", Float, default: 1.1, min 0.05): minimum body size the "flip" candle (the big reversal candle, not the OB candle itself) must have, measured in ATR multiples, for a 2-candle pattern to qualify as an Order Block at all. Example: ATR=$10, minFlipBodyAtr=1.1 → the flip candle's body (|close-open|) must be at least $11; a smaller, half-hearted reversal candle is ignored and no OB is created. Increase → only very aggressive, decisive flip candles create OBs (fewer, higher conviction blocks). Decrease → more OBs created from milder reversals (more blocks, more noise).
- mergeOverlaps ("Merge Overlapping OBs", Boolean, default: true): when enabled, a newly detected OB that significantly overlaps an existing same-direction block is merged into it instead of creating a separate box. true → fewer, wider consolidated zones in areas with repeated flips. false → every flip becomes its own separate block, even if several sit right on top of each other.
- maxMergeAtr ("Max Merge Size ATR", Float, default: 2.0, min 0.5): the maximum height (in ATR) a merged block is allowed to reach; if merging two overlapping blocks would create a zone taller than this, they are kept separate instead. Example: ATR=$10, maxMergeAtr=2.0 → a merged block cannot exceed $20 in height. Increase → allows larger consolidated blocks. Decrease → keeps merged zones tighter, splitting more often into separate blocks.
- directionFilter ("Direction Filter", Options: All / Bullish Only / Bearish Only, default: All): restricts which blocks are eligible to be shown (they still exist and are still scored internally either way).
- showBars (Boolean, default: true): shows/hides the bull%/bear% strength bars inside each visible block.
- showBlockText (Boolean, default: true): shows/hides the "Bullish OB"/"Bearish OB" text label inside each visible block.
- strengthTextSize / blockTextSize (Options: auto/tiny/small/normal/large/huge, default: auto): font size of the strength % labels and the block text label. Purely cosmetic.
- bullColor / bearColor: fill/border colors for bullish and bearish Order Blocks. Purely cosmetic.
- alertNewBull / alertNewBear / alertTopRank / alertBullTouch / alertBearTouch / alertFullyMitigated (Boolean, all default: true): individually enable/disable each of the 6 alert types. Note: turning one off does not remove it from TradingView's "Create Alert" dropdown list - it simply means that alert condition will never actually fire while disabled.
Main Logic Blocks
🎯 Flow 1: What You See — The Top-Ranked Block Display
- Every bar, all Order Blocks currently stored in memory are sorted from highest to lowest qualityScore (see Flow 2 - this score is not the same number as the bull%/bear% shown on screen, see Flow 3).
- The engine walks down this sorted list and marks a block "visible" only if it (a) passes directionFilter and (b) is still within the first maxZones blocks counted so far.
- A visible block is drawn as a colored box spanning the OB candle's high-low range. Inside it: the top half can show a bear-strength bar sized by bearStrength%, the bottom half a bull-strength bar sized by bullStrength%, and (if enabled) a "Bullish OB"/"Bearish OB" text label.
- A hidden (ranked-out) block is not deleted - its box is painted fully transparent and its text cleared, so it can silently re-appear if its quality score climbs back into the top-N.
🔧 Flow 2: How the Internal Quality Score (Ranking) Works
- At creation: qualityScore = obSize×100 + volScore×10 + trendScore×20 - mitigation×50 (mitigation is always 0 at birth). Unlike the bull%/bear% bars, this score is not clamped to 0-100 - it is a raw internal number used purely to sort and rank blocks against each other, and can be much larger than 100 on volatile/high-priced instruments.
- Every bar afterward: qualityScore = size×100 + volumeScore×10 + trendScore×20 - mitigation×50 - age×0.1. Notice the extra - age×0.1 term - every single bar that passes, a stored block loses 0.1 quality points, forever (there is no cap on this decay, and unlike the S/R zones indicator, a block is never automatically deleted just for being old - only full mitigation or exceeding maxStored removes it). In practice this steady decay means an untouched, unmerged block will eventually rank at the very bottom and disappear from view (though not from memory) simply from age, even if it is never revisited by price.
💪 Flow 3: How the Bull%/Bear% Strength Bars Are Calculated
- Calculated once, at the moment the block is created, from 4 components that always add up to a maximum of 100: size relative to ATR (up to 40 points, capped once the OB candle's range reaches 2× ATR), volume relative to average (up to 30 points, capped at 2× average volume), trend alignment (20 points flat if aligned), and "candle cleanliness" - how much of the OB candle's range was real body vs. wicks (up to 10 points).
- Example: an OB candle with a range right at 2× ATR (40/40), volume exactly 2× average (30/30), trend-aligned (20/20), and a clean, nearly full-body candle (9/10) scores mainStrength ≈ 99/100.
- Split into two numbers: bullStrength = mainStrength and bearStrength = 100 - mainStrength for a bullish block (the reverse for a bearish block) - the two percentages always sum to exactly 100 at birth.
- These numbers are then frozen - unlike the ranking score in Flow 2, bull%/bear% are not recalculated as the bar count passes or as the block gets mitigated. The only way they change afterward is if the block gets merged with a new overlapping OB (see Flow 4), in which case each side independently takes whichever value (existing or new) is higher - meaning after a merge the two percentages can stop summing to exactly 100.
🌱 Flow 4: How an Order Block Is Born — Flip Detection & Merge
- Step 1 - Detect the 2-candle flip pattern: this uses only the current candle and the one right before it - no waiting for future bars to confirm, unlike swing-pivot-based indicators. Bullish: the previous candle closed red (close[1] < open[1]) and the current candle closes green (close > open). Bearish: the reverse (previous green, current red).
- Step 2 - Require a decisive flip: the current candle's body size (|close-open|) must be ≥ minFlipBodyAtr×ATR, otherwise the pattern is ignored entirely.
- Step 3 - Build the candidate block: the zone is set to the previous candle's full high-low range (high[1]/low[1]) - that previous, opposite-colored candle is the actual "Order Block". Its volume score, trend score, quality score, and bull%/bear% are all computed as in Flows 2-3.
- Step 4 - Merge or create: if mergeOverlaps is on, the candidate is compared against every existing block on the same direction. It is merged if it overlaps by at least 50% of the smaller block's height and the resulting combined zone would not exceed maxMergeAtr×ATR in height; on merge, the zone widens to cover both, mitigation resets to 0, and each score field takes the higher of the two values. If no match is found (or merging is off), a brand-new block is created and pushed into the active list.
🔄 Flow 5: How an Order Block Lives and Dies — Touch, Mitigation, Storage Limit
- Touch: a bullish block is "touched" once the current bar's low reaches down into its top edge (low ≤ ob.top); a bearish block is touched once the current bar's high reaches up into its bottom edge (high ≥ ob.bottom).
- Mitigation: on a touch, mitigation is recalculated as how far price has pushed through the block from its near edge toward its far edge (0 = just tagged the edge, 1 = fully filled through). This directly lowers the block's quality score (via the - mitigation×50 term) but does not change its already-frozen bull%/bear% bars.
- Full mitigation = deletion: the instant mitigation reaches 1.0 (100% filled), the block's boxes are deleted and it is removed from the array outright - there is no "broken zone" ghost marker left behind (unlike some S/R-style indicators), and no partial "weak/mitigated" text warning beforehand; the block is simply gone once fully used up.
- No age-based expiry: a block that is never touched and never exceeds the storage limit can, in principle, remain in memory indefinitely - it just steadily loses quality score (Flow 2) until it permanently drops out of the visible top-N.
- Storage limit pruning: after re-ranking, if the total active block count exceeds maxStored, the lowest quality-scored blocks are deleted one by one until the count is back within the limit.
🔔 Flow 6: Alerts
- New Bullish/Bearish OB: fires the instant a brand-new (non-merged) block of that direction is created.
- New Top-Ranked OB: fires whenever the #1 block by quality score changes identity.
- Bullish/Bearish OB Touch: fires whenever price touches any active block of that direction (accumulated across all blocks that bar).
- OB Fully Mitigated: fires the instant any block reaches 100% mitigation and is removed.
- Each condition is additionally gated by its own on/off toggle input (see Inputs above).
Outputs & Usage Roles
- Top-ranked Order Block boxes: the primary output - a filtered, ranked shortlist of the most significant recent flip zones to watch for a price reaction.
- Bull%/Bear% strength bars: a snapshot of how convincing the original flip candle was at the moment the block formed - useful for grading a block's initial conviction, not for tracking its current state.
- Zone text label: a simple direction reminder ("Bullish OB"/"Bearish OB").
- Alerts: let block creation, ranking changes, touches, and full mitigation be monitored without watching the chart live.
// This work is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
// https://creativecommons.org/licenses/by-nc-sa/4.0/
// © Zeiierman {
//@version=6
indicator("Ranked Order Block Zones (Zeiierman)", overlay = true, max_boxes_count = 300)
//}
// ~~ Tooltips {
t1 = "Controls how many of the highest-quality Order Block zones are visible on the chart."
t2 = "Maximum number of Order Block zones stored internally before the lowest-quality zones are removed."
t3 = "Length used to calculate the average volume baseline. The OB volume score compares source candle volume against this average."
t4 = "EMA length used to define trend alignment. Bullish OBs above the EMA and bearish OBs below the EMA receive stronger trend scores."
t5 = "Shows or hides the internal bullish and bearish strength bars inside each Order Block zone."
t6 = "Shows or hides the text label inside each Order Block zone."
t7 = "Color used for bullish Order Block zones."
t8 = "Color used for bearish Order Block zones."
t9 = "When enabled, the Order Block zone uses only the candle body. When disabled, the full candle wick range is used."
t10 = "Optional directional filter for visible Order Blocks."
t11 = "Controls the text size used for the strength percentage labels."
t12 = "Controls the text size used for the Order Block text."
t13 = "When enabled, overlapping Order Blocks of the same direction can be merged into one zone."
t14 = "Maximum allowed merged OB height measured in ATR. Prevents overlapping zones from becoming one giant block."
t15 = "Minimum required body size of the flip candle, measured in ATR. This keeps the simple OB logic adaptive across tickers and timeframes."
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Inputs {
gRanking = "Ranking"
gStrength = "Strength Engine"
gDetection = "Order Block Detection"
gDisplay = "Display"
gColors = "Colors"
gAlerts = "Alerts"
maxZones = input.int(10, "Show Top Zones", minval = 1, maxval = 20, tooltip = t1, group = gRanking)
maxStored = input.int(50, "Max Stored OBs", minval = 10, maxval = 200, tooltip = t2, group = gRanking)
volLength = input.int(20, "Volume MA Length", minval = 1, tooltip = t3, group = gStrength)
trendLength = input.int(50, "Trend EMA Length", minval = 1, tooltip = t4, group = gStrength)
minFlipBodyAtr = input.float(1.1, "Min Flip Body ATR", minval = 0.05, step = 0.05, tooltip = t15, group = gDetection)
mergeOverlaps = input.bool(true, "Merge Overlapping OBs", tooltip = t13, group = gDetection)
maxMergeAtr = input.float(2.0, "Max Merge Size ATR", minval = 0.5, step = 0.5, tooltip = t14, group = gDetection)
directionFilter = input.string("All", "Direction Filter", options = ["All", "Bullish Only", "Bearish Only"], tooltip = t10, group = gDisplay)
showBars = input.bool(true, "Show Strength Bars", tooltip = t5, group = gDisplay)
showBlockText = input.bool(true, "Show OB Text", tooltip = t6, group = gDisplay)
strengthTextSize = input.string("auto", "Strength Text Size", options = ["auto", "tiny", "small", "normal", "large", "huge"], tooltip = t11, group = gDisplay)
blockTextSize = input.string("auto", "Block Text Size", options = ["auto", "tiny", "small", "normal", "large", "huge"], tooltip = t12, group = gDisplay)
bullColor = input.color(color.rgb(8, 153, 129, 48), "Bullish OB", inline = "colors", tooltip = t7, group = gColors)
bearColor = input.color(color.rgb(242, 54, 69, 48), "Bearish OB", inline = "colors", tooltip = t8, group = gColors)
alertNewBull = input.bool(true, "New Bullish OB", group = gAlerts)
alertNewBear = input.bool(true, "New Bearish OB", group = gAlerts)
alertTopRank = input.bool(true, "New Top-Ranked OB", group = gAlerts)
alertBullTouch = input.bool(true, "Bullish OB Touch", group = gAlerts)
alertBearTouch = input.bool(true, "Bearish OB Touch", group = gAlerts)
alertFullyMitigated = input.bool(true, "OB Fully Mitigated", group = gAlerts)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ UDT {
type OB
float qualityScore
float top
float bottom
int direction
int bornBar
int leftTime
float size
float mitigation
float volumeScore
float trendScore
int bullStrength
int bearStrength
box body
box bullBar
box bearBar
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Helpers {
volumeMA = ta.sma(volume, volLength)
trendEMA = ta.ema(close, trendLength)
atrValue = ta.atr(14)
barMs = timeframe.in_seconds(timeframe.period) * 1000
method isVisibleDirection(OB ob) =>
directionFilter == "All" or directionFilter == "Bullish Only" and ob.direction == 1 or directionFilter == "Bearish Only" and ob.direction == -1
method overlapsWith(OB ob, float newTop, float newBottom, int newDirection) =>
float overlapTop = math.min(ob.top, newTop)
float overlapBottom = math.max(ob.bottom, newBottom)
float overlapSize = math.max(overlapTop - overlapBottom, 0)
float existingSize = math.max(ob.top - ob.bottom, syminfo.mintick)
float newSize = math.max(newTop - newBottom, syminfo.mintick)
float smallestSize = math.min(existingSize, newSize)
float mergedTop = math.max(ob.top, newTop)
float mergedBottom = math.min(ob.bottom, newBottom)
float mergedSize = mergedTop - mergedBottom
bool sameDirection = ob.direction == newDirection
bool hasOverlap = overlapSize > 0
bool meaningfulOverlap = overlapSize / smallestSize >= 0.50
bool mergeNotTooLarge = mergedSize <= atrValue * maxMergeAtr
sameDirection and hasOverlap and meaningfulOverlap and mergeNotTooLarge
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Simple Candle Flip + ATR Body OB Logic {
flipBodySize = math.abs(close - open)
validFlipBody = flipBodySize >= atrValue * minFlipBodyAtr
bullishOB = close[1] < open[1] and close > open and validFlipBody
bearishOB = close[1] > open[1] and close < open and validFlipBody
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Variables {
var array obs = array.new()
var string topObKey = ""
newBullObAlert = false
newBearObAlert = false
newTopRankAlert = false
bullTouchAlert = false
bearTouchAlert = false
fullyMitigatedAlert = false
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Create Order Blocks {
if bullishOB or bearishOB
int direction = bullishOB ? 1 : -1
float top = na
float bottom = na
if direction == 1
top := high[1]
bottom := low[1]
else
top := high[1]
bottom := low[1]
float obSize = math.abs(top - bottom)
float volScore = volumeMA != 0 ? volume[1] / volumeMA : 1.0
float trendScore = direction == 1 and close > trendEMA ? 1.0 : direction == -1 and close < trendEMA ? 1.0 : 0.0
float mitigation = 0.0
float qualityScore = obSize * 100 + volScore * 10 + trendScore * 20 - mitigation * 50
float sizeStrength = math.min(obSize / atrValue, 2.0) / 2.0 * 40
float volStrength = math.min(volScore, 2.0) / 2.0 * 30
float trendStrength = trendScore * 20
float candleStrength = math.abs(close[1] - open[1]) / math.max(high[1] - low[1], syminfo.mintick) * 10
float totalStrength = sizeStrength + volStrength + trendStrength + candleStrength
int mainStrength = int(math.max(math.min(totalStrength, 100), 0))
int bullStrength = direction == 1 ? mainStrength : 100 - mainStrength
int bearStrength = direction == -1 ? mainStrength : 100 - mainStrength
newBullObAlert := direction == 1
newBearObAlert := direction == -1
bool merged = false
if mergeOverlaps and obs.size() > 0
for i = 0 to obs.size() - 1
OB existing = obs.get(i)
if existing.overlapsWith(top, bottom, direction) and not merged
existing.top := math.max(existing.top, top)
existing.bottom := math.min(existing.bottom, bottom)
existing.size := math.abs(existing.top - existing.bottom)
existing.qualityScore := math.max(existing.qualityScore, qualityScore)
existing.volumeScore := math.max(existing.volumeScore, volScore)
existing.trendScore := math.max(existing.trendScore, trendScore)
existing.mitigation := 0.0
existing.bullStrength := math.max(existing.bullStrength, bullStrength)
existing.bearStrength := math.max(existing.bearStrength, bearStrength)
float mergedMid = math.avg(existing.top, existing.bottom)
box.set_top(existing.body, existing.top)
box.set_bottom(existing.body, existing.bottom)
box.set_top(existing.bearBar, existing.top)
box.set_bottom(existing.bearBar, mergedMid)
box.set_top(existing.bullBar, mergedMid)
box.set_bottom(existing.bullBar, existing.bottom)
obs.set(i, existing)
merged := true
if not merged
color bodyColor = direction == 1 ? bullColor : bearColor
int leftTime = time[1]
int rightTime = time + barMs * 5
box body = box.new(
left = leftTime,
right = rightTime,
top = top,
bottom = bottom,
xloc = xloc.bar_time,
bgcolor = color.new(bodyColor, 78),
border_color = color.new(bodyColor, 25)
)
float mid = math.avg(top, bottom)
box bearBar = box.new(
left = leftTime,
right = leftTime,
top = top,
bottom = mid,
xloc = xloc.bar_time,
bgcolor = bearColor,
border_color = na
)
box bullBar = box.new(
left = leftTime,
right = leftTime,
top = mid,
bottom = bottom,
xloc = xloc.bar_time,
bgcolor = bullColor,
border_color = na
)
obs.push(OB.new(
qualityScore,
top,
bottom,
direction,
bar_index,
leftTime,
obSize,
mitigation,
volScore,
trendScore,
bullStrength,
bearStrength,
body,
bullBar,
bearBar
))
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Update Order Blocks {
if obs.size() > 0
for i = obs.size() - 1 to 0
OB ob = obs.get(i)
int age = bar_index - ob.bornBar
bool touched = ob.direction == 1 ? low <= ob.top : high >= ob.bottom
bullTouchAlert := bullTouchAlert or touched and ob.direction == 1
bearTouchAlert := bearTouchAlert or touched and ob.direction == -1
if touched
float fillDistance = ob.direction == 1 ? ob.top - low : high - ob.bottom
float zoneSize = math.max(ob.top - ob.bottom, syminfo.mintick)
ob.mitigation := math.min(math.max(fillDistance / zoneSize, 0), 1)
ob.qualityScore := ob.size * 100 + ob.volumeScore * 10 + ob.trendScore * 20 - ob.mitigation * 50 - age * 0.1
box.set_right(ob.body, time + barMs * 25)
if ob.mitigation >= 1
fullyMitigatedAlert := true
box.delete(ob.body)
box.delete(ob.bullBar)
box.delete(ob.bearBar)
obs.remove(i)
else
obs.set(i, ob)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Sort OBs by quality score {
obs.sort(order.descending, sort_field = "qualityScore")
if obs.size() > 0
OB topOb = obs.get(0)
string currentTopKey = str.tostring(topOb.leftTime) + "_" + str.tostring(topOb.direction)
newTopRankAlert := currentTopKey != topObKey
topObKey := currentTopKey
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Limit Stored Zones {
while obs.size() > maxStored
OB removed = obs.pop()
box.delete(removed.body)
box.delete(removed.bullBar)
box.delete(removed.bearBar)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Display Zones {
int visibleCount = 0
if obs.size() > 0
for i = 0 to obs.size() - 1
OB ob = obs.get(i)
bool passDirection = ob.isVisibleDirection()
bool showZone = passDirection and visibleCount < maxZones
if showZone
visibleCount += 1
color bodyColor = ob.direction == 1 ? bullColor : bearColor
int left = ob.leftTime
int right = time + barMs * 25
float top = box.get_top(ob.body)
float bot = box.get_bottom(ob.body)
float mid = math.avg(top, bot)
int barMaxWidth = int((right - left) * 0.45)
int bullWidth = int(barMaxWidth * ob.bullStrength / 100)
int bearWidth = int(barMaxWidth * ob.bearStrength / 100)
box.set_bgcolor(ob.body, showZone ? color.new(bodyColor, 78) : color.new(color.gray, 100))
box.set_border_color(ob.body, showZone ? color.new(bodyColor, 25) : color.new(color.gray, 100))
box.set_right(ob.body, right)
if showZone and showBars
box.set_left(ob.bearBar, left)
box.set_right(ob.bearBar, left + bearWidth)
box.set_top(ob.bearBar, top)
box.set_bottom(ob.bearBar, mid)
box.set_bgcolor(ob.bearBar, color.new(bearColor, 10))
box.set_border_color(ob.bearBar, na)
box.set_text(ob.bearBar, str.tostring(ob.bearStrength) + "%")
box.set_text_color(ob.bearBar, color.white)
box.set_text_halign(ob.bearBar, text.align_center)
box.set_text_valign(ob.bearBar, text.align_center)
box.set_text_size(ob.bearBar, strengthTextSize)
box.set_left(ob.bullBar, left)
box.set_right(ob.bullBar, left + bullWidth)
box.set_top(ob.bullBar, mid)
box.set_bottom(ob.bullBar, bot)
box.set_bgcolor(ob.bullBar, color.new(bullColor, 10))
box.set_border_color(ob.bullBar, na)
box.set_text(ob.bullBar, str.tostring(ob.bullStrength) + "%")
box.set_text_color(ob.bullBar, color.white)
box.set_text_halign(ob.bullBar, text.align_center)
box.set_text_valign(ob.bullBar, text.align_center)
box.set_text_size(ob.bullBar, strengthTextSize)
string labelText = ob.direction == 1 ? "Bullish OB" : "Bearish OB"
box.set_text(ob.body, showBlockText ? labelText : "")
box.set_text_halign(ob.body, text.align_right)
box.set_text_valign(ob.body, text.align_center)
box.set_text_color(ob.body, chart.fg_color)
box.set_text_size(ob.body, blockTextSize)
else
box.set_bgcolor(ob.bullBar, color.new(color.gray, 100))
box.set_bgcolor(ob.bearBar, color.new(color.gray, 100))
box.set_border_color(ob.bullBar, color.new(color.gray, 100))
box.set_border_color(ob.bearBar, color.new(color.gray, 100))
box.set_text(ob.bullBar, "")
box.set_text(ob.bearBar, "")
box.set_text(ob.body, "")
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
// ~~ Alerts {
alertcondition(alertNewBull and newBullObAlert, "New Bullish OB", "A new bullish Order Block has formed.")
alertcondition(alertNewBear and newBearObAlert, "New Bearish OB", "A new bearish Order Block has formed.")
alertcondition(alertTopRank and newTopRankAlert, "New Top-Ranked OB", "A new Order Block has become the highest-quality visible zone.")
alertcondition(alertBullTouch and bullTouchAlert, "Bullish OB Touch", "Price has touched or started mitigating a bullish Order Block.")
alertcondition(alertBearTouch and bearTouchAlert, "Bearish OB Touch", "Price has touched or started mitigating a bearish Order Block.")
alertcondition(alertFullyMitigated and fullyMitigatedAlert, "OB Fully Mitigated", "An Order Block has been fully mitigated and removed.")
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
Indicator Insider

HTF Candle Profile Indicator – Strategy
An indicator that builds a Volume Profile chart for exactly one higher-timeframe (HTF) candle currently forming, based on the volume of the current-timeframe bars falling inside it, then projects the whole analysis as a visual diagram right next to the chart.

Bollinger Bands Regression Forecast Indicator– Strategy
Traditional Bollinger Bands combined with a projected “forecast cone” — linearly extrapolating the trend of the middle line and the band width, showing what the bands might look like in the future.

Bollinger Bands Forecast with Signals Indicator – Strategy
Standard Bollinger Bands plus a forward-extrapolated “forecast cone”, an illustrative zigzag+random-walk price path threading through that cone, and an independent mean-reversion signal engine that fires when price gets rejected at the upper/lower band.
