Volatility: True Range, ATR and Bollinger Bands
Everything in Part 34 about how much to risk on a trade rests on being able to say how much this instrument typically moves. That is what this lesson builds. It also contains the three most expensive default-value surprises in AmiBroker’s indicator set, and one function that does not exist however often you have seen it written.
True range: the measurement that survives a gap
Section titled “True range: the measurement that survives a gap”The obvious measure of a bar’s movement is High - Low. It has a hole in it. If a market
closes at 100 and opens the next day at 92 after news, and then trades quietly between 91 and
93, the high-minus-low range is 2 — while the market actually moved 9 points from where it was
last valued.
True range fixes that by including the previous close in the comparison. The published definition takes the largest of three quantities: the bar’s own range, the distance from the high to the previous close, and the distance from the low to the previous close. On an ordinary bar the first is largest and true range equals the range. On a gap bar one of the other two wins, and true range captures the jump.
A gap bar, measured two ways
| Bar | Day 1 | Day 2 | Day 3 |
|---|---|---|---|
Close | 100.0 | 92.5 | 93.0 |
High | 100.8 | 93.0 | 94.0 |
Low | 99.2 | 91.0 | 92.4 |
High - Low | 1.6 | 2.0 | 1.6 |
True range | 1.6 | 9.0 | 1.6 |
There is no TrueRange() function in AFL
Section titled “There is no TrueRange() function in AFL”You can prove the gap behaviour on your own data in one line:
Fragment — not a complete formula
// Non-zero bars are exactly the bars where the true range exceeded High - Low,// which is to say, the bars that gapped away from the previous close.Plot( ATR( 1 ) - ( High - Low ), "ATR(1) minus (High - Low)", colorBlue, styleHistogram );PlotGrid( 0, colorBlack, 9, 1, False );ATR: the smoothed version
Section titled “ATR: the smoothed version”ATR( period ) averages true range. Two documented facts, both of which cost people time:
ATR has no documented default period. The syntax line on the reference page is bare:
atr( period ). Every other indicator in this part shows its defaults in the syntax —
RSI( periods = 14 ), ADX( period = 14 ) — and ATR does not. Always write ATR( 14 )
explicitly. Never ATR().
ATR uses Wilder’s smoothing, not a simple moving average. This is stated on the page by
AmiBroker’s author, in as many words. If you are reconciling AmiBroker’s ATR against a
spreadsheet that takes a simple mean of true ranges, the two will not agree, and neither is
wrong — they are different averages. If you want the simple mean, MA( ATR( 1 ), 14 ) is the
documented way to build it.
Like every recursive indicator in this part, ATR carries its seed forward, so its early values are unreliable for far longer than 14 bars.
Fragment — not a complete formula
BarRange = ATR( 1 ); // the true range of each barVolatility = ATR( 14 ); // Wilder-smoothed, in price unitsVolPercent = 100 * ATR( 14 ) / Close; // comparable between instrumentsSimpleATR = MA( ATR( 1 ), 14 ); // the same idea, simple mean insteadStandard deviation, and the default nobody expects
Section titled “Standard deviation, and the default nobody expects”StDev( ARRAY, periods, Population = True ) computes a rolling standard deviation. The array
and the period are required; the third argument is the trap.
The default is the population standard deviation — dividing by N. AmiBroker documents that
StDev( array, range, True ) matches Excel’s STDEV.P, while StDev( array, range, False )
matches Excel’s STDEV, the sample version that divides by N-1. Most people reconciling AFL
against a spreadsheet reach for STDEV and then hunt for a bug that is not there. The
Population argument only exists from AmiBroker 6.20; older code passes two arguments and
silently gets population behaviour.
Bollinger Bands
Section titled “Bollinger Bands”A Bollinger Band is a moving average of the input with a band drawn a chosen number of standard deviations above and below it. AmiBroker provides the two edges:
Fragment — not a complete formula
// The array comes FIRST and is required. BBandTop( 20 ) is wrong.Upper = BBandTop( Close, 20, 2 );Lower = BBandBot( Close, 20, 2 );Width = 100 * ( Upper - Lower ) / Close; // band width as a percentage of priceThere is no BBandMid() function — you write the middle line yourself. But be careful about
what you write, because AmiBroker does not document which average or which standard-deviation
convention the band functions use internally. Do not assume the identity; check it:
Fragment — not a complete formula
// Is the built-in top band the same as MA + 2 * StDev? Find out on your own data.Assumed = MA( Close, 20 ) + 2 * StDev( Close, 20 );Plot( BBandTop( Close, 20, 2 ) - Assumed, "Built-in minus assumed", colorRed, styleLine );PlotGrid( 0, colorBlack, 9, 1, False );Contraction and expansion
Section titled “Contraction and expansion”Band width — the distance between the two edges, expressed as a percentage of price — is the
standard readout for how tightly recent closes have clustered. A narrow band means the last
n closes sat close to their mean. A wide band means they did not.
Volatility in most price series that have been studied shows clustering: quiet periods tend to be followed by quiet periods and violent ones by violent ones, more often than would happen if each day’s range were drawn independently. That is one of the more durable empirical regularities in market data, and it is the reason a volatility measure computed from the past is useful at all despite being backward-looking.
What clustering does not say is that a narrow band is followed by a large directional move. It says a narrow band tends to be followed by continued narrowness, until it is not. The popular “squeeze” claim — that contraction predicts an imminent breakout — adds a great deal to what clustering supports, and it is testable; the last section of this lesson sketches how.
Common misuse
Section titled “Common misuse”Reading two standard deviations as “95 per cent of the time”. That intuition comes from the normal distribution, and daily price changes in most series that have been studied are not normally distributed — the extremes happen far more often than the normal curve implies. Additionally, the standard deviation here is computed on a short rolling window of the very data whose deviation you are measuring, which is not the setting the textbook rule of thumb describes. Treat the width as a scaling factor, not as a probability.
Using ATR directionally. True range has no sign. A market can have a rising ATR while collapsing, while soaring, or while oscillating violently sideways.
Comparing raw volatility figures across instruments. ATR, standard deviation and band distance are all in price units. Normalise before comparing anything.
Trusting the left edge. ATR is recursive, and both band functions are undocumented as to warm-up. Discard a generous stretch of early bars.
Displaying them in AmiBroker
Section titled “Displaying them in AmiBroker”Bollinger Bands go on the price pane, because they are in price units and share its scale —
drag the entry from Window -> Charts onto the price pane rather than double-clicking it,
which would create a new pane.
ATR goes in its own pane, because a typical daily move of 1.8 would be invisible against a price axis running from 200 to 400. If you plan to compare across years, plot the normalised form instead:
Fragment — not a complete formula
Plot( 100 * ATR( 14 ) / Close, "ATR(14) as % of close", colorBlue, styleLine | styleThick );PlotGrid( 0, colorBlack, 8, 1, False );Band width deserves its own pane too, and is more useful there than the bands themselves are on price, because contraction is much easier to see as a line falling towards zero than as two lines getting closer together.
Turning this into a testable question
Section titled “Turning this into a testable question”The squeeze claim, made specific enough to be wrong:
On my universe, over my chosen period, is the average absolute 20-day forward price change larger following bars whose Bollinger band width was in the lowest fifth of its own trailing two-year range than following all other bars?
Several deliberate choices are in that sentence. Absolute forward change, because the claim
is about the size of the move and not its direction. Its own trailing range, using something
like PercentRank( Width, 500 ), because a band width of 4 per cent means different things on
different instruments and a fixed threshold would smuggle in an extra parameter. And a stated
horizon, because “an imminent breakout” with no time limit cannot be falsified.
Then anticipate the objection before you run it, because someone will raise it: volatility clusters, so low volatility tends to be followed by low volatility. If the test comes back showing smaller subsequent moves after contraction, that is the clustering result and it is evidence against the squeeze claim as usually stated. If it comes back showing larger moves, the interesting follow-up is whether that survives when you compare like with like — for instance, by measuring the forward change relative to the volatility that preceded it rather than in absolute terms.
True range measures a bar’s movement from the previous close, so it survives gaps that
High - Low misses; there is no TrueRange() function in AFL and the documented way to get it
is ATR( 1 ). ATR( period ) has no documented default and uses Wilder’s smoothing rather than
a simple mean, so always pass the period and expect spreadsheet reconciliations to differ.
StDev defaults to the population standard deviation. BBandTop and BBandBot default to
a 15-bar average, not the 20 everyone assumes, and their internal average type is
undocumented, so verify before asserting a midline. All of these are in price units; normalise
before comparing anything to anything.
Check your understanding
Sources for this lesson
6 verified · checked 2026-08-31
- 01AFL Function Reference — ATR§ Comments on Wilder's smoothing and ATR(1)amibroker.com/guide/afl/atr.html2026-08-31
- 02AFL Function Reference — StDevamibroker.com/guide/afl/stdev.html2026-08-31
- 03AFL Function Reference — BBandTopamibroker.com/guide/afl/bbandtop.html2026-08-31
- 04AFL Function Reference — BBandBotamibroker.com/guide/afl/bbandbot.html2026-08-31
- 05AFL Function Reference — PercentRankamibroker.com/guide/afl/percentrank.html2026-08-31
- 06AmiBroker User's Guide — AFL function index§ Checked for TrueRange, which is absentamibroker.com/guide/a_funref.html2026-08-31
Every technical claim on this page was checked against the official AmiBroker documentation on the date shown. Where the course disagrees with folklore, the source is how you can tell which one to trust.