Skip to content

Free TradingView indicator

Order Blocks

Marks bullish and bearish order blocks: the last opposite candle before a displacement leg that breaks structure, with mitigation.

Smart moneyv1.0TradingView (Pine Script v6) Non-repainting signals Alerts

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

  1. Swing highs and lows are confirmed pivots, using the swing length on both sides.
  2. 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.
  3. 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.
  4. 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.
  5. 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

InputDefaultWhat it changes
Swing length5Which swings count as structure
Displacement: min candle body1.0 × ATRStrength needed in the breaking leg
ATR length14ATR used by the displacement filter
Max bars searched back50How far back the leg and block are searched
Block is mitigated whenClose beyondWick touch or close through
After mitigationGrey outGrey out or remove
Max active blocks per side5Number of live blocks kept
Max mitigated blocks kept10Number 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}}")

Key inputs

Key inputs of Order Blocks
InputDefaultDescription
Swing length5Bars on each side of the swing that must be broken.
Displacement: min candle body1.0 × ATRAt least one candle in the breaking leg must be this large (0 = off).
Block is mitigated whenClose beyondMitigate on a wick into the block or only on a close through it.
After mitigationGrey outGrey the block out or remove it from the chart.
Max active blocks per side5Oldest blocks are deleted once this number is reached.

Educational tools, not advice. Indicators and Expert Advisors describe price; they do not predict it, and past behaviour does not guarantee future results. Test everything on a demo account first and never risk money you cannot afford to lose. See our disclaimer.

Keep exploring

Related tradingview indicators