Introduction
One of the most important aspects of AFL is that it is an array processing language. It operates on arrays (or rows/vectors) of data. This way of operation is quite similar to the way popular spreadsheets work (like Microsoft Excel). Anyone familiar with MS Excel should have no trouble quickly picking up AFL. In fact, all the examples in this article were created using MS Excel.
What is an Array?
An array is simply a list (or row) of values. In some books it may be referred to as a vector. Each numbered row of values in the example represents an individual array. AmiBroker has stored in its database 6 arrays for each symbol. One for the opening price, one for the low price, one for the high price, one for the closing price, one for volume (see the rows labeled 1-5 below), and one for open interest. These can be referenced in AFL as open, low, high, close, volume, openint or o, l, h, c, v, oi.
All array indices in AFL are zero-based, i.e., counting bars starting from bar 0 (oldest). The latest (newest) bar has an index of (BarCount-1). The 10-element array will have BarCount = 10 and indices going from 0 to 9 as shown below
(oldest) |
----> time stamps increasing ----> |
(newest) |
|||||||||
| Bar |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
|
| 1 | Open | 1.23 |
1.24 |
1.21 |
1.26 |
1.24 |
1.29 |
1.33 |
1.32 |
1.35 |
1.37 |
Fig 1. Open price array
Any other array is calculated from these 6 arrays using formulae built into AFL. These arrays are not stored in the database but calculated where necessary.
Each individual value in an array has a date associated with it. If you have the tool tip option turned on (Preferences -> Miscellaneous Tab -> Price data tool tips), when you move your cursor over a candle on a daily candle chart, a small yellow rectangle appears. AFL then looks up the open, low, high, close, volume values in the appropriate array and displays them inside the tool tip.
Processing arrays - why is AFL so fast?
Let's see how the following statement is processed:
MyVariable = ( High + Low )/2;
When AFL is evaluating a statement like this (High + Low)/2, it does not need to re-interpret this code for each bar. Instead, it takes the High ARRAY and Low ARRAY and adds corresponding array elements in a single stage. In other words, the + operator (and other operators too) works on arrays at once, and it is executed at full compiled-code speed; then, the resulting array (each element of it) is divided by 2, also in a single stage.
Let's look into the details – see Fig 2. When the AFL engine looks at (High + Low)/2, it first takes the High (1) and Low (2) arrays and produces (in a single compiled step) the temporary array (3). Then it creates the final array (4) by dividing each element of the temporary array by two. This result is assigned to MyVariable
| Bar |
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
|
| 1 | High (built-in array) | 1.24 |
1.27 |
1.25 |
1.29 |
1.25 |
1.29 |
1.35 |
1.35 |
1.37 |
1.29 |
| 2 | Low (built-in array) | 1.20 |
1.21 |
1.19 |
1.20 |
1.21 |
1.24 |
1.30 |
1.28 |
1.31 |
1.27 |
| 3 | High+Low (temporary array created during evaluation) | 2.44 |
2.48 |
2.44 |
2.49 |
2.46 |
2.53 |
2.65 |
2.63 |
2.68 |
2.46 |
| 4 | ( High+Low ) /2 (gets assigned to MyVariable) | 1.22 |
1.24 |
1.22 |
1.245 |
1.23 |
1.265 |
1.325 |
1.315 |
1.34 |
1.23 |
Fig 2. AFL steps when processing ( High + Low ) /2
Moving averages, conditional statements
Let us now consider the following code:
Cond1 = Close > MA( Close, 3 );
Cond2 = Volume > Ref( Volume, -1 );
Buy = Cond1 AND Cond2;
Sell = High > 1.30;
This code generates a buy signal when today's close is higher than the 3-day moving average of close AND today's volume is higher than yesterday's volume. It also generates a sell signal when today's high is higher than 1.30.
If, in your AFL code, you need to see if the closing price is greater than, say, a 3-day simple moving average, AFL will first run through the close array, creating a new array called MA(close,3) for the symbol being analyzed. Each cell in the new array can then be compared one-for-one with the close array. In the example, an array called Cond1 is created this way. For each cell where the closing price is greater than the corresponding cell value in MA(close,3), the cell value for the new array 'Cond1' is set to '1'. If the closing price is not greater than the corresponding price in the close array, the value in 'Cond1' is set to '0'.
AFL can also look forwards or backwards a number of cells in an array using the Ref function (see row 6, where a temporary array is created holding the previous day's volume)
In row 9, a new array called Cond2 has been created by comparing the value of each cell in the volume array with its previous cell, setting the Cond2 cell value to '1' if true and '0' if false.
Row 10 shows an array called 'Buy' created by comparing the cell values in Cond1 with the cell values in Cond2. If the cell in Cond1 has a '1' AND so does the corresponding cell in Cond2, then a '1' is placed in the 'Buy' array cell.
Row 11 shows an array called 'Sell' created whenever the cell value in the close array is greater than $1.30.
| Bar |
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
|
| 1 | Open | 1.23 |
1.24 |
1.21 |
1.26 |
1.24 |
1.29 |
1.33 |
1.32 |
1.35 |
1.37 |
| 2 | High | 1.24 |
1.27 |
1.25 |
1.29 |
1.25 |
1.29 |
1.35 |
1.35 |
1.37 |
1.29 |
| 3 | Low | 1.20 |
1.21 |
1.19 |
1.20 |
1.21 |
1.24 |
1.30 |
1.28 |
1.31 |
1.27 |
| 4 | Close | 1.23 |
1.26 |
1.24 |
1.28 |
1.25 |
1.25 |
1.31 |
1.30 |
1.32 |
1.28 |
| 5 | Volume | 8310 |
3021 |
5325 |
2834 |
1432 |
5666 |
7847 |
555 |
6749 |
3456 |
| 6 | Ref( Volume, -1 ) (temporary array created during eval) | Null |
8310 |
3021 |
5325 |
2834 |
1432 |
5666 |
7847 |
555 |
6749 |
| 7 | MA( Close, 3 ) (temporary array created during eval) | Null |
Null |
1.243 |
1.260 |
1.257 |
1.260 |
1.270 |
1.287 |
1.310 |
1.300 |
| 8 | Cond1 = Close < MA(close,3) (gives 1 (or true) if the condition is met, zero otherwise) | Null |
Null |
1 |
0 |
1 |
1 |
0 |
0 |
0 |
1 |
| 9 | Cond2 = Volume > Ref(volume,-1) | Null |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
| 10 | Buy = Cond1 AND Cond2 | Null
|
Null |
1 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
| 11 | Sell = High > 1.30 | 0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
Obviously Buy and Sell are special arrays whose results can be displayed in the Analyzer window or on screen using a red or green value as needed.
Getting a little bit more complex
The examples above were very simple. Now I will just explain three things that seem to generate some confusion among users:
As written in the Tutorial: Basic charting guide, you can select any quote from the chart and mark a From-To range. The bar selected by the vertical line is called the "selected" bar, while the start and end bars of the range are called "begin" and "end" bars. AFL has special functions that allow referencing the value of the array at the selected, begin, and end bar, respectively. These functions are called SelectedValue, BeginValue and EndValue. There is one more function called LastValue that allows getting the value of the array at the very last bar. These four functions take the array element at a given bar and return a SINGLE NUMBER representing the value of the array at a given point. This allows calculating some statistics regarding selected points. For example:
EndValue( Close ) - BeginValue( Close )
Will give you the dollar change between close prices in the selected From-To range.
When a number retrieved by any of these functions is compared to an array, or any other arithmetic operation involving a number and the array is performed, it works as if the number spanned all array elements. This is illustrated in the table below (rows 2, 6, 7). Green color marks the "begin" bar, and red color marks the "end" bar. The selected bar is marked with blue.
|
Bar
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
|
| 1 | Open |
1.23
|
1.24
|
1.21
|
1.26
|
1.24
|
1.29
|
1.33
|
1.32
|
1.35
|
1.37
|
| 2 | BeginValue( Open ) |
1.24
|
1.24
|
1.24
|
1.24
|
1.24
|
1.24
|
1.24
|
1.24
|
1.24
|
1.24
|
| 3 | EndValue( Open ) |
1.32
|
1.32
|
1.32
|
1.32
|
1.32
|
1.32
|
1.32
|
1.32
|
1.32
|
1.32
|
| 4 | SelectedValue( Open ) |
1.21
|
1.21
|
1.21
|
1.21
|
1.21
|
1.21
|
1.21
|
1.21
|
1.21
|
1.21
|
| 5 | LastValue( Open ) |
1.37
|
1.37
|
1.37
|
1.37
|
1.37
|
1.37
|
1.37
|
1.37
|
1.37
|
1.37
|
| 6 | Close |
1.22
|
1.26
|
1.23
|
1.28
|
1.25
|
1.25
|
1.31
|
1.30
|
1.32
|
1.28
|
| 7 | Close <= BeginValue( Open ) |
1
|
0
|
1
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
| 8 | result = IIF( Close <= BeginValue( Open ), Close, Open ); |
1.22
|
1.24
|
1.23
|
1.26
|
1.24
|
1.29
|
1.33
|
1.32
|
1.35
|
1.37
|
| 9 | Period |
2
|
3
|
4
|
2
|
3
|
5
|
2
|
3
|
4
|
2
|
| 10 | Factor = 2/(Period+1) | 0.667
|
0.500
|
0.400
|
0.667
|
0.500
|
0.333
|
0.667
|
0.500
|
0.400
|
0.667
|
| 11 | 1 - Factor | 0.333 |
0.500 |
0.600 |
0.333 |
0.500 |
0.667 |
0.333 |
0.500 |
0.600 |
0.333 |
| 12 | AMA( Close, Factor ) |
0.8125
|
1.0363
|
1.1138
|
1.2234
|
1.2367
|
1.2399
|
1.2853
|
1.2927
|
1.3036
|
1.2866
|
Now the IIF(condition, truepart, falsepart) function. It works by returning the value of the second (truepart) or third (falsepart) argument depending on the condition. As you can see in the table above, in row 8, the values come from the Close array (truepart) for bars when the condition is true (1) and come from the Open array (falsepart) for the remaining bars. In that case, the array returned by the IIF function consists of some values from the Close and some values from the Open array. Note that both truepart and falsepart are arrays, and they are evaluated regardless of the condition (so this is not a regular IF-THEN-ELSE statement, but a function that returns an array)
The AMA( array, factor) function seems to cause the most problems with understanding it. But, in fact, it is very simple. It works in a recursive way. It means that it uses its previous value for the calculation of the current value. It processes the array bar by bar; with each step, it multiplies a given cell of the first argument (array) by a given cell of the second argument (factor) and adds it to the previous value of AMA multiplied by (1-factor). Let's consider bar number 2 (marked with blue). The value of AMA on that bar is given by multiplying the close price from that bar (1.23) by the factor (0.4). Then we add the previous value of AMA (1.0363), multiplied by (1-factor = 0.6). The result (rounded to 4 places) is 1.23 * 0.4 + 1.0363 * 0.6 = 1.1138.
If you look at the figures in row 12, you may notice that these values look like a moving average of the close. And that's true. We actually presented how to calculate a variable-period exponential moving average using the AMA function.
New looping
With version 4.40, AmiBroker brings the ability to iterate through quotes using for and while loops and adds an if-else flow control statement. These enhancements make it possible to work both ways: either use ARRAY processing (described above) for speed and simplicity, or use LOOPS for doing complex things. As an example of how to implement variable-period exponential averaging (described above) using looping, see the following code:
Period = ... some calculation
vaexp[ 0 ] = Close[ 0 ]; // initialize first value
for( i = 1; i < BarCount; i++ )
{
// calculate the value of smoothing factor
Factor = 2/(Period[ i ] + 1 );
// calculate the value of i-th element of array
// using this bar close ( close[ i ] ) and previous average value
(
vaexp[
i - 1 ] )
vaexp[ i ] = Factor * Close[ i ] + ( 1 - Factor ) * vaexp[ i - 1 ];
}
As you can see, the code is longer, but on the other hand, it is very similar to any other programming language such as C/Pascal/Basic. So, people with some experience with programming may find it easier to grasp.
If you are a beginner, I suggest learning array processing first before digging into more complex looping stuff.
If you're having trouble coding AFL, I suggest you generate the arrays in the example in Excel for yourself. If that's a problem, get some help from a friend, especially if that friend is an accountant.
Once you've got the hang of it, you can code any system from a book on trading, - or build one yourself.
--- Special thanks to Geoff Mulhall for original article in the newsletter that was the basis for this tutorial ---