Skip to content
Level 3 · AFL DeveloperLessonPart 08 · page 7 of 928 min
28Minutes
7AFL functions
8Sources
StandardRequires
AFL functions taught here7

Logic, Conditionals and IIf()

You now have Boolean arrays. This lesson joins them together, chooses values from them, and settles a question that costs people a surprising amount of time: whether AFL’s logical operators are the ones they are used to from other languages.

The short answer is no, and the documentation is unusually clear about it.

AFL’s logical operators are the words AND, OR and NOT. They are documented in the official operator tables, they work element-wise on arrays like everything else, and they produce 1 and 0.

Fragment — not a complete formula

Trend = Close > MA( Close, 200 );
Liquid = MA( Volume, 50 ) > 500000;
Candidate = Trend AND Liquid;
Either = Trend OR Liquid;
Excluded = NOT Liquid;

AND is true where both operands are true on the same bar. OR is true where at least one is. NOT is true where its operand is false. All three answer once per bar, and the result is an array exactly as long as its inputs.

AND, OR and NOT, bar by bar

Each column is decided on its own. Nothing about bar 3 influences the answer at bar 4.
Bar01234567
Trend00111011
Liquid01101110
Trend AND Liquid00101010
Trend OR Liquid01111111
NOT Liquid10010001
Each column is decided on its own. Nothing about bar 3 influences the answer at bar 4.

If you have written C, JavaScript, Java or a dozen other languages, your fingers will type && and || without asking you. Here is what an exhaustive check of the official documentation establishes, and it is worth being precise because the internet is not.

  • AND, OR and NOT are the documented logical operators. They appear in the operator tables in the AFL Reference Manual with defined precedence. Use these.
  • & and | are documented, but they are bit-wise operators, not logical ones. Their job in AFL is combining flag constants: styleLine | styleThick, and every stop-type and composite-flag combination you will meet later. They also sit at a different precedence level from the logical operators, which is a live trap in mixed expressions.
  • && appears in official sample code but in no official operator table. The backtesting tutorial’s custom-stop example contains if( priceatbuy == 0 && Buy[i] ). So the parser evidently accepts it. It is nevertheless undocumented, which means its precedence and its behaviour are not specified anywhere you can point at.
  • || and a unary ! appear nowhere in the User’s Guide, in the AFL language reference, or in any of the 448 function reference pages. There is no documented basis for using them.

The course’s position follows from that: write AND, OR and NOT. They are documented, their precedence is published, and they read better in a language whose expressions describe market conditions. Uptrend AND NOT Quiet says what it means; Uptrend && !Quiet does not say it any better and rests on documentation that does not exist.

Precedence: the mistake AmiBroker documents itself

Section titled “Precedence: the mistake AmiBroker documents itself”

AND, OR and NOT all bind looser than the comparison operators, which is convenient: a > b AND c > d groups as ( a > b ) AND ( c > d ) with no help needed.

But AND binds tighter than OR, and that produces mistake number two on AmiBroker’s own Common Coding Mistakes list. The guide even quotes the user’s complaint that led to it. The intention was: buy when either the close is above its average or the close jumped ten per cent, but only when volume is above its average.

Fragment — not a complete formula

// WRONG - and it compiles, runs, and produces plausible-looking signals.
Buy = Close > MA( Close, 10 )
OR Close == 1.1 * Ref( Close, -1 )
AND Volume > MA( Volume, 10 );

Because AND binds tighter, that reads as ( Close > MA( Close, 10 ) ) OR ( ( Close == 1.1 * Ref( Close, -1 ) ) AND ( Volume > MA( Volume, 10 ) ) ). The volume filter applies to only one of the two branches, so the system buys whenever the first condition alone is true, volume filter or no volume filter. The fix is parentheses:

Fragment — not a complete formula

// CORRECT
Buy = ( Close > MA( Close, 10 )
OR Close == 1.1 * Ref( Close, -1 ) )
AND Volume > MA( Volume, 10 );

Two more precedence facts worth carrying:

  • NOT binds looser than every comparison, so NOT a > b means NOT ( a > b ). That is what an English reader expects, and the opposite of C, where ! binds very tightly.
  • & and | bind tighter than NOT, AND and OR but looser than == and !=. AFL’s ordering here differs from C’s. Never mix bit-wise and logical operators in one expression without parentheses.

IIf( EXPRESSION, TRUE_PART, FALSE_PART ) is how you pick a different value on every bar without a loop. It walks every bar, tests the condition there, and takes the matching element of the second or third argument.

Result = IIf( Close <= BeginValue( Open ), Close, Open )

Where the condition is 1 the value comes from Close; everywhere else it comes from Open. AmiBroker's own worked example.
Bar0123456789
Open1.231.241.211.261.241.291.331.321.351.37
Close1.221.261.231.281.251.251.311.301.321.28
Close <= BeginValue( Open )1010000000
Result1.221.241.231.261.241.291.331.321.351.37
Where the condition is 1 the value comes from Close; everywhere else it comes from Open. AmiBroker's own worked example. Prices in this diagram are invented for the illustration. They are not market data and nothing should be inferred from them.

Three properties of IIf() that its documentation states explicitly, and that each cause trouble when forgotten.

It is a function, not flow control. It returns a value. You assign that value. This is mistake number three on the official list:

Fragment — not a complete formula

IIf( Close > 10, Result = 7, Result = 9 ); // WRONG
Result = IIf( Close > 10, 7, 9 ); // CORRECT

The wrong form also triggers Warning 501, “Assignment within conditional”, because AmiBroker recognises the pattern.

It always evaluates both branches. Even though only one is returned, both the true part and the false part are computed for every bar. There is no short-circuit. If one branch is an expensive calculation you thought was being skipped, it is not, and if one branch has a side effect, that side effect happens.

It returns numbers and arrays, never strings. Mistake number four on the official list is Variable = IIf( Condition, "Text 1", "Text 2" );. For text you want WriteIf(), which returns a string — but note that WriteIf() returns a single string, evaluated at the selected bar, not one string per bar. Despite the name it does not write anything anywhere; AmiBroker’s author describes it as “just TextIIF”.

Two outcomes come free. For three or more, nest the next IIf() in the false part:

Fragment — not a complete formula

PriceColour = IIf( Uptrend, colorGreen,
IIf( Downtrend, colorRed, colorLightGrey ) );

Read it as a ladder: if uptrend, green; otherwise, if downtrend, red; otherwise grey. The indentation above is worth copying — beyond about three levels a nested IIf() becomes genuinely hard to read, and that is the point at which to reconsider whether the classification should be built up from named arrays instead.

This is also the recommended alternative to arithmetic tricks. AmiBroker’s own Knowledge Base shows Shape = Buy * shapeUpArrow + Sell * shapeDownArrow; as an example of what goes wrong — on a bar where both are true, the two constants add to a third one — and gives the nested IIf() as the safe form.

AFL also has an if/else statement, and it is not a substitute for IIf(). It executes one block or another once, for the whole formula run. Its condition must therefore be a single value.

Fragment — not a complete formula

// WRONG - Close > Open is an array. Error 6.
if( Close > Open )
PriceColour = colorGreen;
else
PriceColour = colorRed;
// CORRECT - one decision per bar, made by a function that works on arrays.
PriceColour = IIf( Close > Open, colorGreen, colorRed );

Error 6, “Condition in IF/WHILE/FOR must be Numeric or Boolean”, is what you get for the first version, and the official explanation is worth remembering because it makes the reason obvious rather than arbitrary: there is no way to decide whether to run the block “if for example the array was [True,True,False,…,False,True]”. A string condition gives Error 7.

So when is if right? When the decision genuinely applies to the whole run:

Fragment — not a complete formula

if( BarCount < 250 )
{
_TRACE( "This symbol has too little history for the 250-bar study." );
}

BarCount is one number, so the question has one answer. That is the shape of every legitimate if in ordinary AFL: checks on the amount of data, on which context the formula is running in, on a parameter you set yourself.

You want Use
A different value on each bar IIf()
A different string to display WriteIf()
A decision that applies to the whole run if / else
A decision based on one bar of an array if( SomeArray[ i ] ) inside a loop

Lesson five made the case against loops on performance grounds: the official measurement is that they run between ten and fifty times slower than equivalent array code. That is the default. There is a genuine exception.

A loop is justified when a bar’s value depends on the value the same calculation produced for the previous bar, and no built-in function expresses the relationship. This is called a recursive or path-dependent calculation, and no combination of element-wise operators can express it, because element-wise operators cannot see their own output.

The official example is a variable-period exponential average. AmiBroker publishes both forms: the array form uses AMA( array, factor ), which is recursive internally, and the loop form looks like this:

Pseudocode — not valid AFL

set the first element of the result to the first close
for each bar from 1 to the last bar:
factor = 2 / ( period at this bar + 1 )
result at this bar = factor * close at this bar
+ ( 1 - factor ) * result at the previous bar

Note what makes it irreducible: result at the previous bar is a value this same loop computed a moment ago. Trailing stops that ratchet, position-state machines that must know whether you are already in a trade, and anything that must “remember” across bars fall into the same category. Part 9 shows how far the built-in functions get you before a loop becomes necessary, and the answer is: surprisingly far.

If you do write one, four rules from the official performance guidance:

  • Bound it with BarCount, never a hard-coded number. for( i = 0; i < BarIndex(); i++ ) is mistake number six on the official list — BarIndex() is an array and cannot control a loop.
  • Move every array calculation out of the loop. Calling MA( C, 10 ) inside the body recomputes the whole average once per bar.
  • Inside the loop, use scalars, or index single array elements with [ ].
  • Start the index at 1 if the body reads [ i - 1 ], or C[ -1 ] will raise Error 10 on the first iteration.

This formula does everything the lesson covers: builds three named Boolean arrays, combines them with parentheses that matter, and uses a nested IIf() to colour every bar according to which state it is in.

Complete runnable AFL

regime-colour.afl
// regime-colour.afl
// Part 8 - Logic, Conditionals and IIf()
//
// Purpose: combine several Boolean arrays with AND, OR and NOT, then use
// IIf() to choose a different colour for every bar without writing
// a single loop.
// Assumes: any symbol and interval. The three states below are definitions
// chosen for teaching, not recommendations - a different fast/slow
// pair would classify the same chart differently.
// Apply: Formula Editor -> Apply indicator.
_SECTION_BEGIN( "Regime Colour" );
// ---------------------------------------------------------------------------
// Settings
// ---------------------------------------------------------------------------
FastPeriod = 20;
SlowPeriod = 100;
QuietPercent = 1.5; // ATR below this percentage of price counts as "quiet"
// ---------------------------------------------------------------------------
// Ingredients
// ---------------------------------------------------------------------------
FastMA = MA( Close, FastPeriod );
SlowMA = MA( Close, SlowPeriod );
AtrPercent = 100 * ATR( 20 ) / Close;
// ---------------------------------------------------------------------------
// Three named states, each a Boolean array - one answer per bar
// ---------------------------------------------------------------------------
Uptrend = Close > FastMA AND FastMA > SlowMA;
Downtrend = Close < FastMA AND FastMA < SlowMA;
Quiet = AtrPercent < QuietPercent;
// The parentheses are load-bearing. AND binds tighter than OR, so without them
// this would read as "Uptrend, or else (Downtrend and not quiet)", which is a
// completely different rule that happens to compile perfectly.
Tradeable = ( Uptrend OR Downtrend ) AND NOT Quiet;
// ---------------------------------------------------------------------------
// Choosing a value per bar
// ---------------------------------------------------------------------------
// IIf() walks every bar, tests the condition on that bar, and picks the
// matching element. Nesting a second IIf() in the false part is how you get a
// third outcome. Both branches are always evaluated, whatever the condition.
PriceColour = IIf( Uptrend, colorGreen,
IIf( Downtrend, colorRed, colorLightGrey ) );
// ---------------------------------------------------------------------------
// Drawing
// ---------------------------------------------------------------------------
Plot( Close, "Close", PriceColour, styleCandle );
Plot( FastMA, "MA(" + FastPeriod + ")", colorBlue, styleLine );
Plot( SlowMA, "MA(" + SlowPeriod + ")", colorDarkBlue, styleLine | styleThick );
// The shaded band marks the bars the combined rule accepted. Colour alone is
// never the whole message, so the title states the same thing as numbers.
Plot( Tradeable, "Tradeable", colorOrange,
styleArea | styleOwnScale | styleNoLabel );
Title = StrFormat(
"{{NAME}} {{DATE}} ATR %.2f%% of price Uptrend %g Downtrend %g Quiet %g Tradeable %g",
AtrPercent, Uptrend, Downtrend, Quiet, Tradeable );
_SECTION_END();

Download regime-colour.afl70 lines

Goal. Classify every bar into one of three states — up, down, or neither — and mark separately which bars pass a combined rule that requires a direction and enough movement to be worth acting on. It is a classifier, not a trading system: it says what kind of bar this is, and nothing about what to do next.

How it works. Three ingredients are computed first: a fast average, a slow average, and volatility expressed as a percentage of price, so that the same threshold means something comparable on a five-unit share and a five-hundred-unit one. Three named Boolean arrays follow. Tradeable combines them, and its parentheses are the whole point: without them the rule would group differently and quietly stop applying the volatility filter to the up case. The nested IIf() produces a colour array, which is then handed to Plot() as its third argument — Plot() accepts either a single colour or a whole array of colours, one per bar.

Key functions. IIf( EXPRESSION, TRUE_PART, FALSE_PART ) selects per bar and always evaluates both parts. ATR( period ) is average true range, covered properly in Part 6; here it is only a stand-in for “how much this instrument moves”.

Test it. Pick a bar where the band is absent but the candle is green. The title should show Uptrend 1, Quiet 1, Tradeable 0. That is the volatility filter doing its job, and checking it on a specific bar is how you know the parentheses are grouping as intended. Then delete the parentheses around ( Uptrend OR Downtrend ), re-apply, and find a bar where Tradeable changed. That bar is the official mistake number two, live on your own chart.

Common errors. Bars coloured grey everywhere usually means the two average periods are too close together on the interval you are using. An orange band covering every single bar means QuietPercent is set below anything the instrument ever reaches — check the ATR percentage in the title and pick a threshold from what you actually see. If applying the formula produces Error 6, you have written if where you meant IIf.

Extension. Add a fourth state for “in transition” — bars where the fast average is above the slow one but the close is below the fast one — and give it its own colour by adding one more level to the nested IIf(). Then notice how much harder the nested version has become to read, and consider whether a table of named conditions would serve better. That tension is real, and Part 10 returns to it when the classifier becomes a proper indicator.

AND, OR and NOT are AFL’s documented logical operators and work element-wise like everything else; & and | are bit-wise flag operators at a different precedence; && appears in one official example but in no operator table, and || and ! appear nowhere at all. AND binds tighter than OR, which is AmiBroker’s own second-most-common mistake, and naming each condition then combining the names is the habit that prevents it.

IIf() chooses a value per bar, returns arrays never strings, always evaluates both branches, and is a function rather than flow control. if/else decides once for the whole run and raises Error 6 when handed an array. And a loop is the right tool only when a bar’s value depends on the previous bar’s result — a real category, but a much smaller one than it looks from other languages.

The next lesson deals with the values that are neither true nor false.

Check your understanding

Question 1. What does this formula actually buy on?
Buy = Close > MA( Close, 10 )
      OR Close == 1.1 * Ref( Close, -1 )
      AND Volume > MA( Volume, 10 );
Show the answer and why

Answer: Bars where the close is above its average, regardless of volume, plus bars where the jump and the volume condition both hold

AND binds tighter than OR, so the volume filter attaches only to the second price condition. This is mistake number two on AmiBroker’s official list, and it produces no error — just a system that ignores a filter you thought you had applied. Parenthesise the OR group.

Question 2. Which statement about IIf() is false?
Show the answer and why

Answer: It evaluates only the branch it returns

IIf() always evaluates both the true part and the false part, whatever the condition. The documentation states this as a warning about side effects and about expensive calculations you assumed were being skipped. The other three statements are all true.

Question 3. You want to colour each candle green when it closed up and red when it closed down. Which is correct?
Show the answer and why

Answer: PriceColour = IIf( Close > Open, colorGreen, colorRed );

The first raises Error 6, because Close > Open is an array and if() needs one value. The third produces 1s and 0s, not colours. The fourth is the "IIf is not flow control" mistake and also triggers Warning 501. Only the second asks the question once per bar and returns a colour array.

Question 4. Which of these are genuine reasons to write a bar-by-bar loop in AFL? Select all that apply.
Show the answer and why

Answer: The calculation for each bar depends on the result the same calculation produced for the previous bar, No built-in function expresses the relationship you need

Comparing a bar with the previous one is Ref( array, -1 ), no loop required. Readability is a poor trade for a ten-to-fifty-fold slowdown that multiplies across symbols and optimisation runs. Genuine path dependence, with no built-in equivalent, is the case that justifies a loop.

Question 5. What does the official documentation say about the || operator in AFL?
Show the answer and why

Answer: Nothing at all — it does not appear in the User’s Guide, the language reference or any function reference page

A check of the entire official documentation set finds || and unary ! nowhere as operators. && does appear in one official code sample but in no operator table. The documented logical operators are AND, OR and NOT — use those, because their precedence is published and yours will therefore be predictable.

Sources for this lesson

8 verified · checked 2026-09-01

  1. 01AmiBroker User's Guide — AFL Reference Manual§ Logical operators and precedenceamibroker.com/guide/a_language.html2026-08-31
  2. 02AmiBroker User's Guide — Common Coding Mistakesamibroker.com/guide/a_mistakes.html2026-08-31
  3. 03AFL Function Reference — IIfamibroker.com/guide/afl/iif.html2026-08-31
  4. 04AFL Function Reference — WriteIfamibroker.com/guide/afl/writeif.html2026-08-31
  5. 05AmiBroker User's Guide — Error 6, condition must be numeric or booleanamibroker.com/guide/errors/6.html2026-09-01
  6. 06AmiBroker User's Guide — Performance tuningamibroker.com/guide/x_performance.html2026-08-31
  7. 07AmiBroker User's Guide — Understanding how AFL works§ New loopingamibroker.com/guide/h_understandafl.html2026-08-31
  8. 08AmiBroker User's Guide — AFL keywordsamibroker.com/guide/a_keywords.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.