The Array Model: The Most Important Lesson in This Course
Here is the sentence this entire course is built on. One line of AFL performs its calculation once for every bar on the chart, and gives you back all of the answers.
Not one answer. All of them. When you wrote Average = MA( Close, 50 ); in the project
lesson, you did not compute a moving average. You computed several thousand moving
averages — one as it stood at each bar in the history — and stored the whole set in
Average.
If that idea is already comfortable, this lesson will still be worth thirty-five minutes, because AFL’s specific version of it has details that bite. If it is not comfortable yet, read this lesson twice. Every formula in the remaining twenty-eight parts of the course assumes it, and no amount of syntax knowledge compensates for getting it wrong.
The comparison AmiBroker’s own documentation reaches for
Section titled “The comparison AmiBroker’s own documentation reaches for”The official guide compares AFL to a spreadsheet, and the comparison is exact enough to lean on.
In a spreadsheet you do not write a program that walks down a column. You type one formula
into one cell — =(B2+C2)/2 — and fill it down. The formula is written once and evaluated
once per row, and what you get back is a whole new column. Nobody thinks of that as a loop.
Nobody worries about which row is processed first. You think about the relationship
between columns, and the spreadsheet takes care of applying it everywhere.
AFL is that, with the fill-down already done for you. MidPrice = ( High + Low ) / 2; is
the formula; the whole column is the result; and there is no fill-down step because AFL has
no concept of a single row to begin with.
Two shapes of value
Section titled “Two shapes of value”Every value in an AFL formula has one of two shapes, and almost every confusion in this part of the course comes from mistaking one for the other.
A scalar is a single number. 50 is a scalar. MaPeriod after MaPeriod = 50; is a
scalar. BarCount is a scalar. So is anything a function returns when its documentation
says it returns NUMBER.
An array is an ordered list of numbers — one per bar — with a value for every bar the
formula can see. Close is an array. MA( Close, 50 ) is an array. Close > MA( Close, 50 )
is an array. So is anything a function returns when its documentation says it returns ARRAY.
That word “ARRAY” in capitals on every page of the AFL Function Reference is not decoration. It is the single most useful piece of information on the page, and getting into the habit of reading it will save you more time than anything else in this part.
What is actually in the database
Section titled “What is actually in the database”Your database stores exactly six arrays per symbol: open, high, low, close, volume and open interest. That is all. Everything else — every moving average, every oscillator, every condition you will ever write — is calculated on demand from those six and then thrown away.
You refer to them by name, and AFL accepts both the long and the short form. Because
identifiers are case-insensitive, Close, close and C are all the same thing.
| Long name | Short name | What it holds |
|---|---|---|
Open |
O |
the opening price of each bar |
High |
H |
the highest price traded in each bar |
Low |
L |
the lowest price traded in each bar |
Close |
C |
the closing price of each bar |
Volume |
V |
the volume traded in each bar |
OpenInt |
OI |
open interest, where the instrument has any |
Avg |
(none) | ( High + Low + Close ) / 3, the so-called typical price |
Avg is the exception in two ways: it is derived rather than stored, and it has no
abbreviation. The rest are the raw material.
Here are ten bars of a real symbol, laid out as arrays. These are the numbers from AmiBroker’s own worked example, and every diagram in this lesson uses them, so you can carry your understanding from one picture to the next.
Four of the six stored arrays, ten bars
| Bar | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
Open | 1.23 | 1.24 | 1.21 | 1.26 | 1.24 | 1.29 | 1.33 | 1.32 | 1.35 | 1.37 |
High | 1.24 | 1.27 | 1.25 | 1.29 | 1.25 | 1.29 | 1.35 | 1.35 | 1.37 | 1.29 |
Low | 1.20 | 1.21 | 1.19 | 1.20 | 1.21 | 1.24 | 1.30 | 1.28 | 1.31 | 1.27 |
Close | 1.23 | 1.26 | 1.24 | 1.28 | 1.25 | 1.25 | 1.31 | 1.30 | 1.32 | 1.28 |
Notice the numbering, because it is a habit that has to be established early. Bar 0 is the oldest bar. The newest bar is the one with the highest number. Time runs left to right in these diagrams and top to bottom in the column picture. Users arriving from other platforms routinely assume the reverse and then write formulas that are backwards in time.
One expression, one value per bar
Section titled “One expression, one value per bar”Now watch what an operator does. Take MidPrice = ( High + Low ) / 2;.
AFL does not interpret that expression once per bar. It takes the whole High array and the
whole Low array and adds them in a single compiled step, producing a temporary array. Then
it divides every element of that temporary array by two, also in a single step. The result is
assigned to MidPrice.
( High + Low ) / 2, step by step
| Bar | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
High | 1.24 | 1.27 | 1.25 | 1.29 | 1.25 | 1.29 | 1.35 | 1.35 | 1.37 | 1.29 |
Low | 1.20 | 1.21 | 1.19 | 1.20 | 1.21 | 1.24 | 1.30 | 1.28 | 1.31 | 1.27 |
High + Lowtemporary | 2.44 | 2.48 | 2.44 | 2.49 | 2.46 | 2.53 | 2.65 | 2.63 | 2.68 | 2.46 |
( High + Low ) / 2 | 1.22 | 1.24 | 1.22 | 1.245 | 1.23 | 1.265 | 1.325 | 1.315 | 1.34 | 1.23 |
Three things to take from that picture.
Operations are element-wise. Bar 0 is added to bar 0, bar 1 to bar 1, and so on. Nothing
in High + Low ever mixes bars. When you want a value from a different bar you have to say
so explicitly, with Ref(), and Part 9 is largely about the functions that do that.
Intermediate arrays are real. The High + Low row is not a fiction of the diagram; it is
an actual temporary array that AmiBroker builds and then releases. This is why array code is
fast, and it is also why a loop that rebuilds the same array on every iteration is
catastrophically slow — we come back to that below.
Lengths always match. Both inputs have ten elements, so the output has ten. All arrays in one formula run have the same length. There is no such thing as a short array in AFL.
What happens when a number meets an array
Section titled “What happens when a number meets an array”You have already written MA( Close, MaPeriod ) where MaPeriod was a plain number, and
Close - Average where both sides were arrays. What about mixing them?
The official wording is that when a number is combined with an array, the number “works as if
it spanned all array elements”. A single number is stretched to the full length of the array
and then the operation proceeds element-wise as usual. This is called scalar promotion, and
it is what makes expressions like Close * 1.02 or Volume > 100000 work.
Here it is with a number that came from the data. BeginValue() takes an array and returns
the single value at the start of the selected range — one number, not a column.
An array compared with one number
| Bar | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
Open | 1.23 | 1.24 | 1.21 | 1.26 | 1.24 | 1.29 | 1.33 | 1.32 | 1.35 | 1.37 |
BeginValue( Open )one number | 1.24 | 1.24 | 1.24 | 1.24 | 1.24 | 1.24 | 1.24 | 1.24 | 1.24 | 1.24 |
Close | 1.22 | 1.26 | 1.23 | 1.28 | 1.25 | 1.25 | 1.31 | 1.30 | 1.32 | 1.28 |
Close <= BeginValue( Open ) | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Four functions collapse an array to a single number, and they are worth knowing now because they are the usual source of an accidental scalar:
LastValue( array )— the value at the very last bar.SelectedValue( array )— the value at the bar the vertical selection line is on.BeginValue( array )andEndValue( array )— the values at the start and end of the marked From-To range.
Arrays built from arrays built from arrays
Section titled “Arrays built from arrays built from arrays”Real formulas are chains. Each line takes one or more arrays and produces another, and the whole chain is evaluated column by column. Here is AmiBroker’s own worked example, which takes five lines to build a pair of signal arrays:
Fragment — not a complete formula
Cond1 = Close < MA( Close, 3 );Cond2 = Volume > Ref( Volume, -1 );Buy = Cond1 AND Cond2;Sell = High > 1.30;Five arrays, each derived from the ones above it
| Bar | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
Close | 1.23 | 1.26 | 1.24 | 1.28 | 1.25 | 1.25 | 1.31 | 1.30 | 1.32 | 1.28 |
Volume | 8310 | 3021 | 5325 | 2834 | 1432 | 5666 | 7847 | 555 | 6749 | 3456 |
Ref( Volume, -1 )shifted | Null | 8310 | 3021 | 5325 | 2834 | 1432 | 5666 | 7847 | 555 | 6749 |
MA( Close, 3 ) | Null | Null | 1.243 | 1.260 | 1.257 | 1.260 | 1.270 | 1.287 | 1.310 | 1.300 |
Cond1 = Close < MA( Close, 3 ) | Null | Null | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 1 |
Cond2 = Volume > Ref( Volume, -1 ) | Null | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 |
Buy = Cond1 AND Cond2 | Null | Null | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
Sell = High > 1.30 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
Spend a minute on that picture. It contains, in miniature, most of what a trading system is.
Ref( Volume, -1 ) is the volume array shifted one bar forward in time, so that each bar can
see the previous bar’s volume alongside its own. Bar 0 has no previous bar, so its value is
empty. MA( Close, 3 ) needs three closes before it can produce anything, so its first two
values are empty. Those empty values are Null, and they are the subject of lesson eight —
notice already that Cond1 and Cond2 are Null where their inputs were Null, and that
Buy inherits the emptiness from both. It is not false there. It is unknown.
Sell = High > 1.30; shows scalar promotion again: an array on the left, one number on the
right, one true-or-false answer per bar.
And Buy and Sell are just arrays. There is nothing magic about their names except that
the Analysis window knows to look for them. Everything you do for the rest of this course is
building arrays and giving some of them names AmiBroker recognises.
How long is an array?
Section titled “How long is an array?”BarCount is a single number: how many bars the arrays hold in this particular execution.
Valid positions in an array run from 0 to BarCount - 1.
The important word in that sentence is this particular execution. BarCount does not
change while a formula runs, but it changes freely between runs:
- new bars arrive, so a daily database grows by one most days;
- you change the chart’s zoom, and AmiBroker may calculate only the visible portion;
- you switch to a symbol with a different amount of history;
- you run the formula in the Analysis window over a date range rather than all quotations;
- AmiBroker syntax-checks your formula, using at most a couple of hundred recent bars;
- the debugger runs it, defaulting to 200 bars.
The official Knowledge Base article on this is emphatic: a formula “should be written so it is able to execute without errors with BarCount as small as 1 (ONE)”. Hard-coding a bar count, or assuming there are at least N bars, produces a formula that works on your test chart and fails somewhere else.
There is a companion you will meet constantly, and confusing the two is mistake number six on AmiBroker’s official list:
BarCountis a NUMBER — the count of bars.BarIndex()is an ARRAY — the zero-based number of each bar: 0, 1, 2, 3, and so on.
BarIndex() is genuinely useful. BarIndex() >= 200 is a Boolean array that is false over
the first two hundred bars and true afterwards, which is how lesson eight builds a warm-up
guard. But you cannot use it to control a loop, because a loop needs a single number to
compare against and BarIndex() is a whole column.
BarCount is one number; BarIndex() is an array
| Bar | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
BarIndex() | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
BarCountone number | 10 | 10 | 10 | 10 | 10 | 10 | 10 | 10 | 10 | 10 |
BarIndex() >= 5 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
Reaching into an array
Section titled “Reaching into an array”Occasionally you do want one specific element, and the subscript operator [ ] provides it:
Fragment — not a complete formula
FirstClose = Close[ 0 ]; // the oldest bar in the current arrayLatestClose = Close[ BarCount - 1 ]; // the newest barBoth of those are scalars. Two cautions come with the syntax. Going outside 0 to
BarCount - 1 is Error 10, and a Null used as a subscript is Error 51. And Close[ 0 ] is
not necessarily the first bar in your database — under AmiBroker’s QuickAFL optimisation it
is the first bar of the array currently in use, which may be far more recent. Part 36 covers
QuickAFL in detail; for now, treat Close[ 0 ] as “the start of what I can see” rather than
“the start of history”.
Why AFL usually avoids loops
Section titled “Why AFL usually avoids loops”Every language you may have met before would express the midpoint calculation as a loop: for each bar, take the high, take the low, add them, halve them, store the answer. AFL can do that too, and the syntax exists precisely because some calculations need it.
Fragment — not a complete formula
// This works. It is also the wrong tool for this job.for( i = 0; i < BarCount; i++ ){ MidPrice[ i ] = ( High[ i ] + Low[ i ] ) / 2;}The official performance chapter measures the difference and does not mince words: “Poor formula coding is the foremost reason for slowdown”, and loops are between ten and fifty times slower than the equivalent array code. Its published measurement is for exactly the calculation above:
| Bars | Loop version | Array version | Ratio |
|---|---|---|---|
| 350,000 | 100 ms | 2 ms | 50x |
| 300 | 0.1 ms | 0.01 ms | 10x |
Two milliseconds against a hundred sounds trivial until you multiply it. An exploration over two thousand symbols, or an optimisation running the formula five thousand times, turns a fifty-fold penalty into the difference between a coffee break and an afternoon.
The reason is structural rather than mysterious. ( High + Low ) / 2 performs two operations
at compiled speed over a whole block of memory. The loop version performs BarCount
iterations of interpreted code, with the indexing, the loop test and the increment paid for
on every one of them.
Loops are not forbidden, and lesson seven says exactly when they are justified: when a bar’s
value depends on a value that the same calculation produced for the previous bar, and no
built-in function expresses the relationship. That is a real category — it is why AMA() and
the recursive averages exist — but it is much smaller than beginners assume. Nearly every
“for each bar, if this then that” you can describe in English has a direct array expression.
Thinking in arrays
Section titled “Thinking in arrays”The habit to build is translation. When you catch yourself thinking “for each bar, I want to…”, stop and ask what column of numbers you actually want. Most of the time it already has a name.
| What you were about to loop for | The array expression |
|---|---|
| For each bar, is the close above the 50-bar average? | Close > MA( Close, 50 ) |
| For each bar, what was the close one bar earlier? | Ref( Close, -1 ) |
| For each bar, how far is the close above its average, in percent? | 100 * ( Close - MA( Close, 50 ) ) / MA( Close, 50 ) |
| For each bar, pick the high in an up bar and the low in a down bar | IIf( Close > Open, High, Low ) |
| For each bar, is it both trending and liquid? | Trend AND Liquid |
| For each bar, what bar number is this? | BarIndex() |
Three rules of thumb make the translation easier.
Name every step. A chain of five short named arrays is easier to read, easier to comment and — crucially — easier to debug than one long expression, because you can plot any step and see where the answer stopped being right.
Say the shape out loud. Before writing a line, decide whether the result is a column or a number. If you cannot say which, look up the function; the reference states it in capital letters at the top of every page.
Be suspicious of any function that returns NUMBER. Not because they are bad, but because
mixing one into a per-bar rule by accident is the quiet failure described above. LastValue()
in a Buy line deserves a comment justifying itself.
See it on your own chart
Section titled “See it on your own chart”Reading tables convinces nobody. This short formula puts four arrays on one chart and prints the value of each at whichever bar you click, so you can watch a whole row of numbers change together.
Complete runnable AFL
// see-the-arrays.afl// Part 8 - The Array Model//// Purpose: make the array model visible. Every line below produces a whole// column of numbers - one value for every bar - and the title// reads those columns back at whichever bar you select.// Assumes: any symbol, any interval. A deliberately short average period is// used so the empty warm-up bars are easy to find.// Apply: Formula Editor -> Apply indicator, then click along the chart and// watch every number in the title change together.
_SECTION_BEGIN( "See The Arrays" );
// ---------------------------------------------------------------------------// Settings// ---------------------------------------------------------------------------
AvgPeriod = 3; // small on purpose: the warm-up is then only two bars long
// ---------------------------------------------------------------------------// Four arrays, built one from another// ---------------------------------------------------------------------------
// High and Low are built-in arrays. Adding them produces a temporary array of// the same length; dividing that by 2 produces another. One line, two whole// array operations, no loop anywhere.MidPrice = ( High + Low ) / 2;
// MA() reads one array and returns another of exactly the same length.Average = MA( Close, AvgPeriod );
// Element-wise subtraction: bar 0 minus bar 0, bar 1 minus bar 1, and so on.// Where Average is empty the difference is empty too - that is Null spreading.Gap = Close - Average;
// ---------------------------------------------------------------------------// Drawing// ---------------------------------------------------------------------------
Plot( Close, "Close", colorDefault, styleCandle );Plot( MidPrice, "(High+Low)/2", colorOrange, styleLine );Plot( Average, "MA(Close," + AvgPeriod + ")", colorBlue, styleLine | styleThick );
// Gap is drawn on its own scale so that a small difference in price does not// have to share an axis with the price itself.Plot( Gap, "Close - MA", colorGrey40, styleHistogram | styleOwnScale | styleNoLabel );
// ---------------------------------------------------------------------------// Reading the arrays back// ---------------------------------------------------------------------------
// BarCount is a single number: how many bars the arrays hold.// BarIndex() is an array: the zero-based number of each bar.// SelectedValue() collapses an array to the one value at the selected bar.Title = StrFormat( "{{NAME}} {{DATE}} bar %g of %g Close %g (H+L)/2 %g MA %g Close-MA %g", SelectedValue( BarIndex() ), BarCount, Close, MidPrice, Average, Gap );
_SECTION_END();How it works. Three calculation lines, each producing a whole array: MidPrice from two
built-in arrays, Average from one built-in array and a period, and Gap from one built-in
and one derived array. Four Plot() calls draw them; the last uses styleOwnScale so that a
difference measured in fractions of a currency unit does not have to share an axis with the
price itself. The Title line then reads one value out of each array — at the bar you have
selected — plus two scalars: BarCount, and SelectedValue( BarIndex() ), which is the
number of the selected bar.
Key functions. SelectedValue( array ) collapses an array to the value at the selected
bar. BarIndex() returns the per-bar number as an array. BarCount is not a function at all
but a number the language supplies. StrFormat() substitutes values into a template, using
the selected value whenever it is handed an array.
Test it. Pick any bar in the middle of the chart and check (H+L)/2 by hand from the
high and low shown in AmiBroker’s own data tooltip — they must agree exactly. Then check that
the bar number of the last bar is BarCount - 1, not BarCount. If either fails, you are
not looking at the bar you think you are.
Common errors. If the grey histogram flattens the price into a line, the styleOwnScale
flag was lost from the fourth Plot(). If the title shows the same numbers whichever bar you
click, you have replaced SelectedValue() with LastValue() somewhere. If nothing appears at
all, check that the pane you are watching is the selected one.
Extension. Add a line computing Range = High - Low; and plot it too, then satisfy
yourself that Range is Gap-like in shape: a column of numbers, one per bar, derived from
two other columns. Then try Ref( Close, -1 ) as a fifth row and confirm that its value at
bar n equals Close at bar n − 1 — the shift that Part 9 builds on.
An AFL value is either a single number or a column of numbers with one entry per bar. The
database supplies six such columns; everything else is derived from them on demand. Operators
and functions work element-wise on whole columns at once, producing temporary columns along
the way, and a plain number combined with a column behaves as though it had been repeated
across every bar. Columns in one formula run all have the same length, BarCount, and that
length is a property of the current execution rather than a fact about the symbol.
Loops exist and are occasionally necessary, but the array form of the same calculation is between ten and fifty times faster, and calling an array function inside a loop multiplies the work by the number of bars. The habit worth building is translation: when you think “for each bar…”, find the column instead.
The next lesson takes the most important special case — comparisons — and looks at it closely, because a comparison in AFL does not answer a question. It answers it once per bar.
Check your understanding
Sources for this lesson
7 verified · checked 2026-09-01
- 01AmiBroker User's Guide — Understanding how AFL worksamibroker.com/guide/h_understandafl.html2026-08-31
- 02AmiBroker User's Guide — AFL Reference Manual§ Built-in price arrays and the subscript operatoramibroker.com/guide/a_language.html2026-08-31
- 03AmiBroker User's Guide — Performance tuningamibroker.com/guide/x_performance.html2026-08-31
- 04AmiBroker User's Guide — Common Coding Mistakesamibroker.com/guide/a_mistakes.html2026-08-31
- 05AFL Function Reference — BarIndexamibroker.com/guide/afl/barindex.html2026-08-31
- 06AFL Function Reference — LastValueamibroker.com/guide/afl/lastvalue.html2026-08-31
- 07AmiBroker Knowledge Base — Do NOT make assumptions on number of barsamibroker.com/kb/2014/09/22/do-not-make-assumptions-on-number-of-bars2026-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.