Download as pdf or txt
Download as pdf or txt
You are on page 1of 134

Beginner’s Guide To

AmiBroker
AFL Programming

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
This Book is dedicated to my family members

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Copyright © 2019 by Ajan K K
All rights reserved

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico

AmiBroker is a Trademark of Amibroker.Com


The author has no official partnership with Amibroker.com
Contents

Chapter 0 Before Learning AFL Programming


0.1 An Introduction
0.2 What is Amibroker?
0.3 Is AmiBroker easy to Learn?
0.4 The Formula Editor
0.5 The Guru Chart Commentary
0.6 What Next?

Chapter 1 Introduction to AFL


1.1 Language Basics
1.2 Lexical Elements of AFL
1.3 Operator precedence and parentheses
1.4 Variables and Reserved words

Chapter 2 Array Programming


2.1 Why AFL is called an Array Programming Language
2.2 Array Processing in AFL
2.3 Accessing Array Elements. [ ] The subscript operator
2.4 Population of the built-in Arrays
2.5 Creating Arrays Manually

Chapter 3 Functions in AFL


3.1 What are Functions?
3.2 User defined functions
3.3 AmiBroker Built-in functions
Chapter 4 Branching and Looping in AFL
4.1 Conditional execution
4.2 Nesting of if statements
4.3 Use of Arrays in if statement
4.4 Looping in AFL
4.5 Switch case statement

Chapter 5 Built-in Functions explored


5.1 Introduction
5.2 The Plot( ) function
5.3 ParamColor( ) function
5.4 ParamStyle( ) function
5.5 Param( ) function
5.6 Ref( ) function
5.7 Indicator Functions
5.8 The Cross( ) function
5.9 HHV Highest High Value
5.10 HHVBars bars since highest high over a period
5.11 Highest highest value
5.12 HighestBars bars since highest value
5.13 HighestSince highest value since condition met
5.14 HighestSinceBars bars since highest value since condition met
5.15 BarsSince
5.16 GapDown
5.17 GapUp
Telegram
5.18 Inside
@librosselectosdetrading
5.19 Outside @cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
5.20 BarIndex
5.21 BeginValue
5.22 EndValue
5.23 abs
5.24 ceil
5.25 floor
5.26 int
5.27 frac
5.28 Max and Min
5.29 Round
5.30 Sum
5.31 Cum
5.32 ROC
5.33 ValueWhen
5.34 DayOfWeek
5.35 PlotShapes

Chapter 6 Write Your Own Functions


6.1 User definable functions
6.2 #include

Chapter 7 How to make a Watchlist?


7.1 What is a Watchlist?
7.2 Watchlist Creation
7.3 How to add symbols to an existing Watchlist?
7.4 Create a Watchlist by importing from a text file
7.5 A practical example
Chapter 8 Create Your Own Exploration
8.1 Explore in AmiBroker
8.2 Customized Report Generation

Chapter 9 Back Testing Trading Strategies


9.1 Introducing Trading Strategies
9.2 Amibroker Environment for Back testing
9.3 Reserved variables used in Back testing
9.4 A Back testing example
9.5 Backtester settings from within AFL code

Chapter 10 Revision Exercises

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Chapter 0

Before Learning AFL Programming

0.1 An Introduction
Before we start learning AFL Programming, certain clarifications have to be
made with regard to whom this course is intended for, the prerequisites and
other requirements, if any.
• The material in this book is intended for the absolute beginner in AFL
Programming. Only the most basic aspects of the AFL programming will
be dealt with in this book. Experienced AFL programmers may not get
anything useful in this book.
• This Book will not teach anything about Technical Analysis.
• No attempt is made in this book to teach Trading Strategies, other than
references to it, when coding examples are required to be furnished.
• To learn AFL, you should have AmiBroker installed in your computer.
• This book focuses only on the AFL programming part. The reader should
know how to load charts, place technical indicators on the chart etc.
These are well described in the User Manual of AmiBroker.
0.2 What is AmiBroker?
AmiBroker is a complete and advanced trading solution. It has many tools
including Charting, Backtesting, Optimizing etc. You can display Charts, add
indicators, create watchlists, create trading strategies and back test these
trading strategies, create portfolios etc. It is not a mere Charting tool. Any one
showing interest in AmiBroker, means that he is showing interest in learning
AFL, or at least should know how to use AFL developed by else body in their
trading decisions.
0.3 Is AFL easy to learn?
This is a difficult question to answer. The answer is Yes and No. If you are
already proficient in any other programming language, then there should not
be any problem. It will be a matter of learning the syntax, keywords, functions
etc of the new language. Everything should follow very easily. Otherwise, it
should take time – there is a learning curve. The fact about AFL is that it is
not a general purpose language like C, Pascal, Python etc but only specific to
deal with financial quotation values like Close, Open, High, Low, Volume,
Open Interest etc. And that AFL is an array programming, will make some
confusions to the beginner programmer. Soon the understanding that an
Array is something like the English collective nouns such as a “Library” of
books, a “Herd” of cattle, a “Panel” of experts etc. will relieve many of the
doubts. An Array is simply a collection of numbers. Anything we do on the
Array means, we are intending to do on every member of the Array and not on
any single member of the Array. By studying carefully and doing lot of
exercises, any beginner can learn AFL and master it.
0.4 The Formula Editor
The program statements are entered into an Editor called the Formula Editor.
Like all Windows programs, Amibroker provides multiple ways of launching
the Formula Editor. From the Analysis Menu Click the Formula Editor…. This
will open the Formula Editor as shown below.

Fig 0.1 Formula Editor


It contains a blank Editor where the formula can be entered. See Fig 0.2
below.

Fig. 0.2 Editor Options


There are three editor options wherein we can check the syntax of the
formula, apply the indicator to the chart pane and send the formula to
Analysis window. After entering the formula to the Editor screen, click the
Verify syntax icon. If the formula is violating the syntax of the language,
errors will be reported. We can proceed further only after rectifying the errors.
An indicator formula or any other formula for plotting can be applied to the
charting pane by clicking the Apply to icon. The Analysis window is used for
Scanning, Exploration and Back testing etc. There is a faster way of
launching the Formula Editor. See Fig. 0.3 below.

Fig.0.3 Launch Formula Editor


From the Speed Bar buttons displayed, click on the Wrench like icon, to
launch the Formula Editor.
0.5 The Guru Chart Commentary
This is where we learn and test most of our formulas. There are two ways of
launching the Chart Commentary. From Analysis Menu click Commentary…
or click on the Speed button icon shown in Fig. 0.3 above. Either way, it opens
the Commentary window as shown below.

Fig. 0.4 Chart Commentary window


It has two tabs, Formula and Commentary. The formula can be entered in the
Formula window screen, and the results can be displayed by clicking the
Commentary tab. Write our first program now. In the Formula editor screen
type the following one line program.
printf( “Hello, World.” );
Now click the Commentary tab to see the result as shown below.

Fig. 0.5 Hello World

We have successfully written our first AFL program. The formula contains only
one statement, that is a printf( ) function. We will be using the printf function
quite often from next Chapter onward. So let’s look at the function carefully.
The printf function is used to print formatted output to the output window.
Syntax:
printf( formatstr,…)
The printf function formats and prints a series of characters and values to the
output window. If arguments follow the format string, the format string must
contain specifications that determine the output format for the arguments.
• For numbers use %f or %g formatting.
• To print a single % sign use %% in the format string.
• A \n placed inside the format string will print a new line.
Example of a format string:
“The scores in Math AND History of the student are \n%g\n%g”, 78,89
Whatever within the inverted commas is the format string. 78 and 89 are two
arguments that follow the format string. The number of arguments should
match the number of %g specifications inside the format string. The two \n
are given for printing new lines. Place the above format string inside a printf
statement in the Formula window of Chart commentary. Click the
Commentary tab to see the results as shown in Fig. 0.6 below.
Fig. 0.6 Commentary results
To display a decimal number use the %f format. The total width and number
of decimals to be printed can be specified by %w.d f format, where w will be
the total width used for printing the number and d will be the number of digits
after the decimal point.
printf( “%5.2 f”, 25.6462);
The above printf statement results in the following value.
25.65
Note to accommodate a total width of 5, first a space is printed and the
number is rounded and not truncated.
Exercise 0.1
1. What will be the output of the following printf?
printf( “%g,%3.1f”,8,6.2 );
a) 8 6.2
b) 8,6.2
c) 8 6.20
Solution:
b)8,6.2 Whatever string placed within the quotes will be printed out. The
comma ( , ) is necessarily to be printed.
2. Write a printf formula to get the following output.
My Name is :
Jacob
Solution:
printf( “My Name is : \nJacob”);
3. Which formula gives the correct output as given below.
Your percentage score is 50.6%
a) printf( “Your percentage score is %4.1f%%”,50.6);
b) printf( “Your percentage score is %5.1%”,50.6);
c) printf(“ Your percentage score is %5.1%%”,50.6);
Solution:
The correct answer is a). Though the third option c) looks correct, it will print
an extra space between ‘is’ and 50.6.
4. What is wrong with the following printf statement?.
printf( “Heights of the athletes are %4.1f,%4.1f,%4.1f”,6.3,6.1);
Solution:
Three format specifications given. But only two arguments are provided. These
have to be matched. This will report an error.

0.6 What Next?


From the next Chapter onward we are beginning to learn programming AFL.
While learning programming, try to do as many problems as possible. This is
perhaps the only way to improve the skills in programming. Note that this
book is not a replacement for the AmiBroker User Manual. The Manual is the
ultimate source of information about AmiBroker.

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Chapter 1

Introduction to AFL

1.1 Language Basics

To learn a new language we must first study its basic building blocks.
Assume that we want to learn the English language. How do we start our
study? We start our learning process by first studying the alphabets of the
language. Simple and common words are then grasped – spelling and
meaning. By using the essential building blocks of words, we learn to make
meaningful sentences. The process continues by learning preposition,
adjectives, nouns, verbs, subject, predicate and the grammar of the language.

When learning a computer programming language like the AFL, the idea to
start is the same. We start studying the building blocks of the language. From
elementary building blocks we make complete sentences (in this case
statements). The syntax of the language is then mastered. So what are the
essential building blocks of the AFL? Let us explore it.

1.2 Lexical Elements of AFL

Word-like units used and recognized by the AFL interpreter are known as
Tokens. There are different categories of tokens used in AFL. We can
consider these tokens as the basic building blocks of AFL.
There are five classes of tokens in AFL:
 Identifiers
 Constants
 String literals
 Operators
 Punctuators (also known as separators)

Identifiers

When a child is born, one of the few things we immediately do is to find out a
suitable name for him/her. Why? This is because without a name he or she
loses his/her identity. This is exactly the case with words used in AFL. Those
have to be appropriately named. These are known as identifiers. Identifiers
are the names given to variables and functions used in AFL. There are certain
rules to coin an identifier:
 Identifiers can be of any length
 Identifiers can contain upper case or lower case letters of the alphabet
(a-z, A-Z)
 It can contain the underscore character (“_”)
 It can contain the digits (0-9)
 The first character must be a letter

It is important to note that AFL identifiers are not case sensitive.


Though identifiers can be of any length, very long names will be difficult to
remember and type into the editor. Always try to give meaningful names to
identifiers. That is if an identifier is desired to hold the balance amount in the
account, then give a name as accountBalance rather than xyz12 etc.

Exercise 1.1

Which are legal names for identifiers?


1. myName
2. 1accountHolder
3. MonthlyExpense
4. Account_balance
5. _totalSum
6. BILLTOTAL
7. $125
8. U_24587
9. myMovAverage

Solution:
1. Legal name
2. Illegal. Cannot start with a number. First character must be a letter.
3. Valid name
4. Legally acceptable
5. Illegal. Must start with a character
6. Acceptable name
7. Special characters are not allowed. First character must be a letter.
8. Acceptable name
9. Legal name.
Constants

Tokens representing fixed numeric or character values are known as


Constants. A numeric constant is a number. It can optionally consist of a
decimal point and a decimal fractional part. Negative numeric constant is
prefixed by a unary minus (-) sign.

Examples of numeric constants:


125
8.56
-12
-2.02

String Literals

String Literals or string constants are written as a sequence of any number of


characters surrounded by double quotes.

Examples of string constants:

“This is a String Constant”


“Account Closed”
“Balance Amount is: “
An empty (null) string is written as “”.

Let us see a practical example of using Constants and String Literals and use
of identifiers. Suppose that during the course of a computer program, the
account in your balance is found to be Rs. 1200. You want to assign this
constant value to an identifier and wish to be notified. The following program
segment serves the purpose.

myBalance = 1200;
notify = “Your balance is: “;
printf( notify + NumToStr( myBalance ) );

In the above code fragment myBalance and notify are named identifiers.
myBalance is assigned the numeric constant 1200. Thereafter the identifier
myBalance holds the number value 1200. The second identifier notify is
assigned a String Literal “Your balance is: “. The printf () command combines
the two identifiers and the following result is printed.
Your balance is 1200

The above program is now shown just to explain how constants and identifiers
are used in an AFL. In an actual program myBalance will not be assigned this
way, but will be computed and the result of that computation will be assigned
to myBalance.

Punctuator

The following are the punctuators (also known as separator) available in AFL:
 Parentheses
The open parenthesis ‘(‘ and the close parenthesis ‘)’ are always used
together. They are used for grouping expressions, isolate conditional
expressions and to indicate function calls and parameters. Parentheses
are also used to override normal precedence in calculations.

Examples:

Y=x*5+6

Here x is first multiplied by 5 and the result is get added to 6.


But the user had intended 5 to be added to 6 and the result to be
multiplied by x.
To override the normal precedence parentheses can be used as below.
Y = x * (5 + 6)

myFunc()

Here myFunc is a function call. To identify this as a function the


parentheses are necessary. Functions are described later in the book.

 Comma ,

Functions in AFL may have arguments in its definition. The purpose of


comma (,) in AFL is to separate the argument list in functions.

Examples:

HHV (array, periods)


Here HHV() is the function. The arguments of the function are array and
periods. The comma is used to separate the arguments. The use of
function HHV is described later in the book.

CategorySetName( name, category, number )

Here the function CategorySetName() has three arguments separated by


commas.

 Semicolon ;

In AFL every statement is terminated by a semicolon. If you miss a


semicolon after writing a statement, the interpreter will throw an error.

An expression is a combination of values, variables and operators. A


value all by itself is considered an expression and so is a variable. So
the following are all legal expressions.
17
X
X + 20
(Assume that X has been assigned a value already.)

Any legal AFL expression followed by a semicolon is interpreted as a


statement. This is known as an expression statement.

Example:

myAverage = MA(Close,20);

 Equal Sign =

The equal sign (=) separates variable declarations from its initialization.

X = 20;

In the above example the value 20 is assigned to the variable X and the
equal sign separates the variable declaration and its initialization.
There are numerous built in functions in AFL. Some of these functions
use default values of parameters provided within the function. The
equal sign is used to furnish default values of arguments in functions.
Example:

Macd(fast = 12, slow = 26)

The macd() function uses two parameters, fast and slow. If nothing is
specified by the user, the default values will be fast = 12 and slow = 26.

 The dot .

The dot is a member access operator. We will not be using this in this
book.

Operators

There are six types of operators available in AFL.


 Assignment Operators
 Comparison Operators
 Arithmetic Operators
 Logical Operators
 Compound Assignment Operators
 typeof() Operator

Assignment Operator

= is the assignment operator. Its purpose is to assign a value to the identifier


on the left of the assignment operator =.

result = expression;

Here, ‘result’ is the variable and the value of expression is assigned to the
variable.

Examples:

Total = 5 + 3 + 9;

The sum of the right side of = sign is computed and the value 17 is assigned to
the variable Total.
Heading = “Values are computed as shown”;

The text string on the right of assignment operator is assigned to the variable
Heading.

longMA = MA(Close, 50);

An array of values is calculated on the right hand side and the result is
assigned to the variable longMA. The Moving Average Function MA() will be
discussed later.
In AFL multiple assignments are possible using =.

A = B = C = 52;

Here A,B and C all are assigned the value of 52.

Comparison Operator

Comparison Operators can be of two types - Relational or Equality. The


results of comparison operator will be either a true (1) or false (0).
Equality operator can be either Equal To ( == ) or Not Equal To ( != ).
Relation operators can be the following types:

Symbol Meaning
< Less Than
> Greater than
<= Less than or equal to
>= Greater than or equal to

CAUTION: Do not confuse with the assignment operator ( = ) with equality


check ( == ). This is a common cause of error.

Arithmetic Operator

The following are the arithmetic operators which can be used in formulas.

Symbol Meaning
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulus ( or remainder)
^ Exponentiation
| Bitwise OR
& Bitwise AND

The use of arithmetic operators are illustrated below,


x = 12;
y = 5;
z = x + y;
m = x*y;
p = x/y;
q = x^y;

Logical Operators

There are three logical operators namely AND, NOT and OR.
Symbol Meaning
NOT Logical ‘NOT’. Gives ‘true’ when operated on a ‘false’
value and ‘false’ when operated on a ‘true’ value.
AND Gives ‘true’ only when both operands are ‘true’
OR Gives ‘false’ only when both operands are ‘false’

If a formula requires multiple conditions then logical operators can be used.


Suppose we want to make a condition to buy a security when its close price is
greater than the 50 day moving average and at the same time the 14 day
relative strength is greater than 70, we can frame the condition as:
Condition = (Close > MA(Close,50) ) AND ( RSI(14) > 70 );
Here AND is used to combine the two required conditions and the variable
‘Condition’ on the left side will be ‘true’ only when both the conditions on the
right side become ‘true’.

Compound Assignment Operators

Compound operators are specified in the following form:

destinvar op = expr;
where destinvar is the variable, expr is the expression and op is one of the
following arithmetic operators: +,-,*,/,%,&,|
The destinvar op = expr behaves as destinvar = destinvar op expr.

Example:

total = total +2;


The value of total is incremented by two. This can be written as:
total += 2;

Typeof() operator

It is used in the form of typeof(operand). The typeof() operator operates on an


operand and returns a string indicating the type of the operand. The operand
can be string, variable, function identifier or object for which the type is to be
identified. In the case of identifier, it should be supplied alone without
arithmetic operators, without extra arguments and without parentheses. To
determine the type of the value returned by a function, first assign the
function to a variable and determine the type of the variable.
Possible return values of typeof():
Return value Description
Undefined Identifier not defined
Number Operand represents a number (scalar)
array Operand represents an array
String Operand represents a string
function Operand is a built-in function identifier
User function Operand is a user defined function
Object Operand is represents COM object
member Operand represents member function or COM
object property
handle Operand represents Windows handle

Example:

//Code fragments
X = MACD(); //MACD() is a built-in function which returns an array
Y = LastValue(Close);
//LastValue() returns a scalar, in this case the last period Close Price value
/* A user defined function follows. More about user defined functions in later
Parts of the book
*/
function myFunc()
{
Return 1;
}

With the above code fragments in mind the typeof() operator can be tested.

typeof(X) ; This returns ‘array’


typeof(MACD); This returns ‘function’
typeof(Y); This returns ‘number’
typeof(myFunc); This returns ‘user function’
typeof (“My Name”); This returns ‘string’
typeof(45.23); This returns ‘number’
typeof(Var1); This returns ‘undefined’ . The variable Var1 is not defined.

Launch the Guru Commentary in AmiBroker. It has two tabs Commentary


and Formula. Click the Formula tab and enter the code fragments shown
above. When completed it looks like the figure below.

Fig. 1.1

Now click the Commentary tab to see the results of the typeof() operators used
in the formula window. Fig1.2 below shows the results.
Fig. 1.2

Note that when the type of MACD() is checked only the name MACD is used
without parenthesis. Otherwise it will report an error.

Whitespace

Spaces (blanks), tabs, new line characters and comments are collectively
called whitespace. They can serve to indicate where tokens start and end.
Whitespaces are ignored by the interpreter.

Comments

Comments are pieces of text to annotate a program. These are for the use of
the programmer or anybody else who happen to read the program. The
comments are stripped from the source code while interpreting and parsing.
Two ways of commenting in AFL are:

1. Using two forward slashes //


This is the C++ style of commenting. Anything written after // will be
ignored by the interpreter. It can be placed anywhere in a line.

Examples:

//A comment is placed before the start of a program


X = 5; //A value of 5 is assigned to the variable X
2. Another way of commenting is using a pair of /* and closed by */.
Everything written within the pair are treated as comments and ignored by
the interpreter.

Example:
/*
All the comments can be
placed here……...

*/

1.3 Operator precedence and parentheses

AFL supports parentheses in formulas. Operation precedence is the order in


which the operators are used in calculations. In calculations higher
precedence order operator is used first, next lower is used next etc. However
parentheses can be used to control this precedence. AFL always does the
operations in the innermost parentheses first. When parentheses are not
provided, the precedence is as follows (higher precedence listed first).

No Symbol Meaning
1 ++ Post increment/pre increment
2 -- Post decrement/pre decrement
3 [] Array element (subscript) operator
4 ^ Exponentiation
5 - Negation – Unary minus
6 * Multiplication
7 / Division
8 % Remainder
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. The equality check
16 != Not equal to
17 & Bit-wise AND
18 | Bit-wise OR
19 NOT Logical NOT
20 AND Logical AND
21 OR Logical OR
22 = Variable assignment operator
23 Compound assignment operators

Exercise 1.2

1. Find out the value of the following expression


5 + 6*8
Solution:
53. Since multiplication has higher precedence than addition, 6*8 is first
evaluated and then the result is added to 5.

2. 7*9 – 24/6*2 + (6-3). Evaluate this expression


Solution:
58. Here the division / and multiplication * has same precedence. Since /
came first 24/6 was first calculated and the result is multiplied by 2 to
get a value of 8. The resulting expression becomes 63 – 8 + 3 giving a
value of 58. If the intention was to multiply 6 by 2 and the result is
required to divide 24 we should have explicitly used parentheses as below.
7*9 – 24/(6*2) + (6 – 3)
In this case the value would be 64.

3. Find out the value of 78 + 7*3^2 – 6*2 – 5


Solution:
124. Here ^ has top priority and hence 7 to be multiplied by 3^2 which is
9. The expression becomes 78 + 63 -12 -5 resulting a value of 124.
4. What will be the value of 24/6/2?
Solution:
This evaluate to 2, since for similar operators, the precedence is from left to
right.
5. Put parentheses in the following expression to give a value of 3.
5 – 4*3 /2 - 1
Solution:
(5–4)*3/(2–1)
6. Check whether the following statements are True or False.
a) ( True OR False ) AND True
b) ( False AND True ) OR False
c) ( False AND False ) AND ( True OR False )
d) ( True OR ( False AND True )) OR ( True OR True )
Solution:
a) True
b) False
c) False
d) True

1.4 Variables and Reserved Variables

A variable is an identifier that is assigned to an expression or a constant. The


number of variables that can be used in AFL is unlimited. Variables must be
assigned before it is used in a formula. Or an error will be thrown. Variables
cannot be assigned within a function call. Naming conventions of a variable
are already discussed.
User defined variables cannot be same as built-in function names such as ma,
ema, iif, macd, rsi etc. or they cannot be same as predefined array identifiers
such as O, H, L, C, Open, High, Low, Close etc. While naming variables avoid
these names.
AmiBroker uses some reserved variable names in its formulas such as Buy
and Sell. These reserved variable names should be used only for the intended
purpose and should never be used to name user defined variables. Some
common reserved variables and their use are given in the table below. For an
exclusive list see the AmiBroker User Manual.

Variable Usage
buy Defines “buy”(enter long position) trading rule
sell Defines “sell”(close long position) trading rule
short Defines “short”(short sell ) trading rule
cover Defines “cover”(close short position) trading rule
buyprice Defines buying price array
sellprice Defines selling price array
coverprice Defines buy to cover price array
filter Used in exploration. Controls which symbols/quotes
are accepted

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Chapter 2

Array Programming
2.1 Why AFL is called an array programming Language?

One of the most important aspects of AFL is that it is an array programming


language. It operates on arrays of values.

What is an Array?

An array (or Vector) is simply a list of numbers. Suppose an array A has the
following values in the list 2,6,8,5. Then a mathematical operation like A*2
will change all the values in the original array to be multiplied by 2 and the
result will be a new array with values 4,12,16,10. Similarly if B is an array
with values 4,5,3,2 then the operation A + B yield a new array with values of
the corresponding array values added together and the result is an array with
values 6,11,11,7.

In AFL there are some predefined built-in price array identifiers. They identify
specific price fields that the formula can operate on. The valid price array
identifiers are open, high, low, close, volume, openint, avg. These
identifiers can be abbreviated as shown in the table below. For example for
identifying the open price we can either use open or O. Also note that the
identifiers are not case sensitive. That is high and HIGH both refer to the same
array.
Long Name Abbreviation

Open O

High H

Low L

Close C

Volume V

OpenInt OI

Avg <no abbreviation>


Avg, sometimes called the typical price is calculated as (High+Low+Close)/3.
OpenInt is the total number of outstanding shares and is used in Futures
trading.

An AFL array can be thought of as a list of numbers with one number in the
list for each bar in the currently selected chart. What these numbers depends
on the type of Array. For example for the Open array, each number represents
the opening price of the corresponding bar in the chart. MA(Close,50) array
represents the calculated value of moving average for 50 periods for each
corresponding bar. In this case the first 49 bars will not be having moving
average values due to insufficient data, and these will be filled with ‘Null’.
Example of use of price identifiers in AFL formulas:

MA( Close, 10 );

MA is a built-in function to calculate the moving average. Close is the close


price array. The parameter 10 is the look back period. The above statement
calculates the 10 period moving average of the Close price Array and the
result will be another array. We can assign this to a variable like ma10.

ma10 = MA( Close, 10 );

Now the variable ma10 will be an array, and it will have Barcount number of
elements, just as the case of Close. However, the first 10 elements of ma10
will not have values, and will be filled with “NULL”. When we plot ma10 on the
chart, the plot starts only from the 11th element onward.

2.2 Array Processing in AFL

The array processing in AFL is very fast. Unlike in other conventional


programming languages there is no separate code required for processing the
array elements. The processing in AFL is just one go, like the processing in the
case of scalars. Consider the following statement:

midValue = (High+Low)/2;

When AFL is evaluating statements like the above one, it does not need to re-
interpret the code for each bar. Instead it takes the High array and Low array
and adds corresponding array elements in single stage. Then the resulting
array elements are divided by 2 in single stage. The resulting array is stored
in the variable midValue.

Visualizing Array elements as a row of cells

Let us visualize the Close array as a row of cells. Let there be 7 elements in
the array. The row index starts from 0 and the last index will be 6 and not 7.
Here is how the Close array looks like:

Index 0 1 2 3 4 5 6
Close 10.5 10.6 10.6 10.4 10.3 10.5 10.6

Each index corresponds to a trading bar. It can be daily, 5 minute, 1 hour,


weekly etc.
Operations whether they are mathematical or relational are performed on each
index.
Suppose we add two arrays and assign it to a variable:
A = Open + Close;

Index 0 1 2 3 4 5 6

Close 10.5 10.6 10.6 10.4 10.3 10.5 10.6

Open 10.4 10.5 10.3 10.2 10.5 10.6 10.4

A 20.9 21.1 20.9 20.6 20.8 21.1 21

For each index the value of open and close will be added and a new array
created will be assigned to the variable A.

Another example:

upDay = Close > Open;

Index 0 1 2 3 4 5 6

Close 10.5 10.6 10.6 10.4 10.3 10.5 10.6

Open 10.4 10.5 10.3 10.2 10.5 10.6 10.4

upDay True True True True False False True


Depending upon the values of close and open the upDay array is properly
flagged as True(1) or False(0).

2.3 Accessing Array elements. [ ] - The subscript operator

Each individual array element can be accessed by the subscript operator [ ], a


pair of square brackets. The format is:

arrayidentifier[ expression ];

The expression must be evaluated to a number. It represents the value of the


expression-th element of the array.
In the table given above, Close[0] will be 10.5, Open[2] will be 10.3, upDay[6]
will be ‘True’.

BarCount is a constant in AFL which gives the number of bars in the


predefined arrays like Open, High, Low, Close, Volume etc. The array elements
are numbered from 0 (zero) to BarCount – 1. Hence to get the last bar of an
array we have to use array[BarCount – 1]. The name BarCount should not be
used as user defined variables.

2.4 Population of the built-in price arrays

The price arrays O, H, L, C etc. are populated from the data of the selected
(displayed) chart. As we change the symbols of stocks and hence the displayed
chart, the array values get changed. How many elements will be in the above
arrays? There will be BarCount number of elements in the arrays. Open the
Guru commentary and type the following code to print the barcount value of
the displayed chart.

Fig 2.1 Fig 2.2


Fig 2.1 shows the code for displaying the barcount value. The commentary
window in Fig 2.2 shows the value of the result displayed. Try changing the
selected chart and see how the result of barcount varies.

2.5 Creating Arrays manually

Arrays can be created manually by the following method. Let the array to be
created be named A. We then assign elements of the array using the subscript
operator.

A[0] = 2;
A[1] = 3;
A[2] = 5;
A[3] = 7;
A[4] = 6;

The above assignments creates an array A having its first five elements
assigned as above. Does it mean that the array A have only 5 elements? No,
it will have barcount number of elements and barcount will depend on the
number of bars in the selected chart which is currently displayed. The
Typeof(A) will be ‘array’. Now if we multiply A by 2, each element of A will be
multiplied by 2.

B = A*2;

Typeof(B) will be ‘array’.


B[0] will be 4, B[1] will be 6, B[2] will be 10, B[3] will be 14, and B[4] will be
12.

Let C = A + B;

A new array C is created with each element becoming the sum of


corresponding elements of A and B. C[0] will be 6, C[1] will be 9 etc.

Launch the Guru commentary to verify the above examples.

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Fig. 2.3

Fig 2.4

Fig 2.3 and Fig 2.4 shows the formula and commentary windows for the
example above.

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Exercise 2:

1. With the value of Array C as computed above what will be the return of
typeof(C)?
a. Array
b. Number
c. Undefined
2. What will be the typeof(C[1])?
a. Undefined
b. Number
c. An error is reported
3. Let O[0],O[1],O[2],O[3] respectively be 455,456,455.6 and 457 and C[0],
C[1], C[2], C[3] respectively be 455.7,455.5.456 and 457.3. What will be the
values of the first 4 elements of the array C > O?
4. On a particular day the Close price is 45.6, Open price is 44.8, Low price is
44.2 and High price is 45.1. Which of the following give a True value?
a) C > O
b) O < L
c) H > C
5. Calculate range of the current trading day.

Solution:

1. Answer a. C will be an array.


2. Answer c. Here an error will be reported. You cannot pass subscripts,
parentheses etc. to the typeof operator. The correct method of getting t
he type of C[1] is to assign this to a variable first, and determine
the type of this variable.
U = C[1];
Typeof(U);
This will return ‘number’. Every individual element of array C is a
number.
3. C > O is a logical operation. It is asking whether Close is greater than
Open. The result will be either a True or False. A true is represented by
1 and a false is represented by 0. So the array C>O will have its first 4
elements will have values 1, 0, 1, and 1.
4. a and c will give True, while b will be False

5. The range is defined as the difference between the high price and the
low price. The answer is H – L.
Chapter 3

Functions in AFL
3.1 What are Functions?

In programming, a function is a named sequence of statements that perform a


desired task. When we define a function we specify the name of the function
and the sequence of statements. Later we can call the function by its ‘name’
any number of times.
Functions in AmiBroker can be user definable or built-in. We have already
seen many built-in functions in the previous chapters.

3.2 User defined functions

User definable functions allow to encapsulate user code into easy to use
modules that can be used in many places without need to copy the same code
over and over again. All functions must have a definition. The function
definition includes the function body, the code that executes when the
function is called. The syntax for function definition is given below:

function function_name ( [parameter,…] )


{
Statements;

[return result;]
}

A function definition starts with the keyword function followed by the


function name. The opening and closing parentheses are necessary. Included
within the parentheses optional parameter list can be given. The body of the
function is defined within the curly braces. A function should return a result
to the calling program. The return is a keyword. Where the return is not
used, it is not called a function but a procedure. The square brackets indicates
optional parameters. The square brackets themselves are not part of the
function definition.
Examples:

1. Write a function to find the sum of two numbers

Solution:

function findSum ( num1, num2 )


{
total = num1 + num2;
return total;

Here findSum is the name of the function. Two parameters are num1
and num2. Within the body of the function these are added and
returned. We can call the function such as:
Y = findSum( 5, 8 ); // Gives a value of 13 and assigned to Y

2. Write a function to check if a candle is green/white.

Solution:

function isGreenCandle ( )
{
result = iif( C > O, True, False );
return result;

Scope of variables in functions

Unlike some other common programming languages, AFL does not require
variables and its type to be declared ahead of its use. A variable is treated as
local or global depending upon where it is first used. If a given variable first
appears outside a function, it is treated as a global variable. If a variable first
appears inside a function then it is treated as a local variable and confined
within the function. If you try to access the variable outside the function an
error is reported.
function test(a,b)
{
x = 20;
return (a+b);
}
printf(“%g”,x);

In the above program variable x is initialized inside the function and hence is
a local variable. Trying to print x outside the function throws an error as
shown below.

Error in AFL formula:


x = 20;
return (a+b);
}
printf("%g",x)
-------------^
File: '___COMMENTARY___', Ln: 6, Col: 14
Error 29.
Variable 'x' used without having been initialized.

The default behavior of local and global variables can be overridden by global
and local keywords.

3.3 AmiBroker Built-in Functions

AmiBroker houses a large collection of built-in functions. They can be


classified into various categories:
• Basic Price Pattern detection
• Composites
• Date/Time
• Indicators
• Information / Categories
• Lowest/Highest
• Math functions
• Miscellaneous functions
• Moving averages, summation
• Statistical functions
• String manipulation
• Trading system toolbox
• Exploration / Indicators
• File input/output functions
• Low level graphics
• Referencing other symbol data
• Time Frame functions

Without understanding the AFL built-in functions, no useful formula can be


written. The reader is advised to refer to the User Guide to get familiar with
the functions, its syntax, usage and examples. Practicing with the examples
given will give confidence to write your own codes and apply into scans, back
testing etc. In Chapter 5, we will discuss some of the commonly used
functions and see many examples of using them in practical situations.

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Chapter 4

Branching and Looping in AFL

4.1 Conditional execution

The code fragments we saw till now, starts execution at the top of the
program, goes in a progressive manner sequentially until the end of the
program. Real life problems cannot always be made to program this way.
Depending on situations that may come on the way, some sequential breaks
will have to be made. Sometimes certain parts of the program will have to be
executed a certain number of times. The conditional execution of a program
need the ability to check conditions and change the behavior of the program
accordingly. Conditional statements give us this ability.

The if else statement in AFL is used for conditional execution. The if and else
are keywords in AFL and should not be used for variable names.

The syntax of if else statement is:


if (expression)
statement1
[else
statement2]

The expression is evaluated first. If the expression is true( nonzero ), the if


executes statement1. When else is present, and the expression evaluates to
false, the statement2 is executed. After executing statement1 or statement2,
the control transfers to the next statement.

The else statement2 is indicated in square brackets to show that it is optional.


The if logic can be shown as a flow chart.

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Fig. 4.1 If Logic

The if statement executes statement1 only if the expression evaluates to true.


Otherwise it skips the statement1 group. The flow chart for if else is given
below in Fig. 4.2.

Fig. 4.2 If else logic

Either statement1 or statement2 is executed depending upon the value of


expression. The statement1 and statement2 can be single statements or
compound statements within curly braces { }.

Examples:

1. //Example for simple if statement


X = 4;
If ( x < 10 )
printif( “x is lesser then 10.”);
2. i = 0;
x = 50;
if ( i != 0 )
printf( “Division is permitted”);

else
printf(“Division by zero not allowed”);

3. Write a code to check a variable is even or odd. (Hint: A number is even if


it leaves a remainder of zero when divided by 2.)

if ( p%2 == 0 )
printf(“p is even”);
else
printf(“p is odd”);

4.2 Nesting of if statements

if statements can be nested in the following way.

if (expression1)
statements1
if (expression2)
Statements2
else
Statements3

Using braces will give nesting apparent.

if (i>0)
{
if (j > i )
x = j;
}
else
x = i;

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
4.3 Use of Arrays in if statement.

You cannot use Arrays directly in if statements. The following code will not
work but throws an error.

if ( Close > Open)


printf(“Green Candle”);

But the following code works and checks whether the last candle close was
greater than the open.

If ( Close [barcount -1] > Open [barcount – 1] )


printf( “ Closed above open”);

Exercise 4.1

1. Write a program to compute the maximum between two numbers.

Solution:

//num1 and num2 be the two numbers to be compared


if ( num1 > num2 )
printf( “num1 is maximum);
else
{
if ( num2 > num1 )
printf(“num2 is maximum”);
else
printf(“Both are equal”);
}
2. Write a program to find whether a given year is leap year or not.

Solution:

A year will be leap year if it is divisible by 4, not divisible by 100 and


divisible by 400. Otherwise it is not a leap year.

//Leap year check. Let year_check be the variable holding the year to be
//checked
if ( year_check%400 == 0 )
printf( “Is a leap year”);
else
if ( year_check%100 == 0)
printf( “Is not a leap year”);
else
if ( year_check%4 == 0)
printf( “Is leap year”);
else
printf(“Not a leap lear”);

4.4 Looping in AFL

There are three ways you can make loops in AmiBroker.

• do
• while
• for

The above statements can be used for doing repetitive tasks.

while statement

The while statement lets you repeat a statement until a specified expression
becomes false.

Syntax

while ( expression ) statement.

The expression must be arithmetic (numeric/Boolean type). The execution


proceeds as follows:

1. The expression is evaluated.


2. If the expression is evaluated to true, control transfers to the statement
(it can be compound statement included in curly braces.). Control then
transfers to expression for further evaluation. The statement is again
executed if expression becomes true. The process is continued until the
expression evaluates to false, when control transfers to the next
statement in the program.
3. If the expression is initially false, the statement gets never executed.
4. If the expression is a nonzero constant, the statement gets executed
indefinitely, an undesired trap.
5. There must be a mechanism in the statement portion to make the
expression false within a finite time.

The logic of while statement can be flow-charted as below.

Fig. 4.3 The while logic

Examples:

1. Write a program to print numbers 1 to 5 sequentially.

Solution:

number = 1;
while ( number <= 5 )
{
printf(“%g\n”,number); //%g is the format specifier for printing a
// number ( %d will not work).
number++; //This equivalent to number = number +1
}
Result:
1
2
3
4
5
2. Write a program to print only the even numbers between 1 and 50.

Solution:

i = 1;
while ( i <= 50 )
{
if ( i%2 == 0 ) //Here we check for even number
printf( “%g “,i); //Print only the even number
i++;
}

Result:

2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48
50

do-while statement

The do-while statement lets you repeat a statement or compound statement until
a specified expression becomes false.

Syntax:

do statement while (expression)

The expression is evaluated after the body of the statement. Therefor the
statement is always executed at least once.
The logic proceeds as follows:

1. The statement body is executed first. This is irrespective of the


value of expression.
2. Next the expression is evaluated. If expression is false, the do-
while statement terminates and control passes to the next
statement in the program. If expression is true, the process is
repeated with step 1.
The flow chart of do-while logic can be shown as:

Fig. 4.4 The do-while logic


Examples:

1. x = 10;
do
{
printf(“%g “,x);
x = x -2;
}
while ( x > 0 );

Result: 10 8 6 4 2

Printf statement prints the initial value of x which is 10. The x value is
then decremented by 2. Expression is then tested and since it becomes
true the statements are executed to print value 8, and the process
repeated up to 2. Thereafter the do-while statement terminates.

2. Write a program to find the sum of first 100 natural numbers. That is,
we are required to find 1+2+3+4+………………+99+100.

We will start with two variables i and total. Initialize i to be 1 and total
to 0.
//Sum to the first 100 natural numbers. Demo of do-while loop
i = 1;
total = 0;
do
{
total = total +i;
i++;
}
while ( i <= 100 );

The counter i will be incremented and added to total until i equals 100.
You should get a result of 5050. The Guru Chart commentary for this is
shown in Fig. 4.5 and Fig. 4.6.

Fig. 4.5 Formula

Fig. 4.6 Result

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
for statement

The for statement lets you repeat a statement/compound statement a specified


number of times. The body of a for statement is executed zero or more times
until an optional condition becomes false.

Syntax:

for ( init-expression ; cond-expression ; loop-expression ) statement

The execution process is as below.


1. The init-expression is evaluated. This specifies the initialization for the
loop. There is no restriction on the type of init-expression.
2. The cond-expression is evaluated. If it is true the statement is executed.
Then loop-expression is evaluated.
3. The iteration continues until cond-expression becomes false.

A flow chart description of for statement is given below.

Fig. 4.7 The for statement logic

Examples:

1. A simple example of for loop is given below.


for ( i = 0; i < 6; i++ )
{
printf( “%g “,i );
}
Result: 0 1 2 3 4 5

2. Build a 5 period simple moving average of Close price using for loop.

There is a built-in function in AFL to find the simple moving average.


MA(Close,5) gives the 5 period simple moving average directly. This
example is for practicing the for loop. We can make an array by simply
assigning values to its subscripted elements. For example:

MyAverage[0] =4;
MyAverage[1] =5;
MyAverage[2] = 6;
MyAverage[3] = 2;
MyAverage[4] = 9;

The above statements creates an array MyAverage having its first five
elements assigned the above values. Typeof(MyAverage) will be ‘array’.
The 5 period moving average of Close is the average of the sum of the prior
5 Closes. Since 5 values are required to calculate 5 period moving average, the
first 4 elements of close will not be having the average due to insufficient data.
The first 4 values of moving average array will be Null. If we have a counter
variable i, MyAverage can be generalized as:
MyAverage[i] = ( Close[i] + Close[i-1] + Close[i-2] + Close[i-3] + Close[i-4] )/5.
Note that the array indexes start from zero. We can let i start from 4 and
iterate the calculation until (Barcount -1) which will be the last element of the
Close array. This will make the MyAverage Array.

//MyAverage – 5 period simple moving average of Close


Period = 5;
for ( i = Period-1; i < barcount; i++)
{
MyAverage[i] = ( Close[ i ] + Close[ i-1 ] + Close[ i-2 ] + Close[ i-3 ] +
Close[ i-4 ] )/Period;
}
4.5 switch case statement

A switch case statement allows a variable to be tested for equality against a


list of values. Each value is called a case and the variable being switched on
is checked for each switch case.

Syntax:

switch (expression)
{
case constant-expression1:
Statements;
case constant-expression2:
statements;
……
……
case constant-expressionN:
statements;
default:
statements;
}

Control passes to the statements whose case constant-expression matches the


value of the switch expression. Execution of the statement body begins at the
selected statement and proceeds until the end of the body or until a break
statement transfer control out of the body. The default statement is executed
if no matching of expressions occurs. The default statement is optional. There
can be only one default statement and must come at the end.

Flow chart for a switch case can be shown as below:

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Fig. 4.8 Switch case logic

4.6 Conditional Function IIF()

This is called the immediate if function. The iif() function is used to create
conditional assignments.

Syntax:

IIF( expression, true-part, false-part)

The function returns an array. If the expression evaluates to true, the function
returns the true-part, otherwise the false-part. The returned value should be
assigned to a variable for further use. The iif() function is very important and
commonly used.

Examples:

1. colorMACD = iif( MACD() > 0, colorGreen, colorRed );

The value for the variable colorMACD is assigned depending on the


condition specified in the first parameter. If its value is True, the
second parameter is assigned to the variable otherwise the third
parameter is assigned to the variable. If MACD() is above zero,
colorMACD is assigned colorGreen otherwise it is assigned colorRed.
2. //Grading exam results
Grade = “B”;
switch (Grade)
{
case “A”:
printf(“Excellent”);
break;
case “B”:
case “C”:
printf(“Well Done”);
break;
case “D”:
printf(“Passed”);
break;
case “E”:
printf(“Try next time”);
break;
default:
printf(“Invalid Grade”);
}

Result: Well Done. Here “Well Done” will be printed for either Grade B
or Grade C.

Exercise 4.2

1. Write a program using while loop for finding the HCF and LCM of two
numbers.

Solution:

//HCF and LCM of two numbers


//Assign the numbers to two variables
a = 25;
b = 40;
//Preserve the variables
m = a;
n = b;
while (n != 0)
{
t = n;
n = m%n;
m = t;
}
hcf = m; //Highest common factor
lcm = (a*b)/hcf; //Lowest common multiple
printf(“Highest common factor is %g and Lowest common multiple is
%g”,hcf,lcm);

Result: Highest common factor is 5 and Lowest common multiple is 200

2. It is given that ma60 = MA( C, 60 ).


a) Is ma60 an Array or scalar?
b) If ma60 an Array, how many elements of ma60 will be NULL?
c) Write a code to determine the number of NULLs in ma60.( HINT: The
IsNull() function checks for NULL values.)

Solution:

a) ma60 will be an Array.


b) A 60 period moving average calculation requires minimum 60 look
back period for its calculation. In AmiBroker the first 60 elements of
ma60 will be assigned NULLs. ( Note: Though for the 60th element,
ma60 is possible to be calculated, AmiBroker starts calculation from
the 61st element on words. Some other systems calculate from the
60th onward, leaving only 59 NULLs ).
c) //Find the number of NULLs
ma60 = MA( Close, 60 );
I = 0;
while ( IsNull( ma60[ I ] ) )
{
I++; //Increment I as long as there are NULL values
}
//When loop exits, “I” will hold the number of NULLs.
WriteVal( I, 5.0 );
3. Write a program to print the Fibonacci Series up to 500.

Solution:

In a Fibonacci Series, the current number is the summation of the


previous two numbers. The first two numbers are defined to be 0 and 1.
Thus the series is 0,1,2,3,5,8,…………..

The first two numbers are known. We can print that anyway. The
looping starts from the third number onward.

firstNum = 0;
secondNum = 1;
printf( “%g, %g, “, firstNum, secondNum); //0 and 1 are printed.

X = 500;
nextTerm = firstNum + secondNum;
while ( nextTerm <= X )
{
printf( “%g, “, nextTerm );
firstNum = secondNum;
secondNum = nextTerm;
nextTerm = firstNum + secondNum;
}

Result: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,

Telegram
@librosselectosdetrading
@cursos_trading_rank
@ranking_trading_courses
@Libros_Trading_Algoritmico
Chapter 5

Built-in Functions Explored

5.1 Introduction

AFL has a large collection of built-in functions. A thorough understanding of


the functions are required for writing useful AFL programs to display charts,
scanning of securities, portfolio back testing, developing trading systems,
writing own indicators etc. The syntax and usage of all the functions are
described in the AmiBroker User’s Guide. This book is not a replacement for
the User’s Guide. It is not possible to describe every function of AFL here.
However, some of the most important and often used functions are detailed
here for the guidance of the reader so that he can start using the AFL
programming journey and build skills.

5.2 The plot() function

In AmiBroker we will be dealing mostly with charts and indicators of various


types. These are all plotted with the plot() function. So let us start our study
with the plot() function.

The plot() function plots a graph with the data points given in an array,
provided to the function as a parameter.

The syntax of plot() in its simplest form is given below:

Plot( array, name, color, style );

The function returns a number. The function plots a graph using the array
data.

Parameters:
• array - This contains the data to be plotted
• name - This defines the graph name to be displayed in the title bar
• color - This defines the color of the plot
• style - Style determines the style of the plot, such as line chart,
histogram chart, thick line etc.
Style can be a combination of one or more of the following.
• styleLine normal line chart ( This is the default value )
• styleHistogram histogram chart
• styleThick fat(thick) line – in combination with other styles
• styleDots dotted line
• styleDashed dashed line style
• styleCandle candlestick chart
• styleBar traditional bar chart

This is not a complete list. For complete style constants see the User’s
Guide.

Examples:

1. Plot the Average Directional Index in red color

Solution:

The function ADX() returns an array of Average Directional Index values for
default 14 period.

Plot(ADX(),”ADX”,colorRed,styleLine);

Launch AmiBroker. Click on ‘Analysis’. From the drop down menu click
‘Formula Editor’. This loads the Formula Editor.
Type in the above code. Now the Editor should look like the figure below.

Fig. 5.1 AFL Editor


Click on the Verify Syntax tick button. If no errors are reported, click on the
Apply Indicator button to draw the plot as shown below.

Fig. 5.2 Plot of ADX

2. Plot the MACD value as a histogram with points above zero in green and
those below zero in red.

Solution:

We have the MACD() function to determine the MACD array. Additional


task is given to determine color according to the MACD value. This can be
done using the IIF() function.

ColorMacd = IIf(MACD() > 0, colorGreen, colorRed);


Plot(MACD(),”MACD”, ColorMacd,styleHistogram);

Apply the indicator to plot the function as shown below.

Fig 5.3 Plot of MACD as histogram


5.3 ParamColor() function

This function allows the user to change the color of the plot dynamically while
displaying the chart. Rather than explicitly defining the color in plot function,
we can use the ParamColor() function. The syntax of paramcolor() is:

ParamColor( “name”, defaultcolor )

This function adds a user definable parameter, which will be accessible via the
Parameters dialog. Right-click over the chart pane and select “Parameters” to
change chart parameters.
• “name” defines parameter name that will be displayed in the
Parameters dialog box.
• defaultcolor defines the default color value of the parameter

Example:

Plot( RSI(), "RSI", ParamColor( "RSI Color", colorRed ) );

The Relative Strength Index (RSI) is plotted. The ParamColor() function is used
in the place of color parameter of the plot() function. Note that the style
parameter is not used, which defaults to styleLine.

Launch the AFL Editor and type the above code. Verify the syntax for errors
and apply the indicator to display the following chart.

Fig. 5.4 Plot of RSI with Parameters dialog box displayed


Right-click and bring the Parameters dialog box. The “name” RSI Color shows
the default Red color. The user can click the down arrow to open the color
dialog box to select any color.

5.4 ParamStyle() function

In the previous plots we hard coded the style parameter. With ParamStyle()
function we can allow the user to dynamically change the style of the plot from
the Parameters dialog box. The syntax of paramstyle function is:

ParamStyle( “name”, [defaultstyle = styleLine ], [mask=maskDefault] )

The parameters in square brackets are optional.


• “name” is the parameter name displayed in the parameter
dialog box
• defaultstyle default value of style – takes a combination of style
constants
• mask binary mask that defines which styles should be
visible in the drop down list
maskDefault – show thick, dashed, hidden, own scale
styles
(This is default mask for paramstyle)
maskAll – show all style flags
maskPrice – show thick, hidden, own scale, candle,
bar
maskHistogram – show histogram, thick, hidden, own
scale, area

5.5 Param() Function

The Param function is a very important function and is perhaps one of the
most used functions in AFL. The param function add user definable numeric
parameters.

Syntax:

Param( “name”, defaultval, minval, maxval, [step], [sincr = 0])

The function returns a number. The parameters in square brackets are


optional, but the square brackets are not part of the function. This adds a
user definable parameter dialog box accessible when right clicking the chart
pane. The dialog box allows changes in parameters, whose changes are
reflected immediately on the chart.

• “name” defines parameter name that will be displayed in the


dialog box
• defaultval defines default value of the parameters
• minval, maxval define minimum and maximum values of the
parameter
• step defines minimum increase of the parameter via slider
in the Parameters Dialog Box
• sincr automatic selection increment value

The defaultval/minval/maxval/step have to be constant numbers.

Example:

1. p = Param( “RSI Period”,12, 2, 100 );


Plot( RSI( p ), “RSI”, ParamColor( “RSI Color”, colorRed),
styleLine |styleThick );
The default parameter is 12. When displayed, the plot will be shown with
12 as its look back period, though it can be varied between 2 and 100 from
the dialog box. The ParamColor has a default color of red and it can be
changed from the dialog box. Note the use of combination of styles. This will
plot a thick line. See the plot in the figure below.

Fig 5.5 Demo of Param() Function. The Parameter dialog box is also
shown.
Fig 5.5 shows the plot of the function. Note how the “name” parameters
are mapped to the Parameter dialog box. The default parameter 12 was
changed to 100 in the dialog box.

5.6 Ref() Function

The Ref function is the easy way to refer the previous values of an array. The
use does not limit to arrays O, H, L, C etc. but on all derived arrays. This is an
important function used extensively in AFL programming.

Syntax: Ref ( array, period )

The function returns an array. It references a previous or subsequent element


of the “array” passed. A positive period “p” references “p” periods in the future
while negative period “p” periods ago.

The formula ref(Close, -5) refers to the Close price 5 periods ago. Let’s assign
this to a variable x.

x = ref( Close, -5 );

The variable x will be an array. The value of x will be the close price 5 periods
ago. The figure below demonstrates this. The value of x will be 45.5, the close

Fig. 5.6 Ref() Function

price of candle 5 (See figure 5.6), that is 45.5. Both the arrays Close and x
will have Barcount number of elements. However, the x array elements with
index 0 to 4 (5 elements) will be “null”.
QUIZ::

What will be the value of the two printf statements?


printf( “%g”, x );
printf(“%g”, x[barcount-1]);

a. They will be same


b. They will be different
c. An error will be reported in the second printf()

Solution:
a. They will be same.

Consider an example using the Ref in a practical situation. Suppose it is


desired to enter a long position when the Close is greater than the previous bar’s
High and when Close is greater than the 20 period Moving Acerage of Close.
Such a condition can be verified using the following code.

Condition = Close > Ref(High,-1) AND Close > MA(Close,20);

5.7 Indicator Functions

AFL is equipped with lot of Indicator functions. These are used for the
Technical Analysis of the security concerned. Many of them can be charted
and studied. Some of the functions are given below:

• AccDist accumulation/distribution
• Adx Average directional index
• ATR Average true range
• BBandBot Bottom bollinger band
• BBandTop Top bollinger band
• CCI commodity channel index
• MACD moving average convergence/divergence
• OBV on balance volume
• RSI relative strength index
• StochD stochastic slow %D
• StochK stochastic slow %K

This list is not complete. Refer to the AmiBroker User’s Manual for the full list
of indicator functions, its syntax and usage. As an example let’s see how the
MACD is used. MACD, the moving average convergence/divergence is a trend
indicator. It is the difference between two moving averages.

MACD = EMA(12) – EMA(26)

The EMA is the exponential moving average. 12 and 26 are the usual values
used in the calculation of MACD. The Exponential moving averages are
calculated on the security’s closing price. EMA(Close,12) is the function to
generate the ema values. In the MACD system a 9 period EMA of the MACD
itself is used as a signal line.

The syntax of MACD is macd( fast = 12, slow = 26 ).

This returns an array of values of macd using fast and slow averaging periods.
While calling the function the desired values of the two variables can be given.
If no values are given the default values of 12 and 26 are used.

The syntax of the signal line is signal(fast =12,slow=26,signal=9)

The default values are 12,26 and 9.

Example:

Plot ( MACD(),”Macd”,colorBlue,styleLine);
Plot ( Signal(), “MacdSignal”,colorRed,styleLine);

The above code plots the Macd line in Blue color and Signal line in Red color
in a new pane. The resulting plot is given in Fig. 5.7 below.

Fig. 5.7 Plot of Macd and Signal line

5.8 The Cross Function

This is an important function. It checks for the crossovers of two arrays.

Syntax cross ( array1, array2)

The function returns an array and gives a True when array1 crosses above
array2 from below otherwise the result will be False(0). To determine when
array1 cross array2 from above use cross(array2,array1). Fig. 5.8 shows two
arrays array1 and array2 plotted. At point A array1 is crossing array2 from
below, while at point B, the array1 is crossing array2 from above.
Fig 5.8 Crossing of arrays.

Examples:

1. Moving average cross-over is a frequently used trading system, where one


enter a long position when a fast moving average crosses a slow moving
average and exit the position when the slow moving average crosses the fast
moving average. The code below shows how one identifies the entry and exit.

//Simple moving average cross over system


//Demonstration of the cross() function

lengthFast = 10;
lengthSlow = 50;
fastMA = MA( Close, lengthFast);
slowMA = MA( Close, lengthSlow);
longEntry = Cross( fastMA, slowMA);
longExit = Cross( slowMA, fastMA);

True/False values in longEntry and longExit determine the conditions


for buying the security and exit if already long.

2. ADX, the Average directional index is another trading indicator used


widely in trading. The main indicator is the ADX, the directional index
itself. AmiBroker has a function ADX() to find the adx with a default
period of 14. Associated with the ADX, there are two other indicators
called the Positive Directional Index and Negative Directional Index.
There are two functions in AFL to determine these values respectively
called PDI and MDI. The cross-over of PDI with MDI is sometimes used
for long entry. We can check this entry position with the following code.
//+DI and -DI crossing trading system
//PDI() plots +DI line , MDI() plots -DI line
//All uses the default values of 14 period
positiveDI = PDI();
negativeDI = MDI();
Plot( positiveDI,”+DI”, colorGreen, styleLine | styleThick);
Plot( negativeDI,”-DI”, colorRed, styleLine | styleThick);
entryPoint = Cross( positiveDI, negativeDI);

The procedure of capturing the entry signals will be discussed later.


The Fig. 5.9 shows the buy signal generated of the above system.

5.9 Buy signal for +DI -DI cross-over

5.9 HHV highest high value

Syntax: HHV (ARRAY, periods)

HHV calculates the highest high value in the passed ARRAY over the
preceding periods(periods include the current day). The formula HHV(Close, 5)
gives the highest high close price over the previous 5 periods. Fig 5.10
shows how HHV works. HHV( Close, 5 ) gives 117.15. Most recent candle is
counted in “periods”.
Fig 5.10 HHV function

5.10 HHVBars bars since highest high over a period

Syntax HHVBars( ARRAY, periods)

The function returns an Array, and calculates the number of periods that have
passed since the ARRAY have reached its peak within the period periods. For
the case shown in the above Fig 5.10 HHVBars(Close,5) gives the value 3.

5.11 Highest highest value

Syntax Highest ( ARRAY )

This function calculates the highest value in the ARRAY since the first day
loaded in the chart.

5.12 HighestBars bars since highest value

Syntax HighestBars ( ARRAY )

Calculates the number of bars that have passed since the ARRAY’s highest
value. HighestBars (Close) returns the number of periods that have passed
since the closing price reached its peak.

5.13 HighestSince highest value since condition met

Syntax HighestSince( EXPRESSION, ARRAY, Nth =1 )

Returns the highest ARRAY value since EXPRESSION was true on the Nth
most recent occurrence.
Example:

HighestSince( Cross( Close, MA( Close, 20)), Close, 1 )

This returns the highest close price since the Close has crossed the 20 period
MA of Close. To make this clear see the Fig below.

Fig 5.11 HighestSince()

The example formula returns a value of 8.2.

5.14 HighestSinceBars bars since highest value since condition met

Syntax HighestSinceBars( EXPRESSION, ARRAY, Nth = 1 )

This returns the number of bars since highest ARRAY value since
EXPRESSION was true on the Nth most recent occurrence. If we apply this to
the chart above, the return should be 3, the last three bars.

The functions described from 5.9 to 5.13 have their “Lowest” counterparts
namely LLV, LLVBars, Lowest, LowestBars, LowestSince and
LowestSinceBars. These functions are similar in nature and are not discussed
separately.

5.15 BarsSince

This function takes an array as argument and returns an array.

Syntax BarsSince( ARRAY )

This calculates the number of bars that have passed since ARRAY was true. It
counts how much time (in bars) has passed since an event occurred.
BarsSince( Close > 150 ) returns the number of periods that have passed since
the closing price was greater than 150. If this happens to be currently true, it
will return 0.
Take Fig. 5.12 for illustrating the idea of BarsSince. In the figure the blue line
is the 20 period EMA. Looking at the figure we can see that last seven bars
have passed since the Close was above the 20 period EMA. AFL for getting
number of bars since Close was above 20 EMA can be written as:

x = BarsSince( Close > EMA( Close, 20 ) );

Fig. 5.12 Illustration of BarsSince

This should return a value of 7.

In the same figure the Close has been below the 20 period EMA for the past 7
days. But the following formula will not give the desired result. It will return a
value of 0 since at the current bar the passed array is true.

y = BarsSince( Close < EMA( Close, 20 );

In the above, y will be 0. To determine how many bars the Array was
remaining true, another logic has to be applied.

As another example suppose it is desired to estimate the number of bars that


has passed since the 10 day MA is above the 20 day MA and the 20 day MA is
above the 30 day MA and the condition is still remaining. The code can be
written as:

condition = (MA( Close, 10 ) > MA( Close, 20 ) ) AND (MA( Close, 20) >
MA( Close, 30 ))

To get the number of bars this can be negated and applied to the BarsSince
Function.
barsCount = BarsSince( !Condition );

Fig 5.13 shows an illustration of the condition specified. Note the perfect
alignment of the moving averages shows the possibility of a long entry.

Fig 5.13 BarsSince Function

Exercise 5.1

1. The King Keltner trading strategy is explained. Moving average of


(H+L+C)/3 is found out. An upper band is drawn with moving average +
ATR(Average True Range). A lower band is drawn with moving average -
ATR. A long position is initiated when today’s moving average is greater
than yesterday’s and market action >= upper band. The long position
will be liquidated when today’s market action <= moving average. Use a
period of 10 for the moving average and ATR. Write a code for getting the
buy and sell signals for this condition.

Solution:

avgLength = Param(“Average Length”,10,5,50);


atrLength = 10;
movAvg = MA( (H + L + C)/3, avgLength );
upBand = movAvg + ATR(atrLength ) ; //ATR() calculate the Average
//True Range
dnBand = movAvg – ATR(atrLength);
//Plot the upper band and lower band
Plot(upBand,”Upper Band”,colorGreen,styleLine);
Plot(dnBand,”Down Band”,colorRed,styleLine);

buySignal = (movAvg > Ref(movAvg,-1)) AND (High >= upBand);


exitSignal = High <= movAvg;

2. Suppose in a situation we are required to determine the number of


recent up days. Define an up day as the current Close is greater than or equal
to the prior day Close. Write a code for determining the number of up days.

Solution:

//Determine number of up days


condition = Close >= Ref( Close, -1);
upDays = BarsSince( !condition );

The number of down days can be similarly determined.

5.16 GapDown

There are some basic price pattern detection functions in AFL. One among
them is the GapDown() Function. It returns an Array and gives a value of 1 or
True on the day the security’s price gaps down, meaning that yesterday’s
low is greater than today’s high. Otherwise the result will be False.

Fig. 5.14 A gap down candle

Fig 5.14 shows an example of a gap down candle. In this case the function
GapDown() returns a True value.

QUIZ:

If x = GapDown(), how many elements of the array x will have values of either
1 (True ) or 0 (False)?
a. Barcount number of elements
b. Barcount-1 number of elements
c. Barcount-2 number of elements
Solution:

The answer is c. Barcount-2 number of elements. The maximum


number of elements an array can have is Barcount-1. Only the first element of
x will be Null, leaving the rest Barcount-2 number of elements.

5.17 GapUp

This function gives a True value when the security’s price gaps up, that is,
when yesterday’s high is lower than today’s low.

x = GapUp()

Fig. 5.15 Example of a gap up candle

5.18 Inside

The function Inside(), which returns an array, gives a value of True(1) when
today’s high is less than yesterday’s high and today’s low is greater than
yesterday’s low. Such a candle is called an inside day candle.

Fig. 5.16 Example for inside day


5.19 Outside

Function Outside() detects an outside day, gives a return value of True when
an outside day occurs. Fig 5.17 shows an outside day.

Fig 5.17 Example for an outside day.

Exercise 5.2

1. Consider the formula x = Outside(). Write a program to count the total


number of True elements and total number of False elements in the
array x. Use a for loop.

Solution:

The array x will have ( Barcount-1 ) number of elements. Array element


x[0] will be Null. All the other elements will either be True(1) or False(0)
depending on the return of the OutSide function. Initialize two counters
before the loop starts.

countTrue = 0;
countFalse = 0;
//Generate the x array
x = OutSide();
for ( i = 1; i < barcount ; i++)
{
if (x[i] == True)
countTrue+=1;
else
countFalse+=1;
}
printf(“Total of Trues = %g\nTotal of Falses =
%g”,countTrue,countFalse);
Result in my system:
Total of ‘True’s = 240
Total of ‘False’s = 1778

2. Write a code to generate a buy signal for the strategy given below:

- The indicator ADX(14) is above 20


- Close price crosses above the 30 period EMA
- Relative strength index is higher than 70 for at least 4 bars

Solution:

We are given three conditions to satisfy a buy signal. The first condition
can be written as:

condA = ADX(14) > 20;

We explicitly used the period 14, though it was not necessary. The
second condition can be written as:

condB = Cross( Close, EMA( 30) );

The third condition requires that the Relative Strength Index is greater
than 70 for atleast 4 bars. There is a function called RSI() to determine
the Relative Strength Index. Combine with BarsSince function we can
frame the third condition.

condC = BarsSince( ! RSI() > 70 ) > 4;

condC will be true only when the RSI() is greater than 70 for atleast 4 bars.
Combine all the three conditions to get the buy signal.

buyPoint = condA AND condB AND condC;

5.20 BarIndex

This returns an array of zero based bar indices. That is the array will have
values from 0 to barcount-1.

Syntax BarIndex()

QUIZ

If x = BarIndex(), which of the following statements are true:


a. x will have barcount-1 number of elements
b. x and Barcount have the same values
c. x[0] will be Null
Solution:

None of the statements are true.

5.21 BeginValue

This function accepts an Array as parameter and returns a number. The


function returns the value of the Array at the beginning of the selected range.
If no range is selected then the value at the first bar is returned. To select a
range double click on the chart at the beginning and again double click on the
end of the range.

5.22 EndValue

Similar to the function BeginValue. The return value will be the end of the
range or the last bar as the case may be.

Example:

Define a range by double clicking the start of the range and the end of the
range. Write a program to compute the Close prices at the begin and end of
the range. Determine the percent price rise or decrease as the case may be
and print appropriate message. If the prices happen to be same, notify that
also.

EndPrice = EndValue( Close );


BeginPrice = BeginValue( Close );
if ( EndPrice > BeginPrice )
{
percentRise = (( EndPrice – BeginPrice )/BeginPrice)*100;
printf( “Percent rise in price = %4.2f%%”, percentRise );
}
if ( EndPrice < BeginPrce )
{
percentDecrease = (( BeginPrice – EndPrice )/BeginPrice)*100;
printf( “Percent decrease in price = %4.2f%%”,percentDecrease);
}
else
printf(“There is no change in price”);

Result obtained in my system:


Percent decrease in price = 19.54%

5.23 abs

There are a large number of math functions included in AFL to empower the
math capabilities of the programs written in AFL. The abs function accepts a
number or an Array as its parameter, and returns a number or Array. The
function returns the absolute value of the number or the Array.

Abs(20) returns 20 Abs(-45) returns 45.

5.24 ceil

Accepts a number or Array and calculates the lowest integer that is greater
than the number or Array.

Ceil(6.2) returns 7 while Ceil(-6.2) return -6.

5.25 floor

This calculates the highest integer that is less than the number or Array
passed as parameter.

Floor(23.8) returns 23 and floor(-12.6) returns -13.

5.26 int

After accepting a number or Array as parameter the function removes the


fractional portion and returns the integer part.

Int(24.3) returns 24 and int(-71.9) returns -71.

5.27 frac

The frac eleminates the integer portion of the parameter passed and returns
the fractional part.

frac(5.36) returns 0.36 and frac(-4.21) returns -0.21. Note the negative sign is
retained.

5.28 Max and Min

These functions accepts two parameters numbers and/or arrays. They return
the maximum or minimum of the two parameters passed.

min(Close,10) returns the close price or 10 whichever is minimum.

Example:

If we want to ascertain whether the Close is greater than the 10 MA and also
greater than 20 MA, a formula can be written as:

C > MA(10) AND C> MA(20);


The same situation can be written using the Max() as:

C > Max( MA( 10 ), MA( 20 ) );

5.29 round

This rounds a number/Array to the nearest integer.

Round(12.6) returns 13 and round(-25.3) returns -25.

5.30 Sum

This function takes two parameters Array and periods. It calculates the
cumulative sum of the Array for a specified number of look back periods
including today.

Example:

sum(Close,20) returns the sum of preceding 20 periods closing prices.

Since the first parameter is an Array, which can be any derivative of arrays of
price values, its moving averages or anything like that, many interesting and
useful results can be derived. As a simple example, suppose it is desired to
examine how many bars closed above the 10 day simple moving average of
Close within a period of 40 days. We can use the sum function to retrieve the
desired result as shown below:

x = Sum( Close > MA ( Close, 10 ), 40 );


WriteVal( x, 6.0 );

For the chart in Fig. 5.18, the value of x is 16.

Fig. 5.18 Demo of Sum() function


5.31 Cum

Syntax: Cum ( Array )


Cum ( Value )

It returns an array and calculates the cumulative sum of the Array from the
first period in the chart.

If Array is [ 1, 2, 3, 4, 5 ,….], then cum( Array ) will be [ 1, 3, 6, 10, 15,…. ].

Cum( 1 ) keeps adding 1 to its previous value. The first value will be 1 and it
just keeps adding 1 to its prior value. So what will be the last value of the
array? It should be Barcount. Check it in the Guru commentary.

QUIZ:

If you plot Cum( 1 ), how it will look like in the chart?

Solution:

It should be a straight line starting from 1 to the value of Barcount.

[ Note: In recent versions of AmiBroker, the following directive will be required


to get the above result.

SetBarsRequired( sbrAll );
Plot( Cum( 1 ), “”, colorGold );
Check User Manual for details. ]

5.32 ROC

It may quite often required to find out the percentage change in the price
value or any other array over a specified period of time. The function ROC
gives the percentage rate of change.

Syntax: roc ( array, [ periods = 12 ], [absmode = False] )

The function returns an array. Note the last two parameters are optional as
indicated by the square brackets. If omitted the default values are assumed.

ROC (Close) returns the 12 period percentage rate of change in the Close price
with absmode = False.
Example:

It is desired to enter a long trade if the current closing price is equal to or


greater than 10% from the close price 4 days ago. Write a formula to achieve
this task.

Solution:

This is simple. We have the ROC function to get the percentage rate of change.
The formula can be written as:

buySignal = ROC( Close, 4 ) >= 10:

The value of buySignal will be either True or False. If True long entry can be
initiated.

5.33 ValueWhen

This gives the value of an array when a condition is met.

Syntax:

ValueWhen( Expression, Array, n = 1 )

This function returns the value of the “Array” when the “Expression” was true
on the nth most recent occurrence. The default value of n is 1.

ValueWhen( cross( Close, MA( Close, 5 ) ), MACD( ), 1 );

In the above formula, the return value will be the value of MACD() on the first
occurrence of the crossing of the Close price above its 5 period moving
average.

5.34 DayOfWeek

The returned array has values 0 to 6 representing the days of the week.

0 – Sunday
1 – Monday
2 – Tuesday
3 – Wednesday
4 – Thursday
5 – Friday
6 – Saturday

Example:

x = DayOfWeek(); //x will be an array of numbers 0 to 6


if ( x[ Barcount -1 ] == 2 )//We check the current bar day
printf( “Today is Tuesday”); // 2 represents Tuesday
else
printf( “Today is not Tuesday”);

5.35 PlotShapes

This function plots arrows and shapes on any chart pane.

Syntax:

PlotShapes( shape, color, layer=0, yposition=graph0, offset=-12)

Parameters:
• shape defines the type of the symbol to be plotted. Common shape
constants are shown below.
shapeNone
shapeUpArrow
shapeDownArrow
shapeHollowUpArrow
shapeHollowDownArrow
shapeUpTriangle
shapeDownTriangle
shapeCircle
shapeSquare
There are more constants available.
• Color defines the color of the shape symbol
• layer defines layer number on which shapes are plotted. This defaults to
layer 0
• yposition defines Y-position where shapes are plotted. By default they
are plotted around graph0, the first indicator.
• offset is the distance parameter, defaults to -12.

Example:

A buy signal occurs when the Close price crosses the 10 day moving average.
Plot a green arrow whenever a buy signal occurs.

Solution:

buySignal = Cross( Close, MA( Close, 10 ) );


shape = IIf( buySignal, shapeUpArrow, shapeNone );
PlotShapes( shape, colorGreen );

Fig 5.19 shows a sample plot.


Fig. 5.19 PlotShapes sample

Exercise 5.3

1. Write an AFL code to plot the 20 day moving average of Close. When the
moving average rises the plot should be in green color and while the MA
value decreases, plot in red.

Solution:

20 day moving average is easily found with the MA function. Let us


capture the moving average array into a variable called movAverage.

movAverage = MA( Close, 20 );

Increasing and decreasing of array movAverage can be determined by


comparing current value with the prior value using the Ref() function.
An IIF() function allows us to decide the color depending upon a logical
condition.

colorAverage = IIf( movAverage > Ref( movAverage, -1 ),


colorGreen, colorRed );

In the plot function we can use colorAverage in the place of “color”.


The complete code is given below.

//Plot rising moving average in green, dropping average in red


movAverage = MA( Close, 20);
colorAverage = Iif( movAverge > Ref( movAverage, -1 ),
colorGreen, colorRed );
Plot(movAverage,”Colourful MA”,colorAverage, styleLine|styleThick );

Fig 5.20 Plot of Moving Average with different colors.

2. The Ichimoku Cloud Charts is a Japanese contribution to the trading


community. The system has five components:

• Tenkan Sen
• Kijun Sen
• Senkou Span A
• Senkou Span B
• Chikou

We will try to plot each of the five components as part of our exercise. First
consider the Tenkan Sen. It is calculated by averaging the highest high
and the lowest low for the previous 9 periods.

Tenkan Sen = (Highest high of 9 periods + Lowest low of 9 periods)/2

Write an AFL code to calculate and plot the Tenkan Sen Array. Also plot
the 9 period Simple Moving Average and compare the plots.

Solution:

There should not be any difficulty in doing this. We have the HHV and
LLV functions to calculate the highest high and lowest low values over a
specified period.
//Plot of Tenkan Sen
//Define the Tenkan Sen period
tenkanSenPeriod = Param(“Tenkan Period”,9, 5, 50, 1 );
tenkanSen = ( HHV( High, tenkanSenPeriod ) +
LLV( Low, tenkanSenPeriod )) / 2;
Plot(tenkansen, “Tenkan Sen”,colorGreen,
styleLine | styleDashed | styleThick );
//Plot the simple moving averages
Plot( MA( Close,9 ),”MA-9”, colorLightOrange,styleLine );

Fig 5.21 Plot of Tenkan Sen

Fig. 5.21 shows the plot of Tenkan Sen. While the 9 period Simple
moving average smooths the price, the Tenkan Sen shows periods of
flattening like at points A and B.

2. The Kijun Sen is also known as “Base Line”. It is calculated by


averaging the highest high and lowest low of the previous 26 periods. So
the difference in calculation is only the number of periods used.

Write an AFL to plot the Kijun Sen.

Solution:

KijunSen = (Highest High of 26 periods + Lowest Low of 26 periods )/2.

//Plot of Kijun Sen


kijunSenPeriod = Param(“Kijun Period”,26,10,100,1 );
kijunSen = ( HHV(High, kijunSenPeriod) +
LLV( Low, kijunSenPeriod ))/2;
//Plot the Array
Plot( kijunSen, colorRed, styleLine );

Fig. 5.22 shows a plot of the Kijun Sen.


Fig 5.22 Plot of Kijun Sen

3. Senkou Span A and Senkou Span B are discussed together. The Senkou
Span A is the average of the Tenkan Sen and Kijun Sen and is projected
26 days in the future on the chart.

Senkou Span A = ( Tenkan Sen + Kijun Sen )/2

The Senkou Span B is calculated by averaging the highest high and


lowest low of the prior 52 periods and projecting it 26 periods into the
future.

Senkou Span B = (Highest high of prior 52 periods + Lowest low of


prior 52 periods ) /2.

Write an AFL program to calculate and plot Senkou Span A and Senkou
Span B. The area between the two lines is called the Kumo Cloud. Plot
the Cloud also.

Solution:

//Plot of Tenkan Sen


//Define the Tenkan Sen period
tenkanSenPeriod = Param(“Tenkan Period”,9, 5, 50, 1 );
tenkanSen = ( HHV( High, tenkanSenPeriod ) +
LLV( Low, tenkanSenPeriod )) / 2;
Plot(tenkansen, “Tenkan Sen”,colorGreen,
styleLine | styleDashed | styleThick );
//Plot of Kijun Sen
kijunSenPeriod = Param(“Kijun Period”,26,10,100,1 );
kijunSen = ( HHV( High, kijunSenPeriod) +
LLV( Low, kijunSenPeriod ))/2;
//Plot the Array
Plot( kijunSen, colorRed, styleLine );

//Define a variable for the shifting parameter of Kumo


shiftRight = Param(“Kumo Shift”,26,0,60,1);

//Senkou Span A
senkouSpanAColor = ParamColor(“Senkou Span A Color”,
colorSeaGreen );
senkouSpanA = ( tenkanSen + kijunSen )/2;
//Last parameter of the Plot function projects the plot to 26
//default period to the future.
Plot( senkouSpanA, “Senkou Span A”, senkouSpanAColor,
styleLine,Null,Null,shiftRight );

//Senkou Span B
senkouSpanBPeriod = Param(“Senkou Span B Period,52,20,200,1);
senkouSpanBColor = ParamColor(“Senkou Span B Color”,
colorPink);
senkouSpanB = ( HHV(High,senkouSpanBPeriod) +
LLV(Low,senkouSpanBPeriod))/2;
Plot(senkouSpanB,”Senkou Span B”,senkouSpanBColor,
styleLine,Null,Null,shiftRight );

//Plot the Kumo

kumoColorUp = ParamColor( “Kumo Up Color”,colorSeaGreen);


kumoColorDn = ParamColor(“Kumo Down Color,colorPink);
PlotOHLC(senkouSpanA,senkouSpanA,senkouSpanB,senkouSpanB,
“”,IIf( senkouSpanA>senkouSpanB, kumoColorUp,kumoColorDn,
styleCloud|styleNoLabel,Null,Null,shiftRight );

Fig 5.23 shows a plot of the Cloud.


Fig. 5.23 The Kumo Cloud

The above code used the PlotOHLC() function to plot the Kumo Cloud.
This function plots a customized chart with the open, high, low, close
paramer values appropriately modified.

4. The last component of Ichimoku system is the Chikou. It is nothing but


the current price shifted back 26 periods. Using the Ref() function, this
can be plotted easily. (Exercise to the reader)

5. An investor is curious to know whether the current closing price has


dropped by 10 percent (minimum) within 10 number of bars at the
most. To help him take his further course of action, write a code to
generate a True/False signal to know whether the 10 percent drop
completed within 10 bars.

Solution:

Here the ROC function will come to our rescue. Since the price drop has to
happen within 10 bars, we can limit our ROC calculation up to 10 bars.
We can write the code as given below:
bar = 0;
for ( i = 1; i <= 10; i++ )
{
drop = ROC( Close, i );
if ( drop[ Barcount – 1 ] <= -10 )
{
//Capture the bar count

bar = i;
break;
}
}
dropSignal = drop <= -10;

If the value of dropSignal is True, a 10 percentage drop completed within 10


bars. The ‘bar’ variable will hold the number of bars required to drop to 10
percent. Note the use of drop[Barcount-1] in the if() statement. An array
cannot be used in the if() statement.

6. It is to be determined whether the Close price has been above its 5 day
simple moving average for the past 5 periods. Write a formula to check
this.

Solution:

One possible solution looks like this:

ma5 = MA( Close, 5 );


isOk = C > ma5 AND Ref( C,-1 ) > Ref( ma5, -1 ) AND
Ref( C,-2) > Ref( ma5, -2 ) AND
Ref( C,-3) > Ref( ma5, -3 ) AND Ref( C,-4) > Ref( ma5, -4 );

While this works well, there is a condensed version to find out the same
result. The Sum () function adds the values of an array for a specified
number of times. Using Sum() we can rewrite the formula as:

isOk = Sum( C > ma5 , 5 ) == 5;

Look, how simple the formula becomes. The isOk variable will be True
only when the Sum() returns a 5, that is when the first 5 closes are
above ma5. Fig. 5.21 shows a chart example of this. In this chart isOk
will be True as the last 5 closes are above ma5, the 5 day Moving
Average.
Fig. 5.24 Sum( C > ma5, 5 ) = 5

7. Write a formula to find out whether the 5 day moving average has
crossed the 10 day moving average within past 5 days.

Solution:

The formula can be written using the Sum() function as shown below:

//5 day moving average


ma5 = MA( Close, 5 );
//10 day moving average
ma10 = MA( Close, 10);

crossedOk = Sum( Cross( ma5, ma10 ), 5 ) > 0;

The flag crossedOk determines whether during the prior 5 days period,
ma5 has crossed ma10. A value of True confirms it has crossed. See
Fig.5.25 for a chart demo.

Fig 5.25 crossedOk = True

8. Write a formula that returns the value of the 14 day RSI on the 2nd most
recent occurrence of the closing price closing above its 10 day simple
moving average.

Solution:
ValueWhen is the function to be used here. The “Array” will be RSI( 14 )
and the “Expression” will be Cross( Close, MA( Close, 10 )) and the value of
n = 2. Hence the required formula is:

ValueWhen( Cross( Close, MA( Close, 10 ) ), RSI( 14 ), 2 );

9. Plot a Chandelier Exit graph for liquidating long positions.

Solution:

Chandelier Exit is a volatility based indicator that is used as an exit point


for long/short positions. For finding Chandelier Exit the ATR and the
Highest High Value of High or Close is used with appropriate input
parameters. Usually 22 period value is used. This is since there are 22
trading days in a month. A multiplier of 3.0 is usually used for the ATR,
though these are all varied according to user requirements.

The formula for Chandelier Exit for long is:

22 day Highest High value of High – ATR( 22 )*3.0

The formula can then be written as follows.

chandelierExit = HHV( High, 22 ) - ATR( 22 )*3.0;

We have used values of 22 and 3. This array can be plotted.

//Chandelier Exit plot


periods = 22;
multiplierATR = 3.0;

//Build the array


chandelierExit = HHV( High, periods) – ATR( periods )* multiplierATR;

//Plot the graphic


Plot( chandelierExit,”ChandelierExit”,colorRed,styleLine|styleDashed);

Fig. 5.26 shows a typical plot.


Fig. 5.26 Chandelier Plot
Chapter 6

Write Your Own Functions

6.1 AmiBroker allows user definable functions to be created and used as many
times like the built-in functions. If you find that a particular logic is not
available in AFL, you can write a function yourself according to the syntax of
function definition and use it in the AFL program. Chapter 3 described how
user defined functions can be created with few examples. To recap the syntax,
the definition of function follows:
function function_name ( [parameter,…] )
{
Statements;

[return result;]

}
The function definition begins with the keyword function, followed by the
function name. The parameter list is enclosed in parentheses after the
function name. The body of the function is included within the braces. The
keyword return returns the result to the calling program.
In this chapter we will practice writing many functions. These functions may
not be useful for practical programming use in AFL. Our intention is to
develop AFL writing skills.
1. We have already seen the built-in function BarsSince(ARRAY). This
calculates the number of bars that have passed since the ARRAY
was true. Write a function yourself, that performs the same task as the
built-in function BarsSince.
Solution:
Let’s name the function as myBarsSince. The function has one
parameter of type Array. Start with the function definition:
function myBarsSince( array )
{
body of function
}
The passed parameter is “array”. We can name the parameter anything
of our choice. The only matter concerned is that the parameter will be
an ARRAY and is to be considered so within the body of the function.
The idea is simple. Start from the last bar, traveling backwards counting
the bars each time the passed array remains False until it becomes
True. The counted value is returned from the function when a True
value is detected in the array. Using a while loop, the function can be
written as:
function myBarsSince( array )
{
//Initialize the counters
count = 0;
i = 1;
while ( array[BarCount – i] == False )
{
i++;
count++;
}
return count;
}
To test this function: Use the Guru Commentary.
x = myBarsSince( Close > 536.7 );
y = BarsSince( Close > 536.7 );//Use the built-in function
WriteVal(x,2.0);
WriteVal(y,2.0);
Result in my system: ( See Figure 6.1 )
5
5
Fig 6.1
2. The HHV function returns the highest value of the array within the
range prescribed. As an exercise, write a function yourself to give the
same functionality as the HHV.
Solution:
Name the function as myHHV with parameters array and range. Assume
that the highest will be the last array element itself and log this into a
variable. Now using a for, loop travel backwards, until the range is
reached, each time resetting the highest value. The complete function is
given below.
function myHHV( array, range )
{
myHighest = array;
for ( bar = 2; bar <= range; bar++ )
{
myHighest = Max( myHighest, array[ Barcount – bar ] );
}
return myHighest;
}
Test the function:
x = myHHV( C, 60);//Our function
WriteVal( x, 5.2 );
y = HHV( C, 60 );//The built-in function
WriteVal( y, 5.2 );
Result :
59.10
59.10
3. Write a user defined function that is similar the the LLV built-in
function. Test the function and compare with the LLV. ( Not solved )
4. Write a function to detect a pullback after the security reached a high.
This may be required to enter a trade. The function parameters are the
array (usually High) and the number of periods of pullback required.
Solution:
See the figure 6.2 for the definition of a pullback. The figure shows a 4
period pullback after reaching a peak.

Fig. 6.2 A pull back example


The function HHVBars return the number of bars that have passed since
the array reached its peak during the assigned period. We will use this
function to write our function.
function pullBack( array, period )
{
pbPeriods = HHVBars( array, period+1 );
return IIf( pbPeriods == period, True, False );
}
Note the use of period+1 in the parameter list of HHVBars. The function
returns True if the ‘period’ number of days are pulled back, otherwise a
False is returned.
An example usage is given with reference to the chart in Fig. 6.3 below.
Fig. 6.3 Use of pullBack function

x = pullBack( H, 3 );
In this case x will be true. We are actually checking whether there was a
three day pullback after reaching a peak.
5. Write an AFL function to check for a Bearish Engulfing Pattern.
Solution:
Fig. 6.4 shows a typical Bearish engulfing pattern.

Fig. 6.4 Bearish Engulfing Pattern

The pattern is a two candle pattern. The first candle must be a white
body. The second candle, a black one, must open above the close of the
first candle and should close below the open of the first white body.
Visually, the white body of the first candle appears fully covered by the
black body of the second candle. There should have a prior uptrend
before the engulfing formed. Though, a subjective matter, we define an
uptrend when the prior four highs are higher than their respective prior
highs.
function bearishEngulfing ()
{
//Define a white body and a black body
whiteBody = C > O;
blackBody = O > C;
//Define engulfing
engulfing = Ref(whiteBody,-1) AND blackBody AND
O > Ref(C,-1) AND C < Ref(O,-1);
//Define uptrend
upTrend = BarsSince( !( Ref( H.-1 ) > Ref( H, -2))) >=4;
return upTrend AND engulfing;
}
The function returns an Array whose values are True(1) or False(0)
depending upon a bearish engulfing pattern is detected or not. To test
see the chart portion in Fig. 6.5.

Fig. 6.5 BearishEngulfing ( )


x = bearishEngulfing();
WriteVal( x[Barcount – 71],2.0);
Result: 1 (Bearish engulfing pattern detected at (Barcount-71) th candle.
6. A function to detect a Bullish Engulfing Pattern can be written in
similar way. This is left as an exercise to the reader.
6.2 #include
The #include is a preprocessor include command. This command helps to
include external AFL code into your formula. Those familiar with the C
programming should be using the header files used with their C code. The
#include in AFL is for similar use.
How to use the #include command?
Write all our own functions in the Formula Editor and save with a suitable
name into the “include” folder. File→SaveAs→AmiBroker→Formulas→Include.
See Fig. 6.6.

Fig. 6.6 Include Folder

Once saved in the “Include” folder, we need not specify the path for it
because AmiBroker will search for the include files in the Include folder,
which is the default folder. Later when we desire to use the functions
defined in the include statement, we can use the functions directly
without further defining them.
#include <myFunctionDefinitions.afl>
x = bearishEngulfing();
WriteVal(x,2.0);
Chapter 7

How To Make a WatchList?

7.1 What is a Watchlist?


The Fig. 7.1 shown on the right side is
displayed when AmiBroker opens first. The
bottom shows four tabs namely,Layouts,
Layers, Charts and Symbols.
When the Symbols tab is clicked all the
symbols in the opened database will be
displayed as shown in the Figure. The
vertical and horizontal scroll bars allow
us to navigate through the Symbols. The
Symbols displayed will be a huge list,
sometimes run into few thousands of
Stocks. An investor might not be
interested in all the listed Stocks. He will
always have his own personal choices.
He may be interested in stocks of certain
Sector or those included in a particular
Fig. 7.1
Index. Another person might be long in a number of Stocks, which he may wish
to watch on a daily basis. While, it is possible to watch all the stocks desired
from the Symbols List, it is sometimes inconvenient to go through a long list of
stocks. Every one would wish to have a small list of his choice of stocks easily
accessible and placed conveniently under a Name of his preference. This is where
the Watchlist of AmiBroker helps the user. A Watchlist is actually a subset of the
Symbols in the Database placed under a user defined Name. AmiBroker
provides multiple ways of creating a Watchlist.
We can assign the same symbol to more than one Watchlist. Any number of
Watchlists can be created and maintained.
7.2 Watchlist Creation
Follow these steps to create a WatchList.
1. Start AmiBroker
2. Click the Symbols Tab
3. Click on the > to the left of
Watch Lists
4. Predefined List names List 0
List 1, List 2 etc are displayed.
5. Place the cursor over List 0. A
message displays “List 0(0 symbols)
6. Place the cursor over List 0. Click
after few seconds. List 0 becomes
editable. Type in “myList” Fig. 7.2
7. The name “myList” has replaced List 0. We have just created a new
Watchlist, but no symbols have been added to it.
8. Right-click on myList. From the resultant Menu, Left-click on
“Type-in symbols….”
See Fig. 7.3

Fig. 7.3
Type the three symbols ABB,ACC,SUNTECK. Separate the symbols with
a comma. Any number of symbols can be entered, each separated by a
comma. Click OK. Now the Watchlist myList is created with three
symbols added into it. See Fig. 7.4. Keep in mind that the typed names
have to match exactly like the Symbols available in the database.
Otherwise, though the symbols get added to the Watchlist, a chart
cannot be displayed for the corresponding symbol.
Fig. 7.4 myList Watchlist

7.3 How to add symbols to an existing Watchlist?


Symbols can be added or removed from an existing Watchlist. Follow these
steps.
1. Click the Symbols tab to bring the list of available Symbols.
2. Press the Ctrl key and click the desired Symbols to be added. The
selected symbols will be highlighted.
3. Right-click on any of the highlighted Symbol and select Watchlist from
the resulting Menu.
4. Select “Add Selected symbol(s)” from the next Menu options. This brings
up the following window.
Fig. 7.5
5. Select myList and click OK. The selected Symbols will be saved into
myList.
7.4 Create a Watchlist by importing from a text file.
Creating a Watchlist for the Symbols already in a text file (*.txt) is very simple.
Open the Notepad editor and type in few Symbols, one in each line as shown
in the Fig. 7.6.

Fig. 7.6 Text file with 10 Symbols


Save the file as Portfolio.txt. Create a Watchlist with the name myPortfolio.
Follow these steps to add the 10 symbols to myPortfolio.
1. Right-click on the myPortfolio and select Import… from the resulting
menu.
2. Select myPortfolio and click OK. Ensure that the File radio button is
selected.
3. Select the Portfolio.txt file we created and click Open. This will add the
10 Symbols in the file into myPortfolio Watchlist. See Fig. 7.7.

Fig. 7.7 myPortfolio


7.5 A practical Example
The Nifty 50 is a diversified 50 stock index accounting for 12 sectors of the
Indian economy. Many fund houses use this for bench marking their
portfolios. It is decided to have a Watchlist of the Nifty 50 shares, so that later
we can back test our trading strategies on this Watchlist. As an exercise we
will make a Watchlist of the Nifty 50 shares.
Solution:
If we know the names of the Ticker Symbols, we can easily make a Watchlist
by any of the methods described earlier. Typing in 50 symbols without making
any error can be time consuming. We can look for another method. The
Symbols for Nifty 50 shares are already listed in the Nse India website. We will
make use of this feature. Follow these steps:
1. Use the following link to down load the list of the 50 shares in .csv
format. https://www.nseindia.com/products/content/equities/indices/
nifty_50.htm
2. Save the file to the Desktop. It will look like the Fig. 7.8 below.
Fig. 7.8 Nifty 50 Shares
3. We need only the Symbol column. Delete columns Company Name,
Industry, Series and ISIN Code. The Header row (row 1) is also not
required. Delete row 1 also. We will be left with the Symbol Names
only. Save the resulting file as “Nifty-50List.csv”. CSV is for
the Comma Separated Value.
4. Create a Watchlist “Nifty50”. What remains is to add the Symbols of the
50 shares into the Watchlist. Follow the steps described in 7.4 to add
the Symbols. You will open the Nifty-50List.csv file instead of
Portfolio.txt.
Exercise to the reader:
Create a Watchlist called NiftyFMCG. Add to this all the stock tickers in the
Nifty FMCG Index. The following link lists all the names of stocks required.
www.nseindia.com/products/content/equities/indices/sectoral_indices.htm
Chapter 8

Create Your own Exploration

8.1 Explore in AmiBroker


The Explore function in AmiBroker helps us to filter Stocks according to the
criteria we specify. The criteria can be written in simple AFL. Having said
filter the Stocks, the idea behind this goes to a reserved variable filter. This
variable controls which stocks are to be added to the filtered list. The name
filter is a reserved word and should not be used for any purpose other than
this.
8.2 Customized Report Generation
One good feature of Explore is that the report generated can be customized as
per our requirement. This is possible with the AddColumn() function.
AddColumn Function
The function in its simplest form takes two parameters an Array and a “name”
for the display heading of the Array.
AddColumn( Close, “Close”);
The above statement display a heading of “Close” and displays the Close value
in the tabular statement. Any number of AddColum() statements can be given
in the AFL.
Example 1:
Write an Exploration to filter all the stocks and to display the Close price,
Open price and the Volume.
Filter = 1;//A filter with 1 will display all the stocks. It doesn’t filter any stock.
AddColumn( Close, “Close Price”);
AddColumn( Open, “Open Price”);
AddColumn( Volume, “Volume”);
Example 2:
Write an Exploration to filter all stocks that are traded above the 30 period
Exponential Moving Average. Display with Close price, and EMA(30).
Filter = Close > EMA( Close, 30 );
AddColumn( Close, “Close”);
AddColumn( EMA( Close, 30 ), “EMA – 30” );
Example 3:
Write an Exploration to filter stocks that have a price change of 2% or more.
Run the exploration on the Nifty50 watchlist created earlier and display the
results.
In this example we will describe the exploration process in detail. Open the
Formula Editor and enter the following code.
//Calculate the change in price in percentage
priceChange = ( Close - Ref( Close, -1) )/Ref( Close, -1)*100;
//The priceChange is an array containing %Change in prices.
//Compare this with 2 to get the filter
Filter = priceChange >= 2;
AddColumn( Close,”Close”);
AddColumn( Ref(Close,-1), “Prior Close”);
AddColumn(priceChange,”%Price Change”);
When completed the Editor looks like the Fig. 8.1.

Fig 8.1 Filter for Exploration


We can save the file with a file name such as priceChange.afl. Now click the
Toos Menu and from there click “Send to Analysis window”. The following
screen is displayed.
Fig. 8.2 Analysis Window
Note the file name is displayed in the Formula edit box. At top from left to
right we have options of Scan, Explore, Backtest and Optimize. Below there is
an Apply to Dropbox. We can select from three options All symbols, current
and filter. Click on the filter icon (green funnel). This opens the following
window.

Fig. 8.3 Filter settings window


From the Filter settings dialog box choose Market as Market 1 and Watchlist
as Nifty50. Click Ok. From the Range Dropbox select 1 recent bar(s). Now click
on “Explore”. The result is now displayed. Fig 8.4 shows the result.
Fig. 8.4 Explored list
The result shows three tickers of Nifty50 shares had a price change of 2%
or more on 20/12/2018.
QUIZ:
What will be the result of the following exploration:
Filter = 0;
Solution:
This filters all the stocks. Nothing will be displayed.
Ex ercise 8.1
1. A trader thinks it is best to buy when the Close price is traded above the
100 day simple moving average. While keeping this condition, he wants to
buy only when the 13 period moving average crosses the 40 period moving
average. He wanted to short list possible candidates from the Nifty
LargeMidcap 250 Index. Write an Exploration formula to find outthe results.
Solution:
The Nifty LargeMidcap 250 Index consists of a portfolio of 100 large cap
and 150 mid cap companies listed on NSE. Referring to Chapter 7, first
make a Watchlist called “NiftyLargeMidcap250” containing the Index
Shares. The Ticker Symbols can be downloaded from the NSE website.
The first condition is that the Close has to be above the 100 MA. The
formula for this can be written as:
above100MA = Close > MA( Close, 100 );
above100MA will be an array of True or False elements.
The next condition can be created using the Cross( ) function.
averageThirteen = MA( Close, 13 );
averageForty = MA( Close, 40);
crossThirteenForty = Cross( averageThirteen, averageForty );
The crossThirteenForty will be an array of True or False elements.
These two conditions have to be satisfied for a valid buy. Combine these
two using the AND. The final code will be:
above100MA = Close > MA( Close, 100 );
averageThirteen = MA( Close, 13 );
averageForty = MA( Close, 40 );
crossThirteenForty = Cross( averageThirteen, averageForty );
Filter = above100MA AND crossThirteenForty;
AddColumn( Close,”Close”);
AddColumn( averageThirteen, “MA13”);
AddColumn( averageForty, “MA40”);
Type this code in the Formula Editor and Send to Analysis Window.
Choose the Filter watchlist to NiftyLargeMidcap250 and Explore for 1
recent bar. The result is shown in Fig. 8.5 below.

Fig. 8.5
Suppose we want to filter the entire stocks in the database, choose All
Symbols in the Apply to DropBox. An Exploration gives the following
result as shown in Fig. 8.6.
Fig. 8.6 Exploration Entire database
Chapter 9

Back Testing Trading Strategies

9.1 Introducing Trading Strategies


A trading strategy is basically a set of rules that determine entry and exit
points for a particular stock under consideration. The rules framed should be
objective in nature and there should not be any subjectivity or ambiguity.
Example:
Buy when the Close price crosses above the 50 day SMA.
Sell when the Close price crosses below the 50 day SMA.
The above two rules are objective in nature and there is no ambiguity in its
definition and can be coded in AFL as:
Cross( Close, MA( Close, 50 );
Cross( MA( Close, 50 ), Close);
Trading strategies can be as simple as the one given above or much
complicated with a large number of lines of code. Our intention here is not
learning designing Trading strategies, but only limited to how to make a Back
test of a strategy. Most trading strategies will have a number of rules and will
not be as simple as the one above. Back testing is done on historical data over
a period of time to test the profitability of the strategy. This is very much
necessary before actually putting real money in the market.
9.2 AmiBroker environment for Back testing
The Analysis Window is the interface where you back test your trading
strategy on historical data. This will give an insight into the strengths and
weaknesses of the strategy, before we put real money into trading. You can
reach the Analysis window by a couple of ways. From the Analysis main menu
click New Analysis….This will bring you to the Analysis window. Another
quick option is by clicking the second icon (New Analysis window) on the
Speed Bar Button ( Fig. 0.3 ). Either way, it brings the following Analysis
window as shown in Fig. 9.1.
Fig. 9.1 Analysis window

This we have already seen while discussing the Explore function. We will be
using the Backtest icon( red ellipse ) in the Analysis window. Though, a
window is opened, note that no file is opened by this procedure. You cannot
edit a file directly in the Analysis window. There should be a file with buy and
sell rules to make a back test. If a file is already saved, we can open it from
the Analysis window for back testing. Click on the ‘Pick a file’ icon on the right
extreme of the Analysis window to open the desired file.
9.3 Reserved variables used in Back testing
The first step in Back testing is to have objective rules for entry and exit into
the market. Once the rules are decided, write them as Buy and Sell rules in
AFL. Suppose a person thinks the following rules:
I will buy the stock when its Close price crosses above the 20 day moving
average.
I will sell the stock when its Close prices crosses below the 20 day moving
average.
He has decided the buy and sell rules. These can be converted into AFL as
given below.
Buy = Cross( Close, MA( Close, 20 );
Sell = Cross( MA( Close, 20 ), Close );
Here the Buy and Sell are special Reserved variables which are used for
setting the buy and sell rules for the Back tester. The Back tester engine use
the Buy and Sell array values to determine the back testing process.
A value of 1 in Buy opens a long trade.
A value of 1 in sell closes the long trade.
Similarly “Short” and “Cover” variables can be used.
A value of 1 in Short opens a short trade.
A value of 1 in Cover closes the short trade.
To Back test our system, just click the “Backtest” icon in the Analysis window.
The following are the reserved variable names applicable to the Back testing.
These names should not be used for user defined variables.

Variable Description
Buy Define a buy rule. Enter long position
Sell Define the sell trading rule. Close the long position
Short Define a short sell rule. Enter a short position
Cover Define cover trading rule. Closes the short position
Buyprice Define buying price array
Sellprice Define selling price array
Shortprice Define short selling price array
Coverprice Define buy to cover price array
Exclude A true value of this excludes current symbol from process
Roundlotsize Define round lot sizes used by the back tester
ticksize Define ticksize used to align prices generated by built-in
stops
Pointvalue Allows to read and modify future contract point value
Margindeposit Allows to read and modify future contract margin
positionsize Allows control dollar amount of percentage of portfolio that
is invested into the trade

9.4 A Back testing example


Now let’s see a back testing example. The first step is to create Buy and Sell
rules. For demonstration purposes, we will assume that a good buy will be
when the Macd line crosses the Signal line and at the same time the Adx is
above 30. The long position thus acquired will be closed when the Signal line
crosses the Macd line. The buy and sell conditions described above can easily
be written in AFL very easily as given below:
Buy = Cross( MACD( ), SIGNAL( ) ) AND ADX( ) > 30;
Sell = Cross( SIGNAL( ), MACD( ) );
The above two lines completes the buy and sell rules. The MACD, SIGNAL and
ADX functions are used with its default values. You can experiment with
different values of parameters. The SIGNAL line is the 9 period Exponential
Moving Average of MACD. A typical Buy and Sell signal generated is shown in
Fig. 9.2.

Fig. 9.2 Buy/Sell signals

In the above buy and sell rules the Buy and Sell are arrays and hold either a
True (1) or False (0) value. There are chances that after a buy/sell signal
generation and before closing sell/buy signals further buy/sell signals are
generated. These excessive signals have to be removed. AFL provides a
function for this purpose, the ExRem( ) function. The ExRem function
Removes Excessive signals. We will add ExRem in our code.
Buy = ExRem( Buy, Sell );
Sell = ExRem( Sell, Buy );
Open the Formula Editor and enter the following statements into the editor
window.
//backTest.Afl
//Sample backtesting code
Buy = Cross( MACD( ), SIGNAL( ) ) AND ADX( ) > 30;
Sell = Cross( SIGNAL( ), MACD( ) );
//Remove excessive signals
Buy = ExRem( Buy, Sell );
Sell = ExRem( Sell, Buy );

Save this as backTest.Afl, check syntax and send to the Analysis Window.

To perform the back test, the back tester requires some other parameters sch
as the initial equity to start with, type of trades such as long, short or both,
maximum number of open positions etc. These can be set up on the Settings
window which is available when the Settings tab ( a wrench like icon ) is
clicked. Select the General tab of the Settings window as shown in Fig. 9.3.
Fig. 9.3 Back tester settings
Set the Initial equity as 100000. We will start with an initial capital of 100000.
Set the Positions: to Long. We are testing for long only trades, no shorts.
Set the Periodicity: to Daily. Daily data is used for back testing.
We can set up the Commissions & rates. Settings, if done in this will
appropriately will be reflected in the profit & loss calculation in the trade
report. In our case, for simplicity we are not setting anything in this. A 100 in
the Account Margin: indicates that we are not using any margin amount. Click
on the Trades tab to set up the buy and sell price conditions and trade delays.
Set the Buy price: to Open. This directs to buy at the Open price.
Set the Buy delay: to 1. This ensures a trade delay of 1 day after the signal
generation.
The direction is to buy at the Open price of the next day.
Set the Sell Price: to Close. This directs to sell the Close price.
Set the Sell Delay: to 0. Make the sell on the same day of signal generation.
The selling of the security happens on the signal day at the Close price.
After completing the Trade set ups, we will proceed with the setting of Stops.
Stop losses are very important and the appropriate placement ensures profit
levels and prevent large draw downs. Click on the Stops tab to bring the Stops
Settings window as shown in Fig. 9.4.

Fig. 9.4 Stop settings


Set the Maximum loss stop at 6%.
Set the Profit target at 10%.
Disable the trailing stop. (Trailing stops can be placed at % level, point level or
% of profit level)
Set the N-bar stop at 12 bars. We are allowing the trade to exit after 12 bars.
Now Click on the Report tab.
Set the Result list shows: as Trade list.
Click on the Portfolio tab.
Set Max. open positions: as 4. This means that at any time only four long
positions will be held by the back tester.
Click Ok to close the Backtester settings.
We are almost ready to make the Back test. What remains is the selection of
back testing duration and the portfolio of stocks to be tested. See Fig. 9.5.

Fig. 9.5 Back testing


Click on Filter icon and choose the Nifty50 watchlist. We will test our back
test on the 50 shares of the Nifty50 portfolio. Click on Range and choose
From-To dates. Select the from to dates as 01/01/2015 to 12/31/2018. Now
click the “Backtest” button. The back testing completes within seconds. To see
the results of the back test click on the “Reports” icon ( spread sheet like icon
in Fig. 9.5 ). Part of the report is shown in Fig. 9.6.
The report has 6 tabs namely Statistics, Charts, Trades, Formula, Settings
and Symbols. The Statistics report shows the key metrics of the trade like:
• Initial capital
• Ending capital
• Net profit
• Net profit %
• Exposure %
• Annual return %
• Risk adjusted return %
• Max trade drawdown
• Sharpe ratio
• K-ratio
The above are but few of the metrics in the report. The profitability of the
strategy is evident from the report.
Fig. 9.6 Back test results
The Charts tab shows the Portfolio equity curve and the Drawdown curve.
The Trades tab gives complete details of the actual trades carried out.
9.5 Backtester settings from within the AFL code
While writing the strategy for back testing, we used different settings in the
Setting dialog box. However, we can set the desired settings from within the
code. The settings done in the code will override whatever given in the Settings
dialog box.
To define the maximum open positions, from within the code, we can use the
SetOption function.
SetOption( “MaxOpenPositions”, 4 );
The above code sets maximum open positions to 4.
SetOption( “Initial Equity”, 100000 );
The above code allocates a 100000 as initial equity.
To control the trade delays the SetTradeDelays function can be used.
The syntax is:
SetTradeDelays( buydelay, selldelay, shortdelay, coverdelay )
This sets trade delays applied by the backtester. The settings done from the
Settings window will be overridden by these settings.
SetTradeDelays( 1, 0, 0, 0 );
A buy delay of 1 and sell delay of 0 is set.
Two variables control the price at which the trade is executed. They are the
BuyPrice and SellPrice. Set them appropriately.
BuyPrice = Open;
SellPrice = Close;
To enable more than one security to be traded we have to add PositionSize
variable in our formula, so that funds are appropriately distributed among all
traded symbols.
PositionSize = -25;
The above code indicates 25% of the equity is invested into one trade.
PositionSize = 10000;
The above code implies an amount of 10000 is invested in single trade.
Let totalPosition be the maximum number of open positions.
totalPosition = 4;
SetOption( “MaxOpenPositions”, totalPosition );
PositionSize = -100/totalPosition;
The stoploss placement can be done using the ApplyStop function. The
ApplyStop function instructs AFL to exit the trade when a predetermined
stoploss or target condition is met. The syntax of ApplyStop follows:
ApplyStop( type, mode, amount );
type can be:
stopTypeLoss - maximum stop loss stop
stopTypeProfit – profit target stop
stopTypeTrailing – trailing stop
stopTypeNBar – N – bar stop
mode can be:
0 – disable stop ( stopModeDisable )
1 – amount in percent ( stopModePercent ) or stopModeBars
2 – amount in points ( stopModePoint )
3 – amount in percent of profit (risk)
amount =
percent/point loss/profit trigger/risk amount. This could be a number ( static
stop level ) or an array ( dynamic stop level ).
In our demo case the stop placements can be done as followed.
ApplyStop( stopTypeLoss, stopModePercent, 6 ); //6% maximum stop loss set
ApplyStop( stopTypeProfit, stopModePercent, 10 );//10% profit target settings
ApplyStop( stopTypeTrailing, stopModeDisable, 0 );//Trailing stop disabled
ApplyStop( stopTypeNBar, stopModeBars, 12 );//12 bar exit settings

The entire AFL for the back tested strategy is rewritten below:
//backTest.Afl
//Demo of back testing a strategy
//Set initial equity to 100000
SetOption( “Initial Equity”, 100000 );
//Set buy on next day open at open price and sell on same day close at close
//price
SetTradeDelays( 1, 0, 0, 0 );
BuyPrice = Open;
SellPrice = Close;
//Set maximum open positions and equity allocation
totalPosition = 4;
SetOption( “MaxOpenPositions”, totalPosition );
PositionSize = -100/totalPosition;
//Apply the stop settings
ApplyStop( stopTypeLoss, stopModePercent, 6 ); //6% maximum stop loss set
ApplyStop( stopTypeProfit, stopModePercent, 10 );//10% profit target settings
ApplyStop( stopTypeTrailing, stopModeDisable, 0 );//Trailing stop disabled
ApplyStop( stopTypeNBar, stopModeBars, 12 );//12 bar exit settings
//Define buy and sell rules – the strategy
Buy = Cross( MACD( ), SIGNAL( ) ) AND ADX( ) > 30;
Sell = Cross( SIGNAL( ), MACD( ) );
//Remove excessive signals
Buy = ExRem( Buy, Sell );
Sell = ExRem( Sell, Buy );
Save the file.
Exercise 9.1
1. Write buy and sell rules formula for the trading system described below:
Buy when the prior bar close is below the Bottom Bollinger Band and the
current bar closed above the Bottom Bollinger Band. A buy is also initiated
when the current high has exceeded 2% above the Top Bollinger Band.
Sell rules are exact reverse of the Buy rules.
Solution:
There are two functions in AFL to find the Bollinger Bottom and Top bands
arrays. They are the BBandBot and BBandTop functions. Both accept an
array, a period and a width for standard deviation, as parameters.
bottomBand = BBandBot( Close, 20, 2 );
topBand = BBandTop( Close, 20, 2 );
Buy = ( Ref( Close, -1 ) < Ref( bottomBand, -1 ) AND Close > bottomBand ) OR
Close > 1.02*topBand;
Sell = (Ref( Close, -1 ) > Ref( topBand, -1 ) AND Close < topBand ) OR
Close < 0.98*bottomBand;
Buy and Sell signals generated for the above system for a particular stock is
shown in the Figure 9.7 below.
Fig. 9.7 Buy/Sell Signals

2. Write Buy and Sell rules for a trading system described below:
Buy: A 2% gap up in price on the previous day with at least twice average 5
day volume.
Sell: A 2% gap down in price on the previous day with at least twice average
5 day volume.
Solution:
volAverage = MA( Volume, 5 ); //5 day moving average of volume
Buy = Low > Ref( High, -1 )*1.02 AND Volume >= volAverage*2;
Sell = High < Ref( Low, -1 ) * 0.98 AND Volume >= volAverage*2;
Chapter 10

Revision Exercises

The primary intention of this book was to introduce to the reader the most basic
aspects of AFL programming. It is expected that after reading this book, a beginner
in Amibroker will be able to attempt advanced topics. Before concluding, I will
present few more exercises. Though it may look repetitive, I urge you to do all the
exercises given and compare your results with the solutions provided. Programming
is a task like swimming. To master it, you have to actually do lot of exercises. This
will only make a solid launching pad, from where you can make a take off with
confidence.
1. What will be the output of the following printf statement?
printf( %% );
a) %%
b) %
c) An error will be reported
Solution:
Correct answer is c. The argument to printf statement should be a formatted
string and should be enclosed in double quotes.
2. A number can be converted to a string using the Built-in function NumToStr.
Several strings can be concatenated using the operator +. It is desired to print
the current closing price as “Current closing price is xxxx” (xxxx should replace
the current closing price). Write a printf statement to achieve this.
Solution:
Using the NumToStr function, we can convert the current closing price to a
string. Use + to concatenate with “Current closing price is “. The required
statement is:
printf( “Current closing price is “ + NumToStr( Close ) );
Typical result: Current closing price is 33.950
3. AFL has a function called WriteIf that will return different strings depending
on the true/false value of an expression passed as a parameter. The syntax of
the WriteIf is:
WriteIf( Expression, “True Text”, “False Text” )
This function can be used only within the Guru commentary. If Expression
evaluates to True “True Text” is returned otherwise “False Text” is returned.
Write a formula to display a commentary “Bullish” when the MACD is greater
than zero and “Bearish” otherwise.
Solution:
WriteIf( MACD() > 0, “Bullish”, “Bearish” );
4. In Exercise 3 above, there may be a possibility that the MACD( ) be equal to
zero which we wish to register as “Neutral”. Revise the statement to
accommodate this condition.
Solution:
It is possible to combine several WriteIf calls to handle multiple possibilities.
WriteIf( MACD( ) > 0, “Bullish”, WriteIf( MACD( ) < 0, “Bearish”, “Neutral” ));
5. Which of the following printf statements is wrong?
a) printf( “%g %4.2f”,5,6);
b) printf(“The values are: %g and %3.4f”,23,12);
c) printf(“%g + %f”,3.2,6);
d) printf(“%g %g,45,12,22);
Solution:
None of the printf statements is wrong.
6. Which of the following identifier names are valid?
a) MonthlyMacdValue
b) _AboveAverage
c) ShortTime Average
d) 2ndMovingAverage
e) my_AtR-Value
f) shiftedValue65
g) Bill#
Solution:
a) Legally acceptable name
b) Invalid. The first character must be a letter
c) Invalid name. Spaces are not allowed in identifier names
d) Invalid. Must begin with a letter
e) Illegal. A hyphen is used in the name. This is not allowed
f) Acceptable name
g) Invalid. Special character is not allowed
7. What will be the output of the following code fragment?
Item1 = 500;
Item2 = 200;
Item3 = 150;
billTotal = Item1 + Item2 + Item3;
printf( “Total Cost = “ + NumToStr( BILLTOTAL ) );
a) 850
b) Total Cost = 850.000
c) An error will be reported with variable ‘BILLTOTAL’ used without having
been initialized.
Solution:
Correct answer is b). The three zeros after the decimal point in the result is due
to the default behavior of format specification in the NumToStr. This can be
modified to our requirement.
Kindly note that variable names in AFL are case insensitive. billTotal and
BILLTOTAL represent the same entity.
8. One of the following is a wrong way of commenting. Identify it.
a) shortMALength = 10; //Length of the fast moving average
b) //Define the length of the slow
moving average//
fastMALength = 50;
c) /*Find the cross of moving averages
*/
crossArray = Cross( MA( shortMALength, fastMALength);
Solution:
Answer is b).
9. Which of the following is not an AFL built-in function?
a) MACD( )
b) Typeof( )
c) ATR( )
d) Plot( )
Solution:
Answer is b). Typeof( ) is an operator, though it looks like a function.
10. Write a program to display the days of the last five bars. Enter the formula in
the chart commentary window and show the results. ( Hint: Use the
DayOfWeek function and the switch..case statement ).
Solution:
Depending upon the return of DayOfWeek, we can find out which day it was.
Since the days of the last five bars are to be displayed, we will use an outer for
loop which loops five times. Inside this, a switch..case statement will find out
which day is represented and prints it out. A possible solution is given below.
//Display days of last five bars
arrayDays = DayOfWeek( );
//Outer loop 5 times
for ( bar = 1; bar < 6; bar++ )
{
switch ( arrayDays[ Barcount – bar ] )
{
case 0:
printf( “Sunday\n” );
break;
case 1:
printf( “Monday\n” );
break;
case 2:
printf( “Tuesday\n” );
break;
case 3:
printf( “Wednesday\n” );
break;
case 4:
printf( “Thursday\n” );
break;
case 5:
printf( “Friday\n” );
break;
case 6:
printf( “Saturday\n” );
}
}
Sample output is shown in Fig 10.1 below. Saturday and Sunday will not be
displayed as these are trading holidays.

Fig. 10.1 Last five trading days


11. A cross-over trading system is suggested. Initiate a buy signal when the 10
day exponential moving average line of the Typical Price crosses its own 3
period exponential moving average line. A sell signal is generated when the 3
period smoothed line crosses the 10 period moving average line. Write buy and
sell signal formulas, plot the two average lines and buy and sell shape arrows
on the graph.
Solution:
Typical price is defined as ( High+Low+Close )/3. The Avg gives the Typical
price directly. Avg is an array like the Open, Close etc.
//Cross-over trading system
emaLine = EMA( Avg, 10 ); //10 day EMA of typical price
emaSmoothed = EMA( emaLine, 3 );//Smooth the line - 3 period
//Generate buy and sell arrays
Buy = Cross( emaLine, emaSmoothed );
Sell = Cross( emaSmoothed, emaLine );
//Define a shape parameter so that only one PlotShapes is needed
shape = Buy*shapeUpArrow + Sell*shapeDownArrow;
//Plot the shapes on the graph with buy arrow below the low of the
//candle and sell arrow above the high of the candle. The last parameter
//of the PlotShapes implements this.
PlotShapes( shape, IIf( Buy, colorGreen, colorRed ), 0, IIf( Buy, Low, High ) );

Fig. 10.2 shows a sample plot.


Fig. 10.2 Cross-over Trading

12. Write a formula to detect whether today is a five day high and today’s Close is
below the open.
Solution:
High > Ref( High, -4 ) AND High > Ref( High, -3 ) AND High > Ref( High, -2 )
AND High > Ref( High, -1 ) AND Close > Open ;
There is a condensed version of writing the same.
High > Ref( HHV( High, 4 ), -1 ) AND Close > Open;
13. We are looking for an overbought condition, when the 14 day RSI, has been
remaining above 70 for at least 6 days during a span of 10 days. Write an
exploration formula and short list a portfolio of stocks satisfying this
condition.
Solution:
Launch the Formula Editor and enter the following formula.
Filter = Sum( RSI( 14 ) > 70, 10 ) >= 6;
Click “Filter” and select NiftyLargeMidcap250. In Range select 1 recent bar
and click Explore. Fig. 10.3 shows a short listed portfolio satisfying the
required condition.

Fig. 10.3 Overbought stock list

See Fig. 10.4 for a sample plot for BANKINDIA.

Fig. 10.4 RSI(14) > 70


14. Write a function to calculate the Chaikin Money Flow Index. Write it with a
user variable parameter from within the chart. Use this function to display the
indicator in a lower pane of the chart.
Solution:
Chaikin Money Flow is an oscillator, that measures the buying and selling
pressure over a set period of time. The indicator fluctuates above and below
the zero line. The link below gives a description of the CMF indicator.
https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:chaikin_money_flow_cmf

Calculation steps for a N-period CMF indicator is given below.


1. Money Flow Multiplier = [ ( Close – Low ) - ( High – Close ) ] / ( High – Low )
2. Money Flow Volume = Money Flow Multiplier * Volume for the period
3. N-period CMF = N-period sum of MFV / N-period sum of Volume
When calculating the money flow multiplier, in the event of High and Low
becoming equal, a division by zero error will occur. We have to guard against
this in our AFL code.
//Chaikin Money Flow
function ChaikinMF( period )
{
mfm = IIf( H != L, ( ( C – L) - ( H – C ) ) / ( H – L ), 0 );
mfv = mfm * Volume;
cmf = Sum( mfv, period ) / Sum ( Volume , period );
return cmf;
}
//Define period for Chaikin MF
cmfPeriod = Param( “CMFPeriod”,21,10,100 );
//Build the ChaikinMF indicator array by calling the function
cmf = ChaikinMF( cmfPeriod );
//Define color of the plot. Green for above zero, Red for below zero
cmfColor = IIf( cmf > 0, colorGreen, colorRed );
//Plot the indicator
Plot( cmf, “Chaikin MF”, cmfColor, styleHistogram | styleThick );
Fig. 10.5 shows a plot for Chaikin Money Flow.

Fig. 10.5 Chaikin Money Flow

15. How can you override the Delay settings set in the Backtester settings dialog
box?
Solution:
By using the SetTradeDelays( ) in the AFL code.
SetTradeDelays( BuyDelay, SellDelay, ShortDelay, CoverDelay );

Now we have come to the end of this short course. By this time you should be in a
position to write your own AFL code. If you have a trading idea in your mind, try
converting it into AFL formula. Any quantifiable idea, without ambiguity, can be
converted to a formula. Wish you good luck.
ADX-PPO PINCH TRADING By the same author is available for download from
Amazon.

https://www.amazon.com/ADX-PPO-PINCH-TRADING-AJAN-K-ebook/dp/B07HRLJ
RNX/ref=sr_1_1?ie=UTF8&qid=1549517031&sr=8-1&keywords=ajan+k+k
https://www.amazon.in/ADX-PPO-PINCH-TRADING-AJAN-K-ebook/dp/
B07HRLJRNX/ref=sr_1_1?ie=UTF8&qid=1549517122&sr=8-1&keywords=ajan+k+k

You might also like