
From Basic to Intermediate: Variables (III)
Introduction
The material presented here is for educational purposes only. Under no circumstances should the application be viewed for any purpose other than to learn and master the concepts presented.
In the previous article "From Basic to Intermediate: Variables (II)", we discussed and explained how to use static variables in our code. Such variables allow us to avoid unnecessary use of global variables. So, we are done with the main variables. However, there remains the question of the type of data that each variable can contain, which still has some relevance to this problem. I will not consider this aspect within the framework of the topic of variables. We will talk about this in a separate topic.
However, if we have already talked about local and global variables, how and why to declare a variable as a constant, and even how to use static variables, what else is left to say on this topic? Although many people don't think so, there is a special type of variable that can often be considered a constant, but it is still a special type of variable. I am talking about functions. A function is a special type of variable, although, in principle, many programmers do not think so.
Let's start a new topic, in which we will try to understand that a function is a special variable.
Special variables: Functions
When we talk about functions, the first thing that comes to mind for people with basic programming knowledge is the use of an additional call to execute some procedure in our application. However, this definition is not entirely adequate, and this is due to the fact that there are subroutines that are not functions, but procedures. There is a slight conceptual difference between these two types. The main difference is that a function returns a value, or more precisely, a data type. Unlike a procedure, which does not return any data after execution. However, both types can modify and return different values through their arguments.
Don't worry about this right now, I'm just giving a brief overview of what will be discussed in more detail later. But this small distinction is necessary so that you understand why I say that a function is a special type of variable.
It is important to draw a parallel between different languages here. This is because, depending on the language we want to learn in the future, the concept discussed here may not be applicable. This is because each language performs tasks in its own way.
In scripting languages such as JavaScript and Python, functions are typically implemented as a form of constant variable. However, in C and C++, a function can act as either a constant variable or a variable capable of modifying the value of a static variable within the function without requiring arguments to be passed. This may sound unusual or even counterintuitive to some, as it seems like something that shouldn't be possible. However, in C and C++, pointers allow for this functionality. By using a pointer to reference a static variable inside a function, external code that calls the function can modify the value of that static variable.
This characteristic makes programming in C and C++ both challenging and powerful. While it introduces potential risks, it also provides a high degree of flexibility for developers. In pure MQL5, we find ourselves somewhere in between JavaScript/Python's function handling and the capabilities of C and C++. While we do not have the same level of flexibility as in C and C++, MQL5 offers a more secure coding environment.
To move beyond theory, which can be quite dry, let's explore some simple examples of how functions can be used as constant variables. A common scenario would be using a function as a means of defining a value – except that, instead of directly assigning a value, we use a more meaningful and representative name.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(One_Radian()); 07. } 08. //+------------------------------------------------------------------+ 09. double One_Radian() 10. { 11. return 180. / M_PI; 12. } 13. //+------------------------------------------------------------------+
Code 01
In Code Example 01, we have a very simple example. When this code is executed, you will see the result shown below.
Figure 01
At this point, you may be asking yourself: Why do things this way? Wouldn't it be easier to place the calculation from line 11 directly in line 06 and print the result? Yes, it would be. However, if that was your thought, it means you haven't quite grasped the point I am trying to make with this example. The purpose here is NOT to perform the calculation itself. The goal is to generate a constant that can be used globally throughout your code. If every time you need the value of a constant, you have to retype the code to create it, your work when improving or even correcting the code will be immense. However, if everything is contained within a function with a meaningful name that generates the constant, the process will be much simpler, faster, and smoother, making you more efficient as a programmer.
Consider the case of developing a 3D program where you need to convert values from degrees to radians – not just in one line of code, but across hundreds of lines scattered throughout your entire codebase. Let me ask you: Which would be easier, typing the calculation every time, or placing everything into a function like the one shown in Code Example 01?
Now, that may be one application, but if you found it trivial or pointless, how about we create something a bit more elaborate using the knowledge we've gained from the previous articles? This can be done as shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Counting(); 07. Print(Counting()); 08. Counting(); 09. Counting(); 10. Print(Counting()); 11. } 12. //+------------------------------------------------------------------+ 13. ulong Counting(void) 14. { 15. static ulong Tic_Tac = 0; 16. 17. Tic_Tac = Tic_Tac + 1; 18. 19. return Tic_Tac; 20. } 21. //+------------------------------------------------------------------+
Code 02
When Code Example 02 is executed, you will get a result similar to the one shown below:
Figure 02
In other words, if the first example didn't seem very suitable to you, perhaps the second one might. This is because we may need to count how many times an event has occurred in a given application. Many would use a global variable to track such events. However, as discussed in previous articles, there is a downside to using global variables. By using a function with a similar purpose, we eliminate the inconveniences of global variables while ensuring stability and security in our code. At the same time, it allows us to modify the logic in a much simpler, faster, and more practical way.
Predefined variables
In addition to the topics we've already covered regarding variables and constants, there are other important types in MQL5 that need to be mentioned. This is because they are integral to programs that make extensive use of the language features.
Here, we'll briefly touch on these special types of variables. It’s essential that you understand these variables exist and that you can use them for specific tasks.
In the documentation, you can find more details about these variables under Predefined Variables. However, we won't focus on any specific one here. What's important is understanding that, although they are called predefined variables, they are not typical variables. In our code, they are treated as constants. However, for MetaTrader 5, they are indeed variables. Despite being treated as constants in our code, there are some cases where we can use certain functions or procedures in MQL5 to modify the value of these variables.
It is very important, dear reader, that you understand this. If you attempt to modify the value of one of these variables directly, you will encounter an error, and your code will not compile. By "directly modifying," I mean trying to do something like the example shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(_LastError); 07. _LastError = 2; 08. Print(_LastError); 09. } 10. //+------------------------------------------------------------------+
Code 03
If you are new to MQL5, pay close attention to what I'm about to explain. In line 6 of Code Example 03, we are instructing the terminal to print the current value stored in the predefined variable _LastError. Since this is a predefined variable in MQL5, there is no need to declare it. The compiler can recognize it automatically.
However, when the compiler encounters line 7 in Code Example 03, an error will be triggered, as you can see in the image below:
Figure 03
Why does this happen? The reason is that these predefined variables in MQL5 are treated as constants at the programming level. However, at the code execution level in MetaTrader 5, these same variables are not seen as constants.
This may seem confusing and difficult to understand at first, especially for those just starting in programming. However, before delving into more details about predefined variables, dear reader, you must understand the following: Since these variables will exist in any MQL5 code, you should NOT attempt to create another variable with the same name as these predefined variables. Doing so would violate the security protocols imposed by the platform.
That said, despite the fact that attempting direct access, as shown in Code Example 03, results in an error, the MQL5 language itself provides ways to modify some of these predefined variables for general purposes. There is no specific rule dictating what value you should assign to these variables. However, any modification made, when possible,should be for a justified reason. You should not modify the value of these variables just because it is technically possible, unless you are studying the language itself, as we are doing here.
For example, by using the SetUserError function, we can set a value for the predefined variable _LastError. But not just any value. The value we can assign is limited, as there is a list of error codes reserved for use by MQL5 itself. For many, this may seem discouraging, as they might consider it a limitation. However, this is not necessarily the case.
To see this in practice, you can use the code shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(_LastError); 07. SetUserError(2); 08. Print(_LastError); 09. } 10. //+------------------------------------------------------------------+
Code 04
This code will indeed compile and can be executed. However, when you run it in MetaTrader 5, you will see something similar to what's shown in the image below.
Figure 04
What's that strange number? Wait a minute. In line 7 of Code Example 04, we're assigning the same value as in line 7 of Code Example 03. Okay, Code Example 03 didn't compile for the reasons we've discussed, but still, this value in Figure 04 is not what I was expecting.
Indeed, dear reader, the value shown is the one you intended to assign to the _LastError variable, but it has been offset. This is done to prevent it from conflicting with any predefined error values. To view the exact value you wanted to assign to _LastError, a small adjustment is required. But before we look at that adjustment, let's first understand something else. Typically, many programmers prefer not to use the name of a predefined variable directly in the code. It's generally more advisable to use a function that returns the value of a predefined variable. While nothing prevents you from using the code as shown above, the more common (and generally preferred) approach would be to use a code structure like the one shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(GetLastError()); 07. SetUserError(2); 08. Print(GetLastError()); 09. } 10. //+------------------------------------------------------------------+
Code 05
Note that the code essentially does the same thing. However, for many, code 05 is more readable than code 04. This way we let other programmers reading the code know that we are accessing the value of a predefined variable. And did you notice that what was discussed in the previous topic is also applicable here? The way in which predefined variables are accessed for subsequent reading can be any. But the method of changing their values will always be the same. For example, to remove any value from the _LastError variable, we MUST NOT use the procedure shown in line 7 of the above codes. If we do so, we will assign a new error value. The correct way to clear or remove errors from _LastError is to use another procedure provided in MQL5. It is shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(GetLastError()); 07. SetUserError(2); 08. Print(GetLastError()); 09. SetUserError(0); 10. Print(GetLastError()); 11. ResetLastError(); 12. Print(GetLastError()); 13. } 14. //+------------------------------------------------------------------+
Code 06
Now, if this is your first time encountering this, pay close attention, dear reader. When this code is executed, the result will be as shown below.
Figure 05
Notice the following: When line 6 is executed, the first line you see in Figure 05 will be printed, indicating that there is no error or value in the _LastError variable. Once line 7 is executed, we assign a value to the _LastError variable, as previously seen. However, many beginners attempt to use line 9 to clear the _LastError variable. But when doing this and printing the result, we get a value other than zero. Why? The reason is the same as I mentioned earlier. When we use SetUserError to set a value in _LastError, the value is offset. This is why trying to set any value using this function will not result in the expected outcome. However, since our goal here is to set the value of zero in the _LastError variable, the correct way to do this is by using line 11. When the procedure in line 11 is executed, the value zero will indeed be assigned to _LastError.
This should be noted for all other predefined variables as well. Of course, I won't go through each one of them individually, as you can refer to the documentation to understand the correct procedures, if any, for setting values for these variables. However, reading these values can be done directly, as shown at the beginning of the topic, i.e., by using the name of the predefined variable itself.
But wait a minute. I followed and understood what was explained here, but I still don't fully grasp the concept of value offsetting that you mentioned several times throughout the topic. Could you explain this better? That way, I might be able to use the values I assigned to the _LastError variable, instead of seeing those strange numbers when printing the variable value in the terminal.
Indeed, this is a valid request and deserves a thorough explanation. Let's address it in a new topic, as we will also touch on other related concepts.
Enumerations and Constant Definitions
One of the most challenging aspects to define in any programming language, in my opinion, is the existence of definitions and enumerations in the language. Don't get me wrong, dear reader. Both are widely used and greatly assist in programming. However, it is difficult to determine just by looking at the documentation what might be an enumeration and what would be a definition. This is because there's no way to know this except for the documentation stating what type of modeling is being used.
Without diving into details about these concepts just yet (since we will cover them later), we can begin by understanding what each value represents. More importantly, we should focus on understanding some of the elements that commonly appear in code, such as texts that might not make much sense at first glance. An example of this can be seen in the code below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return INIT_SUCCEEDED; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+
Code 07
In line 6, we have something that may not make much sense to many, even though it appears in almost every indicator code. We'll discuss this in more detail at a later point, dear reader. But I'm showing you this code (Code 07) just to exemplify something that you will often encounter in many codes. Even though the reserved word return expects a variable or a value, here we are using a string. But what does this string mean, and why are we using it instead of another one? In reality, we could use something else. But let's take it one step at a time.
If you look at Constants, Enumerators and Structures, you will see that there are many constants and enumerations defined within MQL5. These enumerations and constants, like the one you can see in line 6 of Code 07, are given names in programming. These entities are known as aliases. And yes, the spelling is exactly as shown, since accents cannot be used in programming languages. The goal is to create a more suitable representation for us to remember or to make the code easier to read. Not only for the person who wrote it, but also for other programmers.
To make this clearer, think about the following, dear reader. Suppose you're programming something, and to indicate that your code was successful in performing an operation, you want to return a true value. How would you do that?
Well, you could use a value greater than zero or even write the word true. Both would work in theory. That is, the value in line 6 of Code 07 could be written as shown below.
//+------------------------------------------------------------------+ #property copyright "Daniel Jose" //+------------------------------------------------------------------+ int OnInit() { return true; }; //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; }; //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Print(reason); }; //+------------------------------------------------------------------+
Code 08
However, if you do this, your code will fail. But why will it fail? That doesn't make any sense – after all, by using the value true, we are indicating that OnInit was successfully completed. Yes, dear reader, when we return true, we are indeed signaling that something was executed without errors. However, even so, your code will fail and will be terminated immediately after OnInit is executed. The reason for this lies in the value associated with the alias INIT_SUCCEEDED.
It's not that you can't use another value there, but you need to understand that INIT_SUCCEEDED is simply a more readable representation of an underlying value. If you check the documentation, you'll see that INIT_SUCCEEDED actually corresponds to 0. In Boolean logic, 0 represents false. So, if you return true instead, Code 08 will certainly fail, even though there are no actual errors in it.
Important detail: If you execute Code 08, you will see the following message printed in the terminal immediately after execution.
Figure 06
I understand that this might not make much sense at the moment, but I assure you that, in time, it will. Instead of INIT_SUCCEEDED, you could use the value 0 or false in line 6 of Code 08, and it would work perfectly, keeping the indicator running until you remove it from the chart. However, this is not the only way to handle this situation. You could also use an approach like the one shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return ERR_SUCCESS; 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnDeinit(const int reason) 15. { 16. Print(reason); 17. }; 18. //+------------------------------------------------------------------+
Code 09
In the case shown in Code 09, just like when using the value 0 or false in line 6 of Code 08, you will obtain the result shown below as soon as the indicator is removed from the chart.
Figure 07
Notice that there isn't a strict rule dictating how a piece of code must be written – only guidelines that you should understand to ensure everything functions correctly. Now, let's assume you are a highly meticulous programmer who does not tolerate any errors in your applications. In this case, you could use the code shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. return GetLastError(); 07. }; 08. //+------------------------------------------------------------------+ 09. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 10. { 11. return rates_total; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnDeinit(const int reason) 15. { 16. Print(reason); 17. }; 18. //+------------------------------------------------------------------+
Code 10
This would be an extreme case because if an error occurs at any point during the execution of the OnInit function, the returned value will be different from zero or the previously mentioned labels indicating a zero value. This is because GetLastError, which we discussed earlier, would show that _LastError contains some value. However, you must understand that the error could occur anywhere in the code, whether or not it is part of what you programmed. Sometimes, errors happen not due to a failure in the code itself but because of interactions between values. This can cause _LastError to hold a nonzero value for various reasons. That's why this type of error handling is considered extreme. It is rare to see code that actually uses this approach. But if you want to experiment with it, you will find that unexpected and sometimes amusing things can happen.
Doing this will help you gain maturity and a deeper understanding of handling unexpected situations. But proceed carefully and with patience, dear reader. Since we haven't yet covered how to filter errors, even the smallest detail can leave you completely puzzled, wondering why an application sometimes works and other times doesn't without any apparent reason.
Now, as the final topic of this article, let's see how using internally defined constants and enumerations in MQL5 can impact our work. As we discussed earlier, by using the SetUserError function, we can assign any value to the system variable _LastError. But what if we want to know exactly what value was set by SetUserError? This is actually quite simple. The MQL5 documentation for SetUserError explains how to adjust for this. Part of the text is given below:
Sets the predefined variable _LastError to a value equal to ERR_USER_ERROR_FIRST + user_error.
In other words, if we subtract ERR_USER_ERROR_FIRST from the value found in _LastError, we can determine the exact value set by SetUserError. Here's an example implementation in the following code:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(GetLastError()); 07. SetUserError(2); 08. Print(GetLastError()); 09. Print(GetLastError() - ERR_USER_ERROR_FIRST); 10. } 11. //+------------------------------------------------------------------+
Code 11
After executing code 11, we can see the following on the terminal:
Figure 08
Note that three values are shown. However, we are interested in the second and third, since in this case the value correction is performed precisely in order to determine what value was assigned by the SetUserError procedure to indicate the error.
Final considerations
I have a small note about what is shown here. You might think that if you use a negative value in SetUserError, you will be able to hit the range of error values defined by MQL5 to report something special. However, negative values will NOT have any effect. This is related to the type of data expected by the SetUserError procedure.
These situations will be considered and explained in the next article. As for the codes given here, some of them can be found in the attachment. Use them to study everything presented in this article on your own. Have a nice study and see you in the next article!
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/15304





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Check out the new article: From Basic to Intermediate: Variables (III).
Author: CODE X
After I have just read the title "Special variables: Functions", I thought that the article would discuss the special type "function pointers".
This will be discussed later in another article created specifically for this purpose. Since in order to understand why to use pointers to functions, it is first necessary to understand another type of concept, which is how to deal with events. 🙂👍