The rules at a glance
- Structure: swing highs and lows with five candles on each side; higher highs and higher lows make an uptrend, lower highs and lower lows a downtrend
- BOS (break of structure): a close beyond the last swing point in the direction of the trend (continuation)
- CHoCH (change of character): the first close beyond the last swing point against the trend (possible reversal)
- Entry: after the break, a limit order halfway back into the leg that made it (the 50% level)
- Stop loss and target: beyond the start of that leg plus a quarter of the ATR; target twice the risk (2R)
- Choose: trade BOS, CHoCH or both; the order is cancelled if it hasn't filled within 20 candles
Market structure in one minute
Prices move in swings. A swing high is a peak with lower highs on both sides of it; a swing low is a dip with higher lows on both sides. In an uptrend, each swing high is higher than the last (a higher high, HH) and each swing low is higher too (a higher low, HL). In a downtrend you get lower highs (LH) and lower lows (LL). Reading this pattern is called reading market structure, and it's the base of both price action trading and smart money concepts.
To keep it objective, the script defines a swing point the same way every time: a candle whose high (or low) is beyond the five candles on each side. That means a swing is only confirmed five candles after it happens, which is a lag, but it stops you redrawing swings to suit yourself.
BOS vs CHoCH
| Break of structure (BOS) | Change of character (CHoCH) | |
|---|---|---|
| What breaks | The last swing point in the trend's direction (the last high in an uptrend) | The last swing point against the trend (the last higher low in an uptrend, or the last lower high in a downtrend) |
| What it suggests | The trend is carrying on | The trend may be turning |
| How often | Often, in a trending market | Less often; many turn out to be false |
| Risk | Trading with the trend | Trading against the trend that was in place |
These are the common SMC definitions, but the terms aren't standardised: some traders call a CHoCH a market structure shift (MSS), and some keep MSS for a stronger break that follows a liquidity sweep.
Both are confirmed by a candle close beyond the swing point, not just a wick. A wick through a swing point that closes back inside is a different event, a liquidity sweep, and it often comes just before a CHoCH in the other direction.
The rules, step by step
1. Let the script track the swings
Every confirmed swing high and low is marked. The first close beyond one of them sets the trend's direction; from then on, each new break is labelled BOS (with the trend) or CHoCH (against it).
2. Choose what to trade
BOS only keeps you with the trend. CHoCH only trades reversals, which are rarer and riskier. Both takes every break. The default is both; test each mode separately before you choose.
3. Don't chase the break: wait for 50%
The candle that breaks structure is often a big one, and buying its close puts your stop a long way off. Instead, take the leg that made the break, from its start (the lowest low since the broken swing high) to the top of the breaking candle, and place a limit order halfway back. SMC traders call the halfway point equilibrium, the lower half of a range the discount zone and the upper half the premium zone, and prefer to buy at or below equilibrium.
4. Stop beyond the start of the leg
The stop goes below the leg's low, minus a quarter of the ATR. If the price goes back below the low the move started from, the break has failed. The target is twice the risk.
5. Cancel stale orders
If the pullback hasn't come within 20 candles, the order is cancelled.
Worked example: EURUSD, 1-hour chart
EURUSD is falling. After a lower high at 1.1748, it breaks its swing low at 1.1714 with a close at 1.1708: a BOS down, confirming the downtrend, and goes on to a lower low at 1.1688. (The script only labels a break once an earlier break has set the trend. This chart starts mid-trend, so assume that has already happened.) Then the buyers take over, and a candle closes at 1.1753, above the lower high. That's a change of character. The leg runs from 1.1688 to the CHoCH candle's high of 1.1756, 68 pips, so the halfway point is 1.1722.
| EURUSD long after the CHoCH | |
|---|---|
| Entry (buy limit at 50% of the leg) | 1.1722 |
| Stop loss | 1.1685 (leg low 1.1688 minus a quarter of the ATR), 37 pips away |
| Target (2R) | 1.1796, 74 pips away |
| Account and risk | $10,000, risking 1% = $100 |
| Lot size | $100 ÷ (37 pips × $10) = 0.27 lots |
| Loss if the stop is hit | $99.90 |
| Profit at the target | $199.80 |
5 hours after the CHoCH, the price pulls back and fills the order. It holds, the new uptrend gets going, and 14 hours after the fill the target is reached. Buying the CHoCH candle's close at 1.1753 instead would have needed a stop about 68 pips away for the same idea, so the same $100 would have bought a much smaller position.
Why most CHoCH signals fail, and what helps
A trend doesn't usually turn on one candle. Many CHoCH breaks are just a deeper pullback inside the old trend, and the price soon carries on the old way. A few things tend to make a CHoCH more convincing; treat them as filters to test:
- A sweep first. The price takes out the last low (a liquidity sweep) before turning up and breaking the lower high.
- A strong break. A big, decisive candle through the swing point, often leaving a fair value gap.
- Higher-timeframe agreement. A CHoCH up on the 15-minute chart at a 4-hour support level means more than one in the middle of nowhere.
- The session. Breaks in London or New York hours carry more volume than ones in the quiet hours; see the ICT kill zones.
Risk management
- Size from the stop, every time. Legs differ in length, so the stop distance changes with each setup.
- Trade CHoCH smaller, or not at all, until you've tested it. Reversal trades go against the trend that was in place.
- Skip huge legs. After a news spike, the 50% level can be very far from the leg's start. A stop that wide doesn't suit a 2R target on an ordinary day.
- Watch the news. Data releases can break structure in both directions within minutes. See the economic calendar.
Common mistakes
- Counting wicks as breaks. Structure breaks on a close.
- Redrawing swings to fit the story. Fixed swing rules, like the script's, keep the reading the same every time.
- Buying every CHoCH as if it were a new trend. Most reversals need confirmation, such as a BOS in the new direction.
- Mixing timeframes without noticing. A BOS on the 5-minute chart can be a pullback on the 1-hour chart. Know which one you're trading.
Backtest it yourself on TradingView
This page doesn't quote a win rate or a profit figure, and you should be wary of anyone who does without showing the test. Results change with the market, the dates, the data feed and the costs you put in. The honest way is to run the test yourself, and the free script below does the work in a couple of minutes.
- Open the chart. On TradingView, open EURUSD on the 1-hour chart.
- Add the script. Open the Pine Editor from the Pine icon in the panel on the right of the chart (older layouts have it as a tab under the chart), delete the code that's already there, paste this strategy's code and click "Add to chart".
- Put in your costs. Open the strategy's settings and go to Properties. Enter your broker's commission and a little slippage. A test without costs flatters every strategy, and short-term ones most of all.
- Read the Strategy Tester. The panel under the chart shows the net profit, the maximum drawdown, the profit factor, the number of trades and a list of every trade. Click a few trades and check on the chart that each one follows the rules. Trade sizes are shown in units rather than lots: 100,000 units is one lot of a forex pair, and 100 ounces is one lot of gold.
- Change one setting at a time. Run the three modes (BOS only, CHoCH only, both) separately first; that's the most useful comparison on this page. Then try the swing strength (3 to 8 candles), the pullback level (38% to 62%), how long the order waits and the target.
As a rough guide, don't trust a result with fewer than 100 trades, a profit factor under 1 once costs are in, or a drawdown you couldn't sit through with real money. Settings tuned to look good on one stretch of history often fail on the next, so check the same settings on dates you didn't tune them on, then demo trade the strategy for a few weeks. How much history you can test depends on your TradingView plan: the free plan loads 5,000 candles, which is a few months of 15-minute candles but years of daily ones.
The script sizes each trade from the stop loss and your risk percentage, starts with $10,000 and assumes 1:100 leverage (a 1% margin setting), so the tester doesn't flag margin calls on normal forex trade sizes. Change these under Properties to match your account. By default TradingView fills orders at the open of the candle after the signal, so the fill can differ a little from the signal candle's close. Because entries are limit orders, the fill is almost always at your limit price, or better if a candle opens beyond it.
Download the free Pine Script and PDF
The script is written in Pine Script version 6 for this guide and follows the rules above. It can't read the economic calendar, so skipping news days is up to you. It's free to use, change and share. The PDF is a one-page cheat sheet with the rules, a worked example, the lot size maths and a checklist, to keep next to your chart.
Show the code (Pine Script v6, 113 lines)
//@version=6
// BOS and CHoCH (market structure) strategy by PipLedger (https://pipledgerfx.com/bos-choch-strategy)
// Tracks swing highs and lows, labels each break as a change of character (CHoCH) or a break of structure (BOS),
// then buys or sells a pullback into the leg that made the break.
// Free to use and change. For education and backtesting only; not financial advice.
strategy("PipLedger: BOS and CHoCH", shorttitle="BOS/CHoCH", overlay=true, initial_capital=10000, pyramiding=0, default_qty_type=strategy.fixed, default_qty_value=1, margin_long=1, margin_short=1, currency=currency.USD)
// ---------- Inputs
swingLen = input.int(5, "Swing strength (bars on each side)", minval=2)
mode = input.string("Both", "Breaks to trade", options=["Both", "CHoCH only (reversals)", "BOS only (continuation)"])
retrace = input.float(50, "Entry: pullback into the break leg (%)", minval=10, maxval=90, step=5)
validFor = input.int(20, "Candles the order stays valid", minval=1)
stopBuf = input.float(0.25, "Stop buffer beyond the leg (x ATR 14)", minval=0.0, step=0.05)
rr = input.float(2.0, "Target (R multiple)", minval=0.5, step=0.25)
riskPct = input.float(1.0, "Risk per trade (% of equity)", minval=0.1, maxval=5.0, step=0.1)
// ---------- Helpers
fx = strategy.convert_to_account(1.0) // one unit of the chart's quote currency in account currency (1 on USD-quoted charts)
calcQty(float entry, float stop) =>
float dist = math.abs(entry - stop)
dist > 0 and not na(fx) ? strategy.equity * riskPct / 100 / (dist * syminfo.pointvalue * fx) : 0.0
atr = ta.atr(14)
ph = ta.pivothigh(high, swingLen, swingLen)
pl = ta.pivotlow(low, swingLen, swingLen)
// ---------- Swing points and trend
var float swHi = na
var float swLo = na
if not na(ph)
swHi := ph
if not na(pl)
swLo := pl
// the break leg starts at the lowest low (or highest high) since the swing point that was broken;
// tracked bar by bar, so the script never needs a long, changing lookback
lowSincePivot = ta.lowest(low, swingLen + 1)
highSincePivot = ta.highest(high, swingLen + 1)
var float legLow = na
var float legHigh = na
legLow := not na(ph) ? lowSincePivot : math.min(legLow, low)
legHigh := not na(pl) ? highSincePivot : math.max(legHigh, high)
var int trend = 0 // 1 = up (higher highs and lows), -1 = down, 0 = not known yet
breakUp = not na(swHi) and close > swHi and close[1] <= swHi
breakDn = not na(swLo) and close < swLo and close[1] >= swLo
chochUp = breakUp and trend == -1
bosUp = breakUp and trend == 1
chochDn = breakDn and trend == 1
bosDn = breakDn and trend == -1
if breakUp
trend := 1
swHi := na // each swing level breaks once
if breakDn
trend := -1
swLo := na
useChoch = mode != "BOS only (continuation)"
useBos = mode != "CHoCH only (reversals)"
takeUp = (chochUp and useChoch) or (bosUp and useBos)
takeDn = (chochDn and useChoch) or (bosDn and useBos)
// ---------- Waiting orders
var float lvlStop = na
var int oDir = 0
var int oBar = na
flat = strategy.position_size == 0
if not flat
oDir := 0
if oDir == 1 and (bar_index - oBar >= validFor or close < lvlStop)
strategy.cancel("Long")
oDir := 0
if oDir == -1 and (bar_index - oBar >= validFor or close > lvlStop)
strategy.cancel("Short")
oDir := 0
// ---------- Orders: a limit order part-way back into the break leg, stop beyond the start of the leg
if flat and takeUp
strategy.cancel("Short")
float px = high - (high - legLow) * retrace / 100
float sl = legLow - atr * stopBuf
float q = calcQty(px, sl)
lvlStop := legLow
oDir := 1
oBar := bar_index
if q > 0 and px < close
strategy.entry("Long", strategy.long, qty=q, limit=px)
strategy.exit("Long exit", "Long", stop=sl, limit=px + (px - sl) * rr)
else
strategy.cancel("Long")
oDir := 0
else if flat and takeDn
strategy.cancel("Long")
float px = low + (legHigh - low) * retrace / 100
float sl = legHigh + atr * stopBuf
float q = calcQty(px, sl)
lvlStop := legHigh
oDir := -1
oBar := bar_index
if q > 0 and px > close
strategy.entry("Short", strategy.short, qty=q, limit=px)
strategy.exit("Short exit", "Short", stop=sl, limit=px - (sl - px) * rr)
else
strategy.cancel("Short")
oDir := 0
// ---------- Chart
plot(swHi, "Last swing high", color=color.new(color.gray, 30), style=plot.style_linebr)
plot(swLo, "Last swing low", color=color.new(color.gray, 30), style=plot.style_linebr)
plotshape(chochUp, "CHoCH up", shape.labelup, location.belowbar, color.teal, text="CHoCH", textcolor=color.white, size=size.tiny)
plotshape(bosUp, "BOS up", shape.labelup, location.belowbar, color.new(color.teal, 40), text="BOS", textcolor=color.white, size=size.tiny)
plotshape(chochDn, "CHoCH down", shape.labeldown, location.abovebar, color.red, text="CHoCH", textcolor=color.white, size=size.tiny)
plotshape(bosDn, "BOS down", shape.labeldown, location.abovebar, color.new(color.red, 40), text="BOS", textcolor=color.white, size=size.tiny)Trading this from India
Residents can trade EURUSD, GBPUSD and USDJPY futures and options on NSE and BSE through a SEBI-registered broker. The contracts are quoted the same way as the spot pair, one lot is 1,000 units of the base currency, and they're cash-settled in rupees. They trade only from 9 a.m. to 7:30 p.m. IST, volumes are thin and not every broker offers them, so check the hours and the spread first. Trading forex or CFDs with offshore online platforms isn't allowed for residents under FEMA, even if the app is easy to download. Details are in is forex trading legal in India.
Related strategies: liquidity sweep, fair value gap, order block, ICT kill zones, XAUUSD FVG + order block. All 34 strategies are compared on one page in forex trading strategies.