Русский 中文 Español Deutsch 日本語 Português
MQL4 Language for Newbies. Difficult Questions in Simple Phrases.

MQL4 Language for Newbies. Difficult Questions in Simple Phrases.

MetaTrader 4Examples | 4 July 2007, 09:49
7 205 3
Antoniuk Oleg
Antoniuk Oleg

Introduction

This is the second article from the series "MQL4 Language for Newbies". The first article "MQL4 Language for Newbies. Introduction" described what can be done using MQL4, we learned to write simple scripts, studied out what a variable is, learned to work with variables, analysed functions, arrays, built-in arrays and variables, cycles 'for' and simple and complex conditions. Now we will examine more complex and advanced constructions of the language, learn new options and see, how they can be applied in everyday practice. You will get acquainted with a new cycle type 'while', new condition type 'switch', operators 'break' and 'continue'. Besides you will learn to write your own functions and work with multidimensional arrays. And for a dessert I have prepared an explanation about a preprocessor.


Advice

Do not start reading this article not having fully understood the first one. You will make a lot of errors and still will understand nothing. This article is based on the old material, so do not hurry! And I want to make you easy - the difficulties that you will meet when learning the new material, are deceitful. The time will come, when you will not have to think over how the cycles are written, what conditions to put - everything will be done mechanically. The more you work with MQL4 language, the easier it will be for you to use it.

New Type of Cycles - while

I would like to mention, that the cycle 'for', described in the previous article, is a universal one and can substitute for any other cycle type that we will learn now. But it is not always convenient and suitable. Sometimes it is more efficient to use while. Soon you will understand, the use of which cycle type is more rational. Let's make a task in two ways: let's find a total volume of all bars, using both cycles and see the difference:

// using the cycle for
double sum = 0.0;
 
for(int a = 0; a < Bars; a++)
    sum += Volume[a];
 
// now using while, the result is the same
double sum = 0.0;
int a = 0;
 
while(a < Bars)
  {
    sum += Volume[a];
    a++;
  }
You see, now the counter is declared and used separately. 'While' here shows that while the condition is true, the cycle goes on. Here is a general form:
while(condition of cycle fulfillment)
  {
    code;
  }

Here is an easier example:

while(I havent eaten up the apple) // condition
  {
    // what to do, if the condition is not fulfilled
bite more;                 
  }
In fact the cycle 'while' differs from the cycle 'for' only in the absence of a counter. If you don't need it, use while, though it is not necessary. For example, I often use while with a counter, it is a matter of taste. Like in case with for, if the cycle body includes only one instruction, you can omit bracers. Also for your development remember the meaning of the word iteration. It is one of multiple operations (repetitions), undertaken by the cycle. I.e. performing a cycle body once means that one iteration is performed.

New Type of Conditions - switch

Like in case with cycles, it should be noted that switch can be substituted by a combination of familiar to you conditions 'if' and 'else'. The construction 'switch' is used when you need to perform some actions depending on the value of a variable. This is like an ordinary mode switch in a microwave oven. For example, imagine that you write an Expert Advisor, and it changes its behaviour depending on the market situation. Let a variable int marketState be responsible for it. It may take the following meanings:

  • 1 - uptrend
  • 2 - downtrend
  • 3 - flat

No matter how this position is defined, our task is to realize some kind of mechanism, so that depending on the market situation the Expert Advisor performed corresponding actions. Well, you know how to do this. The most evident variant is the following:

if(marketState == 1)
  {
    // trading strategy for an uptrend
  }
else 
    if(marketState == 2)
      {
        // strategy for a downtrend
      }
    else 
        if(marketState == 3)
          {
            // strategy for a flat
          }
        else
          {
            // error: this state is not supported!
          }

Here are some peculiarities:

  • all conditions are conducted with one and the same variable;
  • all conditions come to comparing the variable to one of the meanings that the variable can accept.

So, all this refers also to the switch structure. Here is a code, using switch, the result is the same:

switch(marketState)
  {
    case 1:  // trading strategy for an uptrend
             break;
    case 2:  // strategy for a downtrend
             break;
    case 3:  // strategy for a flat
             break;
    default: // error: this state is not supported!
             break;
  }

Note, that first we define, what variable will be compared:

// switch - key word, marketState - 
// a variable for comparison
switch(marketState)

and then indicate, what should be done in specific cases:

case 1:                        // case - key word; 
   // trading strategy         // if marketState is equal to 1, then 
   // for an uptrend           // perform this code
   break;                      // key word that indicates  
                               // the end of actions in this case
 
case 2:                        // if marketState is equal to 2, then
    // startegy for            // perform this
    // a downtrend          
    break;                     // end
 
case 3:                        // identical
    // strategy for flat
    break;
 
default:                       // otherwise, perform this
    // error: this   
    // state is not 
    // supported!
    break;

In a general view switch has the following form:

switch(a variable for comparison)
  {
    case [a variable value]:
    // a code for this case 
    break;
   
    case [another value of the variable]
    // a code for this case
    break;
   
    default:
    // a code for all other cases
    break;
  }

Use switch, when comparing one variable to several values, and a certain code block corresponds to a value. In other cases use a common combination of 'if' and 'else' conditions. Sometimes you need to perform a code in several values of a variable. For example, if marketState == 1 or 2, then perform a certain code. This is how it can be done using switch:

switch(marketState)
  {
    case 1:  // if marketState is equal to 1
    case 2:  // or if marketState is equal to 2, then
             // perform this
             break;
 
    default: // in any other case perform 
             // this code
             break;
  }

Operators: continue and break

We have just seen the operator break. It is used to escape from the body of switch. Besides you can use it for escaping from a cycle. For example, if you don't need the execution of a cycle in some conditions. Suppose we need to find the amount of first bars, needed to enclose 1000 points. We can write the following code:

int a = 0;
double volume = 0.0;
 
while(volume < 1000.0)
  {
    volume += Volume[a]; // equivalent to volume = volume + Volume[a]; 
    a++;
  }
 
// now variable "a" includes the amount of bars, the volume of their sums 
// is no less than 1000 points

Now let's write an analogous code, using the operator break:

int a = 0;
double volume = 0.0;
 
while(a < Bars)
  {
    // if the volume is exceeds 1000 points, then
    if(volume > 1000.0) // exit the cycle   
        break;            
 
    volume += Volume[a];
    a++;
  }

You see, the operator break is easy-to-use and allows to avoid unwanted cycle iterations. One more useful operator continue is intended for 'omitting' unwanted iterations. Suppose we need to calculate a total volume, but we mustn't take into account the volume of bars in the moments of important news. As you know, important news involve large volumes of points. Let's pretend to be naive children and suppose that the bar volume 50 points and more is news. To solve this task, let's use the continue operator:

int a = -1;
double volume = 0.0;
 
while(a < Bars)
  {
    a++;
    // if the volume exceeds 50 points, then it must 
    // be news, omit it 
    if(Volume[a] > 50.0)          
        continue;          
    volume += Volume[a];
  }

You see, the use of the continue operator is quite trivial, but sometimes it may help you. It is clear, that this script is intended for small timeframes.


Writing Own Functions

But why do we need them? The fact is, that very often you can find duplication of your code. I.e. you will use one and the same set of instructions in different cases. To save your time and strength you can write this duplicated code into a separate function. And when you need it, you can simply write the name of the function and it will do everything for you. Let's view how it works. Suppose you need to find the color of a candlestick. It is known that a white candlestick is the one that closed higher than opening, the black one - vice verse. Let's write a code for determining a color of a candlestick:

bool color; // as there are only 2 variants 
            // (white or black candlestick), 
            // then suppose that the velue 
            // false corresponds to a black 
            // candlestick, and true - white
 
if(Close[0] > Open[0])
    color = true;      // white candlestick
    
if(Open[0] > Close[0])
    color = false;     // black candlestick

That is all, now the variable color contains the color of the last candlestick. To determine the color of another candlestick, for example, the last but one, you need to change the index 0 to 1. But aren't you going to put this code everytime you need to find the color of a candlestick? And what if there are dozens of such cases? That is why we need functions. Let's think, how it should work. Such function should accept one argument - the index of a candlestick, which color you need to determine, and return the color - a variable of bool type. Let's imagine that the function is written and we activate it:

bool color; // here will be the color of a wanted candlestick
 
color = GetColor(0);

As you have guessed, our function is named GetColor. In this function call we wanted to find the color of the last candlestick. That is why the only argument is equal to zero. The function returns the color of a candlestick, so we make an assignment at once. It is a very important moment! A variable is created inside the function, then its value will replace the function call. Ultimately the function call and the code of the function determining, described above, will give the same result - the variable color will contain the color of the last candlestick, but using functions we spend less strength.

And now let's unexpectedly get back to the code of an empty script. The fact is, that it already contains a full description of the function start(). The most interesting thing is that all this time you've been writing scripts in this function! When you start your script, the terminal activates the function start(). Let's examine the code of an empty script:

int start()

This line is very important! It includes the name of the function, i.e. a keyword, which you will write in order to activate this function. In our case it is 'start'. It also contains type of a return value - int. It means, that after the performance of the function it will return us some value of int type. The brackets contain list of arguments, but in our case the function does not accept any parameters.

Then in bracers you see the function description, i.e. a code that will be performed at function call:

{
    //----
    // a code that will be performed 
    // at the function call.
    //----
    return(0);
  }

It is clear that we wrote a code in the body of start() function. At the end of the function we see the operator return, which returns the function value. In our case it returns zero.

Now look at the general form of writing a function:

[type of return value] [function name] ([list of arguments])
  {
    // function code
    return([a value, which the function returns]);
  }

Now let's go back to our candlesticks and GetColor function. Look at the code of this function:

bool GetColor(int index)
  {
    bool color;
    if(Close[index] > Open[index])
        color = true;      // white candlestick
    if(Open[index] > Close[index])
        color = false;     // black candlestick
    return(color);
  }

Let's dwell on the first line:

bool GetColor(int index)

Here we have: bool - type of a return value; GetColor - function name; int - type of argument; index - argument name. Note, we use index inside the function body, but in the function call this name is never mentioned. For example:

bool lastColor = GetColor(0);

Then:

{
   bool color;
   if(Close[index]>Open[index])
       color=true;      // white candlestick
   if(Open[index]>Close[index])
       color=false;     // black candlestick

This function body is a general code, which will be performed at each function call. After that:

return(color);
}

The operator return indicates, what the function should return. The return value should correspond to the type, determined at the very beginning. If needed, you may use several operators 'return' in one function, for example:

bool GetColor(int index)
  {
   if(Close[index] > Open[index])
       return(true);      // white candlestick
   if(Open[index] > Close[index])
       return(false);     // black candlestick
  }

It is clear, that the use of several return operators allowed to avoid the variable color. Besides, in the operator return you may use even logical expressions:

return(Close[index] > Open[index]);

It is possible, because the operators of comparison also return variables of bool type (true or false) like some other common functions. Looks like difficult, but soon you will get used to it.

Now let's get back to a list of arguments. Only the argument int index is used in our function. If you need to use several arguments, enumerate them comma separated:

bool SomeСomplicatedFunction(int fistArgument, int secondArgument, 
                             sting stringArgument)

In order to refer to the arguments, use their names like in the previous function. When a function call with several arguments, pay attention to sequence of arguments order: do not mix anything! If the function should not return any value, use the keyword void, to indicate it. Note, that the return operator in this case is not used:

void function()
  {
    // code
  }

One more detail: you may set the values for the function arguments on default. What is this? Suppose you have written a complex function that includes 5 arguments, which influence its behaviour. But several last arguments almost always are used with the same values. Only for two dozens of function call you need to use different values. In order not to indicate each time the values of the last arguments, which are almost always the same, default values of arguments are used. In such a case you simply omit the last arguments, as if they do not exist, though actually they are used, but the values are assigned on default. When you meet that special case, you indicate all the arguments. Let's see how we can declare a function with default arguments:

void someFunction(int argument1, int argument2, 
                  int specialArgument = 1)
  {
    // code
  }

You see, everything is easy: we assign a needed value to a needed argument and now it can be omitted at function call:

someFunction(10,20);   // we omitted the last argument, but 
                       // actually it is assigned a value by default
 
someFunction(10,20,1); // this activation is fully identical to the previous one
 
someFunction(10,20,2); // here we indicate another value,  
                       // it is a rare case

You can indicate as much default values of arguments as you want. But remember an important rule: they all should be placed at the end. For example:

void someFunction(int argument1, int argument2, 
                  int specialArgument = 1)   // all right
 
void someFunction(int argument1, int argument2 = 10, 
                  int specialArgument=1)     // all right
 
void someFunction(int argument1, int argument2 = 10, 
                  int specialArgument)     // wrong! default
                                           // values must stay
                                           // at the end of the 
                                           // list of arguments
                                                                           
void someFunction(int argument1 = 0, int argument2 = 10, 
                  int specialArgument = 1) // you can assign  
                                           // default values  
                                           // to all arguments

Multidimensional Arrays

You will often use arrays during programming, and in major cases one-dimensional arrays will be enough. But in some cases you will need two-dimensional, three-dimensional arrays. Now you will learn to use them.

To begin with, let's visually present one-dimensional arrays, revise declaration, initialization, indexes and value:


Any one-dimensional array can be presented as a row of values of one type. Look, how different references to a one-dimensional array will be processed:



Two-dimensional arrays are like common tables, look:


As seen from the picture, two-dimensional arrays already have two indexes for reference to the value: the first index determines a row, the second index - a column. Like in one-dimensional arrays, a list of values is used for initialization. This is how the values of the table cells are referred to:




You see, everything is clear. Let's see, how we can go through all values of a two-dimensional array. 2 cycles should be used:

int array2D[3][3]={10,20,30,
                   40,50,60,
                   70,80,90};
 
for(int y=0;y<3;y++)
   for(int x=0;x<3;x++)
      MessageBox("array2D["+y+"]["+x+"]="+array2D[y][x]);

In this example the referencing goes downwards from left to right. Just for training try to change the direction, for example upwards.

Three-dimensional arrays differ only in the presence of one more index for referencing to cells values. A three-dimensional array can be easily presented as several tables (two-dimensional arrays). This is how we can go through all the elements of a three-dimensional array:

int array3D[3][3][3] = {11, 12, 13,
                        14, 15, 16,
                        17, 18, 19,
 
                        21, 22, 23,
                        24, 25, 26,
                        27, 28, 29,
 
                        31, 32, 33,
                        34, 35, 36,
                        37, 38, 39};
 
for(int z = 0; z < 3; z++)
    for(int y = 0; y < 3; y++)
        for(int x = 0; x < 3; x++)
            MessageBox("array3D["+z+"]["+y+"]["+x+"]=" + 
                       array3D[z][y][x]);

Thoroughly study out two- and tree-dimensional arrays - it is very important. Once again very attentively look through the explanatory pictures. A lot of different tasks are solved using arrays, so devote enough time to them and they will be very useful for you in future. If you understand the principle of working with arrays, you will have no trouble working with any n-dimensional array.


Some Functions for Working with Arrays

Let's start with simple functions.

int ArraySize(object array[]);

This function returns the amount of elements that the array contains. It works with all types. For example:

// create two different arrays
int arrayInt[] = {1, 2, 3, 4};           
double arrayDouble[] = {5.9, 2.1, 4.7};
// here store the amount of elements 
int amount;                        
 
amount = ArraySize(arrayInt);      // note: 
                                   // to define a specific 
                                   // array, you need to indicate
                                   // only its name.
                                   // Now amount is equal to 4
 
 
amount = ArraySize(arrayDouble);   // amount is equal to 3

Next function:

int ArrayInitialize(object array[],double value);
ArrayInitialize

assigns a value to all array elements, returns the amount of elements, to which a value was assigned. Use this function with arrays of int and double type.


Next:

int ArrayMaximum(double array[], int count = WHOLE_ARRAY, 
                 int start = 0);
int ArrayMinimum(double array[], int count = WHOLE_ARRAY, 
                 int start = 0);

These two functions return the index of a maximal and minimal cell value. To use them, simply indicate in what array they should be looked for:

int array[] = {10, 100, 190, 3, 1};
// will be returned 1, because array[1] - maximal value
ArrayMaximum(array);
// will be returned 4, because array[4] - minimal value
ArrayMinimum(array);

Next:

int ArrayDimension(object array[]);

Using these functions you can determine the dimensionality of an array, I.e. you can determine, whether it is one-dimensional, two-dimensional or n-dimensional. For example:

int array1D[15];
int array4D[3][3][3];
 
ArrayDimension(array1D); // get 1
ArrayDimension(array3D); // 3

Here are more complex and useful functions:

int ArraySort(double&array[], int count = WHOLE_ARRAY, int start = 0,
              int sort_dir = MODE_ASCEND);

This function sorts elements. If you do not directly indicate the arguments on default, for example this way:

int array[5] = {1, 10, 5, 7, 8};
 
ArraySort(array);

elements will be sorted ascending. You can use additional parameters to specify the function behaviour:

  • int count - the number of elements that should be sorted
  • int start - the index of an element, from which the sorting should be started
  • int sort_dir - the direction of sorting (ascending - MODE_ASCEND or descending - MODE_DESCEND)

Here you should wonder: what is MODE_ASCEND and MODE_DESCEND?? According to int, it must be an integer! Don't be nervous, everything will strighten out in the next part - "Preprocessor". For example, if you need to sort descending 5 elements starting from the second, indicate something like this:

ArraySort(array, 5, 1, MODE_DESCEND);

And the last function for today:

int ArrayCopy(object&dest[], object source[], int start_dest = 0,
              int start_source=0, int count=WHOLE_ARRAY);

It is used for copying one array into another. Let's view the obligatory parameters:

  • dest[] - into which array to copy
  • source[] - from which array to copy

Optional parameters:

  • start_dest - index of an array element, into which the copying is performed
  • start_source - index of an array element, from which the copying is performed
  • int count - the amount of elements for copying

The function returns the amount of copied elements. Use ArrayCopy very carefully: make sure that arrays have enough capacity, when you copy in them!


Preprocessor

What is this? Preprocessor is a special mechanism, intended for processing a source code. I.e. first the preprocessor prepares a code and then conveys it for compilation. Today we will learn one more useful option - constants.

What is the main point? To understand this, let's remember an example from the part about switch:

switch(marketState)
  {
    case 1:
    // trading strategy for an uptrend
    break;
 
    case 2:
    // strategy for a downtrend
    break;
 
    case 3:
    // strategy for a flat
    break;
 
    default:
    // error: this state is not supported!
    break;
  }

Here we activated a mechanism, that acts in different ways depending on the market state. Remember? So, it would be easier and more discriptive to write something like TREND_UP, TREND_DOWN, FLAT instead of 1, 2 and 3:

switch(marketState)
  {
    case TREND_UP:
    // trading strategy for an uptrend
    break;
 
    case TREND_DOWN:
    // strategy for a downtrend
    break;
 
    case FLAT:
    // strategy for a flat
    break;
 
    default:
    // error: this state is not supported!
    break;
  }

In this case a source code appears much easier to understand and more vivid, doesn't it? So, constants allow to substitute TREND_UP, TREND_DOWN and FLAT by corresponding values 1, 2 and 3 before the compilation. All you need is to indicate, what the preprocessor should change. It is done by means of the preprocessor directives, which start from a special symbol "#". The preprocessor directives should be placed in the beginning of a source file together with other directives. Let's look at a complete example of using constants:

//+------------------------------------------------------------------+
//|                                                 preprocessor.mq4 |
//|         Copyright © 2007, Antonio Banderass. All rights reserved |
//|                                               banderassa@ukr.net |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2007, Antonio Banderass. All rights reserved"
#property link      "banderassa@ukr.net"
 
#define TREND_UP   1
#define TREND_DOWN 2
#define FLAT       3
 
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
   MessageBox("TREND_UP=" + TREND_UP + " TREND_DOWN=" + TREND_DOWN + 
              " FLAT=" + FLAT);
 
   return(0);
  }

Note, that we have placed the constants declaration in the beginning of the file, under other preprocessor directives. Let's examine the declaration more closely:

#define TREND_UP 1

First we write the key word #define. It shows to the preprocessor that after that comes constant declaration. Then we write the name of the constant, its identifier, i.e. a word, through which you will refer to the constant value. In our case it is TREND_UP. The value - 1 comes after that. Now when the preprocessor sees TREND_UP in the source code, it will replace it by 1, the same is with all other constants. Here is the source code of our example before processing by a preprocessor:

int start()
  {
   MessageBox("TREND_UP=" + TREND_UP + " TREND_DOWN=" + 
              TREND_DOWN + " FLAT=" + FLAT);
   return(0);
  }

and after:

int start()
  {
   MessageBox("TREND_UP=" + 1 + " TREND_DOWN=" + 2 + 
              " FLAT=" + 3);
   return(0);
  }

Now you should understand, what MODE_ASCEND and MODE_DESCEND from the previous part mean. They are constants with correspondent values.



Conclusion

So, you have learned a lot of new material from this article: new cycle type - while; new condition type - switch; operators break and continue. You learned to write own functions and work with multi-dimensional arrays, learned how to use constants. All this is your main instrument, a basis for writing more advanced things, like user's indicators and expert advisors. That is why make sure, that you have thoroughly studied out the material of the article, because it is very important and will be constantly used in future.

Attached files |
preprocessor.mq4 (0.86 KB)
Last comments | Go to discussion (3)
[Deleted] | 6 Mar 2008 at 23:01
Great article, thank you very much!
molanis
molanis | 11 Mar 2010 at 21:42
If you want to avoid coding or learning mql you can use a visual tool to create expert advisors (it's codeless so you don't write code) You can get it at www.molanis.com
Ariyanto
Ariyanto | 10 May 2019 at 08:23

hi author,


you had a typo in ArrayDimension section :

int array1D[15];
int array4D[3][3][3];  // --> it should be array3D[3][3][3];
 
ArrayDimension(array1D); // get 1
ArrayDimension(array3D); // 3

Thank you.
Keep the good work.


              
ZUP - Universal ZigZag with Pesavento Patterns. Part 1 ZUP - Universal ZigZag with Pesavento Patterns. Part 1
The article gives a short description of ideas underlying indicator ZUP - Universal ZigZag with Pesavento Patterns. The article also describes indicators of ZigZag embedded in ZUP.
Running MetaTrader 4 Client Terminal on Linux-Desktop Running MetaTrader 4 Client Terminal on Linux-Desktop
Description of a step-by-step Linux-desktop setup using a non-emulator wine for running MetaTrader 4 Client Terminal on it.
The Basic of Coding A Hedge Expert Advisor The Basic of Coding A Hedge Expert Advisor
An example of hedge Expert Advisor is just presented in this article. The author will choose its very own favorite hedge pair that is EURJPY & GBPJPY. It always moves the same way, easier to set the hedging order type.
MQL4 Language for Newbies. Introduction MQL4 Language for Newbies. Introduction
This sequence of articles is intended for traders, who know nothing about programming, but have a desire to learn MQL4 language as quick as possible with minimal time and effort inputs. If you are afraid of such phrases as "object orientation" or "three dimensional arrays", this article is what you need. The lessons are designed for the maximally quick result. Moreover, the information is delivered in a comprehensible manner. We shall not go too deep into the theory, but you will gain the practical benefit already from the first lesson.