Skip to content
Level 3 · AFL DeveloperLessonPart 08 · page 6 of 930 min
30Minutes
8AFL functions
8Sources
StandardRequires
AFL functions taught here8

Boolean Arrays: Close > MA(Close, 50)

Write this line and read it aloud:

Fragment — not a complete formula

Trend = Close > MA( Close, 50 );

Most people read it as a question with a single answer: is the close above its fifty-bar average? It is not. It is the same question asked separately of every bar on the chart, and Trend holds every one of the answers. On a chart with four thousand bars, that line asked four thousand questions and stored four thousand answers.

This lesson is the previous one applied to comparisons. It is short on new syntax and long on consequences, because nearly every rule you will ever write ends up as a Boolean array, and almost every confusing result later in the course traces back to treating one as if it were a single yes or no.

AFL has no separate Boolean type. Comparison operators — <, >, <=, >=, ==, != — produce “a true (1) or false (0) value”, in the guide’s own words, and the official Knowledge Base article on constants says the same thing: True is 1 and False is 0.

That is not an implementation detail you are supposed to ignore. It is the design, and it is what makes Booleans useful. A comparison produces numbers, so you can add them, average them, plot them and count them with the ordinary arithmetic you already have.

It also means these two lines are equivalent, and the shorter one is the idiom the documentation itself recommends:

Fragment — not a complete formula

Overbought = IIf( RSI( 14 ) > 70, 1, 0 ); // works, but says nothing extra
Overbought = RSI( 14 ) > 70; // the same array, written plainly

If you find yourself writing IIf( something, 1, 0 ), delete the IIf. The comparison already produced the ones and zeros.

Here is Close > MA( Close, 3 ) on the ten bars this part has been using, with the average written out so you can check each answer yourself.

Trend = Close > MA( Close, 3 )

Bar 2: 1.24 is not above 1.243, so the answer is 0. Bar 3: 1.28 is above 1.260, so the answer is 1.
Bar0123456789
Close1.231.261.241.281.251.251.311.301.321.28
MA( Close, 3 )NullNull1.2431.2601.2571.2601.2701.2871.3101.300
TrendNullNull01001110
Bar 2: 1.24 is not above 1.243, so the answer is 0. Bar 3: 1.28 is above 1.260, so the answer is 1. Prices in this diagram are invented for the illustration. They are not market data and nothing should be inferred from them.

Read across the bottom row. It is not “the trend is up”. It is a record of what was true on each individual bar: unknown, unknown, no, yes, no, no, yes, yes, yes, no.

Two features of that row will matter for the rest of the course.

The first two entries are Null, not 0. The three-bar average does not exist on bars 0 and 1, and a comparison against something that does not exist cannot produce an answer. It produces Null, which is neither true nor false. This is the whole subject of lesson eight, and it is the difference between a rule that counts correctly and one that quietly does not.

The row can be plotted. Because it holds only 1 and 0, drawing it gives a square wave — a visual record of exactly when the condition held. This is the single fastest way to check whether a condition means what you think, and the formula at the end of this lesson does it.

Three things you might expect it to be, and are not:

It is not a signal. Trend is true on every bar where the close is above the average, which in a sustained move is hundreds of consecutive bars. If you hand that to the backtester as a Buy array it will attempt to buy on all of them. Turning a state into an event is a distinct step with its own lesson in Part 9.

It is not usable as a condition for if. This is the most common runtime error in AFL, and AmiBroker names it explicitly. if( Close > Open ) raises Error 6, “Condition in IF/WHILE/FOR must be Numeric or Boolean”, and the official explanation is exactly the right one: there would be no way to decide whether to execute the statement, “if for example the array was [True,True,False,…,False,True]”. Lesson seven covers what to write instead.

It is not a count. Trend does not tell you how often the condition held. It tells you where. Counting is a separate operation, and it is the next section.

Because true is 1 and false is 0, counting how often a condition held is just adding the array up. Three functions do it, and choosing the right one is choosing what question you are asking.

Fragment — not a complete formula

Trend = Close > MA( Close, 50 );
TrueSoFar = Cum( Nz( Trend ) ); // running total from the first bar
TrueLast20 = Sum( Nz( Trend ), 20 ); // rolling total over the last 20 bars
FractionUp = Sum( Nz( Trend ), 20 ) / 20; // proportion of the last 20 bars

Cum( array ) is the cumulative sum from the first bar in the array; Sum( array, periods ) is a rolling sum over a fixed window that includes the current bar. Both return arrays: the running total as at each bar, not a single grand total.

Counting a Boolean array two different ways

Cum keeps adding for ever. Sum forgets everything older than its window. The Nulls have been treated as zero here so the arithmetic is visible.
Bar0123456789
Trend0001001110
Cum( Trend )0001112344
Sum( Trend, 3 )0001111232
Cum keeps adding for ever. Sum forgets everything older than its window. The Nulls have been treated as zero here so the arithmetic is visible.

Notice Nz() wrapped around Trend in the code above. It replaces empty values with zero. Without it, the warm-up Null values travel into the total and the count becomes unusable — which is exactly the failure lesson eight is about. Get into the habit now: when you count a Boolean array, say what the unknown bars should count as.

Sometimes you genuinely want one number: is the condition true now?

Fragment — not a complete formula

Trend = Close > MA( Close, 50 );
TrendNow = LastValue( Trend ); // 1 or 0 - a single number

LastValue() returns the value at the last bar. SelectedValue() returns the value at the bar the selection line sits on, which is what you want in a chart title. Both collapse the column to a scalar, and both are perfectly legitimate when a scalar is what you need — a title, a message, a condition for an if statement.

There is also IsTrue( array ), whose documentation says it returns 1 where a value is not {empty} AND not zero. It is the honest way to ask “is this definitely true here”, collapsing both Null and 0 to 0 in one step, and it is worth remembering when you inherit a formula whose arrays might hold anything.

Because Booleans are numbers, you can do arithmetic with them, and there is a whole folklore of clever tricks built on it. Most of them are fragile, and AmiBroker’s own Knowledge Base uses one as a cautionary example:

Fragment — not a complete formula

// The official example of what NOT to do.
Shape = Buy * shapeUpArrow + Sell * shapeDownArrow;

The idea is that only one of Buy and Sell will be 1 on any bar, so the sum picks out the right shape constant. On the bar where both happen to be true, the two constants add together and produce a third, unintended shape. The recommended form states the intent instead of encoding it:

Fragment — not a complete formula

Shape = IIf( Buy, shapeUpArrow, IIf( Sell, shapeDownArrow, shapeNone ) );

The general lesson generalises well beyond shapes. Multiplying by a Boolean to “switch something off” — Position = Size * Trend; — works only while you are certain the array holds exactly 1 and 0, and Boolean arrays acquire Null at their warm-up and can acquire other values when they pass through functions. Write what you mean.

This formula plots a Boolean array directly on the chart, so you can see the ones and zeros as a shape rather than trust a description of them, and reports a running count in the title.

Complete runnable AFL

boolean-count.afl
// boolean-count.afl
// Part 8 - Boolean Arrays
//
// Purpose: show that a comparison between two arrays answers the question
// once per bar, not once for the whole chart, and then count how
// often the answer was true.
// Assumes: any symbol and interval with more than MaPeriod bars of history.
// Apply: Formula Editor -> Apply indicator. The green blocks are the bars
// on which the comparison came out true.
_SECTION_BEGIN( "Boolean Arrays" );
// ---------------------------------------------------------------------------
// Settings
// ---------------------------------------------------------------------------
MaPeriod = 50;
// ---------------------------------------------------------------------------
// One comparison, one answer per bar
// ---------------------------------------------------------------------------
Average = MA( Close, MaPeriod );
// Trend is not "yes" or "no". It is an array the same length as Close, holding
// 1 where the close was above the average and 0 where it was not - and empty
// values over the warm-up, where the average does not exist yet.
Trend = Close > Average;
// Nz() replaces those empty warm-up values with 0 so that they count as
// "not true" rather than poisoning the running total. Lesson 8 of this part
// explains exactly why the total would otherwise be unusable.
TrueBars = Cum( Nz( Trend ) );
TotalBars = Cum( 1 );
// ---------------------------------------------------------------------------
// Drawing
// ---------------------------------------------------------------------------
Plot( Close, "Close", colorDefault, styleCandle );
Plot( Average, "MA(" + MaPeriod + ")", colorBlue, styleLine | styleThick );
// Because Trend only ever holds 1 or 0, plotting it directly gives a square
// wave. That square wave is the visible proof that the comparison produced a
// column of answers rather than a single answer.
Plot( Trend, "Close > MA", colorGreen,
styleHistogram | styleOwnScale | styleNoLabel );
Title = StrFormat(
"{{NAME}} {{DATE}} Trend %g true on %g of %g bars so far (%.1f%%)",
Trend, TrueBars, TotalBars, 100 * TrueBars / TotalBars );
_SECTION_END();

Download boolean-count.afl53 lines

How it works. One moving average, one comparison, and two counters. Trend is the comparison — a full-length array of 1s and 0s with Null over the warm-up. TrueBars is a running count of the true bars, with Nz() deciding that unknown counts as not-true. TotalBars is Cum( 1 ), the running bar number, which is the standard idiom for “how many bars have we seen so far”. The final Plot() draws Trend itself on its own scale, which turns it into a square wave sitting behind the price.

Key functions. Cum( array ) — cumulative sum from the first bar, returning an array. Nz( x ) — replaces empty, not-a-number and infinite values with zero. Sum( array, periods ) is its fixed-window sibling, used in the fragments above but not in this formula.

Test it. Click on a bar where the green block starts. The title should read Trend 1, and the candle should be the first one whose close is above the blue line. Click the bar immediately before it: Trend 0. If the block’s edges do not line up with the line crossings, you are plotting a different array from the one you think.

Common errors. If the price chart collapses into a flat line, the styleOwnScale flag has been dropped from the Trend plot, so a series of 1s and 0s is being drawn on the price axis. If the percentage in the title stays at 100%, you have removed Nz() and the count has been poisoned by the warm-up. If the block covers every bar, check that you compared with > and not with something like >= against zero.

Extension. Add TrueLast20 = Sum( Nz( Trend ), 20 ); and plot it. You now have two descriptions of the same condition: an on-off record, and a count of how many of the last twenty bars it held. Compare them during a choppy stretch of the chart. The count is the more informative of the two, and it is the shape of measure that Parts 13 and 16 are built on.

A comparison in AFL evaluates once per bar and returns a full-length array of 1s and 0s, with Null wherever an input was unknown. True is 1 and false is 0, which is why counting a condition is just summing it — cumulatively with Cum(), or over a window with Sum() — and why IIf( condition, 1, 0 ) is always redundant. LastValue() and SelectedValue() collapse such an array to a single number, which is what a title or an if statement needs and what a per-bar rule must never contain. And a Boolean array is a state, not a signal: it says where the condition held, not when something happened.

The next lesson combines these arrays with AND, OR and NOT, and shows how to choose a different value on every bar without writing a loop.

Check your understanding

Question 1. Given the code below, what is Trend?
Trend = Close > MA( Close, 200 );
Show the answer and why

Answer: A Boolean array across bars

Comparing an array with an array produces one Boolean result per bar. Trend has exactly as many entries as the chart has bars, holding 1 where the close was above the average, 0 where it was not, and Null over the warm-up where the average did not yet exist.

Question 2. Why does if( Close > Open ) raise Error 6?
Show the answer and why

Answer: The comparison produces an array, and there is no single answer for if() to branch on

The official explanation is the memorable one: there would be no way to decide whether to run the statement if the array were [True,True,False,...,False,True]. An if() needs one value. Either index a single bar, or — usually better — use IIf(), which chooses per bar and returns an array.

Question 3. Trend has 4,000 entries, of which the first 199 are Null. What does Cum( Trend ) give you, and what does Cum( Nz( Trend ) ) give you?
Show the answer and why

Answer: A total that is contaminated by the unknown warm-up values, versus a total that counts them explicitly as zero

Null is not zero, and it is not false. Deciding what unknown should count as is your decision, not the language’s, and Nz() is where you write that decision down. Counting a Boolean array without settling this question is one of the ways a formula produces a confident, wrong number.

Question 4. Which of these uses of LastValue() are safe? Select all that apply.
Show the answer and why

Answer: Showing the current value of a condition in the chart title, Deciding, in an if() statement, whether to print a diagnostic message

LastValue() is for the cases where one number is genuinely what you want: display, and one-off decisions. Inside a per-bar rule it stretches a value taken from the end of the chart back across all of history, which is a look-ahead bug with no error message. Its own documentation carries this warning.

Question 5. Overbought = IIf( RSI( 14 ) > 70, 1, 0 ); can be written more simply. How?
Show the answer and why

Answer: Overbought = RSI( 14 ) > 70;

The comparison already returns 1 and 0, so the IIf adds nothing but noise. Recognising this pattern is a good early habit: it means you have internalised that comparisons produce numbers, not a special Boolean type.

Sources for this lesson

8 verified · checked 2026-09-01

  1. 01AmiBroker User's Guide — AFL Reference Manual§ Comparison operatorsamibroker.com/guide/a_language.html2026-08-31
  2. 02AmiBroker Knowledge Base — What are constants in AFL and how they workamibroker.com/kb/2014/10/05/what-are-constants-in-afl-and-how-they-work2026-08-31
  3. 03AmiBroker User's Guide — Understanding how AFL worksamibroker.com/guide/h_understandafl.html2026-08-31
  4. 04AFL Function Reference — Cumamibroker.com/guide/afl/cum.html2026-08-31
  5. 05AFL Function Reference — Sumamibroker.com/guide/afl/sum.html2026-08-31
  6. 06AFL Function Reference — LastValueamibroker.com/guide/afl/lastvalue.html2026-08-31
  7. 07AFL Function Reference — IsTrueamibroker.com/guide/afl/istrue.html2026-08-31
  8. 08AmiBroker User's Guide — Error 6, condition must be numeric or booleanamibroker.com/guide/errors/6.html2026-09-01

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.