What it measures
Market structure describes a trend as a sequence of swings. An uptrend makes higher highs (HH) and higher lows (HL); a downtrend makes lower highs (LH) and lower lows (LL). Two events mark changes in that sequence:
- A break of structure (BOS) is a close beyond the last swing in the direction of the trend, such as a close above the last high in an uptrend. It's read as continuation.
- A change of character (CHoCH) is the first break against the trend, such as a close above the last high while structure was bearish. It's read as an early sign that the trend may be turning.
The terms come from smart money concepts, but the idea of trends as swings is much older; see smart money concepts explained without the hype.
The formula in words
- Swings. A swing high is a high with N lower highs on each side (a pivot); a swing low is the mirror image. With N = 5, a swing is confirmed five bars after it happens.
- Labels. Each new swing high is an HH if it's above the previous swing high and an LH if it's below; each swing low is an HL or an LL in the same way.
- Breaks. A bullish break is the first close above the latest confirmed swing high; a bearish break is the first close below the latest confirmed swing low. Each swing can be broken once.
- BOS or CHoCH. A bullish break while structure is bullish is a BOS. A bullish break while structure is bearish is a CHoCH, and structure turns bullish. Bearish breaks mirror this.
Two details change the result: whether a break needs a close or just a wick beyond the swing (this script uses the close), and the swing strength N. Educators differ on both, which is why two "BOS" indicators rarely agree.
How traders read it
- Trend state. The last break tells you which way structure points. Many traders take longs only after a bullish BOS or CHoCH, and shorts only after a bearish one.
- CHoCH as a warning. A CHoCH after a long trend is read as a possible reversal, but the old trend often resumes. Some traders wait for a BOS in the new direction to confirm it.
- Pullbacks. After a BOS, traders look for price to pull back towards the broken level, or to an order block or fair value gap left by the move.
- Multiple timeframes. Structure on a higher timeframe sets the direction; a CHoCH on a lower timeframe in that direction is used for timing.
Common settings
| Setting | Typical value | What changing it does |
|---|---|---|
| Swing strength | 3 to 10 bars | Low values track minor swings and break often; high values track major swings and confirm late. |
| Break rule | Close beyond the swing | A wick rule gives earlier, more frequent breaks. |
| Timeframe | Any | The same rules on a higher timeframe describe the larger trend. |
Pine Script v6 code
The complete, self-contained script is below. Copy it into a new indicator in TradingView's Pine Editor, or download the .pine file.
//@version=6
indicator("Market structure: BOS and CHoCH", shorttitle = "Structure", overlay = true, max_lines_count = 500, max_labels_count = 500)
swingLen = input.int(5, "Swing strength (bars on each side)", minval = 1, maxval = 50, group = "Swings")
showSwings = input.bool(true, "Label swings (HH, HL, LH, LL)", group = "Display")
bullCol = input.color(color.teal, "Bullish breaks", group = "Display")
bearCol = input.color(color.red, "Bearish breaks", group = "Display")
// Swings are pivots: a high with swingLen lower highs on each side, confirmed swingLen bars later.
ph = ta.pivothigh(high, swingLen, swingLen)
pl = ta.pivotlow(low, swingLen, swingLen)
var float lastHigh = na
var int lastHighBar = na
var bool highBroken = true
var float lastLow = na
var int lastLowBar = na
var bool lowBroken = true
var int trend = 0 // 1 = bullish structure, -1 = bearish, 0 = not known yet
bosUp = false
chochUp = false
bosDown = false
chochDown = false
if barstate.isconfirmed
if not na(ph)
if showSwings
label.new(bar_index - swingLen, ph, na(lastHigh) ? "H" : ph > lastHigh ? "HH" : "LH", yloc = yloc.abovebar, style = label.style_none, textcolor = color.gray, size = size.small)
lastHigh := ph
lastHighBar := bar_index - swingLen
highBroken := false
if not na(pl)
if showSwings
label.new(bar_index - swingLen, pl, na(lastLow) ? "L" : pl < lastLow ? "LL" : "HL", yloc = yloc.belowbar, style = label.style_none, textcolor = color.gray, size = size.small)
lastLow := pl
lastLowBar := bar_index - swingLen
lowBroken := false
// A close above the last swing high: CHoCH if structure was bearish, BOS otherwise.
if not highBroken and close > lastHigh
highBroken := true
chochUp := trend == -1
bosUp := not chochUp
trend := 1
line.new(lastHighBar, lastHigh, bar_index, lastHigh, color = bullCol, style = chochUp ? line.style_dashed : line.style_solid)
label.new(int(math.avg(lastHighBar, bar_index)), lastHigh, chochUp ? "CHoCH" : "BOS", style = label.style_label_down, color = color.new(bullCol, 100), textcolor = bullCol, size = size.small)
// A close below the last swing low: CHoCH if structure was bullish, BOS otherwise.
if not lowBroken and close < lastLow
lowBroken := true
chochDown := trend == 1
bosDown := not chochDown
trend := -1
line.new(lastLowBar, lastLow, bar_index, lastLow, color = bearCol, style = chochDown ? line.style_dashed : line.style_solid)
label.new(int(math.avg(lastLowBar, bar_index)), lastLow, chochDown ? "CHoCH" : "BOS", style = label.style_label_up, color = color.new(bearCol, 100), textcolor = bearCol, size = size.small)
alertcondition(bosUp, "Bullish BOS", "Bullish break of structure on {{ticker}} ({{interval}}) at {{close}}")
alertcondition(bosDown, "Bearish BOS", "Bearish break of structure on {{ticker}} ({{interval}}) at {{close}}")
alertcondition(chochUp, "Bullish CHoCH", "Bullish change of character on {{ticker}} ({{interval}}) at {{close}}")
alertcondition(chochDown, "Bearish CHoCH", "Bearish change of character on {{ticker}} ({{interval}}) at {{close}}")Swings come from ta.pivothigh() and ta.pivotlow(). Each confirmed swing gets an HH, HL, LH or LL label drawn back on the bar where it happened, and each break draws a line from the swing to the breaking bar, solid for a BOS and dashed for a CHoCH, with a matching label.
The first break on the chart is labelled BOS because there's no earlier structure for it to change. All updates run on closed bars only, and four alert conditions cover bullish and bearish BOS and CHoCH; use Once per bar close.
Add it to your chart
- Open any chart on TradingView and open the Pine Editor.
- Create a new indicator, delete the template code and paste the script.
- Click Save, then Add to chart. Change lengths and levels from the indicator's settings.
Stuck on an error? See how to add a Pine Script indicator to TradingView.
Pitfalls
- Swing labels appear late. A swing is known only N bars after it happens. The label is drawn back on the swing bar, but it wasn't visible there in real time.
- Wick versus close. Compared with an indicator that breaks on wicks, signals will differ. Neither is "right"; pick one.
- Short swings, constant breaks. With N = 2 or 3 almost every small wiggle becomes a swing, and BOS and CHoCH labels pile up.
- CHoCH is not a reversal. It marks the first break against the trend. Many are followed by the old trend resuming.
- Repainting versions. Scripts that draw structure the moment a high forms are using unconfirmed swings and will change their past labels; see repainting explained.
Related reading
- Smart money concepts explained without the hype
- Pine Script repainting: how to spot it, test for it and fix it
- Multi-timeframe Pine Script with request.security(), without repainting
- Risk/Reward Calculator (free tool)
- Fibonacci Retracement Calculator (free tool)
The script is an educational example: test it on your own markets before relying on it. Indicators describe the past; they don't predict the future. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.