Introduction
One of 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 how 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 all 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 opening price, one for the low price, one for the high price, one for the closing price and one for volume (see the rows labelled 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 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?
Lets see how the following statement is processed:
MyVariable = ( High + Low )/2;
When AFL is evaluating 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 single stage. In other words + operator (and other operators too) work 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 single stage.
Let's look into the details - see fig 2.. When AFL engine looks at the ( High + Low )/2 it first takes High (1) and Low (2) arrays and produces (in single compiled step) the temporary array (3). Then it creates the final array (4) by dividing each element of 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 todays close is higher than 3 day moving average of close AND todays 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 analysed. Each cell in the new array can then be compared one for one in 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 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 temporary array is created holding previous day 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 condition 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 Analyser window or on screen using a red or green value as needed.
Getting little bit more complex
The examples above were very simple. Now I will just explain 3 things that seem to generate some confusion among the users:
As written in the Tutorial: Basic charting guide you can select any quote from the chart and you can mark From-To range. The bar selected by verticall line is called "selected" bar while start and end bars of the range are called "begin" and "end" bars. AFL has special functions that allow to reference value of the array at selected, begin and end bar respectively. These functions are called SelectedValue, BeginValue and EndValue. There is one more function called LastValue that allows to get the value of the array at the very last bar. These four functions take the array element at given bar and return SINGLE NUMBER representing the value of the array at given point. This allows to calculate some statistics regarding selected points. For example:
EndValue( Close ) - BeginValue( Close )
Will give you dollar change between close prices in selected from-to range.
When number retrieved by any of these functions is compared to an array or any other arithmetic operation involving number and the array is performed it works like the number spanned all array elements. This is illustrated in the table below (rows 2, 6, 7). Green color marks "begin" bar and red color marks "end" bar. 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 that it returns the value of second (truepart) or third (falsepart) argument depending on condition. As you can see in the table above in row 8 the values come from Close array (truepart) for bars when condition is true (1) and come from Open array (falsepart) for the remaining bars. In that case the array returned by IIF function consists of some values from Close and some values from 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 function that returns 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 recursive way. It means that it uses its previous value for the calculation of current value. It processes array bar by bar, with each step it multiplies given cell of first argument (array) by given cell of second argument (factor) and adds it to the previous value of AMA multiplied by (1-factor). Lets consider bar number 2 (marked with blue). The value of AMA on that bar is given by multipling close price from that bar (1.23) by factor (0.4). Than 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 the row 12 you may notice that these values look like a moving average of close. And that's true. We actually presented how to calculate variable-period exponential moving average using AMA function.
New looping
With version 4.40 AmiBroker brings ability to iterate through quotes using for and while loops and adds 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 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 as C/Pascal/Basic. So people with some experience with programming may find it easier to grasp.
If you are beginner I suggest to learn 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 of this tutorial ---