Русский 中文 Español Deutsch 日本語 Português
preview
From Basic to Intermediate: Variables (II)

From Basic to Intermediate: Variables (II)

MetaTrader 5Examples | 3 February 2025, 13:39
1 326 0
CODE X
CODE X

Introduction

The materials presented here are intended for didactic 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 (I)", we started talking about variables and some aspects related to them. For example, we discussed how to convert variables into constants. We also started to talk about the lifetime and visibility of variables.

Here we will continue this topic, based on the assumption that the reader has properly understood the previous material. One thing we find when talking about the variable lifetime and visibility can be a bit difficult for beginners to understand. The reason is that in many cases we don't want global variables to create inconvenience. We want variables to exist only within a single block of code. However - and this is where things get complicated - we don't want the variable's value to die or disappear after the block ends.

This situation is one of the most confusing in the minds of many programmers, including beginners who want to become professionals. This happens because many people don't realize that some programming languages have mechanisms that allow a variable to retain its value in memory. This complexity is likely due to the fact that popular scripting languages such as Python do not use this implementation. For this reason, it is very difficult for a programmer accustomed to Python to understand this concept. Variables do not always lose, or rather forget, their value when the block to which they belong ceases to exist.

However, C/C++ programmers are generally well-acquainted with this concept, as it is deeply ingrained in their experience. Since MQL5 is similar to these languages, it has adopted this same principle - a feature that, in my opinion, is highly beneficial. Without it, developers would have to rely on global variables within the application scope, which is often a significant inconvenience.

Now, to explain how this works, let's begin with a new topic.


Static Variables

Static variables are one of the most powerful yet, at the same time, one of the most challenging concepts for many beginner programmers to understand. At first glance, they often seem counterintuitive. However, as you study them and begin to grasp how they work, particularly when and where they can be applied, you may come to deeply appreciate their utility. They address many problems that would otherwise require far more effort to resolve.

Let's start with a fairly simple case. Although we haven't delved into certain programming concepts yet, I encourage you to focus solely on the variables for now. Forget everything else. Try to understand what is happening with the variables, as this will be the critical point to grasp here.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     uchar counter = 0;
07.     ulong value = 1;
08. 
09.     Print("Factorial of ", counter, " => ", value);
10.     counter = counter + 1;
11.     value = value * counter;
12.     Print("Factorial of ", counter, " => ", value);
13.     counter = counter + 1;
14.     value = value * counter;
15.     Print("Factorial of ", counter, " => ", value);
16.     counter = counter + 1;
17.     value = value * counter;
18.     Print("Factorial of ", counter, " => ", value);
19.     counter = counter + 1;
20.     value = value * counter;
21.     Print("Factorial of ", counter, " => ", value);
22. }
23. //+------------------------------------------------------------------+

Code 01

I know we could use a loop here. However, let's consider that you're starting from scratch in learning programming. You know absolutely nothing about the subject, and everything you've learned so far is based on the previous article. With that in mind, we need to start from the beginning. It may feel a bit more complicated at first, but things will improve soon.

The key detail here is this: When we run Code 01 in the MetaTrader 5 terminal, the result will be as shown below.


Figure 01

As you can see, it works. What we are doing here is calculating the factorial of a number. But instead of simply calculating it and returning the result, we are breaking it down and calculating it step by step. This approach is essential to understanding the point I want to make.

Now, consider the following, dear reader: In line six, we create and initialize a variable. This variable will track the current factorial being calculated. Then, in line seven, we declare another variable and initialize it with an appropriate value. And yes, the factorial of zero is one, just as the factorial of one is also one. Knowing this is crucial for any programming task, as programming involves solving problems mathematically. Contrary to what many people believe, that programming is just typing random commands and getting the computer to execute them, the essence of programming is transforming mathematical formulas into code that a machine can understand. For this reason, having a solid understanding of mathematics is essential for becoming a good programmer.

Returning to our question, in lines 09, 12, 15, 18 and 21 we output the results in a human-readable form. Between each of these steps, we instruct the machine on how to calculate the next value. Now, pay attention to this critical point: Each time we calculate the factorial of the current number, we use the value of the previous factorial. In other words, even though the variables counter and value are initially constants, with each new calculation, those constant values change, effectively becoming new constants. This is one of the most fundamental concepts about variables that you, as a programmer, should carry with you for the rest of your career.

Now that we are on the same page, we can try to simplify things by reducing the number of lines of code. To achieve this, we'll need to use another concept that will be explained more thoroughly later, but it's necessary at this point. This concept is routine.

A routine is essentially a task that the machine will perform whenever we instruct it to do so. It can be thought of as a repetitive and tedious task that we don't want to rewrite as code every time. This is similar to what happens in several lines of Code 01. There are essentially two types of routines: functions and methods. However, to explain static variables, we'll use a method, as it provides a simpler way to understand what's happening.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. uchar counter = 0;
05. ulong value = 1;
06. //+------------------------------------------------------------------+
07. void OnStart(void)
08. {
09.     Factorial();
10.     Factorial();
11.     Factorial();
12.     Factorial();
13.     Factorial();
14. }
15. //+------------------------------------------------------------------+
16. void Factorial(void)
17. {
18.     Print("Factorial of ", counter, " => ", value);    
19.     counter = counter + 1;
20.     value = value * counter;
21. }
22. //+------------------------------------------------------------------+

Code 02

Notice that code 02 is much easier to understand. At first glance, it is noticeable that we are calculating the factorial values from zero to four. This happens because we have four calls to the Factorial metho9d within the main block of code, which is the OnStart method. As mentioned earlier, we will look at this later. But for now, let's focus on understanding variables.

Note that despite the apparent difference between code 01 and code 02, in both cases the result will be Figure 01. However, here in code 02 we are using two global variables that appear when the code is executed and disappear only after it is finished. Between one moment and the next, these same variables can be changed by any method or function that has access to them. Even if this change was done to our mistake or oversight while implementing the code. This is not uncommon in complex codes.

Even though these variables are global and do not belong to any particular block, these values must be initialized. As you can see, we perform initialization at the moment we declare them. However, this can be done at any other time, which makes it difficult to track possible changes in their value. But for simplicity, we initialize them right when we declare them. At this point, a perfectly valid question arises: If I'm designing something complex and I'm concerned about accidentally modifying the values of global variables during the coding process, wouldn't it be better to declare the variables counter and value within the Factorial method instead? This way, we wouldn't risk altering these variables unintentionally.

Yes, dear reader, the best approach would indeed be to remove counter and value from the global scope and declare them within the Factorial method. Doing so would give us better control over the variables in the code. So, let's make that adjustment and see what happens. At first glance, both Code 01 and Code 02 seem to work as intended. And if they work, it means we are heading in the right direction. By implementing the proposed change, the updated code will look as follows:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     Factorial();
07.     Factorial();
08.     Factorial();
09.     Factorial();
10.     Factorial();
11. }
12. //+------------------------------------------------------------------+
13. void Factorial(void)
14. {
15.     uchar counter = 0;
16.     ulong value = 1;
17. 
18.     Print("Factorial of ", counter, " => ", value);    
19.     counter = counter + 1;
20.     value = value * counter;
21. }
22. //+------------------------------------------------------------------+

Code 03

Great! Now we no longer have the problem of global variables in the code. Our program is indeed much more organized. But what about the result? Are we still calculating the factorial values from zero to four? Let us have a look. After compiling the code and running it in the MetaTrader 5 terminal, the following result is displayed:


Figure 02

Wait... It didn't work. But why? It was working before. Now I'm genuinely confused because, at first glance, it seems we're executing the code five times, as expected. However, it looks like we're always pointing to the same values. Hmm, let me think for a moment. Hold on.

Ah, now I get it! Each time one of the calls in the main block (the OnStart method) is executed, the variables 'counter' and 'value' are being redeclared. And every time we do this, they're also being reinitialized to the initial values defined earlier in the code. That's the reason it didn't work! This helped me understand the concept of a variable's lifetime. But I still have a question: What if I don't initialize the values in lines 15 and 16? Maybe that would work! No, my dear reader, you could try that, but you'd run into the issues we discussed in the previous article.

So, could we pass the values to the method instead? That would work. Yes, in this case, it would solve the problem. However, that approach would be premature. Since I haven't yet explained how to pass values between procedures and functions, you can't use that technique yet. You need to come up with another way to make Code 03 work and show the same output as in Figure 01, and not this strange result we see in Figure 02. Are you giving up?

If that's the case, it's time to put an end to your frustration. To solve this problem without resorting to any "magic fixes" and while keeping the variables counter and value inside the Factorial procedure, we need to use what is known as a static variable. When a variable is declared as static, it does not lose its value between distinct calls to the same procedure or function. Does that sound complicated? It might at first, but in practice, it’s much simpler than it seems. As long as you pay attention to what you’re doing, everything will work out fine. However, if you’re careless, things can spiral out of control quickly. To understand this better, let’s take a look at how the code would look in practice. You can see the updated version below: When a variable is declared as static, it does not lose its value between distinct calls to the same procedure or function.

Does that sound complicated? It might at first, but in practice, it's much simpler than it seems. As long as you pay attention to what you're doing, everything will work out fine. However, if you're careless, things can spiral out of control quickly. To understand this better, let's take a look at how the code would look in practice. It can be seen below.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     Factorial();
07.     Factorial();
08.     Factorial();
09.     Factorial();
10.     Factorial();
11. }
12. //+------------------------------------------------------------------+
13. void Factorial(void)
14. {
15.     static uchar counter = 0;
16.     static ulong value = 1;
17. 
18.     Print("Factorial of ", counter, " => ", value);    
19.     counter = counter + 1;
20.     value = value * counter;
21. }
22. //+------------------------------------------------------------------+

Code 04

Notice that the only change between Code 03 and Code 04 is the presence of the reserved keyword static on lines 15 and 16. However, this single change ensures that when Code 04 is executed in the MetaTrader 5 terminal, the result matches exactly what is shown in Figure 01. In other words, we achieved the expected success. But due to the static nature of variables declared as static, they should be treated with care. This is because there are specific rules governing their behavior. While you're not strictly obligated to follow certain practices, it's crucial to understand how static variables work to avoid issues.

A static variable is primarily designed to exist inside a block. While there are rare situations where they can be used outside of a block for specialized purposes, you should generally think of static variables as being tied to a block of code.

Second, except for very specific use cases, you should never initialize a static variable outside its declaration. Doing so without proper consideration can result in losing control over your code. In this simple, didactic example, it may feel like you're in complete control. However, don’t let that fool you into thinking this is always the case. A single mistake or oversight can quickly lead to hours (or even days) spent debugging issues. To make this point crystal clear, let's make a small change to Code 04 and see what happens. The updated code is shown below:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     Factorial();
07.     Factorial();
08.     Factorial();
09.     Factorial();
10.     Factorial();
11. }
12. //+------------------------------------------------------------------+
13. void Factorial(void)
14. {
15.     static uchar counter;
16.     static ulong value = 1;
17. 
18.     counter = 1;
19.     Print("Factorial of ", counter, " => ", value);    
20.     counter = counter + 1;
21.     value = value * counter;
22. }
23. //+------------------------------------------------------------------+

Code 05

I'm including the full code so that you can follow the changes more closely. This way, you can better understand what happens when a small mistake is made in an application. Pay attention and compare the difference between Code 04 and Code 05. At first glance, this may seem trivial, almost harmless. Something you wouldn't expect to affect the outcome. However, when executed, what should have been Figure 01 displayed in the MetaTrader 5 terminal now turns into something completely different, as shown in the figure below:


Figure 03

It's strange. What should have been a factorial calculation has turned into a power-of-two calculation. How did this happen? Well, my dear reader, there's a small trick to working with static variables correctly. An experienced programmer, when facing code that isn't behaving as expected, will quickly suspect an issue with variable usage. If the language allows static variables and the code contains them, the first thing you should check is whether those variables are being initialized correctly

and whether a change in their values is expected. These are the first points to check. Since changing the value of a variable is something we expect (otherwise we would use constants), we can check whether the variable changes and forgets the last value assigned to it. There are various debugging techniques for this. I encourage you to study debugging methods in detail, as each situation may require a slightly different approach.

But back to the question of why the code behaves strangely despite appearing fine. The first thought that comes to mind is that everything seems correct. However, static variables are initialized when they are created. If this initialization is missed, the variable will behave as if it's local. This means it's as though the variable is being redeclared each time, losing its static behavior. Naturally, this means that what is shown in line 18 of code 05 should be avoided as much as possible inside the block. This will cause the static variable to break and malfunction. This is why the code behaved strangely: What was a static variable was now a regular variable.

With that we now have a good foundation to start working on the next points.


Data Types

Now, let's quickly touch on data types, as it's important to give you an initial understanding of this concept. As we proceed, you'll notice that there's much more involved.

The topic at hand is the data type or variable type. Many programming languages, especially scripting languages like JavaScript and Python, don't require the programmer to explicitly declare the type of data being stored. These are referred to as dynamically typed languages. On the other hand, languages like C/C++ and MQL5 are statically typed. This means the programmer must specify the data type expected in each variable. What does this mean in practice? In dynamic languages like Python, a variable can hold any type of value, and the language will automatically adapt the variable to the appropriate type. In MQL5, however, a variable cannot store any value - it must be declared with a specific type. This is theoretical, as there are ways to get around such inconveniences.

But for those who program in strictly typed languages, doing some things becomes more complicated because it's not enough to just declare a variable and put whatever you want into it. There are rules and restrictions for this. However, languages like Python allow a variable to take a text or even any other value, and the language will automatically set the most appropriate type for the variable. Thus, the complexity of programming is significantly lower, which makes the creation of code much easier, faster and does not require such a great deal of specialization from the programmer. This is different from statically typed languages, where we need to know what each type can or cannot contain.

At this point, you might be thinking about giving up on understanding MQL5 and looking for something simpler, like Python or JavaScript. However, once you get used to statically typed languages, you might find working in dynamically typed languages less interesting, as they require less thought in terms of data handling. Working with data involves understanding what it is, how it can be classified into types, and what the limits of each type are. Learning how to deal with different data types opens up many opportunities, such as working with encryption or data compression.

But that's a topic for another time. For now, let's cover the basics of data types in MQL5.

In MQL5, there are a few basic types that you can use: integers, booleans, literals, floating point numbers, enumerators, and strings. They also appear in C/C++ code. However, in addition to these types, common in many C/C++-based languages, MQL5 also has special ones, such as color data and date and time data. Although, as we will see later, they are derivatives of other types, the very fact of their existence can be useful at certain times.

Complex data types exist in both C/C++ and MQL5. These are structures and classes. As for classes, they do exist in C++. But here in MQL5 classes are much easier to understand and work with than in C++. Although at times I, as a C++ programmer, miss some class resources that are present in C++ but do not exist in MQL5. But we adapt so that the work is done and the goal are achieved.

This is a basic introduction to the topic, but we can go into more detail later. Before finishing this article, we'll quickly discuss the limits of the basic types. Understanding the limits of each type will help immensely in your future coding. Let's start with a simple table that outlines the limits of these types, which you can see below.

Type Number of bytes Lowest value Highest value
char 1 -128 127
uchar 1 0 255
bool 1 0 (False) 1 (True)
short 2 -32 768 32 767
ushort 2 0 65 535
int 4 - 2 147 483 648 2 147 483 647
uint 4 0 4 294 967 295
color 4 -1 16 777 215
float 4 1.175494351e-38 3.402823466e+38
long 8 -9 223 372 036 854 775 808 9 223 372 036 854 775 807
ulong 8 0 18 446 744 073 709 551 615
datetime 8 0 (01/01/1970 00:00:00) 32 535 244 799 (31/12/3000 23:59:59)
double 8 2.2250738585072014e-308 1.7976931348623158e+308

Table of main types

This table only shows simple data types. We do not analyze complex types here as we will consider them later. However, understanding this table of simple types will help us choose the most appropriate type depending on what we need for our calculations. This is because each type has a lower and upper limit that it can represent.

However, there are two elements in this table that we will look at in more detail in the future: the 'double' and 'float' types. This is because both have their own characteristics that need to be explained separately.

But let's briefly discuss this table. You can see the similarity in the names, where in some cases the letter u precedes the type name, as in char and uchar. The letter u has a special meaning: when this letter appears before a type name, it means that the type starts from zero and goes up to a certain limit. Because this initial letter u comes from the word "unsigned". But what does "unsigned" mean in practice? When we have an unsigned value, it means that it will always be interpreted as positive. If a value is signed, it means it can be either positive or negative. This will become clearer when we talk about mathematical operations. Generally, this can be interpreted as follows: if we want to store a negative value, we can put it in a signed type. However, if all the values to be stored are positive, an unsigned type can be used. But why do I speak about what is possible? Wouldn't it be more correct to speak about what is necessary? Actually, it is not necessary. We will consider this in more detail later, when talking about mathematical operations. There is a fundamental rule here: integer values, that is, those that do not use floating point, are exact. Floating point values are not exact. While looking at this table you might think that it is always better to use 'double' data types since they cover a large range, in practice this is not always the case. Therefore, a good programmer needs to know how to choose the right data type.

However, you are probably looking at this table and thinking: where is type 'string', isn't it a basic type? Yes, 'string' is a basic type. But it is a special type that fits better into another category, which we will discuss later. For this reason, it is not presented in the table.


Final considerations

In this article, we mainly discussed static type variables. We did some experiments with them and showed how they can be used. The article focuses on explaining one possible use of static type, as such variables can also be used for other purposes. However, in the vast majority of cases, when we encounter static type variables, they are used as shown here. Later we will look at other ways to use static variables for different purposes. For now, what you have seen here will help you understand many programs, and also implement more efficient solutions that do not depend on excessive use of global variables.

You also got a brief introduction to the topic of the next article, which is Data Types. There are differences and limitations for each data type in MQL5, but we have note seen why these limitations exist and how they can be bypassed to extend them for more specific purposes.

But don't worry, we'll see it soon. On this note, I come to the end of this article. I am leaving the codes attached to the article so that you can experiment with them and draw your own conclusions about what was explained here. See you soon

Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/15302

Attached files |
Anexo.zip (1.75 KB)
Automating Trading Strategies in MQL5 (Part 5): Developing the Adaptive Crossover RSI Trading Suite Strategy Automating Trading Strategies in MQL5 (Part 5): Developing the Adaptive Crossover RSI Trading Suite Strategy
In this article, we develop the Adaptive Crossover RSI Trading Suite System, which uses 14- and 50-period moving average crossovers for signals, confirmed by a 14-period RSI filter. The system includes a trading day filter, signal arrows with annotations, and a real-time dashboard for monitoring. This approach ensures precision and adaptability in automated trading.
Chaos theory in trading (Part 2): Diving deeper Chaos theory in trading (Part 2): Diving deeper
We continue our dive into chaos theory in financial markets. This time I will consider its applicability to the analysis of currencies and other assets.
Developing a Replay System (Part 57): Understanding a Test Service Developing a Replay System (Part 57): Understanding a Test Service
One point to note: although the service code is not included in this article and will only be provided in the next one, I'll explain it since we'll be using that same code as a springboard for what we're actually developing. So, be attentive and patient. Wait for the next article, because every day everything becomes more interesting.
Generative Adversarial Networks (GANs) for Synthetic Data in Financial Modeling (Part 2): Creating Synthetic Symbol for Testing Generative Adversarial Networks (GANs) for Synthetic Data in Financial Modeling (Part 2): Creating Synthetic Symbol for Testing
In this article we are creating a synthetic symbol using a Generative Adversarial Network (GAN) involves generating realistic Financial data that mimics the behavior of actual market instruments, such as EURUSD. The GAN model learns patterns and volatility from historical market data and creates synthetic price data with similar characteristics.