Skip to content

Free TradingView indicator

Market Structure BOS and CHoCH

Labels swing highs and lows as HH, HL, LH or LL and marks break of structure and change of character on closed candles.

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

What it does

This indicator turns a price chart into a readable map of market structure. It labels every confirmed swing as a higher high (HH), lower high (LH), higher low (HL) or lower low (LL), and it draws a line whenever price closes through the most recent swing. A break in the direction of the existing structure is marked BOS (break of structure). A break against it is marked CHoCH (change of character), an early hint that the swing sequence may be turning.

It is a smart money concepts building block, but the logic is plain swing analysis. It works on any market and any timeframe.

How it works

  1. A swing high is a candle whose high is the highest of the candles on each side of it, with the number of candles set by the swing length. Swing lows work the same way with lows. This means a swing is only confirmed after that many further candles have closed.
  2. Each new swing high is compared with the previous swing high: higher is HH, lower is LH. Lows are compared the same way to give HL or LL.
  3. The script remembers the latest unbroken swing high and swing low. When a closed candle finishes above the swing high (or, if you choose, just trades above it), that is a bullish break. A close below the swing low is a bearish break.
  4. The script tracks the current structure direction. A bullish break while structure is bearish is a bullish CHoCH. A bullish break while structure is already bullish (or not yet set) is a bullish BOS. Bearish breaks mirror this. Each swing level can only be broken once.

BOS lines are solid and CHoCH lines are dashed. Both start at the swing and end on the breaking candle.

How to use it

  • Read the trend from the labels first. A run of HH and HL describes an uptrend. A run of LH and LL describes a downtrend. Mixed labels usually mean a range.
  • Treat BOS as confirmation that the current leg is continuing, and CHoCH as a warning rather than a reversal signal on its own. Many traders wait for a CHoCH on a lower timeframe to line up with a level from a higher timeframe, such as support and resistance.
  • Use a swing length of 3 to 5 for intraday charts and 8 to 15 if you want only the major swings.
  • Plan risk before acting on any break. The Position Size Calculator helps you size a trade from your stop distance.

Alerts are available for bullish BOS, bearish BOS, bullish CHoCH and bearish CHoCH. They fire on candle close.

Settings

InputDefaultWhat it changes
Swing length5Bars on each side of a swing; higher values mean fewer, bigger swings
Show HH / HL / LH / LL labelsOnSwing labels on the chart
Break confirmed byCloseClose or wick through the swing level
Show BOSOnBreak-of-structure lines
Show CHoCHOnChange-of-character lines
Plot unbroken swing levelsOnFaint lines at the live swing high and low
Colors and label sizeTeal / red, smallAppearance only

Limitations

Swings are confirmed late by design: with a swing length of 5, a swing label appears five candles after the actual high or low. Labels never move once printed, but a break can only be measured against swings that have been confirmed. In choppy, sideways markets the script will print alternating BOS and CHoCH labels that carry little meaning. Wick-based breaks react faster but produce more false breaks, especially around news and when spreads widen. Structure is only a description of past price. It does not predict the next move.

Pine Script source

In TradingView open Pine Editor, paste the code, click Save and then Add to chart.

FXP_MarketStructureBosChoch.pine

100 lines

// © FXPIPS ONLINE — https://fxpips.online/tradingview-indicators/market-structure-bos-choch/
// License: MIT. Educational tool — not financial advice. Trading involves substantial risk.
//@version=6
// Labels confirmed swing points (HH / HL / LH / LL) and marks structure breaks.
// A close through the latest swing in the direction of the current structure is a
// break of structure (BOS); a close through it against the structure is a change of
// character (CHoCH). Breaks are evaluated on closed bars only.
indicator("FXP Market Structure BOS CHoCH", shorttitle = "FXP Structure", overlay = true, max_labels_count = 500, max_lines_count = 500)

// ── Inputs ─────────────────────────────────────────────────────────────
grpSwing   = "Swings"
swingLen   = input.int(5, "Swing length (bars each side)", minval = 1, maxval = 50, group = grpSwing, tooltip = "A swing high needs this many lower highs on each side. Larger values = fewer, more significant swings (confirmed later).")
showSwings = input.bool(true, "Show HH / HL / LH / LL labels", group = grpSwing)

grpBreak   = "Structure breaks"
breakSrc   = input.string("Close", "Break confirmed by", options = ["Close", "Wick"], group = grpBreak)
showBos    = input.bool(true, "Show BOS", group = grpBreak)
showChoch  = input.bool(true, "Show CHoCH", group = grpBreak)
showLevels = input.bool(true, "Plot unbroken swing levels", group = grpBreak)

grpStyle   = "Style"
colBull    = input.color(#26a69a, "Bullish color", group = grpStyle)
colBear    = input.color(#ef5350, "Bearish color", group = grpStyle)
colSwing   = input.color(color.gray, "Swing label color", group = grpStyle)
sizeIn     = input.string("Small", "Label size", options = ["Tiny", "Small", "Normal"], group = grpStyle)

lblSize = switch sizeIn
    "Tiny"  => size.tiny
    "Small" => size.small
    => size.normal

// ── Swing detection ────────────────────────────────────────────────────
ph = ta.pivothigh(high, swingLen, swingLen)
pl = ta.pivotlow(low, swingLen, swingLen)

var float swingHigh       = na
var int   swingHighBar    = na
var bool  swingHighBroken = true
var float swingLow        = na
var int   swingLowBar     = na
var bool  swingLowBroken  = true
var int   trend           = 0   // 1 = bullish structure, -1 = bearish, 0 = undefined

if not na(ph)
    string tagH = na(swingHigh) ? "H" : ph > swingHigh ? "HH" : "LH"
    if showSwings
        label.new(bar_index - swingLen, ph, tagH, style = label.style_label_down, color = color.new(color.black, 100), textcolor = colSwing, size = lblSize)
    swingHigh       := ph
    swingHighBar    := bar_index - swingLen
    swingHighBroken := false

if not na(pl)
    string tagL = na(swingLow) ? "L" : pl < swingLow ? "LL" : "HL"
    if showSwings
        label.new(bar_index - swingLen, pl, tagL, style = label.style_label_up, color = color.new(color.black, 100), textcolor = colSwing, size = lblSize)
    swingLow       := pl
    swingLowBar    := bar_index - swingLen
    swingLowBroken := false

// ── Structure breaks (closed bars only) ────────────────────────────────
float upSrc = breakSrc == "Wick" ? high : close
float dnSrc = breakSrc == "Wick" ? low : close

bool bullBreak = barstate.isconfirmed and not swingHighBroken and not na(swingHigh) and upSrc > swingHigh
bool bearBreak = barstate.isconfirmed and not swingLowBroken and not na(swingLow) and dnSrc < swingLow

bool bullChoch = bullBreak and trend == -1
bool bullBos   = bullBreak and trend != -1
bool bearChoch = bearBreak and trend == 1
bool bearBos   = bearBreak and trend != 1

drawBreak(int x1, float y, bool isChoch, bool isBull) =>
    color c = isBull ? colBull : colBear
    line.new(x1, y, bar_index, y, color = c, style = isChoch ? line.style_dashed : line.style_solid, width = 1)
    int mid = math.round((x1 + bar_index) / 2.0)
    label.new(mid, y, isChoch ? "CHoCH" : "BOS", style = isBull ? label.style_label_down : label.style_label_up, color = color.new(color.black, 100), textcolor = c, size = lblSize)
    true

if bullBreak
    if (bullChoch and showChoch) or (bullBos and showBos)
        drawBreak(swingHighBar, swingHigh, bullChoch, true)
    swingHighBroken := true
    trend := 1

if bearBreak
    if (bearChoch and showChoch) or (bearBos and showBos)
        drawBreak(swingLowBar, swingLow, bearChoch, false)
    swingLowBroken := true
    trend := -1

// ── Unbroken swing levels ──────────────────────────────────────────────
plot(showLevels and not swingHighBroken ? swingHigh : na, "Swing high level", color = color.new(colBear, 50), style = plot.style_linebr)
plot(showLevels and not swingLowBroken ? swingLow : na, "Swing low level", color = color.new(colBull, 50), style = plot.style_linebr)

// ── Alerts ─────────────────────────────────────────────────────────────
alertcondition(bullBos, "Bullish BOS", "{{ticker}} {{interval}}: bullish break of structure at {{close}}")
alertcondition(bearBos, "Bearish BOS", "{{ticker}} {{interval}}: bearish break of structure at {{close}}")
alertcondition(bullChoch, "Bullish CHoCH", "{{ticker}} {{interval}}: bullish change of character at {{close}}")
alertcondition(bearChoch, "Bearish CHoCH", "{{ticker}} {{interval}}: bearish change of character at {{close}}")

Key inputs

Key inputs of Market Structure BOS and CHoCH
InputDefaultDescription
Swing length5Bars required on each side of a swing high or low.
Break confirmed byCloseWhether a candle close or just a wick must cross the swing level.
Show BOSOnDraws break-of-structure lines and labels.
Show CHoCHOnDraws change-of-character lines and labels.
Plot unbroken swing levelsOnShows the latest swing high and low until price breaks them.

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