Skip to content
Level 1 · Chart ReaderLessonPart 06 · page 8 of 1128 min
28Minutes
7AFL functions
6Sources
StandardRequires
AFL functions taught here7

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

On day 2 the high-minus-low range says the market barely moved. True range measures from the previous close and reports 9.0. Every stop distance and position size you set later depends on which of those two numbers you used.
BarDay 1Day 2Day 3
Close100.092.593.0
High100.893.094.0
Low99.291.092.4
High - Low1.62.01.6
True range1.69.01.6
On day 2 the high-minus-low range says the market barely moved. True range measures from the previous close and reports 9.0. Every stop distance and position size you set later depends on which of those two numbers you used. Prices in this diagram are invented for the illustration. They are not market data and nothing should be inferred from them.

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( 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 bar
Volatility = ATR( 14 ); // Wilder-smoothed, in price units
VolPercent = 100 * ATR( 14 ) / Close; // comparable between instruments
SimpleATR = MA( ATR( 1 ), 14 ); // the same idea, simple mean instead

Standard 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.

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 price

There 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 );

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.

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.

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.

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

Question 1. You need the true range of each bar in AmiBroker. What do you write?
Show the answer and why

Answer: ATR( 1 )

TrueRange() is not an AmiBroker function — it is absent from both official indexes. ATR( 1 ) is the documented way, and the ATR page uses it to build alternative averages such as MA( ATR(1), period ). High - Low misses gaps entirely.

Question 2. BBandTop( Close ) on an AmiBroker chart does not match the upper Bollinger Band on a colleague’s platform. What is the most likely reason?
Show the answer and why

Answer: AmiBroker’s default period is 15, while the usual convention elsewhere is 20

The documented syntax is BBandTop( ARRAY, periods = 15, width = 2 ). The width default matches convention; the period default does not. Passing the period explicitly removes the problem permanently.

Question 3. Which statements are supported by AmiBroker’s documentation? Select all that apply.
Show the answer and why

Answer: ATR uses Wilder’s smoothing rather than a simple moving average, StDev defaults to the population standard deviation, ATR has no documented default period

The first, second and fourth are stated on the respective pages. The band pages state neither the internal average type nor the standard-deviation convention, so the identity is an assumption you should verify on your own data rather than a documented fact.

Question 4. A test shows that after unusually narrow Bollinger bands, the average absolute 20-day forward move was smaller than after other bars. What does that most directly suggest?
Show the answer and why

Answer: It is consistent with volatility clustering and is evidence against the squeeze as usually stated

Quiet periods tending to be followed by quiet periods is the well-documented clustering behaviour. It is the null expectation such a test has to beat, which is exactly why the objection is worth anticipating before running the test rather than after seeing the answer.

Sources for this lesson

6 verified · checked 2026-08-31

  1. 01AFL Function Reference — ATR§ Comments on Wilder's smoothing and ATR(1)amibroker.com/guide/afl/atr.html2026-08-31
  2. 02AFL Function Reference — StDevamibroker.com/guide/afl/stdev.html2026-08-31
  3. 03AFL Function Reference — BBandTopamibroker.com/guide/afl/bbandtop.html2026-08-31
  4. 04AFL Function Reference — BBandBotamibroker.com/guide/afl/bbandbot.html2026-08-31
  5. 05AFL Function Reference — PercentRankamibroker.com/guide/afl/percentrank.html2026-08-31
  6. 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.