What it does
The indicator highlights order blocks: the last candle that moved against the trend right before a strong move broke market structure. A bullish order block is the last down candle before a rally that closed above a swing high. A bearish order block is the last up candle before a drop that closed below a swing low. Each block is drawn as a box that extends to the right until price comes back and mitigates it.
How it works
- Swing highs and lows are confirmed pivots, using the swing length on both sides.
- When a closed candle finishes above the latest unbroken swing high, the script looks back over the leg that caused the break (up to the "max bars searched back" limit) and finds the lowest candle of that leg.
- From that candle it steps back up to five candles to find the most recent bearish candle. That candle's high and low become the bullish order block. Bearish blocks mirror the logic using the highest candle of the falling leg and the last bullish candle.
- Displacement filter: at least one candle between the block and the break must have a body of at least the set multiple of ATR (14). Breaks made by slow, overlapping candles are skipped.
- Mitigation is checked on every closed candle. With "Close beyond", a bullish block is mitigated when a candle closes below its low. With "Wick into block", any trade back into the block counts. Mitigated blocks are greyed out or removed.
Active and greyed blocks are stored in bounded lists, so old boxes are deleted before TradingView's drawing limit is reached.
How to use it
- Fresh, unmitigated blocks in the direction of the latest structure break are the ones most traders watch. A bullish block below price is a possible area for a pullback to pause.
- Combine the blocks with a structure tool and a higher-timeframe bias. A block that sits in the lower half of the recent range has more context than one in the middle.
- Use "Wick into block" if you treat the first touch as the event of interest, and "Close beyond" if you only want to retire blocks that have clearly failed.
- A stop just beyond the far side of the block is a common reference. Size the position with the Position Size Calculator and check the risk-reward ratio before you act.
Alerts: new bullish block, new bearish block, bullish block mitigated, bearish block mitigated.
Settings
| Input | Default | What it changes |
|---|---|---|
| Swing length | 5 | Which swings count as structure |
| Displacement: min candle body | 1.0 × ATR | Strength needed in the breaking leg |
| ATR length | 14 | ATR used by the displacement filter |
| Max bars searched back | 50 | How far back the leg and block are searched |
| Block is mitigated when | Close beyond | Wick touch or close through |
| After mitigation | Grey out | Grey out or remove |
| Max active blocks per side | 5 | Number of live blocks kept |
| Max mitigated blocks kept | 10 | Number of greyed blocks kept |
Limitations
A block is only created after the structure break closes, and the break itself depends on swings that confirm several candles late. The definition of an order block varies between traders, so this script's choice (last opposite candle near the start of the leg) will not match every textbook drawing. Blocks are zones, not exact prices: in fast markets price can pierce a block and keep going, and on low timeframes the spread can be a large part of the block's height. No zone guarantees a reaction.
Pine Script source
In TradingView open Pine Editor, paste the code, click Save and then Add to chart.
FXP_OrderBlocks.pine
164 lines
// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/order-blocks/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
//@version=6
// Marks order blocks: the last opposite-colored candle before a displacement leg that
// closes through the latest confirmed swing. Blocks extend right until mitigated, then
// are greyed out or removed. Storage is bounded so drawing limits are never exceeded.
indicator("FXP Order Blocks", shorttitle = "FXP OB", overlay = true, max_boxes_count = 500, max_bars_back = 500)
// ── Inputs ─────────────────────────────────────────────────────────────
grpDet = "Detection"
swingLen = input.int(5, "Swing length (bars each side)", minval = 1, maxval = 50, group = grpDet)
dispMult = input.float(1.0, "Displacement: min candle body (x ATR)", minval = 0.0, step = 0.1, group = grpDet, tooltip = "At least one candle in the breaking leg must have a body of this many ATRs. 0 = no displacement filter.")
atrLen = input.int(14, "ATR length", minval = 1, group = grpDet)
maxSearch = input.int(50, "Max bars searched back", minval = 5, maxval = 300, group = grpDet, tooltip = "Limits how far back the script looks for the start of the leg and the order-block candle.")
grpMit = "Mitigation"
mitMode = input.string("Close beyond", "Block is mitigated when", options = ["Wick into block", "Close beyond"], group = grpMit)
afterMit = input.string("Grey out", "After mitigation", options = ["Grey out", "Remove"], group = grpMit)
grpDisp = "Display"
maxActive = input.int(5, "Max active blocks per side", minval = 1, maxval = 50, group = grpDisp)
maxGrey = input.int(10, "Max mitigated blocks kept", minval = 0, maxval = 100, group = grpDisp)
showBull = input.bool(true, "Show bullish blocks", group = grpDisp)
showBear = input.bool(true, "Show bearish blocks", group = grpDisp)
colBull = input.color(color.new(#26a69a, 80), "Bullish block", group = grpDisp)
colBear = input.color(color.new(#ef5350, 80), "Bearish block", group = grpDisp)
colGrey = input.color(color.new(color.gray, 88), "Mitigated block", group = grpDisp)
// ── Types and storage ──────────────────────────────────────────────────
type OrderBlock
box bx
float top
float bottom
bool isBull
var array<OrderBlock> bullObs = array.new<OrderBlock>()
var array<OrderBlock> bearObs = array.new<OrderBlock>()
var array<OrderBlock> greyObs = array.new<OrderBlock>()
retire(OrderBlock ob) =>
if afterMit == "Remove"
ob.bx.delete()
else
ob.bx.set_right(bar_index)
ob.bx.set_bgcolor(colGrey)
ob.bx.set_border_color(color.new(color.gray, 60))
ob.bx.set_text_color(color.gray)
greyObs.push(ob)
while greyObs.size() > maxGrey
OrderBlock old = greyObs.shift()
old.bx.delete()
true
// ── Swing structure ────────────────────────────────────────────────────
float atr = ta.atr(atrLen)
ph = ta.pivothigh(high, swingLen, swingLen)
pl = ta.pivotlow(low, swingLen, swingLen)
var float swingHigh = na
var int swingHighBar = na
var bool highBroken = true
var float swingLow = na
var int swingLowBar = na
var bool lowBroken = true
if not na(ph)
swingHigh := ph
swingHighBar := bar_index - swingLen
highBroken := false
if not na(pl)
swingLow := pl
swingLowBar := bar_index - swingLen
lowBroken := false
// ── Mitigation of existing blocks (closed bars) ────────────────────────
bool bullMitigated = false
bool bearMitigated = false
if bullObs.size() > 0
for i = bullObs.size() - 1 to 0
OrderBlock ob = bullObs.get(i)
bool hit = barstate.isconfirmed and (mitMode == "Close beyond" ? close < ob.bottom : low <= ob.top)
if hit
bullMitigated := true
bullObs.remove(i)
retire(ob)
else
ob.bx.set_right(bar_index)
if bearObs.size() > 0
for i = bearObs.size() - 1 to 0
OrderBlock ob = bearObs.get(i)
bool hit = barstate.isconfirmed and (mitMode == "Close beyond" ? close > ob.top : high >= ob.bottom)
if hit
bearMitigated := true
bearObs.remove(i)
retire(ob)
else
ob.bx.set_right(bar_index)
// ── New blocks on structure breaks ─────────────────────────────────────
bool bullBreak = barstate.isconfirmed and not highBroken and not na(swingHigh) and close > swingHigh
bool bearBreak = barstate.isconfirmed and not lowBroken and not na(swingLow) and close < swingLow
bool newBull = false
bool newBear = false
if bullBreak
highBroken := true
int span = math.min(bar_index - swingHighBar, maxSearch)
// lowest point of the leg that produced the break
int legOff = 0
for i = 1 to span
if low[i] < low[legOff]
legOff := i
// last bearish candle at (or just before) the start of the leg
int obOff = -1
for i = legOff to legOff + 5
if close[i] < open[i]
obOff := i
break
if obOff > 0
float maxBody = 0.0
for i = 0 to obOff - 1
maxBody := math.max(maxBody, math.abs(close[i] - open[i]))
if maxBody >= dispMult * nz(atr) and showBull
box b = box.new(bar_index - obOff, high[obOff], bar_index, low[obOff], border_color = color.new(colBull, 40), bgcolor = colBull, text = "Bull OB", text_color = color.new(colBull, 0), text_size = size.small, text_halign = text.align_right, text_valign = text.align_bottom)
bullObs.push(OrderBlock.new(b, high[obOff], low[obOff], true))
newBull := true
while bullObs.size() > maxActive
OrderBlock old = bullObs.shift()
old.bx.delete()
if bearBreak
lowBroken := true
int span = math.min(bar_index - swingLowBar, maxSearch)
// highest point of the leg that produced the break
int legOff = 0
for i = 1 to span
if high[i] > high[legOff]
legOff := i
// last bullish candle at (or just before) the start of the leg
int obOff = -1
for i = legOff to legOff + 5
if close[i] > open[i]
obOff := i
break
if obOff > 0
float maxBody = 0.0
for i = 0 to obOff - 1
maxBody := math.max(maxBody, math.abs(close[i] - open[i]))
if maxBody >= dispMult * nz(atr) and showBear
box b = box.new(bar_index - obOff, high[obOff], bar_index, low[obOff], border_color = color.new(colBear, 40), bgcolor = colBear, text = "Bear OB", text_color = color.new(colBear, 0), text_size = size.small, text_halign = text.align_right, text_valign = text.align_top)
bearObs.push(OrderBlock.new(b, high[obOff], low[obOff], false))
newBear := true
while bearObs.size() > maxActive
OrderBlock old = bearObs.shift()
old.bx.delete()
// ── Alerts ─────────────────────────────────────────────────────────────
alertcondition(newBull, "New bullish order block", "{{ticker}} {{interval}}: new bullish order block")
alertcondition(newBear, "New bearish order block", "{{ticker}} {{interval}}: new bearish order block")
alertcondition(bullMitigated, "Bullish order block mitigated", "{{ticker}} {{interval}}: bullish order block mitigated at {{close}}")
alertcondition(bearMitigated, "Bearish order block mitigated", "{{ticker}} {{interval}}: bearish order block mitigated at {{close}}")

