AFL is a special programming language used to define and create custom indicators, scans, explorations, back-tests and guru commentaries.
This chapter describes the different categories of word-like units (tokens) recognized by the AFL language interpreter.
Whitespace is the collective name given to spaces (blanks), tabs, new line characters and comments. Whitespace can serve to indicate where tokens start and end, but beyond this function, any surplus whitespace is discarded.
Comments are pieces of text used to annotate a program. Comments are
for the programmer's use only; they are stripped from the source code before
parsing. The are two ways to delineate comments: C-like comments and C++ like
comments. A C-like comment is any sequence of characters placed after the symbol
pair /*. The comment terminates at the first occurrence of the pair */ following
the initial /*. The entire sequence, including the four comment-delimiter symbols,
is replaced by one space. A C++ like comments are single-line comments that
start by using two adjacent slashes (//) in any position within the line and
extend until the next new line.
AFL does not allow nested comments.
AFL recognizes five classes of tokens:
Identifiers are arbitrary names of any length given to functions and
variables. Identifiers can contain the letters (a-z, A-Z), the underscore character
("_"), and the digits (0-9). The first character must be a letter.
AFL identifiers are NOT case sensitive.
Constants are tokens representing fixed numeric or character values.
Numeric constants consist of decimal integer and optionally: decimal point and
decimal fraction part. Negative numeric constants have unary minus (-) prefixed.
String constants, also known as string literals, form a special category
of constants used to handle fixed sequences of characters and are written as
a sequence of any number of characters surrounded by double quotes:
"This is literally a string"
The null (empty) string is written "". The characters inside the double quotes can include escape sequences ("\n" - a new line escape sequence).
A Constant expression is an expression that always evaluates to a constant. They are evaluated just as regular expressions are.
Punctuator (also known as separator) in AFL is one of the following
characters:
( ) , ; = .
Parentheses (open ( and close ) ) group expressions, isolate conditional
expressions and indicate function calls and function parameters:
d = c * ( a + b ) /* override normal precedence */
a= (b AND c) OR (d AND e) /* conditional expression */
func() /* function call no arguments */
The comma (,) separates the elements of a function argument list
The semicolon (;) is a statement terminator. Any legal AFL expression
followed by a semicolon is interpreted as a statement, known as expression statement.
The expression is evaluated and its value
is discarded (except Guru Commentaries where string values are written to output
window)
The dot (.) is a member access operator. It is used to call COM object methods. If myobj variable holds the object, using dot operator we can call the methods (functions) of myobj object:
myobj.Method();
The equal sign (=) separates variable declarations from initialization
lists:
x = 5;
It also indicates the default value for a parameter (see built-in function description):
macd( fast = 12; slow = 26 ) /* default values for fast and slow arguments)
Each formula in AFL contains of one or more expression statements. Each statement MUST be terminated by semicolon (;). In this way you are able to break long expressions into several physical lines (in order to gain clarity) and AmiBroker will still treat it like a single statement until terminating semicolon. Examples:
x = ( y + 3 ); /* x is assigned the value of y + 3 */
x = y = 0; /* Both x and y are initialized to 0 */
proc( arg1, arg2 ); /* Function call, return value discarded */
y = z = ( f( x ) + 3 ); /* A function-call expression */
my_indicator = IIf( MACD() > 0,
Close - MA(Close,9),
MA( Close, 9 ) - Close );
/* one statement in several lines */
Identifiers in AFL are used to identify variables and functions.
There are some predefined identifiers referencing built-in arrays and functions.
The most important are price array identifiers. They identify specific
price fields that the formula should operate on. The valid price array identifiers
are open, high, low, close,
volume, openint, average.
Price array identifiers can be abbreviated as shown in the following table.
Note that these are not case-specific.
Long name | Abbreviation | Comment |
Open | O | |
High | H | |
Low | L | |
Close | C | |
Volume | V | |
OpenInt | OI | |
Avg | <none available> | (High+Low+Close)/3 - so called "typical price" |
Examples of the use of price array identifiers in formulas are shown below.
MA( Close, 10 ); IIf( H > Ref(H,-1), MA(H,20), MA(C,20)
);
Comparision operators are divided into two types:
Symbol | Meaning |
< | Less than |
> | Greater than |
<= | Less than or equal to |
>= | Greater than or equal to |
== | Equal to |
!= | Not equal to |
These operators give true (1) or false (0) value as a result of comparison.
Symbol | Meaning |
= | Store the value of the second operand in the object specified by the first operand (“simple assignment”). |
The assignment operator assigns a value to a variable:
result = expression;
where result is variable identifier and expression is any numerical, array or text expression.
As the = operator behaves like other operators, expressions using it have
a value in addition to assigning that value into variable. This means that
you can chain assignment operators as follows:
j = k = l = 0;
j, k, and l equal zero after the example statement is executed.
Attention: please DO NOT confuse assignment operator (=) with equality check (==)
These are two different operators and you must not use assignment (=) to check for equality.
if( Name() = "MSFT" ) // WRONG !!! - variable assignment operator
used instead of equality check
{
}
if( Name() == "MSFT" ) // CORRECT - equality operator used properly
{
}
This is one of common coding mistakes listed here.
Formulas can contain the following mathematical operators:
Symbol | Meaning |
+ | Addition |
- | Subtraction (or negative value) |
* | Multiplication |
/ | Division |
% | Modulus (or remainder) (AFL 1.7+) |
^ | Exponentiation (raising to a power) |
| | Bit-wise "Or" (AFL 2.1+) |
& | Bit-wise "And" (AFL 2.1+) |
The following formulas illustrate the use of operators in a formula:
var1 = ( H + L ) / 2;
var2 = MA(C,10)-MA(C,20) / (H + L + C);
var3 = Close + ((1.02 * High)-High);
Symbol | Meaning |
NOT | Logical "Not" - gives "True" when operand is equal to false |
AND | Logical "And" - gives "True" result if BOTH operands are true at the same time |
OR | Logical "Or" - gives "True" result if ANY of operands is true |
If a formula requires multiple conditions, you can combine the conditions with AND and OR operators. For example, maybe you'd like to plot a +1 when the MACD is greater than zero and the RSI is greater than 70:
Condition = MACD() > 0 AND RSI(14) > 70;
You can add as many conditions within a formula as you like.
Introduced in version 5.00, the compound operatos are specifeid in the form of:
destinvar op= expr;
where destinvar is the variable, expr is the expression, and op is one of the following artithmetic operators: +, -, *, /, %, &, |
The destinvar op= expr form behaves as:
destinvar = destinvar op expr;
This is shortcut form for common assignment statements like k = k + 2; so you can write it shorter as:
k += 2;
and it will work the same but little faster.
Full list of available assignment operators is here:
No | Symbol | Meaning |
1 | = | Store the value of the second operand in the object specified by the first operand (“simple assignment”). |
2 | *= | Multiply the value of the first operand by the value of the second operand; store the result in the object specified by the first operand. |
3 | /= | Divide the value of the first operand by the value of the second operand; store the result in the object specified by the first operand. |
4 | %= | Take modulus of the first operand specified by the value of the second operand; store the result in the object specified by the first operand. |
5 | += | Add the value of the second operand to the value of the first operand; store the result in the object specified by the first operand. |
6 | –= | Subtract the value of the second operand from the value of the first operand; store the result in the object specified by the first operand. |
7 | &= | Obtain the bitwise AND of the first and second operands; store the result in the object specified by the first operand. |
8 | |= | Obtain the bitwise inclusive OR of the first and second operands; store the result in the object specified by the first operand |
The typeof operator is used in the following way:
typeof (operand)
The typeof operator returns a string indicating the type of the *unevaluated*
operand. operand is the string, variable, function identifier, or object for
which the type is to be returned.
When supplying identifier, it should be provided alone, without arithmetic
operators, without extra arguments and without braces.
If you want to check the type of value returned by the function, you must first
assign the return value to a variable and then use
typeof( variable ).
Possible return values are:
if( typeof(
somevar ) == "undefined" )
{
/// when somevar is undefined the code here will execute
}
The following sample COMMENTARY code shows the output of
typeof() in some common situations:
x = MACD();
y = LastValue( x );
function testfun()
{ return 1;
};
printf( typeof(
test ) + "\n" ); //
the undefined variable
printf( typeof( 1 )
+ "\n"); //
literal number
printf( typeof( "checking" )
+ "\n"); //
literal string
printf( typeof(
x ) + "\n"); //
array variable
printf( typeof(
y ) + "\n"); //
scalar variable
printf( typeof( MACD )
+ "\n"); //
function identifier
printf( typeof(
testfun ) + "\n" ); //
user function identifier
AFL supports parentheses in formulas.
Parentheses can be used to control the operation precedence (the order in which the operators are calculated). AmiBroker always does operations within the innermost parentheses first. When parentheses are not used, the precedence is as follows (higher precedence listed first):
No | Symbol | Meaning |
1 | ++ | Post-increment/pre-increment (i++ works like i = i + 1) |
2 | -- | Post-decrement/pre-decrement |
3 | [ ] | Array element (subscript) operator |
4 | ^ | Exponentiation |
5 | - | Negation - Unary minus |
6 | * | Multiplication |
7 | / | Division |
8 | % | Reminder (Modulo operator) |
9 | + | Addition |
10 | - | Subtraction |
11 | < | Less than |
12 | > | Greater than |
13 | <= | Less than or equal to |
14 | >= | Greater than or equal to |
15 | == | Equal to |
16 | != | Not equal to |
17 | & | Bit-wise "And" (AFL 2.1+) |
18 | | | Bit-wise "Or" (AFL 2.1+) |
19 | NOT | Logical "Not" |
20 | AND | Logical "And" |
21 | OR | Logical "Or" |
22 | = | Variable assignment operator |
23 | *= /= %= += -= & = |= |
Compound assignment |
The expression
H + L / 2;
(without parenthesis) would be calculated by AmiBroker as "L / 2" plus "H", since division has a higher precedence. This would result in a much different value than
(H + L)/2;
A few words about increment/decrement operators. There are two kinds of them: postfix and prefix.
The unary operators (++ and --) are called “prefix” increment or decrement operators when the increment or decrement operators appear before the operand. Postfix increment and decrement has higher precedence than prefix increment and decrement operators. When the operator appears before its operand, the operand is incremented or decremented and its new value is the result of the expression.
i = 5;
j = ++i; // i will be incremented first and
result (number 6) will be assigned to j.
The result of the postfix increment or decrement operation is the value of the postfix-expression before the increment or decrement operator is applied. The type of the result is the same as that of the postfix-expression but is no longer an l-value. After the result is obtained, the value of the operand is incremented (or decremented).
i = 5;
j = i++; // j will be assigned the value of
5 (before incrementation) and then i will be incremented to 6.
An array identifier followed by an expression in square brackets ([ ]) is a subscripted representation of an element of an array object.
arrayidentifier [ expression ]
It represents the value of expression-th element of array.
BarCount constant gives the number of bars in array (such as Close, High, Low, Open, Volume, etc). Array elements are numbered from 0 (zero) to BarCount-1. BarCount does NOT change as long as your formula continues execution, but it may change between executions when new bars are added, zoom factor is changed or symbol is changed.
To get the first bar you can use array[ 0 ], to get the last bar of array you can use array[ BarCount - 1 ];
For example:
Close[ 5 ]
;
Represents the sixth element (bar) of the close array.
Close[ 0 ];
Represents the very first available bar of the close array.
High[ BarCount - 1 ];
Represents the last bar of High array.
Matrices are two-dimensional arrays of numbers.
To create a matrix use:
my_var_name = Matrix( rows, cols, initvalue);
To access matrix elements, use:
my_var_name[ row ][ col ];
where
row is a row index (0... number of rows-1)
and
col is a column index (0... number of columns-1)
Matrices and their elements support all scalar (element-wise) arithmetic and logical operations.
All these standard operators are performed on matrices element-wise. For that reason for example to add two matrices they must be the same size (the number of rows and columns must be the same). If they are not the same it is up to you how to perform calculation on each element via loop.
So you can for example add, subtract, multiply, divide two matrices if they have same dimensions with one call.
x = Matrix( 5, 6, 9 ); //
matrix 5 rows 6 columns, initial value 9
y = Matrix( 5, 6, 10 ); //
matrix 5 rows 6 columns, initial value 10
z = y - x; // will give you matrix 5 rows and 6 columns
filled with elements holding value 1 (difference between 10 and 9).
All those operations are performed ELEMENT-WISE.
You can also apply any arithmetic and logical operation on matrix AND scalar
value. This would perform element-wise
operation on each element of source matrix and given scalar value.
m = Matrix( 10, 10, 0 ); //
m will be 10x10 matrix filled with zeros
z = m; // z is now also a matrix
for( i = 0;
i < 10; i++ )
{
z[ i ][ 4 ] = i; //
fill z with some other values, note that m will remain unaffected.
}
for( i = 0;
i < 10; i++ )
_TRACEF( "%g = %g,
%g, %g\n", i, m[i][1], m[ i][4],
z[ i][4]);
// scalar addition (element wise)
z += 3;
m += 5;
for( i = 0;
i < 10; i++ )
_TRACEF( "%g = %g,
%g, %g\n", i, m[i][1], m[ i][4],
z[ i][4]);
There is one special operator that works only on matrices - it is matrix
product. The operator for matrix product is @ (the
'at' sign). Matrix product is the linear algebra way to multiply matrices.
If you write C = A @ B, it multiplies matrix A(n,k) by matrix B(k,m) to produce
matrix C(n,m) so the number of columns in matrix A must be equal to number
of rows in matrix B. For more info see: https://en.wikipedia.org/wiki/Matrix_multiplication The
precedence of matrix product @ operator is the same as * (so it has higher
precedence than addition and subtraction).
A = Matrix( 1, 3 );
B = Matrix( 3, 2 );
// matrix A = [ 1, 4, 6 ]
// matrix B =
// [ 2, 3 ]
// [ 5, 8 ]
// [ 7, 9 ]
A[ 0 ][ 0 ] = 1;
A[ 0 ][ 1 ] = 4;
A[ 0 ][ 2 ] = 6;
B[ 0 ][ 0 ] = 2;
B[ 0 ][ 1 ] = 3;
B[ 1 ][ 0 ] = 5;
B[ 1 ][ 1 ] = 8;
B[ 2 ][ 0 ] = 7;
B[ 2 ][ 1 ] = 9;
X = A @ B;
_TRACEF("%g %g",
X[ 0 ][ 0 ], X[ 0 ][ 1 ]
);
A compound statement consists of zero or more statements enclosed in curly braces ({ }). A compound statement can be used anywhere a statement is expected. Compound statements are commonly called “blocks.”
{
statement1;
....
statementN;
}
(this is 'borrowed' from C language, users of other programming languages are used to use BEGIN for { and END for } )
if(
Amount > 100 )
{
_TRACE("Amount
above 100");
Balance = Balance + Amount;
}
else
Balance = Balance - Amount;
In addition to mathematical operators, AmiBroker contains over 70 built-in functions that perform mathematical operations.
The following formula consists of a single function that gives the square roots of the closing prices:
sqrt( Close );
The following formula consists of a single function that gives a 14-period RSI indicator:
Graph0 = RSI(14);
The following formula consists of two functions. The result is the difference between the MACD indicator and a 9-period exponential moving average of the MACD:
Graph0 = MACD() - EMA(MACD(),9);
All function calls must consist of function identifier (name) followed by a pair of parentheses.
As has been eluded to in earlier examples, a function can be "nested" within a function. The nested function can serve as the main function's data array parameter. The following examples show functions nested within functions:
MA( RSI(15), 10 );
MA( EMA( RSI(15), 20), 10 );
The first example calculates a 10-period simple moving average of a 15-period Relative Strength Index (RSI). The second example calculates a 20-period exponential moving average of a 15-period RSI, and then calculates a 10-period simple moving average of this moving average.
The iif() function is used to create conditional assignments. It contains three parameters as shown in the following example.
dynamicrsi = IIf( Close > MA(C,10), RSI(9), RSI(14) );
The above "iif" statement reads (in English) as follows: If today's close is greater than today's 10-day simple moving average of the close, then assign a 9-day RSI to the dynamicrsi variable, otherwise, assign a 14-day RSI. The next formula assigns positive volume to volresult variable if the close is greater than the median price. Otherwise, "negative volume" is assigned.
volresult = IIf( Close > (High+Low)/2, Volume, -Volume );
If you simply want an expression to be evaluated as either true or false, it can be done without the use of the iif() function. The following formula will result in either a 1 (true) or a 0 (false):
result = RSI(14) > 70;
The same done with iif() gives the same results, but the formula is longer.
result = IIf(RSI(14) > 70, 1, 0 );
Please note that IIF is a function - so the result of evaluation is returned by that function and should be assigned to some variable.
IIf always evaluates both TRUE_PART and FALSE_PART, even though it returns only one of them. Because of this, you should watch for undesirable side effects. IIF function is NOT a flow-control statement. If you need flow control (conditional execution of some code parts) you should look for if-else conditional statement described later in this document.
The following example shows one common error made with IIF function:
IIf( condition, result = 7, result = 9 ); // THIS IS WRONG
Correct usage is:
result = IIf( condition, 7, 9 );
/* 7 or 9 is *returned* and assigned to result variable depending on condition */
In order to shorten, simplify, enhance, and make the maintenance of complex formulas easier, you may want to use variables. In fact using variables you can significantly improve formula calculation speed. So it is strongly recommended to use variables and there is no limit on number of variables you can define.
A variable is an identifier that is assigned to an expression or a constant. The number of variables used in a formula is not limited. Variables must be assigned before the variable is used in the formula. Variables cannot be assigned within a function call.
User-defined variable names (identifiers) cannot duplicate names already used by functions (e.g., ma, rsi, cci, iif, etc.) or predefined array identifiers (e.g., open, high, low, close, simple, o, c, l, h, s, a).
AmiBroker uses some reserved variable names in its formulas, for example in Auto-Analysis window you have to assign values to 2 variables named 'buy' or 'sell' to specify the conditions where "buy" and "sell" conditions occur. For example (system that buys when MACD rises above 0 line, and sells when MACD falls below 0 line)
Buy = Cross( MACD(), 0 );
Sell = Cross( 0, MACD() );
AmiBroker uses the following reserved variable names. Please note that variables marked as obsolete should NOT be used in new coding. They are left for backward compatibility only and new formulas should use modern functions like Plot() to plot indicators and AddColumn() to define exploration columns.
Variable | Usage | Applies to |
buy | defines "buy" (enter long position) trading rule | Automatic Analysis, Commentary |
sell |
defines "sell" (close long position) trading rule |
Automatic Analysis, Commentary |
short | defines "short" (enter short position - short sell) trading rule | Automatic Analysis |
cover | defines "cover" (close short position - buy to cover) trading rule | Automatic Analysis |
buyprice | defines buying price array (this array is filled in with the default values according to the Automatic Analyser settings) | Automatic Analysis |
sellprice | defines selling price array (this array is filled in with the default values according to the Automatic Analyser settings) | Automatic Analysis |
shortprice | defines short selling price array (this array is filled in with the default values according to the Automatic Analyser settings) | Automatic Analysis |
coverprice | defines buy to cover price array (this array is filled in with the default values according to the Automatic Analyser settings) | Automatic Analysis |
title | defines title text (overrides any graphNname) | Indicators |
tooltip | Obsolete in 5.40. Use Data window instead or use Plot() with styleHidden if you want to add your custom values to data tooltip. |
Indicators |
graphxspace | defines percentage extra space added at the top and the bottom of the chart | Indicators |
graphzorder | GraphZOrder variable allows to change the order of plotting indicator lines. When GraphZOrder is not defined or is zero (false) - old ordering (last to first) is used, when GraphZOrder is 1 (true) - reverse ordering is applied. | Indicators |
exclude | If defined, a true (or 1) value of this variable excludes current symbol from scan/exploration/back test. They are also not considered in buy and hold calculations. Useful when you want to narrow your analysis to certain set of symbols. | Automatic Analysis |
roundlotsize | defines round lot sizes used by backtester (see explanations below) | Automatic Analysis (new in 4.10) |
ticksize | defines tick size used to align prices generated by built-in stops (see explanations below) (note: it does not affect entry/exit prices specified by buyprice/sellprice/shortprice/coverprice) | Automatic Analysis (new in 4.10) |
pointvalue | allows to read and modify future contract point value (see
backtesting futures) CAVEAT: this AFL variable is by default set to 1 (one) regardless of contents of Information window UNLESS you turn ON futures mode (SetOption("FuturesMode", True )) |
Automatic Analysis (new in 4.10) |
margindeposit | allows to read and modify future contract margin (see backtesting futures) | Automatic Analysis (new in 4.10) |
positionsize |
Allows control dollar amount or percentage of portfolio that is invested into the trade (more information available in the "Tutorial: Backtesting your trading ideas") |
Automatic Analysis (new in 3.9) |
positionscore | Defines the score of the position. More details: "Tutorial: Portfolio Backtesting") | Automatic analysis |
numcolumns | Exploration only: defines the number of your own columns (excluding predefined ticker and date columns) and assign the column value to the variable | Automatic Analysis |
filter | Exploration only: controls which symbols/quotes are accepted.
If "true" (or 1) is assigned to that variable for given symbol/quote
it will be displayed in the report.
So, for example, the following formula will accept all symbols with closing prices greater than 50 :
|
Automatic Analysis |
columnN (obsolete) |
Exploration only: defines Nth column value. Example:
|
Automatic Analysis |
columnNformat (obsolete) |
Exploration only: allows you to define the formatting applied to numbers. By default all variables are displayed with 2 decimal digits, but you can change this by assigning a different value to this variable: 1.5 gives 5 decimal digits, 1.0 gives no decimal digits. So, in our example, typing:
will give closing prices displayed with 4 decimal digits. |
Automatic Analysis |
columnNname (obsolete) |
Exploration only: allows you to define the header name. Assigning
will change the name of the first custom column from the default "Column 0" to more appropriate "Close". |
Automatic Analysis |
maxgraph (obsolete) |
specifies maximum number of graphs to be drawn in custom indicator window (default=3) | Indicators |
graphN (obsolete) |
defines the formula for the graph number N (where N is a number 0,1,2,..., maxgraph-1) | Indicators |
graphNname (obsolete) |
defines the name of Nth graph line. This will appear in the title of the chart pane | Indicators |
graphNcolor (obsolete) |
defines the color index of Nth graph line (color indexes are related to the current palette - see Preferences/Color) colorCustom1 = 0 colorBlack = 16 colorDarkRed = 24 colorRed = 32 colorPink = 40 colorRose = 48 |
Indicators |
graphNbarcolor (obsolete) |
defines the array that holds palette indexes for each bar drawn | Indicators |
graphNstyle (obsolete) |
defines the style of Nth graph. Style is defined as a combination (sum) of one or more following flags: styleLine = 1 - normal (line) chart (default) Not all flag combinations make sense, for example (64+1) (candlestick
+ line) will result in candlestick chart (style=64) |
Indicators |
graphNbarcolor (obsolete) |
defines the array of color indexes for the bars and candlesticks in Nth graph ine (color indexes are related to the current palette - see Preferences/Color) | Indicators |
SEE ALSO: