Errors, Debugging and Sanity Checks
There are two kinds of broken formula, and they need completely different treatment.
The first kind will not run. AmiBroker refuses it, names a line, and you fix it. These are irritating and cheap.
The second kind runs perfectly, produces a chart, fills a trade list, and is wrong. Nothing warns you. This is the expensive kind, and the whole point of this lesson is that you cannot find it by staring at the formula harder. You find it by making the formula show you its intermediate values and then checking those against something you already know.
Errors that stop the formula
Section titled “Errors that stop the formula”Check syntax and what it actually checks
Section titled “Check syntax and what it actually checks”The Formula Editor has Tools → Check syntax (Ctrl+F5 in the default keyboard layout, and also available as a toolbar button). It compiles the formula and reports the first problem it finds, with a line number.
Two things about it are worth internalising early.
It checks the formula, not your idea. A formula that computes a 200-period average of the wrong array passes syntax checking with no complaint whatsoever.
And it runs the formula against whatever data the editor has to hand, which is not the full
history. This matters because a formula that indexes into an array — Close[500], say — can
pass here and fail on a symbol with fewer bars, or vice versa. Never treat “check syntax
passed” as “the formula is correct”. Treat it as “the formula is spelled correctly”.
The error messages you will actually meet
Section titled “The error messages you will actually meet”The exact wording belongs to your AmiBroker version, so match on the shape rather than the sentence.
“Error 30. Syntax error” and its relatives point at a line where AmiBroker stopped being able to parse. The real fault is very often on the line above — a missing semicolon means the parser reads two statements as one and only notices when the grammar breaks. When the named line looks fine, read the line before it.
“Variable … used without having been assigned” means exactly what it says, and the usual
cause is a typo. AFL identifiers are case-insensitive, so MyPeriod and myperiod are the
same variable — this is not the cause. MyPeriod and MyPeriodd are not, and that usually is.
“Function … called with too few / too many arguments” almost always means you have mis-remembered a signature. Do not guess at it: put the cursor on the function name and press F1, which opens that function’s page in the AFL Function Reference. Getting into this habit now saves you from an entire category of self-inflicted wound later.
Operator precedence, the silent one
Section titled “Operator precedence, the silent one”This one is documented in the same official page, and it is worth reproducing because it produces a formula that runs, looks right, and does the wrong thing:
Fragment — not a complete formula
// WRONG. AND binds tighter than OR, so this reads as:// ( Close > MA( Close, 10 ) ) OR ( bigMove AND Volume > MA( Volume, 10 ) )Buy = Close > MA( Close, 10 ) OR Close == 1.1 * Ref( Close, -1 ) AND Volume > MA( Volume, 10 );The author wanted the volume condition to apply to both branches. It applies to one.
Fragment — not a complete formula
// CORRECT. Parentheses say what was meant.Buy = ( Close > MA( Close, 10 ) OR Close == 1.1 * Ref( Close, -1 ) ) AND Volume > MA( Volume, 10 );The rule to adopt is not “learn the precedence table”. It is: when a condition mixes AND
with OR, parenthesise it even when you do not have to. The cost is four characters. The
benefit is that the next person to read it — which is you, in six months — cannot get it wrong.
Array where a scalar is required
Section titled “Array where a scalar is required”if, while and for need a single true/false value. They cannot take an array, because
there would be no way to decide what to do with an array that is true on some bars and false
on others. This is documented explicitly.
Fragment — not a complete formula
// WRONG. Close > Open is an array of BarCount values.if( Close > Open ) BarTint = colorGreen;else BarTint = colorRed;Fragment — not a complete formula
// CORRECT. IIf() decides per bar, which is what was wanted.BarTint = IIf( Close > Open, colorGreen, colorRed );The mirror image of this mistake is using IIf() to build a string. IIf() returns an array
of numbers; it cannot hold text. WriteIf() is the string version, and it returns one
string built from the selected bar, not an array of strings.
Errors that do not stop the formula
Section titled “Errors that do not stop the formula”Now the expensive kind. A formula runs; the output is plausible; it is wrong. There are four diagnostic tools, in increasing order of effort.
Escalation ladder for a formula that runs but is wrong
- Count somethingHow many bars is the condition true on? Zero and BarCount are both alarming answers.
- Plot the intermediatesNot just the final signal — the average, the ratio, the raw indicator. Bugs are visible in the middle of a calculation and invisible at the end.
- Trace it_TRACE and _TRACEF write to Window -> Log. Cheap, always available, works in every Analysis mode.
- Run it under the debuggerBreakpoints, single stepping, the Watch window and its Arrays tab. Reach for this when you need to watch a loop.
Count something first
Section titled “Count something first”The fastest sanity check in AFL is a count. Before you look at performance, ask how often your condition is even true:
Fragment — not a complete formula
Setup = Close > MA( Close, 50 ) AND RSI( 14 ) > 50;SetupBars = LastValue( Cum( Nz( Setup ) ) );
_TRACEF( "Setup true on %g of %g bars", SetupBars, BarCount );Nz() is there because Cum() propagates Null: a single empty value at the front of the
array would otherwise make the running total empty for the entire rest of the array. That is
itself a bug people spend an hour on.
Interpret the count immediately. Zero means a condition never fires — usually a filter that
contradicts itself, or a warm-up problem. BarCount means a condition is always true —
usually the = versus == error, or a comparison against the wrong array. Anything between is
not proof of correctness, but at least it is not proof of failure.
Plot the intermediates
Section titled “Plot the intermediates”A chart formula gives you a free debugger, and almost nobody uses it. If Buy is never true,
do not stare at the Buy line. Plot the pieces:
Fragment — not a complete formula
// Not the signal — the ingredients of the signal.Plot( RSI( 14 ), "RSI", colorOrange, styleLine | styleOwnScale | styleNoLabel );Plot( Setup, "Setup", colorGreen, styleArea | styleOwnScale | styleNoLabel );The moment you can see that RSI is flat at 50 for the whole chart, or that Setup is a solid
green block, you know where the fault is. Reading the shape of an intermediate array is faster
than reasoning about it.
_TRACE and _TRACEF
Section titled “_TRACE and _TRACEF”_TRACE( "string" ) writes a line of text. _TRACEF( "format", args… ) does the same with
printf-style formatting, so you can print numbers without string-concatenation gymnastics.
Both write to the system debug viewer and to AmiBroker’s own Log window, which you open
with Window → Log. You do not need any external tool to use them.
One documented convenience is worth knowing on day one: _TRACE( "!CLEAR!" ); clears the
internal Log window. Put it at the top of the formula and every run starts from an empty log,
so you can never mistake the previous run’s output for this one’s.
Fragment — not a complete formula
_TRACE( "!CLEAR!" );_TRACEF( "BarCount = %g, first bar = %s", BarCount, DateTimeToStr( DateTime()[0] ) );The debugger
Section titled “The debugger”For anything involving a loop, the visual debugger earns its keep. In the Formula Editor, Debug → Go (F5) runs under the debugger, F9 toggles a breakpoint, F10 steps over a line and F11 steps into a user-defined function. Hovering over a variable shows its type and value. Window → Watch opens the Watch window, and its Arrays tab shows watched arrays as a table with bar numbers and timestamps — which is the single most useful view in AmiBroker for understanding what an array actually contains.
Four documented details that will otherwise waste your afternoon:
BarCountunder the debugger defaults to 200 bars. It is a preference —Tools → Preferences, the Debugger page, “Limit BarCount to”. A formula that needs 250 bars of warm-up will look completely empty under the debugger until you raise it.- Breakpoints only bind to certain lines: regular statements ending in a semicolon,
for,while,do-while(on thewhileclause),if,return,switch/caseandbreak. A breakpoint on a blank line, a comment or a lone brace will not trigger. If your red circle is never hit, check the line type before you conclude the code is not running. - As of version 6.10 the debugger runs the code in the
actionBacktestcontext. Code guarded byif( Status( "action" ) == actionIndicator )will not execute, and neither will a breakpoint inside it. GetBacktesterObject()returns an empty object outside the second phase of a portfolio backtest, so custom-backtester code cannot be fully debugged this way. Part 36 says more.
The harness
Section titled “The harness”This formula is deliberately ordinary — a moving average, an RSI and a condition combining them. What makes it worth reading is that every step announces itself.
Complete runnable AFL
// debug-harness.afl// Part 8 - Errors, Debugging and Sanity Checks//// Purpose: a small, ordinary calculation instrumented so that every step can// be SEEN rather than guessed at. The pattern - trace the shape of// the data, plot the intermediates, never assume a bar count -// transfers to formulas of any size.// Assumes: any symbol and interval. Open Window -> Log before applying, so// the trace output has somewhere to appear.// Warning: a chart formula re-runs on every redraw, so tracing from a chart// produces a lot of output. Remove the trace lines, or comment them// out, once the formula behaves.
_SECTION_BEGIN( "Debug Harness" );
// ---------------------------------------------------------------------------// Settings// ---------------------------------------------------------------------------
MaPeriod = 20;RsiPeriod = 14;
// ---------------------------------------------------------------------------// Instrumentation// ---------------------------------------------------------------------------
_TRACE( "!CLEAR!" ); // wipe the Log window so this run is not read as the last one_TRACEF( "Run started. BarCount = %g", BarCount );
// BarCount is not a constant of the universe. Syntax checking uses at most a// couple of hundred recent bars, the debugger defaults to 200, and a zoomed// chart can be shorter still. Say so out loud instead of assuming.if( BarCount < MaPeriod ){ _TRACEF( "Only %g bars available - MA(%g) will be empty everywhere.", BarCount, MaPeriod );}
// ---------------------------------------------------------------------------// The calculation under inspection// ---------------------------------------------------------------------------
Average = MA( Close, MaPeriod );Momentum = RSI( RsiPeriod );
_TRACEF( "MA(%g): %g empty bars at the front.", MaPeriod, NullCount( Average, 1 ) );_TRACEF( "RSI(%g): %g empty bars at the front.", RsiPeriod, NullCount( Momentum, 1 ) );
Setup = Close > Average AND Momentum > 50;
// A count is the fastest sanity check there is. Zero and BarCount are both// suspicious answers; anything in between at least deserves a look.SetupBars = LastValue( Cum( Nz( Setup ) ) );_TRACEF( "Setup is true on %g of %g bars.", SetupBars, BarCount );
// ---------------------------------------------------------------------------// Drawing - including the intermediates// ---------------------------------------------------------------------------
Plot( Close, "Close", colorDefault, styleCandle );Plot( Average, "MA(" + MaPeriod + ")", colorBlue, styleLine | styleThick );
// Plotting an intermediate array finds bugs that staring at the final answer// never will: if RSI is flat, or Setup is on for every bar, you see it here.Plot( Momentum, "RSI(" + RsiPeriod + ")", colorOrange, styleLine | styleOwnScale | styleNoLabel );Plot( Setup, "Setup", colorGreen, styleArea | styleOwnScale | styleNoLabel );
Title = StrFormat( "{{NAME}} {{DATE}} Close %g MA %g RSI %.1f Setup %g (%g setup bars)", Close, Average, Momentum, Setup, SetupBars );
_SECTION_END();What it demonstrates
Section titled “What it demonstrates”It clears the log, prints BarCount, warns if there are not enough bars for the average it is
about to compute, reports how many leading Null values each indicator has using
NullCount( array, 1 ), counts the bars on which the setup is true, and then plots both
intermediates alongside price rather than only the final answer.
None of that is clever. All of it is the difference between “the formula doesn’t work” and
“the RSI is Null for the first 14 bars and my Cum() was swallowing the whole array”.
A checklist you can actually run
Section titled “A checklist you can actually run”When a formula is wrong and you do not know why, work down this list. It is ordered by how often each step finds the fault.
- Does it run at all? Check syntax. Read the line above the reported one.
- Are
=and==right everywhere? Search the formula for=insideIIf(and inside any comparison. - Are
AND/ORmixes parenthesised? If not, parenthesise them and see whether the answer changes. If it changes, one of the two readings was wrong. - Count your conditions. Zero and
BarCountare both bugs. - How many bars of warm-up?
NullCount( x, 1 )for each indicator. Compare with the length of the array you are testing over. - Plot the intermediates. Look at the shapes, not the numbers.
- Check the array lengths you assumed.
BarCountis not a constant — it changes with the symbol, the interval, the date range, QuickAFL and the debugger’s own limit. Never hard-code a bar index you have not guarded. - Trace the values at the boundaries. First bar, last bar, the bar where the answer first goes wrong.
- Only then use the debugger, and only if there is a loop or a function call chain that the previous eight steps could not resolve.
Syntax errors are cheap: read the line above the one AmiBroker names, and press F1 on any
function whose signature you are not certain of. Silent errors are expensive, and there are
only two ways to find them — make the formula report what it computed, or compare its answer
with something you can verify by hand. Counting is the cheapest report, plotting intermediates
is the fastest, _TRACE/_TRACEF into the Log window is the most flexible, and the debugger
is what you reach for when a loop is involved. And “check syntax passed” means the formula is
spelled correctly. It says nothing at all about whether it is right.
Check your understanding
Sources for this lesson
6 verified · checked 2026-08-31
- 01AmiBroker User's Guide — Common Coding Mistakes in AFLamibroker.com/guide/a_mistakes.html2026-08-31
- 02AmiBroker User's Guide — How to use AFL debuggeramibroker.com/guide/h_debugger.html2026-08-31
- 03AFL Function Reference — _TRACEamibroker.com/guide/afl/_trace.html2026-08-31
- 04AFL Function Reference — _TRACEFamibroker.com/guide/afl/_tracef.html2026-08-31
- 05AFL Function Reference — NullCountamibroker.com/guide/afl/nullcount.html2026-08-31
- 06AmiBroker User's Guide — AFL language reference§ Operator precedenceamibroker.com/guide/a_language.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.