MQL5 Programming For Traders by MetaQuotes
MQL5 Programming For Traders by MetaQuotes
Contents
Contents
MQL5 Programming for Traders 13
Part11. Introduction to MQL5 and development environment
..................................................................................................................................................................................................... 15
1.1 Editing,......................................................................................................................................................................................16
compiling, and running programs
1.2 MQL Wizard and program draft
......................................................................................................................................................................................19
1.3 Statements, code blocks, and functions
......................................................................................................................................................................................22
1.4 First program ......................................................................................................................................................................................24
1.5 Data types and values
......................................................................................................................................................................................26
1.6 Variables and identifiers
......................................................................................................................................................................................28
1.7 Assignment and initialization, expressions and arrays
......................................................................................................................................................................................29
1.8 Data input ......................................................................................................................................................................................31
1.9 Error fixing and debugging
......................................................................................................................................................................................33
1.10 Data output ......................................................................................................................................................................................37
1.11 Formatting, indentation, and spaces
......................................................................................................................................................................................39
1.12 Mini summary ......................................................................................................................................................................................41
Part2 2. Programming fundamentals
..................................................................................................................................................................................................... 42
2.1 Identifiers ......................................................................................................................................................................................43
2.2 Built-in......................................................................................................................................................................................44
data types
2.2.1 Integers .............................................................................................................................................................................. 46
2.2.2 Floating-point numbers
.............................................................................................................................................................................. 50
2.2.3 Character types
.............................................................................................................................................................................. 53
2.2.4 String..............................................................................................................................................................................
type 54
2.2.5 Logic ..............................................................................................................................................................................
(Boolean) type 55
2.2.6 Date and time
.............................................................................................................................................................................. 56
2.2.7 Color .............................................................................................................................................................................. 57
2.2.8 Enumerations .............................................................................................................................................................................. 58
2.2.9 Custom enumerations
.............................................................................................................................................................................. 61
2.2.10 Void..............................................................................................................................................................................
type 64
2.3 Variables ......................................................................................................................................................................................64
2.3.1 Declaration and definition of variables
.............................................................................................................................................................................. 65
2.3.2 Context, scope, and lifetime of variables
.............................................................................................................................................................................. 66
2.3.3 Initialization .............................................................................................................................................................................. 68
2.3.4 Static..............................................................................................................................................................................
variables 74
2.3.5 Constant variables
.............................................................................................................................................................................. 76
2.3.6 Input ..............................................................................................................................................................................
variables 77
2.3.7 External variables
.............................................................................................................................................................................. 78
2.4 Arrays......................................................................................................................................................................................81
2.4.1 Array ..............................................................................................................................................................................
characteristics 82
2.4.2 Description of arrays
.............................................................................................................................................................................. 83
2.4.3 Using..............................................................................................................................................................................
arrays 85
2.5 Expressions ......................................................................................................................................................................................88
2.5.1 Basic ..............................................................................................................................................................................
concepts 88
2.5.2 Assignment operation
.............................................................................................................................................................................. 90
2.5.3 Arithmetic operations
.............................................................................................................................................................................. 92
2.5.4 Increment and decrement
.............................................................................................................................................................................. 94
2.5.5 Comparison operations
.............................................................................................................................................................................. 95
2.5.6 Logical operations
.............................................................................................................................................................................. 97
2.5.7 Bitwise operations
.............................................................................................................................................................................. 98
2.5.8 Modification operations
.............................................................................................................................................................................. 100
2.5.9 Conditional ternary operator
.............................................................................................................................................................................. 102
2.5.10 Comma .............................................................................................................................................................................. 104
2.5.11 Special operators sizeof and typename
.............................................................................................................................................................................. 104
2
Contents
3
Contents
3.2.4 OOP..............................................................................................................................................................................
fundamentals: Polymorphism 183
3.2.5 OOP..............................................................................................................................................................................
fundamentals: Composition (design) 183
3.2.6 Class..............................................................................................................................................................................
definition 184
3.2.7 Access rights
.............................................................................................................................................................................. 187
3.2.8 Constructors: default, parametric, and copying
.............................................................................................................................................................................. 188
3.2.9 Destructors .............................................................................................................................................................................. 194
3.2.10 Self-reference: this
.............................................................................................................................................................................. 195
3.2.11 Inheritance .............................................................................................................................................................................. 198
3.2.12 Dynamic creation of objects: new and delete
.............................................................................................................................................................................. 203
3.2.13 Pointers .............................................................................................................................................................................. 205
3.2.14 Virtual methods (virtual and override)
.............................................................................................................................................................................. 211
3.2.15 Static members
.............................................................................................................................................................................. 219
3.2.16 Nested types, namespaces, and the context operator '::'
.............................................................................................................................................................................. 221
3.2.17 Splitting class declaration and definition
.............................................................................................................................................................................. 225
3.2.18 Abstract classes and interfaces
.............................................................................................................................................................................. 228
3.2.19 Operator overloading
.............................................................................................................................................................................. 230
3.2.20 Object type сasting: dynamic_cast and pointer void *
.............................................................................................................................................................................. 241
3.2.21 Pointers, references, and const
.............................................................................................................................................................................. 245
3.2.22 Inheritance management: final and delete
.............................................................................................................................................................................. 249
3.3 Templates ......................................................................................................................................................................................250
3.3.1 Template header
.............................................................................................................................................................................. 251
3.3.2 General template operation principles
.............................................................................................................................................................................. 252
3.3.3 Templates vs preprocessor macros
.............................................................................................................................................................................. 254
3.3.4 Features of built-in and object types in templates
.............................................................................................................................................................................. 255
3.3.5 Function templates
.............................................................................................................................................................................. 259
3.3.6 Object type templates
.............................................................................................................................................................................. 264
3.3.7 Method templates
.............................................................................................................................................................................. 269
3.3.8 Nested templates
.............................................................................................................................................................................. 275
3.3.9 Absent template specialization
.............................................................................................................................................................................. 276
Part4.....................................................................................................................................................................................................
4. Common APIs 280
4.1 Built-in type conversions
......................................................................................................................................................................................280
4.1.1 Numbers to strings and vice versa
.............................................................................................................................................................................. 281
4.1.2 Normalization of doubles
.............................................................................................................................................................................. 285
4.1.3 Date..............................................................................................................................................................................
and time 286
4.1.4 Color.............................................................................................................................................................................. 295
4.1.5 Structures .............................................................................................................................................................................. 298
4.1.6 Enumerations .............................................................................................................................................................................. 300
4.1.7 Type..............................................................................................................................................................................
complex 302
4.2 Working with strings and symbols
......................................................................................................................................................................................303
4.2.1 Initialization and measurement of strings
.............................................................................................................................................................................. 304
4.2.2 String concatenation
.............................................................................................................................................................................. 308
4.2.3 String comparison
.............................................................................................................................................................................. 309
4.2.4 Changing the character case and trimming spaces
.............................................................................................................................................................................. 315
4.2.5 Finding, replacing, and extracting string fragments
.............................................................................................................................................................................. 317
4.2.6 Working with symbols and code pages
.............................................................................................................................................................................. 321
4.2.7 Universal formatted data output to a string
.............................................................................................................................................................................. 328
4.3 Working with arrays
......................................................................................................................................................................................334
4.3.1 Logging arrays
.............................................................................................................................................................................. 335
4.3.2 Dynamic arrays
.............................................................................................................................................................................. 338
4.3.3 Array..............................................................................................................................................................................
measurement 345
4.3.4 Initializing and populating arrays
.............................................................................................................................................................................. 346
4.3.5 Copying and editing arrays
.............................................................................................................................................................................. 348
4.3.6 Moving (swapping) arrays
.............................................................................................................................................................................. 360
4.3.7 Comparing, sorting, and searching in arrays
.............................................................................................................................................................................. 362
4.3.8 Timeseries indexing direction in arrays
.............................................................................................................................................................................. 377
4.3.9 Zeroing objects and arrays
.............................................................................................................................................................................. 381
4
Contents
5
Contents
6
Contents
7
Contents
8
Contents
9
Contents
10
Contents
11
Contents
7.8.2 Project plan of a web service for copying trades and signals
.............................................................................................................................................................................. 1944
7.8.3 Nodejs based web server
.............................................................................................................................................................................. 1945
7.8.4 Theoretical foundations of the WebSockets protocol
.............................................................................................................................................................................. 1947
7.8.5 Server component of web services based on the WebSocket protocol
.............................................................................................................................................................................. 1948
7.8.6 WebSocket protocol in MQL5
.............................................................................................................................................................................. 1957
7.8.7 Client programs for echo and chat services in MQL5
.............................................................................................................................................................................. 1967
7.8.8 Trading signal service and test web page
.............................................................................................................................................................................. 1976
7.8.9 Signal service client program in MQL5
.............................................................................................................................................................................. 1981
7.9 Native python support
......................................................................................................................................................................................1997
7.9.1 Installing Python and the MetaTrader5 package
.............................................................................................................................................................................. 1997
7.9.2 Overview of functions of the MetaTrader5 package for Python
.............................................................................................................................................................................. 2000
7.9.3 Connecting a Python script to the terminal and account
.............................................................................................................................................................................. 2002
7.9.4 Error checking: last_error
.............................................................................................................................................................................. 2003
7.9.5 Getting information about a trading account
.............................................................................................................................................................................. 2004
7.9.6 Getting information about the terminal
.............................................................................................................................................................................. 2006
7.9.7 Getting information about financial instruments
.............................................................................................................................................................................. 2008
7.9.8 Subscribing to order book changes
.............................................................................................................................................................................. 2012
7.9.9 Reading quotes
.............................................................................................................................................................................. 2014
7.9.10 Reading tick history
.............................................................................................................................................................................. 2019
7.9.11 Calculating margin requirements and evaluating profits
.............................................................................................................................................................................. 2022
7.9.12 Checking and sending a trade order
.............................................................................................................................................................................. 2023
7.9.13 Getting the number and list of active orders
.............................................................................................................................................................................. 2028
7.9.14 Getting the number and list of open positions
.............................................................................................................................................................................. 2031
7.9.15 Reading the history of orders and deals
.............................................................................................................................................................................. 2033
7.10 Built-in support for parallel computing: OpenCL
......................................................................................................................................................................................2037
8
Conclusion
..................................................................................................................................................................................................... 2045
12
MQL5 Programming for Traders
Modern trading relies heavily on computers, a transformation that has extended far beyond the confines
of exchange floors and brokers' offices. Now, even everyday users have access to specialized software
products, with MetaTrader standing out as one of the pioneering programs dating back to the early
2000s. Today, MetaTrader 5 continues to evolve with new features, notably its embedded
programming language, MQL5, which undergoes constant refinement. This availability propels traders
into the realm of algorithmic trading, allowing them to translate their ideas into applications—custom
indicators, one-time operation scripts, or Expert Advisors (EAs), essentially automated trading systems.
Operating seamlessly 24/7 without user intervention, an EA based on MQL5 can track financial
instrument prices, send notifications via email or mobile phones, and execute various useful actions.
MQL5 opens the door to implementing a myriad of trading ideas, from strategies based on moving
averages or market analysis methods to digital signal processing, neural networks, and geometric
constructions. Essentially, MQL5 merges the characteristics of a domain-specific language for
algorithmic trading with those of a general-purpose language. Recent enhancements, such as 3D
graphics support, parallel computing in OpenCL, integration with Python, and network functions, further
solidify its versatility.
To fully harness the potential of these tools, mastering MQL5 is essential, and this textbook serves as a
guide for achieving that proficiency.
Assuming the reader's familiarity with the MetaTrader 5 Client Terminal as a user, the book delves into
the basic principles of operating the terminal within a distributed information system supporting trading.
The terminal is continuously connected to a server in a dealing center, ensuring up-to-date information
on market conditions, prices, instrument specifications, and trading account settings.
The terminal's graphical interface offers an array of tools for visual technical analysis and manual
trading. All procedures and interactive control features are described in detail in the User Manual of the
terminal and will not be replicated here.
The book deals with MQL5-embedded APIs, which provide functionality similar to the user interface.
Furthermore, by supporting flexibly automated actions, such as testing various conditions, combining
and looping the sequences of operations, and using algorithms based on MQL5 functions, these APIs
enable sophisticated scenarios beyond manual control, offering extended features, such as working with
the SQLite database, reading and writing data that utilize network functions, and generating 3D images,
which might not be readily available in the user interface.
Applications and manual control tools can certainly be used in the client terminal simultaneously, being
mutually complementary.
MQL5 allows the automation of various terminal operation aspects, while enhancing their speed and
user-friendliness, as well as eliminating manual routines. In scenarios like high-frequency trading, MQL5
support becomes indispensable, while in other cases, such as balancing a currency basket or stock
instruments, it may not be strictly necessary but is highly practical.
The book is divided into 7 parts, each focusing on different aspects of MQL5 programming.
• Part 1 introduces basic MQL5 programming principles and MetaEditor, the standard MQL5
framework. Users experienced in programming in other languages should note the features of the
framework.
13
• Part 2 explains the basic terms, such as types, instructions, operators, expressions, variables, code
blocks, program structures. It describes how these terms are utilized in MQL5 procedural
programming style. Those users who know MQL4 well can skip this part and start reading Part 3.
• Part 3 deals with object oriented programming (OOP) in MQL5. Despite its similarity to other
languages that support the OOP paradigm (especially to C++), MQL5 has certain specific features.
To taste, MQL5 is sort of C±±.
• Part 4 describes common embedded functions which are applicable to in any program.
• Part 5 covers the architectural features of MQL programs and their "majoring" in types to perform
various trading tasks, such as technical analysis using indicators, chart management and marking
the charts with imposing graphical objects onto them, and responses to interactive actions and
events involving MQL programs.
• Part 6 explains how to analyze trading environment and automate trading operations using robots.
This part also presents the program interaction with tester in various modes, including strategy
optimization.
• Part 7 contains information regarding the extended set of dedicated APIs facilitating the MQL5
integration with adjacent technologies, such as databases, network data exchange, OpenCL,
Python, etc.
Throughout the book, the material is presented in a balanced manner, combining common approaches,
examples, and technical details. The reader is guided through transitioning from one concept to
another, resembling a chicken-and-egg problem inherent in learning programming. To reinforce
understanding, most MQL programs discussed in the book are available as source codes for practical
exploration in MetaEditor/MetaTrader 5.
14
Part 1. Introduction to MQL5 and development environment
In a sense, this book is aiming at making complex things simple. It is not to replace, but to be added to
the MQL5 Language Reference that is supplied with the terminal and also available on the mql5.com
website.
In this book, we are going to consistently tell you about all the components and techniques of
programming in MQL5, taking baby steps so that each iteration is clear and the OOP technology
gradually unlocks its potential that is especially notable, as with any powerful tool, when it is used
properly and reasonably. As a result, the developers of MQL programs will be able to choose a preferred
programming style suitable for a specific task, i.e., not only the object-oriented but also the 'old'
procedural one, as well as use various combinations of them.
Users of the trading terminal can be conveniently classified into "programmers" (those who have
already some experience in programming in at least one language) and "non-programmers" ("pure"
traders interested in the customization capacity of the terminal using MQL5). The former ones can
optionally skip the first and the second parts of this book describing the basic concepts of language and
immediately start learning about the specific APIs (Application Programming Interfaces) embedded in
MetaTrader 5. For the latter ones, progressive reading is recommended.
Among the category of "programmers," those knowing C++ have the best advantages, since MQL5 and
C++ are similar. However, this "medal" has its reverse side. The matter is that MQL5 does not
completely match with C++ (especially when compared to the recent standards). Therefore, attempts
to write one structure or another through habit "as on pluses" will frequently be interrupted by
unexpected errors of the compiler. Considering specific elements of the language, we will do our best to
point out these differences.
Technical analysis, executing trading orders, or integration with external data sources – all these
functions are available to the terminal users both from the user interface and via software tools
embedded in MQL5.
Since MQL5 programs must perform different functions, there are some specialized program types
supported in MetaTrader 5. This is a standard technique in many software systems. For example, in
Windows, along with usual windowing programs, there are command-line-driven programs and services.
• Indicators – programs aimed at graphically displaying data arrays computed by a given formula,
normally based on the series of quotes;
• Expert Advisors – programs to automate trading completely or partly;
• Scripts – programs intended for performing one action at a time; and
• Services – programs for performing permanent background actions.
We will discuss the purposes and special features of each type in detail later. It is important to note
now that they all are created in MQL5 and have much in common. Therefore, we will start learning with
common features and gradually get to know about the specificity of each type.
15
Part 1. Introduction to MQL5 and development environment
The essential technical feature of MetaTrader consists in exerting the entire control in the client
terminal, while commands initiated in it are sent to the server. In other words, MQL-based applications
can only work within the client terminal, most of them requiring a 'live' connection to the server to
function properly. No applications are installed on the server. The server just processes the orders
received from the client terminal and returns the changes in the trading environment. These changes
also become available to MQL5 programs.
Most types of MQL5 programs are executed in the chart context, i.e., to launch a program, you should
'throw' it onto the desired chart. The exception is only a special type, i.e., services: They are intended
for background operation, without being attached to the chart.
We recall that all MQL5 programs are inside the working MetaTrader 5 folder, in the nested folder
named /MQL5/<type>, where <type> is, respectively:
• Indicators
• Experts
• Scripts
• Services
Based on the MetaTrader 5 installation technique, the path to the working folder can be different
(particularly, with the limited user rights in Windows, in a normal mode or portable). For example, it can
be:
C:/Program Files/MetaTrader 5/
or
C:/Users/<username>/AppData/Roaming/MetaQuotes/Terminal/<instance_id>/
The user can get to know where this folder is located exactly by executing the File -> Open data catalog
command (it is available in both terminal and editor). Moreover, when creating a new program, you
don't need to think of looking up the correct folder due to using the MQL Wizard embedded in the
editor. It is called for by the File -> New command and allows selecting the required type of the MQL5
program. The relevant text file containing a source code template will be created automatically where
necessary upon completing the Master and then opened for editing.
In the MQL5 folder, there are other nested folders, along with the above ones, and they are also
directly related to MQL5 programming, but we will refer to them later.
MQL5 Programming for Traders – Source Codes from the Book. Part 1
Examples from the book are also available in the public project \MQL5\Shared Projects\MQL5Book
16
Part 1. Introduction to MQL5 and development environment
Source code is a text written according to the MQL5 rules and saved as a file having the extension of
mq5. The file containing a compiled program will have the same name, while its extension will be ex5.
In the simplest case, one executable file corresponds with one file containing the source code; however,
as we will see later, coding complex programs frequently requires splitting the source code into multiple
files: The main one and some supporting ones that are enabled from the main file in a special manner.
In this case, the main file must still have the extension of mq5, while those enabled from it must have
the extension of mqh. Then statements from all source files will get into the executable file being
generated. Thus, multiple files containing the source code may be the starting point for creating one
executable file/program. All this mentioned here to complete the picture is going to be presented in the
second part of the book.
We will use the term MQL5 syntax to denote the set of all rules that allow constructing programs in
MQL5. Only the strict adherence to the syntax allows coding programs compatible with the compiler. In
fact, teaching to code consists of sequentially introducing all the rules of a particular language that is
MQL5, in our case. And this is the main purpose of this book.
To compile a source code, we can use the command MetaEditor File -> Compile or just press F7.
However, there are some other, special methods to compile — we will discuss them later. Compiling is
accompanied by displaying the changing status in the editor log (where an MQL5 program consists of
multiple files containing the source code, and enabling each file is marked in a single log line).
An indication of a successful compilation is zero errors ("0 errors"). Warnings do not affect the
compilation results, they just inform on potential issues. Therefore, it is recommended to fix them on
the same basis as errors (we will tell you later how to do that). Ideally, there should not be any
warnings ("0 warnings").
Upon the successful compilation of an mq5 file, we get a same-name file with the extension of ex5.
MetaTrader 5 Navigator displays as a tree all executable ex5 files located in folder MQL5 and its
subfolders, including the one just compiled.
Ready programs are launched in the terminal using any methods familiar to the user. For instance, any
program, other than a service, can be dragged with the mouse from Navigator to the chart. We will talk
about the features of services separately.
Besides, developers often need the program to be executed in the debugging mode to find what causes
the errors. There are multiple special commands for this purpose, and we will refer to them in Bug fixing
and debugging.
The simplest MQL5 programs are scripts. Therefore, it is a script that we are going to try and create.
For this purpose, let's start MQL5 Wizard (File -> New). In the first step, we will select Script in the list
of types and press Next:
In the second step, we will introduce the script name in the Name field, having added it after the
default folder mentioned above and a backslash: "Scripts\". For instance, let's name the script
"Hello" (that is, the Name field will contain the line: "Scripts\Hello") and, without changing anything
else, press Finish.
As a result, the Wizard will create a file named Hello.mq5 and open it for editing. The file is located in
folder MQL5/Scripts (standard location for scripts) because we have used the default folder; however,
we could add any sub-folder or even a sub-folder hierarchy. For instance, if we write
"Scripts\Exercise\Hello" in the Name field at Wizard Step 1, then the Exercise sub-folder will be created
in the Scripts folder automatically, and the file Hello.mq5 will be located in that sub-folder.
All examples from this book will be located in the MQL5Book folders inside catalogs allocated for the
MQL programs of relevant types. This is necessary to facilitate installing the examples into your
working copy of the terminal and rule out any name conflicts with other MQL programs you have
already installed.
For example, file Hello.mq5 delivered as part of this book is located in MQL5\Scripts\MQL5Book\p1\,
where p1 means Part 1 this example relates to.
//+------------------------------------------------------------------+
//| Hello.mq5 |
//| Copyright 2021, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart()
{
}
//+------------------------------------------------------------------+
It is this script that is shown in the preceding screenshots of MetaEditor and MetaTrader 5.
All strings starting with "//" are the comments and do not affect the program intent. They are neither
processed by the compiler nor executed by the terminal. They are only used to exchange explanatory
information among developers or to visually emphasize the code parts to enhance the text readability.
For instance, in this template, the file starts with a block containing a comment where the script name
and the author's copyright are expected to be specified. The second block of comments is the heading
for the main function of the script – it is referred to in more detail below. Finally, the last comment
string visually emphasizes the file end.
Three strings starting with a special directive, #property, provide the compiler with some attributes it
builds into the program in a special manner. In our case, they are not important so far and can even be
deleted. The specific directories are available to each MQL program type – we will know about them as
soon as we proceed to learning the particular program types.
The main part of the script, where we are going to describe the essence of the program actions, is
represented by the OnStart function. Here we have to learn the concepts of 'code block' and 'function'.
void OnStart()
{
}
It is exactly our first subject matter within the context of programming in MQL5. Here again, we
immediately encounter unknown concepts and character sequences. To explain them, we shall make a
short digression.
A program must usually implement the following typical stages when running:
• Defining variables, i.e., named cells in the computer memory to store data;
• Organizing the source data input;
• Processing the data – an applied algorithm; and
• Organizing the output of results.
All these stages are not necessary in terms of maintaining the syntactic correctness of the program.
For example, if we create a program that computes the product of "2*2", it obviously does not need
any input data, because numbers necessary for multiplying are integrated in the program text.
Moreover, since 2 and 2 are constant values in this expression, no named cells (variables) are required
in the program. Since we know it anyway what twice two is, we don't really need to communicate the
product number. Such a program would lack any real function, of course. However, it would be
absolutely correct from a technical point of view.
More interestingly, the program may contain no statements on processing. Our script template
specifically represents a sample 'null' program. But what is the above text fragment?
In his day, Niklaus Wirth, one of the big names in programming, gave a simple generalized definition of
programming as a symbiosis of algorithms and data structures.
Unfortunately, in most practical tasks, the number of statements is so large that they must be
systematized somehow for the human to recognize and control the program behavior.
Here too, the divide-and-conquer algorithm comes to help, which is used practically everywhere in
programming and in different guises. We will learn all of them as we continue in this book, now just
noting the essence.
As known, the algorithm reduces to dividing a large complex task into smaller and simpler ones. Here,
we can compare this process with constructing a house or assembling a spacecraft. Both "products"
consist of multiple different modules that, in turn, consist of components, and the latter ones of even
smaller parts, etc.
Extending this similarity to algorithms, we can say that statements are small parts, while the entire
program is a house/spacecraft. Therefore, we need structural blocks sized intermediately.
This is why it is customary, when implementing algorithms, to combine logically related statements into
larger named fragments, the functions. In the required places of the program, we can address the
function by its name (call it) and doing so, ask the computer to execute all statements contained inside
the function. The entire program is, in fact, the largest external block and therefore, it can also be
presented by the function, from which smaller functions are called or statements are executed
immediately if they are not many. Now we're approaching the OnStart function.
Name OnStart is reserved in scripts to denote the ultimate function that is called by the terminal itself
as a response to the user's actions when the user launches the script using the context menu
command or dragging the mouse over the chart. Thus, the preceding fragment of the code defines the
function OnStart that predetermines the behavior of our entire script.
Those who know programming in other languages, such as C, C++, Rust, or Kotlin, can notice the
similarity of this function with the function main — the core point of entering into the program.
Any script must contain the function OnStart. Otherwise, the compilation may finish with an error.
Empty function OnStart, as ours, starts being executed by the terminal (as soon as the script is
launched in any manner) and immediately finishes its operation. Strictly speaking, there is no applied
algorithm in our script yet, but there is already a stub function to add it.
In other types of MQL programs, there are also special functions to be defined by the programmer in
their code. We will get into the specific features in the relevant parts of the book.
We will consider the function definition syntax in detail in the second part of this book. For a hands-on
review of it, it is sufficient to mention the following basic essentials to understand the description of
OnStart.
Since functions are usually intended for obtaining an applicable result, the characteristics of the
expected value are described in a special manner in their definition: What data types should be obtained
and whether the data is even necessary. Some functions can perform actions that do not require
returning the value. For example, a function can be intended for changing the settings of the current
chart or to send push notifications when the predefined drawdown level is reached on the account. All
this can be programmed by the statements in the function, and it does not create any new data
(reasonable to be returned to any other parts of the program).
In our case, the situation is similar: As the main function of the script, OnStart could return its result to
the external environment only (directly into the terminal) when completed, but this would not affect the
operation of the script itself in any way (because it has already finished off).
That is exactly why, before the OnStart function name, there is the word void that informs the compiler
that the result is not important to us (void means emptiness). void is one of many procedure words
reserved in MQL5. The compiler knows the meanings of all reserved words, and it is guided by them in
reviewing the source code. Particularly, a programmer may use reserved words to define new terms for
the compiler, such as the function OnStart itself.
Parentheses after the name are integral to the description of any function: They may enclose the list of
function parameters. For instance, if we were writing a function taking a square of a number, we would
have to provide it with one parameter for that number. Then we could call this function from any part of
the program, having sent one argument over it, i.e., the specific value for the parameter. We will see
later how to describe the list of parameters; it is not in this current example. This requirement is posed
on the function OnStart for it is called by the terminal itself, and it never sends anything to this function
as parameters.
At last, braces are used to mark the beginning and the end of the block containing statements.
Immediately following the function name string, such a block will contain a set of operations performed
by this function. It is also named the function body. In this case, there is nothing inside the braces.
Therefore, the script template does not do anything.
The above sequence of word void, name OnStart, an empty list of parameters, and an empty code
block defines the least, empty implementation of the function OnStart for the compiler. Later, adding
statements into the function body, we will extend the definition of function OnStart.
Having executed the Compile command, we will make sure that the script can be successfully compiled,
and that the ready program appears in the Navigator of the terminal in the folder
Scripts/MQL5Book/p1. This results from the fact that, on the disk in the relevant folder, there is now
the file of Hello.ex5. It can easily be checked in any file manager.
We can run the script on a chart, but the only confirmation of its execution will be the entries in the
terminal log (tab Log in the Tools window; not to be mixed with the toolbar):
That is, the script is loaded, the control is sent to the function OnStart, but immediately returned to
the terminal because the function does not do anything, and after that, the terminal unloaded the
script from the chart.
In many programming textbooks, the initial example prints the sacramental "Hello, world". In MQL5, a
similar greeting could appear as follows:
void OnStart()
{
Print("Hello, world");
}
void OnStart()
{
Print("Hello, ", Symbol());
}
Thus, we have added only one string with some language structures.
Here, Print is the name of the function embedded in the terminal and intended to display messages in
the Expert Advisors log (tab Expert Advisors in the Tools window; despite its name Expert Advisors, the
tab collects messages from MQL programs of all types). Unlike the function OnStart that we are
defining independently, the Print function is defined for us in advance and forever. Print is one of many
embedded functions constructing the MQL5 API (application programming interface).
The new line in our code denotes the statement to call the Print function sending into it the list of
arguments (in parentheses) that will be printed in the log. Arguments in the list are separated by
commas. In this case, there are two arguments: Line "Hello " and call for another embedded function,
Symbol, that returns the name of the active instrument on the current chart (the value obtained from it
will immediately get into the list of arguments of function Print, into the location from which the Symbol
function has been called).
The Symbol function does not have any parameters and, therefore, nothing is sent into it inside
parentheses.
For instance, if the script is located on the "EURUSD" chart, then calling the function Symbol() will
return "EURUSD" and, in terms of the program being executed, the statement regarding calling the
function Print will have a new look: Print("Hello, ", "EURUSD"). From a user's point of view, of course, all
these calls for functions and the dynamic substitution of intermediary results are smooth and
immediate. However, for a programmer, it is important to fully realize how the program is executed
step by step to avoid logical errors and achieve strict compliance with the plan conceived.
The "Hello " line in double quotation marks is referred to as the literal, i.e., a fixed sequence of
characters perceived by the computer as a text, as it is (as it is introduced in the source code of the
program).
Thus, the printing statement above must print the two arguments one by one in the log, which should
result in actually joining the two lines and obtaining "Hello, EURUSD".
Importantly, the comma inside the quotation marks will be printed in the log as a part of the line and is
not processed in any special manner. Unlike that, the comma that is placed after the closing quotation
mark and before calling Symbol() is the separating character in the argument list, i.e., affects the
program behavior. If the first comma is omitted, the program will not lose its correctness, although it
will print the word "Hello" without a comma after it. However, if the second comma is omitted, the
program will stop being compiled, since the syntax of the function argument list will be broken: All
values in it (in our case, these are two lines) must be separated by commas.
The compiler 'complains' of the lack of something before mentioning Symbol. This will break the
compilation, and the executable file of the program is not created. Therefore, we will put the comma
back in place.
This example shows us how important it is to strictly follow the syntax of the language. The same
characters can work differently, being in different parts of the program. Thus, even a small omission
may be critical. For instance, note the semicolon at the end of the line calling Print. The semicolon
means the end of the statement here. If we forget to put it, strange compiler errors may occur.
To see this, we will try to remove this semicolon and re-compile the script. This results in obtaining new
errors with the description of the problem and its place in the source code.
The first error explicitly specifies the absence of the semicolon expected by the compiler. The second
error is propagated: The closing brace signaling the end of the program had been detected before the
current statement ended. In the compiler's opinion, it continues, because it has not encountered the
semicolon yet. It is obvious how to fix the errors: The semicolon must be placed back in the right
position in the statement.
Let's compile and launch the fixed script. Although it is executed very quickly and removed from the
chart practically immediately and a record confirming the script operation appears in the Experts log.
It is logical to suggest that the definition structure of the new function must be similar to that of the
function OnStart already familiar to us. However, its name must be unique, i.e., it should not duplicate
the names of other functions or reserved words. We will study the list of these words further in this
textbook, while now luckily suggesting that the word Greeting can be used as a name.
Like the Symbol function, this function must return a string; this time, however, the string must be one
of the following phrases, depending on the time of day: "Good morning", "Good afternoon", or "Good
evening".
Guided by common sense, we are using the common concept of string here. Apparently, it is familiar to
the compiler, because we saw how it had generated a program printing the predefined text. Thus, we
have smoothly approached to the concept of types in the programming language, one of the types
being a string, i.e., a sequence of characters.
In MQL5, this type is described by the keyword string. This is the second type we know, the first one
was void. We have already seen a value of this type, without knowing it was that: It is the literal "Hello,
". When we just insert a constant (particularly, something like a quoted text) into the source code, its
type description is not required: defines the correct type automatically.
Using the OnStart function description as a sample, we can suggest how the function Greeting should
appear for a first approximation.
string Greeting()
{
}
This text indicates our intention to create the Greeting function, which can return an arbitrary value of
the string type. However, for the function to really return something, it is necessary to use a special
statement with the return operator. It is one of many MQL5 operators: We will explore them all later. If
the function has a return value type other than void, it must contain the operator return.
Particularly, to return the former greeting string "Hello, " from the function, we should write:
string Greeting()
{
return "Hello, ";
}
Operator return stops the function execution and sends out what is to the right of it, as a result. "Out"
hides the source code fragment, from which the function was called.
We have not explored all the options for writing expressions that could form an arbitrary string.
However, the simplest instance with the quoted text is transferred here without any changes. It is
important that the return value type coincides with the function type, as in our case. At the end of the
statement, we put a semicolon.
However, we wanted to generate different greetings depending on the time of day. Therefore, the
function must have an hour-defining parameter that can take values ranging from 0 through 23.
Obviously, the hour number is an integer, i.e., a number that has no fractional part. It is clear that the
time does not stop within an hour, and minutes are counted in it, the number of minutes being an
integer, too. Then again, it is pointless to determine the time of day accurately to a minute. Therefore,
we will limit ourselves to choosing the greeting by the hour number only.
For integer values, there is a special type int in MQL5. This value should be sent to the function
Greeting from another place in the program, from which this function will be called. Here we have first
faced the necessity of describing a named memory cell, that is, a variable.
Basically, there is a stricter term, identifier, in the program, which term is used for the names of
variables, functions, and many other entities to be learned later herein. Identifier follows some rules. In
particular, it may only contain Latin characters, numbers, and underscores; and it may not start with a
number. This is why the word 'Greeting' chosen for the function earlier meets these requirements.
Values of a variable can be different, and they can be changed using special statements during the
program execution.
Along with its type and name, a variable is characterized by the context, i.e., an area in the program,
where it is defined and can be used without any errors of compiler. Our example will probably facilitate
understanding this concept without any detailed technical reasoning in the beginning.
The matter is that a particular instance of a variable is the function parameter. The parameter is
intended for sending a certain value into the function. Hereof it is obvious that the code fragment,
where there is such a variable, must be limited to the body of the function. In other words, the
parameter can be used in all statements inside the function block, but not outside. If the programming
language allowed such liberties, this would become a source of many errors due to the potential
possibility to 'spoil' the function inside from a random program fragment that is not related to the
function.
In any case, it is a slightly simplified definition of a variable, which is sufficient for this introductory
section. We will consider some finer nuances later.
Hence, let's generalize our knowledge of variables and parameters: They must have type, name, and
context. We write the first two characteristics in the code explicitly, while the last one results from the
definition location.
Let's see how we can define the parameter of the hour number in the Greeting function. We already
know the desired type, it's int, and we can logically choose the name: hour.
This function will still return "Hello," whatever the hour. Now we should add some statements that
would select different strings to return, based on the value of parameter hour. Please remember that
there are three possible function response options: "Good morning", "Good afternoon", and "Good
evening". We could suppose that we need 3 variables to describe these strings. However, it is much
more convenient to use an array in such cases, which ensures a unified method of coding algorithms
with access to elements.
int array[5];
Array size is specified in square brackets after the name. Elements are numbered from 0 through N-1,
where N is the array size. They are accessed, i.e., the values are read, using a similar syntax. For
example, to print the first element of the above array into the log, we could write the following
statement:
Print(array[0]);
Please note that index 0 corresponds to the very first element. To print the last element, the
statement would be replaced with the following:
Print(array[4]);
It is supposed, of course, that before printing an element of the array, a useful value has once been
written into it. This record is made using a special statement, i.e., assignment operator. A special
feature of this operator is the use of the symbol '=', to the left of which the array element (or variable)
is specified, in which the record is made, while to the right of it the value to be recorded or its
'equivalent' is specified. Here, 'equivalent' hides the language ability to compute expressions of
arithmetic, logic, and other types (we will learn them in Part 2). Syntax of the expressions is mostly
similar to the rules of writing the equations learned in school-time arithmetic and algebra. For example,
operations of addition ('+'), subtraction ('-'), multiplication ('*'), and division ('/') can be used in an
expression.
Below are examples of operators to fill out some elements of the array above.
array[0] = 10; // 10
array[1] = array[0] + 1; // 11
array[2] = array[0] * array[1] + 1; // 111
These statements demonstrate various methods of assignment and constructing expressions: In the
first string, literal 10 is written into element array[0], while in the second and third lines, the
expressions are used, computing which leads to obtaining the results specified for visual clarity in
comments.
Where array elements (or variables, in a general case) are involved in an expression, the computer
reads their values from memory during program execution and performs the above operations with
them.
It is necessary to distinguish the use of variables and array elements to the left of and to the right of
the '=' character in the assignment statement: On the left, there is a 'receiver' of the processed data
(it is always single), while on the right, there are the 'sources' of initial data for computing (there can
be many 'sources' in an expression, like in the last string of this example, where the values of elements
array[0] and array[1] are multiplied together).
In our examples, the '=' character was used to assign the values to the elements of a predefined array.
However, it is sometimes convenient to assign initial values to variables and arrays immediately upon
defining them. This is called initialization. The '=' character is used for it, too. Let's consider this
syntax in the context of our applied task.
Let's describe the array of strings with the greeting options inside the function Greeting:
In the statement added, not only the messages array with 3 elements is defined, but also its
initialization, i.e., filling with the desired initial values. Initialization highlights the '=' character upon
variable/array name and type description. For a variable, it is necessary to specify only one value after
'=' (without braces), while for an array, as we can see, we can write several values separated by
commas and enclosed in braces.
Do not confuse initialization with assignment. The former is specified in defining a variable/array (and is
made once), while the latter occurs in specific statements (the same variable or array element can be
assigned with different values over and over again). Array elements can only be assigned separately:
MQL5 does not support assigning all elements at a time, as is the case with initialization.
The messages array, being defined inside the function, is available only inside it, like the parameter
hour. Then we will see how we can describe variables available throughout the program code.
How shall we transform the incoming value of hour with the hour number into one of the three
elements?
Recall that, according to our idea, hour can have values from 0 through 23. If we divide it by 8 exactly,
we will obtain the values from 0 through 2. For instance, dividing 1 by 8 will give us 0, and 7 by 8 will
give 0 (in exact division, the fractional part is neglected). However, dividing 8 by 8 is 1, so all numbers
through 15 will give us 1 when divided by 8. Numbers 16-23 will correspond with the division result of
2. Integers 0, 1, and 2 obtained shall be used as indexes to read the messages array element.
In MQL5, operation '/' allows computing the exact division for integers.
Expression to obtain the division results is similar to those we have recently considered for the array,
just the parameter hour and operation '/' must be used. We will use the following statement as a
demonstration of a possible implementation of the hour transformation into the element index:
Here, a new integer variable, index, is defined and initialized by the value of the above expression.
However, we can omit saving the intermediate value in the index variable and immediately transfer this
expression (to the right of '=') inside square brackets, where the array element number is specified.
Then in the statement with operator return, we can extract the relevant greeting as follows:
The function is more or less ready. After a couple of sections, we will make some corrections, though.
So far, let's save the project in a file under another name, GoodTime0.mq5, and try to call our function.
For this reason, in OnStart, we will use the call for Greeting inside the Print call.
void OnStart()
{
Print(Greeting(0), ", ", Symbol());
}
We have saved the separating comma (put inside lateral "Hello, ") between the greeting and the
instrument name. Now there are three arguments in the Print function call: The first and the last ones
will be computed on the fly using calls, respectively, of functions Greeting and Symbol, while the
comma will be sent for printing as it is.
So far, we are sending the constant '0' into the function Greeting. It is its value that will get into the
hour parameter. Having compiled and launched the program, we can make sure that it prints the
desired text in the log.
However, in practice, greetings must be selected dynamically, depending on the time specified by the
user.
• It is placed in the text outside of all blocks (we have learned just the blocks constituting the body
of functions yet, but we will learn about the other ones later) or, in other words, beyond any pairs
of braces;
• It starts with the keyword input; and
• It is initialized with a default value.
It is usually recommended to place input parameters at the start of the source code.
For instance, to define an input parameter for entering the hour number in our script, the next string
should be added immediately upon the triple of directives #property:
• First, there is the GreetingHour variable in the script now, which is available from any place of the
source code, including from inside of any function. This definition is called a global-level definition,
which is due to the execution of item 1 from the list above.
• Second, using the input keyword makes such a variable visible inside the program and in the user
interface, in the MQL5 program properties dialog, which opens when it starts. Thus, when starting
the program, a user sets the necessary value of parameters (in our case, one parameter
GreetingHour), and they become the values of the corresponding variables during the execution of
the program.
Let's note again that the default value that we have specified in the code will be shown to the user in
the dialog. However, the user will be able to change it. In this case, it is that new, manually entered
value that will be included in the program (not the initialization value).
The initial value of input parameters is affected by both the initialization in the code and the user's
interactive choice in launching them, and the MQL5 program type, and the way it is launched. The
matter is that different types of MQL5 programs have different life cycles after being launched on
charts. Thus, upon a one-time placement in the chart, indicators and Expert Advisors are 'registered' in
it forever, until the user removes them explicitly. Therefore, the terminal remembers the latest settings
selected and uses them automatically, for example, upon the terminal restart. However, scripts are not
saved in charts between the terminal sessions. Therefore, only the default value may be shown to us
when we launch the script.
Unfortunately, for some reason, the description of an input parameter does not guarantee calling the
dialog of settings at the script start (for scripts as an independent MQL5 program type). For this to
happen, it is necessary to add one more, script-specific directive #property into the code:
#property script_show_inputs
As we will see further, this directive is not required for other types of MQL5 programs.
We needed GreetingHour to transfer its value into the Greeting function. To do so, it is sufficient to
insert it into the Greeting function call, instead of 0:
void OnStart()
{
Print(Greeting(GreetingHour), ", ", Symbol());
}
Considering the changes we have made to describe the input parameter, let's save the new script
version in file GoodTime1.mq5. If we compile and start it, we will see the data entry dialog:
For instance, if we edit the value GreetingHour to 10, then the script will display the following greeting:
Just for the fun of it, let's run the script again and enter 100. Instead of any meaningful response, we
will get:
We have just encountered a new phenomenon, i.e., runtime error. In this case, the terminal notifies
that in position 18 of string 19, our script has tried to read the value of an array element having a non-
existing index (beyond the array size).
Since errors are a permanent and necessary companion of a programmer and we have to learn how to
fix them, let's talk in some more details about them.
Nobody is insured against errors in coding programs. Errors may occur at different stages and are
conveniently divided into:
• Compilation errors returned by the compiler when identifying a source code that does not meet the
required syntax (we have already learned about such errors above); it is easiest to fix them
because the compiler searches for them;
• Program runtime errors returned by the terminal, if an incorrect condition occurs in the program,
such as division by zero, computing the square root of a negative, or an attempt to refer to a non-
existing element of the array, as in our case; they are more difficult to detect since they usually
occur not at any values of input parameters, but only in specific conditions;
• Program designing errors that lead to its complete shutdown without any tips from the terminal,
such as sticking at an infinite loop; such errors may turn out to be the most complex in terms of
locating and reproducing them, while the reproducibility of a problem in the program is a necessary
condition for fixing it afterward; and
• Hidden errors, where the program seems to work smoothly, but the result provided is not correct;
it is easy to detect if 2*2 is not 4, while it is much more difficult to notice the discrepancies.
But let's get back to the specific situation with our script. According to the error message provided to
us by the MQL program runtime environment, the following statement is wrong:
return messages[hour / 8]
In computing the index of an element from the array, depending on the value of the hour variable, a
value may be obtained that goes beyond the array size of three.
The debugger embedded in MetaEditor allows making sure that it really happens. All its commands are
collected in the Debug menu. They provide many useful functions. Here we are going to only settle on
two: Debut -> Start on Real Data (F5) and Debug -> Start on History Data (Ctrl+F5). You can read about
the other ones in the MetaEditor Help.
Both commands compile the program in a special manner – with the debugging information. Such a
version of the program is not optimized as in standard compilation (more details on optimization, please
see Documentation), while at the same time, it allows using the debugging information to 'look inside'
the program during execution: See the states of variables and function call stacks.
The difference between debugging on real data and on history data consists in starting the program on
an online chart with the former one and on the tester chart in a visual mode with the latter one. To
instruct the editor on what exactly chart and with which settings to use, i.e., symbol, timeframe, date
range, etc., you should preliminarily open the dialog Settings -> Debug and fill out the required fields in
it. Option Use specified settings must be enabled. If it is disabled, the first symbol from the Market
Watch and timeframe H1 will be used in online debugging, while tester settings are used when
debugging on history data.
Please note that only indicators and Expert Advisors can be debugged in the tester. Only online
debugging is available to scripts.
Let's run our script using F5 and enter 100 in parameter GreetingHour to reproduce the above problem
situation. The script will start executing, and the terminal will practically immediately display an error
message and request for opening the debugger.
Having responded in the affirmative, we will get into MetaEditor where the current string is highlighted
in the source code, in which the error has occurred (please give a notice of the green arrow in the left
field).
The current call stack is displayed in the lower left window part: All functions are listed in it (in bottom-
up order), which had been called before the code execution stopped at the current string. In particular,
in our script, the OnStart function was called (by the terminal itself), and the Greeting function was
called from it (we called it from our code). An overview panel is in the lower right part of the window.
Names of variables can be entered into it, or the entire expressions into the Expression column, and
watch their values in the Values columns in the same string.
For instance, we can use the Add command of the context menu or double-click with the mouse on the
first free string to enter the expression "hour / 8" and make sure that it is equal to 12.
Since debugging stopped resulting from an error, there is no sense to continue the program; therefore
we can execute the Debug -> Stop command (Shift+F5).
In more complex cases of a not so obvious problem source, the debugger allows the string-by-string
monitoring of the sequence of executing the statements and the contents of variables.
To solve the problem, it is necessary to ensure that, in the code, the element index always falls within
the range of 0-2, i.e., complies with the array size. Strictly speaking, we should have written some
additional statements checking the data entered for correctness (in our case, GreetingHour can only
take a value within the range of 0-23), and then either display a tip or fix it automatically in case of
violation of the conditions.
Within this introductory project, we will not go beyond a simple correction: We will improve the
expression that computes the element index so that its result always falls within the required range. For
this purpose, let's learn about one more operator – the modulus operator that only works for integers.
To denote this operation, we use symbol '%'. The result of the modulus operation is the remainder of
the integer division of dividend by the divisor. For example:
11 % 5 = 1
Here, with the integer division of 11 by 5, we would obtain 2, which corresponds with the largest factor
of 5 within 11, which is 10. The remainder between 11 and 10 is exactly 1.
To fix the error in function Greeting, suffice to preliminarily perform the modulus division of hour by 24,
which will ensure that the hour number will range within 0-23. Function Greeting will look as follows:
Although this correction will surely work well (we are going to check it in a minute), it does not concern
another problem that is left beyond our focus. The matter is that the GreetingHour parameter is of the
int type, i.e., it can take both positive and negative values. If we tried to enter -8, for instance, or a
'more negative' number, then we would get the same runtime error, i.e., going beyond the array; just,
in this case, the index does not exceed the highest value (array size) but becomes smaller than the
lowest one (particularly, -8 leads to referring to the -1st element, interestingly, the values from -7 to -
1 being displayed onto the 0th element and do not cause any error).
To fix this problem, we will replace the type of parameter GreetingHour with the unsigned integer: We
will use uint instead of int (we will tell about all available types in part two, and here it is uint that we
need). Guided by the limit for the non-negativity of values, built in at the compiler level for uint, MQL5
will independently ensure that neither the user (in the properties dialog) nor the program (in its
computation) "goes negative."
Let's save the new version of the script as GoodTime2, compile, and launch it. We enter the value of
100 for the GreetingHour parameter and make sure that, this time, the script is executed without any
errors, while the greeting "Good morning" is printed in the terminal log. It is the expected (correct)
behavior since we can use a calculator and check that the remainder of the modulus division of 100 by
24 gives 4, while the integer division of 4 by 8 is 0, which means morning, in our case. From the user's
point of view, of course, this behavior can be considered as unexpected. However, entering 100 as the
hour number was also an unexpected user action. The user probably thought that our program would go
down. But this did not happen, and this is a good point. Of course, with real programs, the values
entered must be validated and the user must be notified about bugs.
As an additional measure of preventing from entering a wrong number, we will also use a special MQL5
feature to give a more detailed and friendly name to the input parameter. For this purpose, we will use
a comment after the input parameter description in the same string. For example, like this:
Please note that we have written the words from the variable name separately in the comment (it is not
an identifier in the code anymore, but a tip for the user in it). Moreover, we added the range of valid
values in parentheses. When launching the script, the previous GreetingHour will appear in the dialog to
enter the parameters as follows:
Now we can be sure that, if 100 is entered as the hour, it is not our fault.
A careful reader may wonder why we have defined the Greeting function with the hour parameter and
send GreetingHour into it if we could use the input parameter in it directly. Function, as a discrete
logical fragment of a code, is formed for both dividing the program into visible and easy-to-understand
parts and reusing them subsequently. Functions are usually called from several parts of the program or
are part of a library that is connected to multiple different programs. Therefore, a properly written
function must be independent of the external context and can be moved among programs.
For instance, if we need to transfer our function Greeting into another script, it will stop being
compiled, since there won't be the GreetingHour parameter in it. It is not quite correct to require
adding it, because the other script can compute the time in another manner. In other words, when
writing a function, we should do our best to avoid unnecessary external dependencies. Instead, we
should declare the function parameters that can be filled out with the calling code.
The simplest way to communicate some simple momentary information to the user without making him
or her looking into the log (which is a service tool for monitoring the operation of programs and may be
hidden from the screen) is provided by the MQL5 API function Comment. It can be used exactly as that
of Print. However, its execution results in displaying the text not in the log, but on the current chart, in
its upper left corner.
For instance, having replaced Print with Comment in the text script, we will obtain such a function
Greeting:
void OnStart()
{
Comment(Greeting(GreetingHour), ", ", Symbol());
}
Having launched the changed script in the terminal, we will see the following:
If we need both display the text for the user and draw their attention to a change in the environment,
related to the new information, it is better to use function Alert. It sends a notification into a separate
terminal window that pops up over the main window, accompanying it with a sound alert. It is useful, for
example, in case of a trade signal or non-routine events requiring the user's intervention.
The image below shows the result of the Alert function operation.
Script versions with functions Comment and Alert are not attached to this book for the reader to
independently try and edit GoodTime2.mq5 and reproduce the screenshots provided herein.
If there is a separating symbol (we will learn more about them in Part 2) between some elements of the
statement, such as a comma ',' between function parameters, then there is no need for using any
spaces at all.
Changes in formatting the source code do not modify the executable code.
Basically, there are many non-free-form languages. In some of them, forming a code block, which is
performed using brace matching in MQL5, is based on equal indents from the left edge.
Due to free formatting, MQL5 allows programmers to use multiple different techniques to form the
source code in order to improve its readability, visibility, and easier internal navigation.
Let's consider some examples of how the source text of the Greeting function can be recorded from our
script, without changing its intent.
Here is the most 'packed' version without any excessive spaces or line breaks (a line break denoted
here with the symbol '\' is only added to comply with the restrictions on publishing source codes in this
book).
Here is the version, in which excessive spaces and line breaks are inserted.
string
Greeting ( int hour )
{
string messages [ 3 ]
= {
"Good morning" ,
"Good afternoon" ,
"Good evening"
} ;
MetaEditor has a built-in code styler that allows automatically formatting the source code of the
current file in compliance with one of the styles supported. A specific style can be selected in dialog
Tools -> Settings -> Styler. A style is applied using Tools -> Styler command.
You should keep in mind that your spacing freedom is limited. In particular, you may not insert spaces
into identifiers, keywords, or numbers. Otherwise, the compiler won't be able to recognize them. For
example, if we insert just one space between digits 2 and 4 in the number 24, the compiler will return a
bunch of errors trying to compile the script.
'GoodTime2.mq5' GoodTime2.mq5 1 1
'4' - some operator expected GoodTime2.mq5 19 28
'[' - unbalanced left parenthesis GoodTime2.mq5 19 18
'8' - some operator expected GoodTime2.mq5 19 32
']' - semicolon expected GoodTime2.mq5 19 33
']' - unexpected token GoodTime2.mq5 19 33
5 errors, 0 warnings 6 1
Compiler messages may not always appear clear. It should be considered that, even upon the very first
(in succession) error, there is a high probability that the internal representation of the program (as the
compiler perceived it in 'mid-sentence') differs considerably from what the programmer has suggested.
In particular, in this case, only the first and the second errors contain the key to understanding the
problem, while all other ones are propagated.
According to the first error, the compiler expected to find the symbol of an operation between 2 and 4
(as it perceives 2 and 4 as two different numbers and not as 24 separated by a space). Alternative
logic consists in the fact that a closing square bracket is omitted here, and the compiler displayed the
second error: "'[' - unbalanced left parenthesis." After that running through the expression gets
completely shattered, due to which the subsequent number 8 and closing bracket ']' appear
inappropriate to the compiler. But in fact, if we just delete the excessive space between 2 and 4, the
situation will become normal.
It is, of course, much easier to perform such an error analysis where we have intentionally added the
issue. We do not always understand in practice how to remedy one situation or another. Even in the
case above, supposing that you have received this broken code from another programmer and the
array elements do not contain such trivial information, another correction option is easy to suspect:
Either 2 or 4 must be left, because the author has probably desired to replace one number with another
and not cleaned the 'footprints'.
In the subsequent sections of this book, we will start to explore in detail these and many other features
of MQL5, the technical aspects of programming, and its applications for trading.
The material assists our readers in progressing to the independent practical application of the
procedural programming, This is one of the very first programming trends to solve various problems. In
fact, it is the formation of a program from small steps (statements) to be executed in the required
sequence for data processing. The text script shown in Part 1 of this book is an example of such a
style.
This section covers a broad spectrum of fundamental concepts and tools essential for successful MQL5
programming, including the following subsections:
Identifiers:
· Identifiers form the foundation of any program code. This subsection discusses the purpose and
rules for naming identifiers in MQL5.
· MQL5 includes a variety of built-in data types, each designed to store and process specific types of
information. This section provides a comprehensive understanding of basic data types.
Variables:
Variables are used to store and manage data in a program. The "Variables" section teaches the basics
of working with variables and considers how to declare, initializing, and assign values to them.
Arrays:
· Arrays provide a structured way to store data. This section covers the basics of creating and using
arrays in MQL5.
Expressions:
· Expressions form the basis of calculations and program logic. From this subsection, you will learn
how to construct and evaluate expressions in MQL5.
Type conversion:
· Data type conversion is an integral part of programming. The "Type Conversion" section provides
an understanding of the process related of converting data between different types in MQL5.
Statements:
· Statements are commands that control program execution. In this section, we will look at various
types of statements and their applications.
Functions:
· Functions allow for code structuring and reuse. This section dives into the basics of creating and
calling functions in MQL5.
Preprocessor:
· The MQL5 preprocessor processes the source code before compilation. The "Preprocessor" section
describes the principles of using preprocessor directives and their impact on the code.
42
Part 2. Programming fundamentals
Procedural programming principles will act as the basis for the subsequent learning of a more powerful
paradigm, i.e., Object-Oriented Programming (OOP). It will be referred to in Part 3.
MQL5 Programming for Traders – Source Codes from the Book. Part 2
Examples from the book are also available in the public project \MQL5\Shared Projects\MQL5Book
2.1 Identifiers
As we are going to see soon, programs are built of multiple elements that must be referred to by unique
names to avoid confusion. These names are exactly what is called identifiers.
Identifier is a word composed by certain rules: Only Latin characters, underscore characters ('_'), and
digits may be used in it, and the first character may not be a digit. Letters can be small (lower-case)
and capital (upper-letter).
The maximum identifier length is 63 characters. The identifier may not coincide with any service words
of MQL5, such as type names. You can find the full list of service words in the Help. Violating any of the
identifier forming rules will cause a compilation error.
i // single character
abc // lower-case letters
ABC // upper-case letters
Abc // mixed-case letters
_abc // underscore at the beginning
_a_b_c_ // underscore anywhere
step1 // digit
_1step // underscore and digit
We have already seen in the script HelloChart how identifiers are used as names of variables and
functions.
It is recommended to provide identifiers with meaningful names, from which the purpose or content of
the relevant element becomes clear. In some cases, single-character identifiers are used, which we will
discuss in the section dealing with loops.
There are some common practices for composing identifiers. For instance, if we choose a name for a
variable that stores the value of profit factor, the following options will be good:
In many programming languages, different styles are used to name different entities. For example, a
practice may be followed, in which variable names only start with a lower-case letter, while class names
(see Part 3) with upper-case letters. This helps the programmer analyze the source code when working
in a team or if they return to their own code fragment after a long break.
Along the above ones, there are other styles, some of which are used in special cases:
43
Part 2. Programming fundamentals
Unlike numbers, text information, such as the name of a trading instrument, conforms to other rules.
Here we can build a word of letters or a sentence of words, but it is impossible to compute the
progressive total or arithmetic mean of several lines. Thus, 'line' or 'string' is another data type, not a
numeric one.
Along with the purpose and a typical set of operations that are meaningful for each type, there is
another important thing that differs types from each other. It's their size. For instance, the week
number cannot exceed 52 within a year, while the number of seconds that have elapsed from the
beginning of the year represents an astronomical shape. Therefore, to efficiently store and process
such different values in the computer memory, differently sized segments can be singled out. This leads
us to understand that, in fact, the generalizing concept of a 'number' may hide different types.
MQL5 allows the used of some number types differing both in the sizes of memory cells allocated for
them and in some additional features. In particular, some numbers may take negative values, such as
floating profit in pips, while the other ones may not, such as account numbers. Moreover, some values
cannot have a fractional part and therefore, it is more cost-efficient to represent them with a stricter
type of 'integers', as opposed to those of random 'numbers with a decimal point'. For instance, an
account balance or the price of a trading instrument generally have values with a decimal point. At the
same time, the number of orders in history or, again, the account number is always an integer.
MQL5 supports a set of universal types similar to those available in the vast majority of programming
languages. The set includes integer types (different sizes), two types of real numbers (with a decimal
point) of different precision, strings, and single characters, as well as the logical type that only has two
possible values: true and false. Moreover, MQL5 provides its own, specific types operating with time and
color.
For the sake of completeness, let's note that MQL5 allows expanding the set of types, declaring applied
types in the code, i.e., structures, classes, and other entities typical of OOP; but we are going to
consider them later.
Since the size of the cell where the value is stored is an important type attribute, let's touch on
memory methodology.
The smallest unit of computer memory is a byte. In other words, a byte is the smallest size of a cell
that a program can allocate for a separate value. A byte consists of 8 smaller 'particles', bits, each
2.1 Identifiers
44
Part 2. Programming fundamentals
being able to be in two states: Enabled (1) or disabled (0). All modern computers use such bits at the
lower level because such a binary representation of information is convenient to be embodied in
hardware(in random-access memory, in processors, or while transferring the data by network cables or
via WiFi).
Processing the values of different types is ensured due to the different interpretations of the bit states
in memory cells. The compiler deals with this. Programmers usually do not go as low as bits; however,
the language provides tools for that (see Bitwise operations).
There are special reserved words in MQL5 to describe data types. We have already known some of
them, such as void, int, and string, from Part 1. A complete list of types is given below, each with a
quick reference and size in bytes.
By their purpose, they can be conditionally divided into numeric and character-coded data (marked in
the relevant columns), as well as other, specialized types, such as strings, logical (or boolean) types,
date/time, and color. Type void stands apart and indicates there is no value at all. In addition to scalar
types, MQL5 provides object types for operations with complex numbers, matrices, and vectors:
complex, vector, and matrix. These types are used to solve various problems in linear algebra,
mathematical modeling, machine learning, and other areas. We will study them in detail in Part 4 of the
book.
Size
Type Number Character Note
(bytes)
Size
Type Number Character Note
(bytes)
string 10+
String
variable
void 0 Void
Depending on its size, different value ranges may be stored in the numeric type. Along with the above,
the range may considerably vary for the integers and floating-point numbers of the same size, because
different internal representations are used for them. All these cobwebs will be considered in the
sections dealing with specific types.
A programmer is free to choose a numeric type based on the anticipated values, efficiency
considerations, or for reasons of economy. Particularly, the smaller type size allows fitting more values
of this type in memory, while integers are processed faster than floating-point numbers.
Please note that numeric and character-coded types are partly crossed. This happens because a
character is stored in memory as an integer, i.e., a code in the relevant table of characters: ANSI for
single-byte chars or Unicode for two-byte ones. ANSI is a standard named after an institute (American
National Standards Institute), while Unicode, you guessed it, means Universal Code (Character Set).
Unicode characters are used in MQL5 to make strings (type string). Single-byte characters are usually
required in integrating the programs with external data sources, such as those from the Internet.
As mentioned above, numeric types can be divided into integers and floating-point numbers. Let's
consider them in more detail.
2.2.1 Integers
Integer types are intended for storing numbers without decimal points. They should be chosen if the
applied sense of the value excludes fractions. For example, the numbers of bars on a chart or of open
positions are always integers.
MQL5 allows choosing integer types sized 1-4 bytes using keywords char, short, int, and long,
respectively. They all are the signed types, i.e., they can contain both positive and negative values. If
necessary, integer types having the same sizes can be declared unsigned (their names starting with 'u'
for 'unsigned'): uchar, ushort, uint, and ulong.
Based on the type size and being signed/unsigned, the following table shows the ranges of potential
values.
uchar 0 255
ushort 0 65535
uint 0 4294967295
ulong 0 18446744073709551615
There is no need to memorize the above limiting values for each integer. There are many predefined
named constants in MQL5, which can be used in a code instead of 'magic' numbers, including the
lowest/highest integers. This technology is considered in a section dealing with the preprocessor. Here,
we just list the relevant named constants: CHAR_MIN, CHAR_MAX, UCHAR_MAX, SHORT_MIN,
SHORT_MAX,USHORT_MAX, INT_MIN, INT_MAX, UINT_MAX, LONG_MIN, LONG_MAX, and
ULONG_MAX.
Let's explain how these values are obtained. This requires returning to bits and bytes.
The number of all possible combinations of different states of 8 bits, enabled and disabled, within one
byte, is 256. This produces the range of values 0-255 that can be stored in a byte. However,
interpreting them depends on the type, for which this byte is allocated. Different interpretations are
ensured by the compiler, according to the programmer's statements.
The low-order (rightmost) bit in a byte means 1, the second 2, the third 4, and so on through the high-
order bit that means 128. It's plain to see that these numbers are equal to two raised to a power
equaling the bit number (numbering starts from 0). This is the effect of using the binary system.
Number 7 6 5 4 3 2 1 0
Value 128 64 32 16 8 4 2 1
Where all bits are set, this produces the sum of all powers of two, i.e., 255 is the highest value for a
byte. If all bits are reset, we get zero. If a low-order bit is enabled, the number is odd.
In coding signed numbers, the high-order bit is used to mark negative values. Therefore, for a single-
byte integer within the positive range, 127 becomes the highest value. For negative values, there are
128 possible combinations, i.e., the lowest value is -128. Where all bits in a byte are set, it is
interpreted as -1. If the lower-order bit is reset in such a number, we will get -2, etc. If only the higher-
order bit (sign) is set and all other bits are reset, we get -128.
This coding that may seem to be irrational is called "additional." It allows you to unify computations of
signed and unsigned numbers at the hardware level. Moreover, it allows you not to lose one value, which
would happen if the positive and negative regions were coded identically: Then we would have got two
values for zero, i.e., a positive 0 and a negative 0. What is more, this would bring ambiguity.
Numbers with more bytes, i.e., 2, 4, or 8, have a similar consecutive numbering of bits and the
progression of their respective values. In all cases, a criterion for the number negativity is the set high-
order bit of the high-order byte.
Thus, we can use a byte to store an unsigned integer (uchar, i.e., unsigned character abbreviated)
within the range of 0-255. We can also write a signed integer into the byte (for which purpose we will
describe its type as char). In this case, the compiler will divide the available amount of combinations of
256 equally between positive and negative values, having displayed it onto the region from -128
through 127 (the 256th value is zero). It's plain to see that values 0-127 will be coded equally at the
bit level for signed and unsigned bytes. However, large absolute values, starting from 128, will turn into
negative ones (according to the scheme described in the insertion above). This "transformation" only
takes place at the moment of reading or performing any operations with the value stored, with the
identical internal data representation (state of bits).
We will consider this matter in more detail in the section dealing with typecasting.
In a similar manner as with single-byte integers, it is easy to calculate that the number of bit
combinations is 65536 for 2 bytes. Hence, the ranges are formed for the signed and unsigned two-byte
integer, short and ushort. Other types allow storing even larger values due to increasing their byte
sizes.
Please note that using an unsigned type with the same size allows doubling the highest positive value.
This may be necessary for storing potentially very large quantities, for which no negative values may
appear. For example, the order number in MetaTrader 5 is a value of the ulong type.
We have already encountered the integer description samples in Part 1. In particular, input parameter
GreetingHour of type uint was defined there:
Except for the additional keyword, input, that makes the variable visible in the list of parameters of an
MQL program, other components, i.e., type, name, and optional initialization after the '=' sign, are
intrinsic to all variables.
Variable description syntax will be considered in detail in the Variables section. So far, please note the
method of recording the constants of integer type. In describing a variable, constants can be specified
as a default value (in the example above, it is 0). Moreover, constants can be used in expressions, for
instance, in a formula event.
It should be reminded that constants of any type, inserted in the source code, are named literals
(textually: "word-for-word"). Their name derives from the fact that they are introduced into the
program "as is" and used immediately at the point of description. Literals, unlike many other elements
of the language, particularly variables, have no names and cannot be referred to from other points of
the program.
For negative numbers, it is required to provide the minus sign '-' before the number; however, the plus
sign '+' can be omitted for positive numbers, i.e., forms +100 and just 100 are identical.
It should be noted that numeric values are usually recorded in the source code within our habitual
decimal notation. However, MQL5 allows using the other one, i.e., hexadecimal. It is convenient for
processing bit-level information (see Bitwise operations).
Numbers from 0 through 9 are permitted in all digit order numbers in decimal constants, while for
hexadecimal ones, along with digits, Latin symbols from A through F or from a through f (that is, case
does not matter) are used additionally. "Hexadecimal digit" A corresponds with number 10 of decimal
notation, B – 11, C – 12, etc., up through F equal to 15.
A distinctive feature of a hexadecimal constant is the fact that it begins with prefix 0x or 0X, followed
by the significant digit orders of the number. For instance, number 1 is recorded as 0x1 in the
hexadecimal system, while 16 as 0x10 (an additional higher order digit is required because 16 is
greater than 15, that is, 0xF). Decimal 255 turns into 0xFF.
Let's give some more examples illustrating various situations of using integer types in describing
variables (attached in script MQL5/Scripts/MQL5Book/p2/TypeInt.mq5):
void OnStart()
{
int x = -10; // ok, signed integer x = -10
uint y = -1; // ok, but unsigned integer y = 4294967295
int z = 1.23; // warning: truncation of constant value, z = 1
short h = 0x1000; // ok, h = 4096 in decimal
long p = 10000000000; // ok
int w = 10000000000; // warning, truncation..., w = 1410065408
}
Variable z is assigned with the floating-point number 1.23 (they will be considered in the next section),
and the compiler warns about the truncation of the fractional part. As a result, integer 1 gets into the
variable.
The large value 10000000000 is recorded in variables p and w, the former of which is of a long integer
type (long) and processed successfully, while the latter one of the normal type (int) and, therefore,
calls for the compiler warning. Since the constant exceeds the maximum value for int, compiler
truncates the excessive higher order digits (bits) and, in fact, 1410065408 gets into w.
This behavior is one of the potential negative developments of type conversions that may or not may be
implied by the programmer. In the latter case, it is fraught with a potential error. Clearly, in this
particular example, wrong values were selected intentionally to demonstrate warnings. It is not always
that obvious in a real program, which values the program is attempting to save in the integer variable.
Therefore, you should look into the compiler warnings very carefully and try to make away with them,
having changed the type or explicitly specified the required typecast. This will be considered in the
section dealing with Typecasting.
For integer types, arithmetic, bitwise, and other types of operations are defined (see chapter
Expressions).
We use numbers with a decimal point, or real numbers, in everyday life just as often as integers. The
name 'real' itself indicates that using such numbers, you can express something tangible from the real
world, such as weight, length, body temperature, i.e., everything that can be measured by a non-
integer amount of units, but with "a little more."
We often use real numbers in trading, too. For instance, they are used to express symbol prices or
volumes in trading orders (normally permitting the fractional parts of a full-sized lot).
There are 2 real types provided in MQL5: float for normal accuracy and double for double accuracy.
In the source code, the constant values of types float and double are usually recorded as an integer
and a fractional part (each being a sequence of digits), separated by the character '.', such as 1.23 or
-789.01. There can be no integer or fraction (but not both at a time), but the point is mandatory. For
instance, .123 means 0.123, while 123. means 123.0. Simply 123 will create a constant of integer
type.
However, there is another form of recording real constants, the exponential one. In it, the integer and
fractional part are followed by 'E' or 'e' (case does not matter) and an integer representing the power,
to which 10 should be raised to obtain an additional factor. For instance, the following representations
display the same number, 0.57, in exponential form:
.0057e2
0.057e1
.057e1
57e-2
When recording real constants, the latter ones are defined by default as type double (they consume 8
bytes). To set type float, suffix 'F' (or 'f') should be added to the constant on the right.
Types float and double differ by their sizes, ranges of values, and number representation accuracy. All
this is shown in the table below.
Range of values is shown for them in absolute terms: Minimum and maximum determine the amplitude
of permitted values in both positive and negative regions. Similar to integer types, there are embedded
named constants for these limiting values: FLT_MIN, FLT_MAX, DBL_MIN, DBL_MAX.
Please note that real numbers are always signed, that is, there are no unsigned analogs for them.
Accuracy shall mean the quantity of significant digits (decimal digits) the real number of the relevant
type is able to store undistorted.
Indeed, the numbers of real types are not as accurate as those of integer types. This is the price to be
paid for their universality and a much wider range of potential values. For instance, if an unsigned 4-
byte integer (uint) has the highest value of 4294967295, i.e., about 4 million, or 4.29*109, then the 4-
byte real one (float) has 3.4 * 1038, which is by 29 orders of magnitude higher. For 8-byte types, the
difference is even more perceptible: ulong can house 18446744073709551615 (18.44*1018, or ~18
quintillion), while double can house 1.80 * 10308, that is, by 289 orders of magnitude more. Insertion
provides more detail regarding accuracy.
Mantissa and Exponent
The internal representation of real numbers in memory (in the bytes allocated for them) is quite
tricky. The higher-order bit is used as a marker of the negative sign (we have also seen that in
integer types). All other bits are divided into two groups. The larger one contains the mantissa of
the number, i.e., significant digits (we mean binary digits, i.e., bits). The smaller one stores the
power (exponent), to which 10 must be raised to obtain the stored number upon multiplying it by
the mantissa. Particularly, for type float mantissa is sized 24 bits (FLT_MANT_DIG), while for
double it is 53 (DBL_MANT_DIG). In terms of conventional decimal places (digits), we will get the
same accuracy that has been shown in the table above: 6 (FLT_DIG) is the lowest quantity of
significant digits for float, while 15 (DBL_DIG) is that for double. However, depending on the
particular number, it can have "lucky" combinations of bits, corresponding to a greater quantity of
decimal digits. Sizes of the parameters are 8 and 11 bits for float and double, respectively.
Due to the exponent, real numbers get a much larger range of values. At the same time, with the
increase in the exponent, the "specific weight" of the low-order digit of mantissa increases, too.
This means that two neighboring real numbers that can be represented in the computer memory
are substantially different. For instance, for number 1.0 the "specific weight" of the low-order bit is
1.192092896e–07 (FLT_EPSILON) in case of float and 2.2204460492503131e-016
(DBL_EPSILON) in case of double. In other words, 1.0 is indistinguishable from any number near it
if such a number is below 1.192092896e–07. This may seem not very important or "not a big
deal," but this uncertainty region gets larger for larger numbers. If you store in float a number
about 1 billion (1*109), the last 2 digits will stop being safely stored or restored from memory (see
the code sample below). However, basically, the problem is not the absolute value of a number, but
the maximum quantity of digits in it, which should be recalled without losses. Equally "well," we can
try to fit a number represented as 1234.56789 (which is structurally much like the price of a
financial instrument) in float; and its two last digits will "float" due to the lack of accuracy in their
internal representation.
For double, a similar situation will start showing for much greater numbers (or for a much greater
quantity of significant digits), but it is still possible and often happens in practice. You should
consider this when operating very large or very small real numbers and write your programs with
additional checks for potential loss of accuracy. In particular, you should compare a real number
with zero in a special manner. We will deal with it in the section on comparison operators.
It may seem to a careful reader that the sizes of mantissa and exponent above are specified
wrongly. Let's explain that exemplified by float. It is stored in the memory cell sized 4 bytes, that
is, consumes 32 bits. At the same time, the sizes of mantissa (24) and exponent (8) sum to 32
already. Then where is the signed bit? The matter is that IT professionals arranged to store
mantissa in the 'normalized' form. It will be easier to understand what it is if we consider the
exponential form of recording a normal decimal number first. Let's say number 123.0 could be
represented as 1.23E2, 12.3E1, or 0.123E3. A designation is considered to be the normalized
form, where only one significant digit (i.e., not zero) is placed before the point. For this number, it is
1.23E2. By definition, digits from 1 through 9 are considered significant digits in decimal notation.
Now we are smoothly going to the binary notation. There is only one significant digit in it, 1. It
appears that the normalized form in binary notation always starts with 1, and it can be omitted (not
to spend memory on it). In this manner, one bit can be saved in the mantissa. In fact, it contains
23 bits (one more higher-order unity is implicit and added automatically when reconstructing the
number and retrieving it from memory). Reducing mantissa by 1 bit makes room for the signed bit.
Predominantly, where the floating-point type should be used, we choose double as a more accurate
one. Type float is only used to save memory, such as when working with very large data arrays.
Some examples of using the constants of real types are shown in script
MQL5/Scripts/MQL5Book/p2/TypeFloat.mq5.
void OnStart()
{
double a0 = 123; // ok, a0 = 123.0
double a1 = 123.0; // ok, a1 = 123.0
double a2 = 0.123E3;// ok, a2 = 123.0
double a3 = 12300E-2;
// ok, a3 = 123.0
double b = -.75; // ok, b = -0.75
double q = LONG_MAX;// warning: truncation, q = 9.223372036854776e+18
// LONG_MAX = 9223372036854775807
double d = 9007199254740992; // ok, maximal stable long in double
double z = 0.12345678901234567890123456789; // ok, but truncated
// to 16 digits: z = 0.1234567890123457
double y1 = 1234.56789; // ok, y1 = 1234.56789
double y2 = 1234.56789f; // accuracy loss, y2 = 1234.56787109375
float m = 1000000000.0; // ok, stored as is
float n = 999999975.0; // warning: truncation, n = 1000000000.0
}
Variables a0, a1, a2, and a3 contain the same numbers (123.0) written in different methods.
In the constant for variable b, the insignificant zero is omitted before the point. Moreover, here is the
demonstration of recording a negative number using the minus sign, '-'.
An attempt is made to store the greatest integer in variable q. At this place, the compiler gives a
warning, because double cannot represent LONG_MAX accurately: Instead of
9223372036854775807, there will be 9223372036854776000. It obviously demonstrates that,
even though the ranges of the double values exceed those of integers vastly, it is achieved due to losing
the low-order digits.
As a comparison, the maximum integer that the double type is able to store without any distortions is
given as the value of variable d. In the sequence of integers, it will be followed by sporadic skips, if we
use double for them.
Variable z reminds us again about the limitation on the maximum quantity of significant digits (16) — a
longer constant will be truncated.
Variables y1 and y2, in which the same number is recorded in different formats (double and float), allow
seeing the loss of accuracy due to the transition to float.
In fact, variables m and n will be equal, because 999999975.0 is roughly stored in the internal
representation and turns into 1000000000.0.
Numeric types are often used to calculate using formulas; a wide set of operations is defined for them
(see Expressions).
Computations can sometimes lead to incorrect results, that is, they cannot be represented as a
number. For example, the root of a negative number or the logarithm of zero cannot be defined. In such
cases, real types can store a special value named NaN (Not A Number). In fact, there are several
different types of such values that allow, for instance, telling the difference between plus infinity and
minus infinity. MQL5 provides a special function, MathIsValidNumber, that checks whether the double
value is a number or one of NaN values.
In fact, character types are integer ones, since they store an integer code of a character from the
relevant table: For char, it is the table of ASCII characters (codes 0-127); for uchar, it is extended
ASCII (codes 0-255); and for short/ushort, it is the Unicode table (up to 65535 characters in the
unsigned version). If it is of any interest to you, ASCII is the abbreviated American Standard Code for
Information Interchange.
For MQL5 strings, 2-byte chars ushort are used. 1-byte uchar types are normally used to integrate with
external programs when transferring the arrays of random data that are packed and unpacked in other
types according to applied protocols, such as for connecting to a crypto platform.
Constants of characters are recorded as letters enclosed in single quotes. However, you can also use
the integer notation (see Integers) considered above. At the same time, the integer must be within the
range of values for 1- or 2-byte format.
Additionally, we can use the notation of escape sequences. They use a backslash ('\') as the first
character followed by one of the predefined control characters and/or a numerical code. MQL5
supports the following escape sequences:
• \n – new line
• \r – carriage return
• \t – tabulation
• \\ – backslash
• \" – double quote
• \' – single quote
• \X or \x – prefix to subsequently specify a numerical code in hexadecimal format
• \0 – prefix to subsequently specify a numerical code in octal format
Basic methods of using the constants of character types are given in script
MQL5/Scripts/MQL5Book/p2/TypeChar.mq5.
void OnStart()
{
char a1 = 'a'; // ok, a1 = 97, English letter 'a' code
char a2 = 97; // ok, a2 = 'a' as well
char b = '£'; // warning: truncation of constant value, b = -93
uchar c = '£'; // ok, c = 163, pound symbol code
short d = '£'; // ok
short z = '\0'; // ok, 0
short t = '\t'; // ok, 9
short s1 = '\x5c'; // ok, backslash code 92
short s2 = '\\'; // ok, backslash as is, code 92 as well
short s3 = '\0134';// ok, backslash code in octal form
}
Variables a1 and a2 get the value of character 'a' (English letter) in two different ways.
There is an attempt to record '£' in variable b, but its code 163 is beyond the range char (127);
therefore it is "transformed" into the signed -93 (compiler gives a warning). The variables of types
uchar (c) and short (d) that follow it perceive this code as normal.
Characters can be processed with the same operations as integers (see Expressions).
By reason of the specific nature of strings, their size is a variable value that is equal to the doubled
length of the text (quantity of characters multiplied by the "width" of a character, i.e., 2 bytes) plus
one more character. This additional character is intended for the 'terminating zero' (a char coded as 0)
that denotes the end of the line. Moreover, MQL5 uses some space to store service information, i.e., a
reference to the place in memory where the string starts.
Unlike C++, no address of a string or any other variable can be obtained in MQL5. Direct memory
access is prohibited in MQL5.
A string literal is recorded in the source code as a sequence of characters embedded in double-quotes.
For example: "EURUSD" or "$". We should distinguish between strings consisting of one character, like
"$", and the same single characters, like '$'. These are different data types.
An empty string appears as "". Considering the implicit terminating zero, it consumes 2 bytes, apart
from service information.
Should it be necessary to use the double quote character inside the string, it must be preceded by the
backslash character, transforming into a control sequence, such as "Press \"OK\"".
void OnStart()
{
string h = "Hello"; // Hello
string b = "Press \"OK\"";
// Press "OK"
string z = ""; //
string t = "New\nLine";
// New
// Line
string n = "123"; // 123, text (not an integer value)
string m = "very long message "
"can be presented "
"by parts";
// equivalent:
// string m = "very long message can be presented by parts";
}
Variable t will get a text that, when printed in the log using the Print function or displayed by other
methods, will be divided into 2 strings.
String "123" recorded in variable n is not a number, although it looks like that. There are some
functions in MQL5 to convert text into numbers and back (see section Data transformation). Moreover,
there is a separate set of functions for working with strings.
For convenience, long literals can be written in several strings, as for variable m. The general rule is as
follows: All literals up to the semicolon that marks the end of the variable description are merged by the
compiler. In such formatting, the key is not to forget to add an intervening space inside each fragment
of the string, if necessary (for instance, to separate the words in the message as in the example
above).
For strings, the summation (concatenation) operation is defined, denoted with the character '+'. We
will discuss it in the chapter dealing with expressions (see Arithmetic operations).
String characters can be read separately, referring to them as array elements (see Use of arrays): If s
is a string, then s[i] is the code of the ith character in it, type ushort.
Logic type is defined in MQL5 under the bool keyword and consumes 1 byte of memory. For this type,
two constants are reserved: true and false. Moreover, situations are permissible (and programmers
often make use of it), in which bool is the result of computations with integers and real numbers, value
0 being interpreted as false, and any others as true.
Back-interpretation of the bool type value as a number is supported, as well: true is considered as 1
and false as 0.
void OnStart()
{
bool t = true; // true
bool f = false; // false
bool x = 100; // x = true
bool y = 0; // y = false
int i = true; // i = 1
int j = false; // j = 0
}
For logic type, a set of special logic operations is provided (see Logical (Boolean) Operations and
Comparison Operations).
Values of this type can be used in programs to monitor events, such as trading hours, news
publications, or timeouts for temporarily disabling the EA trading after bad transactions.
The datetime size in memory is 8 bytes. The internal representation of data is completely identical with
the ulong type, since the quantity of seconds elapsed since January 1, 1970, is stored inside. The
maximum date supported is December 31, 3000.
The datetime constants are recorded as a literal string enclosed in single quotes, preceded by the
character 'D'. 6 fields are allocated inside the string, with the numbers for all components of date and
time in the following formats:
D'YYYY.MM.DD HH:mm:ss'
D'DD.MM.YYYY HH:mm:ss'
Here, YYYY means year, MM month, DD day, HH hours, mm minutes, and ss seconds. You can skip
either date or time. It is also possible not to specify seconds or minutes with seconds.
For the maximum permitted value of date, a special constant, DATETIME_MAX, is provided in MQL5,
equaling to the integer value 0x793406fff, which corresponds with D"3000.12.31 23:59:59".
Examples of recording the values of the datetime type are shown in file
MQL5/Scripts/MQL5Book/p2/TypeDateTime.mq5.
void OnStart()
{
// WARNINGS: invalid date
datetime blank = D''; // blank = day of compilation
datetime noday = D'15:45:00'; // noday = day of compilation + 15:45
datetime feb30 = D'2021.02.30'; // feb30 = 2021.03.02 00:00:00
datetime mon22 = D'2021.22.01'; // mon22 = 2022.10.01 00:00:00
// OK
datetime dt0 = 0; // 1970.01.01 00:00:00
datetime all = D'2021.01.01 10:10:30'; // 2021.01.01 10:10:30
datetime day = D'2025.12.12 12'; // 2025.12.12 12:00:00
}
The first four variables call the compiler warning about the incorrect date. In the case of blank, the
literal is completely empty. In the noday variable, there is no day. In both cases, the compiler
substitutes the compilation date in the constant. Variables feb30 and mon22 contain incorrect numbers
of the day and month. The compiler corrects them automatically, transferring the overflow into the
higher-order field (February 30 turns into March 2, while the 22nd month becomes the 10th month of
the subsequent year). However, it is always recommended to get rid of warnings.
Variable dt0 demonstrates the initialization of the datetime value with an integer.
Type datetime supports the set of operations inherent in integers (see Expressions). This, for instance,
allows adding a predefined quantity of seconds to the time (obtaining a moment in the future) or
computing the difference between dates.
2.2.7 Color
MQL5 has a special type for working with color. This allows the coloring of graphical objects.
To denote the type, the color keyword is used. For the color type value, 4 bytes of memory are
allocated. Its internal representation is an unsigned integer containing a color in the RGB (Red, Green,
Blue) format, that is, with separate intensity levels for red, green, and blue colors. Mixing these three
components allows getting any visible color shade. Green and red will produce yellow, red and blue will
do purple, etc.
1 byte is allocated for each component, that is, it can take values from 0 through 255. For instance,
three zeros in all components produce a black color, while three maximum values of 255 are blended
into white.
If we present color as uint in the hexadecimal notation, then the colors are distributed as follows:
0x00BBGGRR, where RR, GG, and BB are single-byte unsigned integers.
For its user's convenience, MQL5 supports a special form of literals to record color constants. Literal
represents a triplet of numbers separated by commas and enclosed in single quotes. Character 'C' is
placed before the literal. For instance, C'0,128,255' means a color with 0 for its red component, 128
for the green one, and 255 for the blue one. Hexadecimal notation of numbers can also be used:
C'0x00,0x80,0xFF'.
Besides, a long list of predefined color shades is embedded in MQL5, all starting with clr. For example,
clrMagenta, clrLightCyan, and clrYellow. They also include the primaries, of course: clrRed, clrGreen,
and clrBlue. The full list can be found in the MetaEditor Help.
void OnStart()
{
color y = clrYellow; // clrYellow
color m = C'255,0,255'; // clrFuchsia
color x = C'0x88,0x55,0x01'; // x = 136,85,1 (no such predefined color)
color n = 0x808080; // clrGray
}
2.2.8 Enumerations
Enumerations are a group of types built in MQL5, each containing a set of named constants to describe
related concepts or properties. These constants are also referred to as enumeration elements.
For example, enumeration ENUM_DAY_OF_WEEK contains constants for all days of the week:
SUNDAY Sunday 0
MONDAY Monday 1
TUESDAY Tuesday 2
WEDNESDAY Wednesday 3
THURSDAY Thursday 4
FRIDAY Friday 5
SATURDAY Saturday 6
There are a few dozens of various enumerations. Their names are prefixed with "ENUM_". We are going
to learn them as we move through the relevant domain areas.
Each enumeration is an independent type. However, their internal representation is identical, i.e., four-
byte integer (int). Each enumeration constant is coded with one number or another, but in most cases,
the programmer does not need to remember these numbers, since the whole point of using
enumeration is exactly to replace internal representations with evident identifiers.
The compiler ensures that the enumeration value is always one of the redefined constants. Otherwise, a
warning or compilation error will occur (contextually, see the example).
void OnStart()
{
ENUM_DAY_OF_WEEK sun = SUNDAY; // sun = 0
ENUM_DAY_OF_WEEK mon = MONDAY; // mon = 1
ENUM_DAY_OF_WEEK tue = TUESDAY; // tue = 2
ENUM_DAY_OF_WEEK wed = WEDNESDAY; // wed = 3
ENUM_DAY_OF_WEEK thu = THURSDAY; // thu = 4
ENUM_DAY_OF_WEEK fri = FRIDAY; // fri = 5
ENUM_DAY_OF_WEEK sat = SATURDAY; // sat = 6
int i = 0;
ENUM_DAY_OF_WEEK x = i; // warning: implicit enum conversion
ENUM_DAY_OF_WEEK y = 1; // ok, equals to MONDAY
ENUM_ORDER_TYPE buy = ORDER_TYPE_BUY; // buy = 0
ENUM_ORDER_TYPE sell = ORDER_TYPE_SELL; // sell = 1
// ...
All constants of the days of the week are coded with numbers from 0 through 6, Sunday being the
starting point. Basically, constants should not necessarily have consecutive numbers or start with 0.
There are enumerations where this is not the case.
Please note that the same constants can mean different things in different enumeration types. For
instance, for orders ORDER_TYPE_BUY and ORDER_TYPE_SELL in the ENUM_ORDER_TYPE
enumeration, the same values (0 and 1) are used as for the days of week SUNDAY and MONDAY in
ENUM_DAY_OF_WEEK.
When copying the value from a simple integer variable i into the enumeration variable x, the compiler
gives a warning, since there can be a value other than the permitted constants in variable i at the
program execution stage.
In variable y, we record number 1 which means MONDAY, and the compiler considers this to be a
correct operation.
An attempt to write the constant of one enumeration into the variable of another enumeration (as
MONDAY for variable type in the example above) may cause a warning about an implicit type
conversion. This happens if the constant being written has the same value as one of the target
enumeration elements. In other words, each of the two enumerations has its own element with the
relevant value. Then the compiler performs an implicit conversion in the programmer's place
automatically, but it uses a warning to "ask" the programmer to check whether everything is going as
intended: The fact that MONDAY will be replaced with ORDER_TYPE_SELL is weird, indeed; however, we
did that intentionally here for illustrative purposes.
If the element being copied does not match by its value with any element of another enumeration, a
compilation error is generated, since an implicit conversion is impossible, such as when writing
ORDER_TYPE_CLOSE_BY in variable day.
The commented string with variable z causes a compilation error, too, since the value 10 does not
belong to ENUM_DAY_OF_WEEK. If the programmer is sure that, in an exotic case, there is still a need
for recording a random value in the enumeration type variable, they can use explicit typecasting.
Explicit and implicit typecasting will be discussed in the section entitled Typecasting.
MQL5 allows a programmer to declare their own applied enumerations using the keyword, enum. This
feature is described in the next section, Custom Enumerations (enum).
To describe your own enumeration in the MQL5 code, you will use the keyword enum. The simplest
description form is as follows:
enum name
{
element1,
element2,
element3
};
This description registers in the program an enumeration type named name with brace-enclosed
comma-separated elements (their amount is only limited by the highest int value, which can be
considered as no limitations in terms of practical tasks). Identifiers element1, element2, and element3
can be then used in the program within the context, in which they have been defined: Globally (i.e.,
outside of all functions) or inside of a function (see section Context, visibility, and lifetime of variables).
Please consider the semicolon following the closing brace. It is needed since the enumeration
description is a separate statement, and semicolons must be placed after any MQL5 statement.
By default, identifiers take constant values, starting with 0, each subsequent being 1 greater than the
preceding one. If necessary, the programmer may define a specific value for each element, after '=' to
the right of the identifier. For instance, the entry above is equivalent to this one:
enum name
{
element1 = 0,
element2 = 1,
element3 = 2
};
It is permitted to specify as value only constants or expressions the compiler can compute at the
compilation stage (for more details, please see the example below).
If the values are not defined for all elements, the skipped values are computed automatically based on
the nearest known (preceding) ones by adding 1. For example,
enum name
{
element1 = 1,
element2,
element3 = 10,
element4,
element5
};
Here, the first two elements take values 1 and 2 (computed), while those starting with the third one
take 10 (specified explicitly), 11, and 12 (the last two ones are computed based on 10).
enum
{
MILLION = 1000000
};
enum RISK
{
// OFF = zero, // error: constant expression required
LOW = -1,
MODERATE = -2,
HIGH = -3,
};
enum INCOME
{
LOW = 1,
MODERATE = 2,
HIGH = 3,
ENORMOUS = MILLION,
};
void OnStart()
{
enum INTERNAL
{
ON,
OFF,
};
Enumeration INTERNAL shows the possibility of describing it inside of the function and, in doing so,
limits the visibility/availability region of this type, which is useful in terms of name collisions.
Enumeration RISK shows that elements may be assigned with negative values. Commented element
OFF cannot be described due to the attempt to initialize it with a non-constant expression: In this case,
variable zero is specified there, the value of which cannot be computed by the compiler.
In enumeration INCOME, element ENORMOUS is initialized successfully by the value from the MILLION
element of the other enumeration defined above. Enumerations are created at the moment of compiling
and therefore, they are available in initialization expressions.
Enumeration with MILLION has no name, such enumerations are called anonymous. Their basic
application is to declare constants. However, named enumerations are used more often for constants,
since they allow grouping elements by their meanings.
Since there 2 enumerations defined in the example, both having elements with identical names,
specifying the LOW identifier when declaring variable x leads to the "ambiguous access" compilation
error, because it is not clear the element of which enumeration is meant. Please note that identifiers
may have (and they do, in this case) different values.
To solve this issue, there is a special context operator: Two colons, "::". They help form the complete
identifier of the language element, i.e., the enumeration element, in our case: First, the enumeration
name is specified, then operator "::", and after that the element identifier. Example: RISK::LOW and
INCOME::LOW. We will get to know about all operators in the relevant section.
It is impossible to use type void to describe variables; however, it is the basic type in describing
references to the random objects of classes. This possibility is described in Part 3 dealing with object-
oriented programming.
2.3 Variables
In this chapter, we will learn the basic principles of working with variables in MQL5, namely those
relating to embedded data types. In particular, we will consider the declaration and definition of
variables, special features of initialization as the context requires, lifetime, and basic modifiers changing
the properties of variables. Later on, relying on this knowledge, we will extend the abilities of variables
with new custom types (unions, custom enumerations, and aliases), classes, pointers, and references.
Variables in MQL5 provide a mechanism for storing data of various types, playing an important role in
organizing program logic and operations with market information. This section includes the following
subsections:
· Variable declaration is the step of creating them in a program. In this section, we look at how to
declare and define variables, as well as how to specify their types.
· Variables can exist in different contexts and scopes, which affects their availability and lifetime.
This subsection covers these aspects, helping you understand how variables interact with your
code.
Initialization:
· Initialization of variables involves assigning them initial values. We study methods of initialization,
helping to avoid undefined program behavior.
Static variables:
· Static variables retain their values between function calls. This section explains how to use static
variables to store information between different code executions.
Constant variables:
· Constant variables represent values that do not change during program execution. This section
describes their usage and characteristics.
Input variables:
· Input variables are used in trading robots to configure strategy parameters. We will see how to use
them to create flexible and customizable trading systems.
External variables:
· External variables allow users to interact with the program as their values can be changed without
the need to modify the code. This section explains how external variables work.
It would be safe to assume that a declaration contains a description of a program element with all its
attributes necessary for being used in the program. Definition, however, contains the specific
implementation of this element, corresponding with the declaration.
Declarations allow the compiler to interconnect all the elements of the program. Based on definitions,
the compiler generates an executable code.
In the case of variables, their declaration practically always acts as their definition, since it ensures
allocating memory and interpreting their contents in accordance with their types (this is exactly an
implementation of a variable). The only exception is the declaration of variables with the word
'extern' (for more details, see section External Variables).
Only upon the description of a variable, you can use special statements to enter values into it, read
them, and refer to the variable name to move it from one part of the program into another.
type name;
Here, name must meet the requirements of constructing identifiers. As a type, you can specify any of
the embedded types that we have considered in the preceding section or some other custom types —
we will learn a bit later how to create them. For example, integer variable i is declared as follows:
int i;
If necessary, you can describe several variables of the same type simultaneously. In this case, their
names are specified in the statement, separated by commas.
int i, j, k;
An important factor is the place in the program, where the statement is located, which contains the
variable description. This affects the lifetime of the variable and its accessibility from various parts of
the program.
2.3 Variables
65
Part 2. Programming fundamentals
MQL5 belongs to programming languages that use braces to group statements into code blocks.
Recall that a program consists of blocks with statements, and one block must exist definitely. In the
script samples from Part 1, we saw the OnStart function. The body of this function (the brace-enclosed
text following the function name) is exactly such a necessary code block.
Inside each block, the local context is formed, i.e., a region that limits the visibility and lifetime of
variables described inside it. So far we have only encountered examples where braces define the body
of functions. However, they can also be used to form compound operators, in the syntax of the
description of classes and namespaces. All these methods also define visibility regions and will be
considered in the relevant sections. At this stage, we only consider one type of local blocks, namely
those inside of functions.
Along local regions, every program also has one global context, i.e., a region with the definitions of
variables, functions, and other entities made beyond other blocks.
On the simple script side, in which the MQL Wizard has created the only void function OnStart, then
there will only be 2 regions in it: A global one and a local one (inside the OnStart function body,
although it is empty). The script below illustrates this with comments.
// GLOBAL SCOPE
void OnStart()
{
// LOCAL SCOPE "OnStart"
}
// GLOBAL SCOPE
Please note that the global region stretches everywhere apart from function OnStart (both before and
after it). Basically, it includes everything beyond any functions (if there were many), but there is
nothing in this script, apart from OnStart.
We can describe variables, such as i, j , k, on the top of the file, and they will become global.
// GLOBAL SCOPE
int i, j, k;
void OnStart()
{
// LOCAL SCOPE "OnStart"
}
// GLOBAL SCOPE
Global variables are created immediately upon starting an MQL program in the terminal and exist for
the entire period of program execution.
The programmer can record and read the contents of global variables from any place in the program.
It is basically recommended to describe global variables just at the top, but it is necessary. If we move
the declaration below the entire function OnStart, nothing will change basically. It will just be difficult
for other programmers to immediately make sense of the code with variables, the definitions of which
one has still to get to.
Interestingly, the OnStart function itself is declared in the global context, too. If we add another
function, it will also be declared in the global context. Recall how we created the Greeting function in
2.3 Variables
66
Part 2. Programming fundamentals
Part 1 and called it from the OnStart function. This is the effect of the function name and the method
of referencing to it (how to execute it) being known throughout the source code. Namespaces add
some niceties to it; however, we will learn them later.
A local region inside each function only belongs to it: One local region is inside OnStart, and another is
inside Greeting, which is its own and differs from both the local region of OnStart and the global one.
Variables described in the function body are called local. They are created according to their
descriptions as of calling the relevant function during the program execution. Local variables can be
only used inside the block that contains them. They are not visible or accessible from the outside.
When leaving the function, local variables are destroyed.
// GLOBAL SCOPE
int i, j, k;
void OnStart()
{
// LOCAL SCOPE "OnStart"
int x, y, z;
}
// GLOBAL SCOPE
It should be noted that pairs of braces can be used in both describing the function and other
statements and as themselves to form the internal code block. Unit nesting is unlimited.
Nested blocks are usually added to minimize the scope of variables used in a logically isolated small
code location (if it is not set by a function for one reason or another). This allows the reduction of the
probability of a false modification of the variable where it was not provided for or some undesired side
effects due to the attempt to re-purpose the same variable for various needs (it is not a good practice).
Below is a sample function where unit nesting level is 2 (if we consider the block with the function body
to be the first level), and 2 such blocks are created and will be executed consecutively.
2.3 Variables
67
Part 2. Programming fundamentals
void OnStart()
{
// LOCAL SCOPE "OnStart"
int x, y, z;
{
// LOCAL SUBSCOPE 1
int p;
// ... use p for task 1
}
{
// LOCAL SUBSCOPE 2
// y = p; // error: 'p' - undeclared identifier
int p; // from now 'p' is declared
// ... use p for task 2
}
Inside both blocks, variable p is described, which is used for various purposes in them. In fact, these
are two different variables, although having the same name visible inside each block.
If the variable were taken out to the initial list of the local variables of the function, it could contain
some remaining value upon exiting from the first block, thus breaking the operation of the second
block. Moreover, the programmer could occasionally involve p in something else at the very beginning
of the function, and then the side effects could take place in the first block.
Beyond either of the two nested blocks, variable p is unknown and therefore, an attempt to refer to it
from the common block of the function leads to a compilation error ("undeclared identifier").
It should also be noted that a variable can be described not at the very beginning of the block, but in its
middle or even closer to the end. Then it is defined not throughout the block, but only below its
definition. Therefore, when referring to the variable above its description, the same error will occur.
Thus, the variable scope region may differ from the context (the entire block).
Both versions of the problem are illustrated in an example: Try to include any of the strings with
statements p = x and y = p and compile the source code.
Memory is allocated for all the local variables of the function as soon as the control is passed inside the
function. However, this is not the end of their creation. Then they are initialized (initial values are set),
initialization being defined explicitly by the programmer or implicitly by the default values of the
compiler. At the same time, context is of the essence, in which the variables are described.
2.3.3 Initialization
In describing variables, there is a possibility to set the initial value; it is specified following the variable
name and symbol '=' and must correspond with the variable type or be cast to it (typecasting can be
found in the relevant section).
2.3 Variables
68
Part 2. Programming fundamentals
int i = 3, j, k = 10;
Both a constant (literal of the relevant type) and an expression (a kind of formula for calculations) can
be specified as the initial value. We will set out expressions separately. In the meantime, a simple
example:
int i = 3, j = i, k = i + j;
Here, variable j takes the same value as variable i, while variable k takes the sum of i and j . Strictly
speaking, in all three cases, we see expressions here. However, constant (3) is a special, degenerate
expression option. In the second case, the only variable name is an expression, i.e., the expression
result will be the value of this variable without any transformations. In the third case, two variables, i
and j , are accessed in the expression, the addition operation is executed with their values, and after
that, the result gets into variable k.
Since the statement containing the description of several variables is processed from left to right, the
compiler already knows the names of previous variables when analyzing yet another description.
A program usually contains many statements with variable descriptions. They are read by the compiler
in a natural top-down manner. In later initializations, names can be used taken from earlier
descriptions. Here are the same variables described by two separate statements.
int i = 3, j = i;
int k = i + j;
Variables without an explicit initialization also get some initial values, but they depend on the place
where the variable was described, i.e., on its context.
Where there is no initialization, local variables take random values at the moment of their generation:
The compiler just allocates memory for them according to the type size, while it is unknown what will
be at a specific address (various computer memory areas are often re-allocated to be used in different
programs after they have become unnecessary for those executed earlier).
It is usually suggested that working values will be entered in local variables without initialization
somewhere later in the algorithm code, such as using assignment operations we will talk about later on.
Syntactically, it is similar to initialization, since it also uses the equal sign, '=', to transfer the value
from the "structure" placed on the right of it (it can be a constant, variable, expression, or function
call, into the variable on the left. Only a variable can be to the left of '='.
The programmer should ensure that reading from the uninitialized variable only takes place upon a
meaningful value is assigned to it. Compiler gives a warning if this is not the case ("possible use of
uninitialized variable").
An example of global variables is the GreetingHour input parameter of the GoodTime2 script from Part
2. The fact that the variable was described with keyword input does not affect its other properties as a
variable. We could exclude its initialization and describe it as follows:
This would not change anything in the program, because global variables are implicitly initialized by the
compiler using zero if there is no explicit initialization (while we also had explicit initialization with zero
before).
2.3 Variables
69
Part 2. Programming fundamentals
Whatever the variable type is, implicit initialization is always performed by a value equivalent to zero.
For example, for a bool variable, false will be set, while for a datetime variable there will be
D'1970.01.01 00:00:00'. There is a special value, NULL, for strings. It is, if you like, an even
"emptier" string than empty quotes "" because there is still some memory allocated for them, where
the only terminal null character is placed.
Along with local and global variables, there is another type, i.e., static variables. The compiler initializes
them with zero implicitly, too, if the programmer has not written an explicitly initial value. They will be
considered in the next section.
Let's create a new script, VariableScopes.mq5, with examples of describing local and global variables
(MQL5/Scripts/MQL5Book/VariableScopes.mq5).
// global variables
int i, j, k; // all are 0s
int m = 1; // m = 1 (place breakpoint on this line)
int n = i + m; // n = 1
void OnStart()
{
// local variables
int x, y, z;
int k = m; // warning: declaration of 'k' hides global variable
int j = j; // warning: declaration of 'j' hides global variable
// use variables in assignment statements
x = n; // ok, 1
z = y; // warning: possible use of uninitialized variable 'y'
j = 10; // change local j, global j is still 0
}
// compilation error
// int bad = x; // 'x' - undeclared identifier
It should be remembered that, at launching an MQL program, the terminal first initializes all global
variables and then calls a function that is the starting point for the programs of a relevant type. In this
case, it is OnStart for scripts.
Here, only variables i, j , k, m, n are global since they are described outside the function (in our case, we
only have one function, OnStart, which is necessary for scripts). i, j , k take the value of 0 implicitly. m
and n contain 1.
You can run the script in the debugging mode on a step-by-step basis and make sure that the values of
variables change exactly in this manner. For this purpose, you should preliminarily set a breakpoint onto
the string with the initialization of one of the global variables, such as m. Put the text cursor onto this
string and execute Debug -> Toggle Breakpoint (F9), and the string will be highlighted with a blue sign in
the left field, which signals that the program execution will stop here if it starts working on the
debugger.
Then you should actually run the program for debugging, for which purpose execute command Debug ->
Start on real data (F5). At this moment, a new chart will open in the terminal, in which this script starts
being executed (caption "VariableScopes (Debugging)" in the upper right corner), but it suspends
immediately, and we get back to MetaEditor. We should see a picture in it as follows.
2.3 Variables
70
Part 2. Programming fundamentals
A string containing a breakpoint is now marked with an arrow sign – it is the current statement the
program is preparing to execute but has not executed yet. The current stack of the program is shown
lower left, which consists so far of only one entry: @global_initializations. You can enter expressions
lower right to monitor their real-time values. We are interested in the values of variables; therefore,
let's consecutively enter i, j , k, m, n, x, y, z (each in a separate string).
You will see further that MetaEditor automatically adds variables from the current context for viewing
(for instance, local variables and the function inputs, where statements are executed inside the
function). But now, we are going to add x, y, and z manually and in advance, just to show that they are
not defined outside the function.
Please note that, for local variables, it is written "Unknown identifier" instead of a value, because there
has not been the OnStart function block yet, where they are located. Global variables i and j will first
have zero values. Global variable k is not used anywhere and, therefore, it is excluded by the compiler.
2.3 Variables
71
Part 2. Programming fundamentals
If we execute one step of the program execution (execute the statement on the current code line)
using commands Step Into (F11) or Step Over (F10), we will see how variable m takes value 1. Another
step will continue initialization for variable n, and it will also become 1.
Here, the descriptions of global variables end and, as we know, terminal calls function OnStart upon
completion of the initialization of global variables. In this case, to step into function OnStart in the
stepwise mode, press F11 once again (or you can set another breakpoint in the beginning of the
OnStart function).
Local variables are initialized when the execution of the program statements reaches the code block
where they have been defined. Therefore, variables x, y, z are only created upon stepping into the
OnStart function.
When the debugger gets inside the OnStart function, with a little luck, you will be able to see that there
are really initially random values in x, y, and z. "Luck" here consists in the fact that these random
values may well be zero ones. Then it will be impossible to differ them from the implicit initialization
with zero, compiler performs for global variables. If the script is launched repeatedly, the "garbage" in
local variables will likely be different and more illustrative. They are not initialized explicitly and,
therefore, their contents may be of any kind.
In the sequence of images below, you can see the evolution of variables using the step-by-step mode of
the debugger. The current string to be executed (but not executed yet) is marked with a green arrow
on the fields with enumeration.
2.3 Variables
72
Part 2. Programming fundamentals
It is demonstrated further in the code how these variables could be used in the simplest manner in
assignment operators. The value of the global variable n is copied into the local x without any problems
since n has been initialized. However, in the string where the contents of variable y are copied to
variable z, a warning from the compiler appears, because y is local and, as of this moment, nothing has
been written in it; i.e., there is not an explicit initialization, as well as other operators that can set its
value.
Inside a function, it is permitted to describe variables with the same names as already used for global
variables. A similar situation may occur in nested local blocks if a variable is created in an internal block
with the name existing in an external block. However, this practice is not recommended, since it may
lead to logical errors. In such cases, the compiler gives a warning ("declaration hides global/local
variable").
Due to such redefining, a local variable, such as k in the example above, overlaps the homonym global
one inside the function. Although they have the same name, these are two different variables. Local k is
known inside OnStart, while global k is known everywhere apart from OnStart. In other words, any
inside-the-block operations with variable k will only affect the local variable. Therefore, upon exiting
function OnStart (as if it were not the only and core function of the script), we would discover that
global variable k is still equal to zero.
Local variable j does not only overlap global variable j but is also initialized by the value of the latter
one. In the string containing the description of j inside OnStart, the local version of j is still being
created when the initial value for it is read from the global version of j . Upon a successful definition of
local j , this name overlaps the global version, and it is the local version, to which the subsequent
changes in j belong.
2.3 Variables
73
Part 2. Programming fundamentals
At the end of the source code, we have commented on the attempt to declare one more global
variable, bad, in the initialization of which the value of variable x is called. This string causes a compiler
error since variable x is unknown beyond the OnStart function, in which it has been defined.
Such a variable cannot be local, because then it will lose its "long memory," since it will be created
every time at calling the function and removed at exiting it. Technically, it could be described globally;
however, if the variable is only used in this function, this approach is wrong in terms of program design.
First, a global variable can accidentally be changed from any place in the program.
Second, imagine what "zoo" of variables would be made in the global region of the program if we
declare a global variable at the slightest pretext. Instead, it is recommended to declare variables in the
smallest block (if there are several nested ones), in which they are used.
Therefore, the counter of function executions should be described inside the function. This is where the
new attribute of variables helps, their static nature.
A special keyword (modifier), static, placed before the variable type in its declaration allows prolonging
its lifetime up to the entire duration of program execution, that is, makes it similar to global ones. As a
rule, a static variable is only defined locally, in one of the functions. Therefore, its visibility is limited by
the relevant code block, as in a normal local variable.
Static variables can also be described at a global level, but do not differ from the normal global ones in
any way (at least, as of writing this book). It varies from their behavior in C++: There, their visibility is
limited by the file they are described in. In MQL5, a program is assembled based on one main mq5 file
and, perhaps, some header files (see directive #include); therefore, both static and normal global
variables are available from all source files of the program.
A local static variable is created only once – at the moment when the program first steps into the
function where this variable is described. Such a variable will only be removed at unloading the
program. If a function has never been called, the local static variables described in it, if any, will never
be created.
As an example, let's modify the Greeting function from Part 1 so that it gives different greetings at
each call. Let's name the new script GoodTimes.mq5.
We will remove the input of the script GreetingHour and the parameter of the Greeting function. Inside
the Greeting function, we will describe a new static variable, counter, of integer type, with the initial
value of 0. It should be reminded that it is exactly initialization, and it will be executed only once
because the variable is static.
2.3 Variables
74
Part 2. Programming fundamentals
string Greeting()
{
static int counter = 0;
static string messages[3] =
{
"Good morning", "Good day", "Good evening"
};
return messages[counter++ % 3];
}
Since we know modifier static now, it is reasonable to also use it for array messages. The matter is that
it was declared as local before, and it would be re-created every time at multiple calls of function
Greeting (and removed at exit). This is not efficient.
It should be reminded that an array is a named set of several values of the same type, available by
index specified in square brackets after the name. Much of what has been said about variables applies
directly to arrays. Further nuances of working with arrays will be covered in section Arrays.
But let's get back to our current problem. An option is chosen from the array based on the value of the
counter variable in the return statement and so far appears quite cabbalistically:
We have already mentioned casually the modulus operation performed using character '%' in Part 1.
With it, we guarantee that the element index will not be able to exceed the array size: Whatever be
counter, its division modulo by 3 will either be 0 or 1, or 2.
The same applies to structure counter++, it means adding 1 to the variable value (single increment).
It is important to note that, in this notation, incrementation will take place upon having computed the
entire expression, in this case, upon division counter % 3. This means that counting will start from zero,
i.e., initial value. There is a possibility to make an increment before computing the expression, having
written: ++counter % 3. Then counting would start from 1. We will consider the operations of this type
in section Increment and Decrement.
void OnStart()
{
Print(Greeting(), ", ", Symbol());
Print(Greeting(), ", ", Symbol());
Print(Greeting(), ", ", Symbol());
// Print(counter); // error: 'counter' - undeclared identifier
}
As a result, we will see the anticipated three strings with all greetings one after another in the log.
If we continue calling the function, the counter will increase, and the messages will rotate.
2.3 Variables
75
Part 2. Programming fundamentals
An attempt to refer to the counter variable at the end of OnStart (commented) will not allow the code
to be compiled, since the static variable, although it continues to exist, is only available inside function
Greeting.
Please note that braces are used for both forming the code blocks and initializing arrays. You should
distinguish among their applications. Arrays will be considered in detail in the relevant section.
However, these are not all applications of braces: Using them, we will later learn how to define custom
types, structures, and classes. Static variables can also be defined inside structures and classes.
The compiler will just prevent assigning the constant with a value: The error "constant cannot be
modified" will appear in the relevant string.
Modifier const is aimed at explicitly showing the programmer's intention not to change the relevant
variable, if a commonly known fixed value, such as the EUR index to compute the USD index, the
number of weeks in a year, etc. It is recommended to always use modifier const if you are not going to
change the variable. This helps avoid potential errors later, if the programmer themselves or somebody
from among their colleagues accidentally tries to write something else into the constant.
For example, we can add modifier const for the messages array in the Greeting function. This does not
appear plainly useful for such a small program. However, since programs tend to grow out, any string
may sooner or later "find itself" in a much more complex software environment, such as added
statements, operation modes, etc. Therefore, it makes sense to have a plan B; particularly as it is so
simple.
string Greeting()
{
static int counter = 0;
static const string messages[3] =
{
"Good morning", "Good day", "Good evening"
};
// error demo: 'messages' - constant cannot be modified
// messages[0] = "Good night";
return messages[counter++ % 3];
}
In the commented string, we test recording the "Good night" string into the first element of the array
(remember that numbering starts from 0). In this case, the sense of this action is just to make sure
that the compiler prevents from doing that.
As is easily seen, modifiers static and const can be combined. The order of recording them is not
important.
By the way, in MQL5, variables become constants in both using modifier const and declaring them with
the input variables of the program.
2.3 Variables
76
Part 2. Programming fundamentals
When launched, all programs in MQL5 can inquire parameters from the user. The only exception is
libraries that are not executed independently, but as parts of another program (see the relevant section
to know more about Libraries).
Input parameters of MQL programs are global variables described in the code having a special modifier
of input or sinput. They become available in the dialog of program properties for the user to enter
values. We saw a description of the GreetingHour input variable in the scripts of Part 1.
A special feature of input variables is the fact that their value cannot be changed in the program code,
i.e., it behaves like a constant.
Input variables can only be of simple built-in types or enumerations. For enumerations, you enter the
values via a drop-down list; while you use input fields in all other cases. It is not permitted to describe
as input: Arrays, structures or unions, and classes.
The developer can set the input parameter name other than the variable identifier. This name will be
shown to the user in the program properties dialog. A detailed description should be added as a sing-
string comment upon the definition of the input parameter.
This allows making the interface user-friendlier, detailed, and free of syntactic constraints imposed by
MQL5 on identifiers. Moreover, names (as well as comments) can be in your native language.
For example, MetaTrader 5 comes with the source code of indicator MQL5/Indicators/Examples/Custom
Moving Average.mq5 with input variables:
2.3 Variables
77
Part 2. Programming fundamentals
The maximum length of the text representation of an input variable as an identifier=value pair, including
character "=", may not exceed 255 characters (This constraint is imposed by the internal data
exchange protocols of the terminal and testing agents). This limit is especially important for string
variables since the values of other types never go beyond it. As we know, the length of an identifier is
limited to 63 characters; therefore, depending on the identifier length, 191-253 characters are left for
the value of the input string variable. The entire text exceeding the combined threshold of 255 chars
may be cropped when being transferred to the tester. If a longer string has to be entered into your
MQL program, use multiple input fields (to be continued) or allow the user to specify the name of the
file, from which the text should be read.
For convenience in operating MQL programs, inputs can be combined in named blocks using keyword
group (semicolon in the group string end is not necessary).
All variables with modifier input following the group description (up to the description of another group
or to the file end) are visually displayed as a nested list under the group header in the properties dialog
of the MQL program. Moreover, groups of parameters can be deployed or collapsed by a mouse click in
the strategy tester applicable to both indicators and EAs.
The sinput keyword is the abbreviation of static input, both forms being equivalent.
Variables described with modifiers sinput and static input cannot be involved in optimization. It only
makes sense to use them in Expert Advisors being the only MQL program type supporting optimization.
For more details, see the section dealing with Testing and optimizing Expert Advisors.
MQL5 allows describing variables as external ones. This is made using the extern keyword and is only
permitted in the global context.
For an external variable, syntax basically repeats a normal description but it additionally has the
'extern' keyword while initialization is prohibited:
Describing a variable as external means that its description is delayed and must occur later in the
source code, usually in another file (connecting files using the #include directive will be considered in
the chapter dealing with the preprocessor). Several different source files can have a description of the
same external variable, that is, those having identical types and identifiers. All such descriptions refer
to the same variable.
It is assumed that this variable will be completely described in one of the files. If the variable is not
defined anywhere in the code without the extern keyword, the "unresolved extern variable" compilation
error is returned (similar to a linker error in C++ in such cases).
2.3 Variables
78
Part 2. Programming fundamentals
Describing an external variable allows using it efficiently in the source code of a particular file. In other
words, it enables compiling a given module, although the variable is not created in this module.
Using extern in MQL5 is not so insistent as in C++ and in most cases, may be replaced by enabling a
header file with general descriptions of the variables to be declared as extern. It is sufficient to perform
these definitions conventionally. The compiler ensures adding each attached file to the source code
only once. Considering that in MQL5 a program always consists of one compilable unit mq5, there is no
C++ problem here, with the potential error of the multiple definitions of the same variable due to
enabling the header in different units.
Even an additional mq5 (not mqh) file is attached in the #include directive, it does not equally compete
with the main unit, for which compilation is launched; instead, it is considered as one of the headers.
Unlike C++, MQL5 does not allow specifying an initial value for an external variable (initialization in C++
leads to ignoring the word extern). If you try to set an initial value, you will get a compilation error
"extern variable initialization is not allowed".
Generally, describing a variable as external can be considered a kind of "soft" description: It ensures
the appearance of the variable and excludes the overriding error that would occur if the variable is
described in several files without the extern modifier.
However, this can be a source of errors. If in different header files, by coincidence, identical variables
are described for different purposes, then no keyword extern allows identifying a collision, while with
extern, the variables will become one, and the program operation logic will most likely be broken.
As external, both variables and functions can be described (they will be considered below). For
functions, describing them with the attribute as external is a rudiment (i.e., it is compiled, but does not
make any changes). The following two declarations of a function are equivalent:
In this sense, the presence/absence of extern can only be used to stylistically distinguish between a
forward description of a function from the current unit (no extern) or from an external one (extern is
present).
You can use extern in both the mq5 unit to be compiled and header files to be attached.
Let's consider some options for using extern: They are entered in different files, i.e., main script
ExternMain.mq5 and 3 attachable files: ExternHeader1.mqh, ExternHeader2.mqh, and
ExternCommon.mqh.
In the main file, only ExternHeader1.mqh and ExternHeader2.mqh are attached, while we will need
ExternCommon.mqh a bit later.
In header files, two conditionally useful functions are defined: In the first one, function inc for the x
variable increment, while in the second, function dec for the x variable decrement. It is variable x that is
described in both files as external:
2.3 Variables
79
Part 2. Programming fundamentals
// ExternHeader1.mqh
extern int x;
void inc()
{
x++;
}
// -----------------
// ExternHeader2.mqh
extern int x;
void dec()
{
x--;
}
Due to this description, each of the mqh files is compiled in a regular way. When they are included in an
mq5 file together, the entire program is compiled, too.
If the variable were defined in each file without the word extern, the re-defining error would occur in
compiling the program as a whole. If we had transferred the definition of x from header files into the
main unit, header files would have stopped being compiled (it is not a problem for somebody, perhaps;
however, in larger programs, developers like checking the compilation ability of immediate corrections
without compiling the entire project).
In the main script, we define a variable (in this case, with an initial value of 2, while if we do not specify
the value, the default 0 will be used) and call the conditionally useful functions, as well as print the x
value.
int x = 2;
void OnStart()
{
inc(); // uses x
dec(); // uses x
Print(x); // 2
...
}
In file ExternHeader1.mqh, there is the description of variable short z (without extern). A similar
description is commented upon in the main script. If we make this string active, we will get the error
mentioned before ("variable already defined"). This is done to illustrate the potential problem.
In ExternHeader1.mqh, extern long y is described, too. At the same time, in file ExternHeader2.mqh, the
homonym external variable has another type: extern short y. If the latter description were not "moved"
into a comment preemptively, the types incompatibility error ("variable 'y' already defined with
different type") would occur here. Summary: Either types must coincide or variables must not be
external. If both options are not good, it means that there is a mistype in the name of one of the
variables.
Moreover, it should be noted that variable y is not explicitly initialized. However, the main script calls it
successfully and prints 0 in the log:
2.3 Variables
80
Part 2. Programming fundamentals
long y;
void OnStart()
{
...
Print(y); // 0
}
Finally, there is a possibility provided in the script to try an alternative of the external twin variables,
exemplified by the already known variable x. Instead of describing extern int x, each of the files
ExternHeader1.mqh and ExternHeader2.mqh can include another common header, ExternCommon.mqh,
in which there is the description of int x (without extern). It becomes the only description of x in the
project.
This alternative mode of assembling the program is enabled when activating macro
USE_INCLUDE_WORKAROUND: It is in the comment at the beginning of the script:
In this configuration, particular include files will still be compilable, as well as the entire project. In a
real project, without using this method, the common mqh file would be included in ExternHeader1.mqh
and ExternHeader2.mqh unconditionally (no USE_INCLUDE_WORKAROUND conditions). In this example,
switching between the two threads of instructions is based on USE_INCLUDE_WORKAROUND is only
needed to demonstrate both modes. For example, the simplified version of ExternHeader2.mqh should
appear as follows:
// ExternHeader2.mqh
#include "ExternCommon.mqh" // int x; now here
void dec()
{
x--;
}
We can check in the MetaEditor log that file ExternCommon.mqh loaded only once, although it is
referenced in both ExternHeader1.mqh and ExternHeader2.mqh.
'ExternMain.mq5'
'ExternHeader1.mqh'
'ExternCommon.mqh'
'ExternHeader2.mqh'
code generated
If the x variable is "registered" in ExternCommon.mqh, we shall not re-define it (without extern) in the
main unit since this would cause a compilation error, but we can simply assign to it the desired value at
the beginning of the algorithm.
2.4 Arrays
An array is a tool for cluster-based storing and processing the data of random types. They are
supported practically in any programming language. They are especially important in MQL5 because
2.3 Variables
81
Part 2. Programming fundamentals
they represent a convenient method of arranging serial data relevant to trading tasks. Quotes, readings
of indicators, account trading history with orders and transactions, and news are all examples of serial
data, that is, the sequences of time-varying values.
The array can be considered a container variable: It can contain a predefined quantity of values of the
same type, which are identified by both their name and index (position number).
In this section, we are going to consider the common syntax of describing arrays and calling them,
exemplified by embedded data types. In the subsequent parts of this book, with acquiring information
on how to extend the system of types due to the object-oriented technology, we will use arrays in
conjugation with them to get new opportunities.
The core characteristic of an array is the number of dimensions. In a one-dimension array, its elements
are placed one by one, like a row of soldiers, and just one number (index) is sufficient to refer to them.
Bar-by-bar prices of opening a financial instrument to the given history depth can be saved in such an
array.
In a two-dimensional array, its elements diverge in two logically perpendicular directions, forming a kind
of a square (or rectangular, in a general case), two indices being required for each element, i.e., one in
each dimension. Such an array could be used to store price quads (Open, High, Low, and Close) for
each history bar. Bar numbers would be counted with the first dimension, while the second one is used
for numbers from 0 through 3, denoting one of the price types.
A three-dimensional array is the equivalent of a cube (or, more strictly in terms of geometry, right-
angled parallelepiped) with three axes. Continuing the example with the array of bar-by-bar prices, we
could add to it the third dimension responsible for iterating financial instruments from the Market
Watch.
For each dimension, the array has a certain length (size) setting the range of possible indexes. If
history is supposed to be loaded for 1,000 bars and 10 instruments, we would get an array sized 1,000
elements in the first dimension, 4 elements in the second one (OHLC), and 10 in the third one.
The product of sizes in all dimensions provides the total number of the array elements; in our case, it is
40,000. In MQL5, it may not exceed 2147483647 (maximum for int).
It is already difficult to imagine a solid shape for a 4-dimensional array because we live in a 3D world.
However, MQL5 permits the creation of arrays having up to four dimensions.
It should be noted that you can always use a one-dimensional array instead of a multidimensional one
with a random number of dimensions, including more than 4. This is just a matter of arranging the
recomputing of several indexes into a continuous one. For example, if a two-dimensional array has 10
columns (dimension 1, axis X) and 5 rows (dimension 2, axis Y), it can be transformed into a one-
dimensional array with the same quantity of elements, i.e., 50. In this case, the element index will be
obtained by the following formula:
index = Y * N + X
Here, N is the number of elements in the first dimension, in our case, 10; it is the size of each row; Y is
the row number (0..4); and X is the column number (0..9) in the row.
2.4 Arrays
82
Part 2. Programming fundamentals
Sizes across dimensions are another characteristic that separates an array from a variable. Thus, the
number of dimensions and size in each dimension must be specified in some manner in the description,
along with the array name and data type (see the following section).
You should distinguish between the size of a variable (array element) in bytes and that of an array as
the number of elements in it. Theoretically, the full array size in terms of memory it consumes must be
the product of the size of one element (depending on the data type) and the number of elements.
However, this formula does not always work in practice. Particularly, since strings may have different
lengths, it is quite difficult to evaluate the memory volume consumed by a string array.
A fixed-size array is described in the code with exact sizes in all dimensions. It is impossible to resize it
later. However, practical tasks often occur, in which the amount of data to be processed is contingent
and therefore, it is desirable to resize the array during the algorithm operation. Dynamic arrays exist
for this particular purpose. As we will see further, they are described without specifying the first-
dimension size and can then be "stretched" or "compacted" using the special MQL5 API functions.
MQL5 Documentation uses ambiguous terminology that names fixed-size array static. This concept is
also used for the 'static' modifier that can be applied to the array. If such an array is declared
dynamic, then it is simultaneously non-static in terms of memory allocation and static in terms of the
'static' modifier. To exclude ambiguousness, the static character in this book will only mean the
declaration attribute.
Along with dynamic and fixed-size arrays, there are special arrays in MQL5 to store quotes and the
buffers of technical indicators. Such arrays are named timeseries arrays since their indexes correspond
with timing. In fact, these arrays are one-dimensional and dynamic. However, unlike other dynamic
arrays, the terminal itself allocates memory for them. We will consider them in the sections dealing
with timeseries and indicators.
type static1D[size];
Here, type and static1D denote the type name of elements and the array identifier, respectively, while
size in square brackets is a size-defining integer constant.
For multidimensional arrays, several sizes must be specified, according to the quantity of dimensions:
type static2D[size1][size2];
type static3D[size1][size2][size3];
type static4D[size1][size2][size3][size4];
Dynamic arrays are described in a similar manner, except that a skip is made in the first square
brackets (before using such an array, the required memory volume must be allocated for it using the
ArrayResize function, see the section dealing with dynamic arrays).
2.4 Arrays
83
Part 2. Programming fundamentals
type dynamic1D[];
type dynamic2D[][size2];
type dynamic3D[][size2][size3];
type dynamic4D[][size2][size3][size4];
For fixed-size arrays, initialization is permitted: Initial values are specified for the elements after the
equal sign, as a comma-separated list, the entire list being enclosed in braces. For example:
Here, a 3-sized integer array takes the values of 10, 20, and 30.
With an initialization list, there is no need to specify the array size in square brackets (for the first
dimension). The compiler will assess the size automatically by the list length. For example:
Initial values can be both constants and the constant expressions, i.e., formulas the compiler can
compute during compilation. For example, the following array is filled with the number of seconds in a
minute, hour, day, and week (representation as formulas is more illustrative than 86400 or 604800):
Such values are usually designed as a preprocessor macro in the code beginning, and then the name of
this macro is inserted everywhere where it is necessary in the text. This option is described in the
section related to the Preprocessor.
The number of initializing elements may not exceed the array size. Otherwise, the compiler will give the
error message, "too many initializers". If the quantity of values is smaller than the array size, the
resting elements are initialized by zero. Therefore, there is a brief notation to initialize the entire array
by zeros:
Here, the first-dimension size of the array is 3; therefore, two commas frame 3 elements inside the
external braces. However, since the array is two-dimensional, each of its elements is an array, in turn,
the size of each being 2. This is why each element represents a list in braces, each list containing 2
values.
Supposing, we need a transposed array (the first size is 2, and the second one is 3), then its
initialization will change:
We can skip one or more values in the initialization list, if necessary, having marked their places with
commas. All skipped elements will also be initialized by zero.
2.4 Arrays
84
Part 2. Programming fundamentals
The language syntax permits placing a comma after the last element:
string messages[] =
{
"undefined",
"success",
"error",
};
This simplifies adding new elements, especially for multi-string entries. Particularly, if we forget to
enter a comma before the newly added element in a string array, the old and the new strings will turn
out to be fused within one element (with the same index), while no new element will appear. Moreover,
some arrays may be generated automatically (by another program or by macros). Therefore, the
unified appearance of all elements is natural.
"Heap" and "Stack"
With arrays that can potentially be large, it is important to make the distinction between global
and local location in memory.
Memory for global variables and arrays is distributed within the 'heap', i.e., free memory available
to the program. This memory is not practically limited by anything, apart from the physical
characteristics of your computer and operating system. The name of 'heap' is explained by the fact
that differently sized memory areas are always either allocated or deallocated by the program,
which results in the free areas being randomly scattered within the entire bulk.
Local variables and arrays are located in the stack, i.e., a limited memory area preliminarily
allocated for the program, especially for local elements. The name of 'stack' derives from the fact
that, during the algorithm execution, the nested calls of functions take place, which accumulate
their internal data according to the "piled-up" principle: For instance, OnStart is called by the
terminal, a function from your applied code is called from OnStart, then your other function is called
from the previous one, etc. At the same time, when entering each function, its local variables are
created that continue being there when the nested function is called. It creates local variables, too,
which get onto the stack somewhat over the preceding ones. As a result, a stack usually contains
some layers of the local data from all functions that had been activated on the path to the current
code string. Not until the function being on the top of the stack is completed, its local data will be
removed from there. Generally, the stack is a storage that works according to the FILO/LIFO (First
In Last Out, Last In First Out) principle.
Since the stack size is limited, it is recommended to create only local variables in it. However,
arrays can be quite large to exhaust the entire stack very soon. At the same time, the program
execution is completed with an error. Therefore, we should describe arrays at a global level as
static (static) or allocate memory for them dynamically (this is also done from the heap).
2.4 Arrays
85
Part 2. Programming fundamentals
array1D[0] = 11;
Indexing starts with 0. The index of the last element is equal to the quantity of elements minus 1. Of
course, we can use as an index both a constant and any other expression that can be reduced to the
integer type (for more details on expressions, see the following chapter), such as an integer variable, a
function call, or an element of another array with integers (the indirect addressing).
int index;
// ...
// index = ... // assign an index somehow
// ...
array1D[index] = 11;
array2D[index1][index2] = 12;
Permitted integer types exclude long and ulong for indices. If we try to use the value of a "long integer"
as an index, it will be implicitly converted into int, wherefore the compiler gives the warning "possible
loss of data due to type conversion."
Reading access to the array elements is arranged according to the same principle. For example, this is
how an array element can be printed in the log:
Print(array2D[1][2]);
In script GoodTimes, we have already seen the description of the local static array messages with the
strings of greetings (inside the Greeting function) and the use of its elements in the return operator.
string Greeting()
{
static int counter = 0;
static const string messages[3] = // description
{
"Good morning", "Good day", "Good evening" // initialization
};
return messages[counter++ % 3]; // using
}
When executing return, we read the element that has the index defined by the expression: counter++
% 3. Division modulo 3 (denoted as '%') ensures that counter increased every time increased by 1 will
be forced to the range of the correct values of indices: 0, 1, or 2. If there were not modulo divisions,
the index of the requested element would exceed the array size, starting from the 4th call of this
function. In such cases, the program execution time error occurs ("array out of range"), and it is
unloaded from the chart.
MQL5 API includes universal functions for many operations with arrays: Allocating memory (for
dynamic arrays), filling, copying, sorting, and searching in arrays are all considered in the section
Working with Arrays. However, we are presenting one of them now: ArrayPrint allows the printing of the
array elements in the log in a convenient format (considering dimensions).
Script Arrays.mq5 demonstrates some examples of describing arrays, and the results are printed in the
log. We will consider manipulations with the elements of arrays later, upon having studied loops and
expressions.
2.4 Arrays
86
Part 2. Programming fundamentals
void OnStart()
{
char array[100]; // without initialization
int array2D[3][2] =
{
{1, 2}, // illustrative formatting
{3, 4},
{5, 6}
};
int array2Dt[2][3] =
{
{1, 3, 5},
{2, 4, 6}
};
ENUM_APPLIED_PRICE prices[] =
{
PRICE_OPEN, PRICE_HIGH, PRICE_LOW, PRICE_CLOSE
};
// double d[5] = {1, 2, 3, 4, 5, 6}; // error: too many initializers
ArrayPrint(array); // printing random "garbage" values
ArrayPrint(array2D); // showing the 2D array in the log
ArrayPrint(array2Dt); // a "transposed" appearance of the same data 2D
ArrayPrint(prices); // getting to know the values of the price enumeration elements
}
The array named array does not have any initialization and therefore, memory allocated for it may
contain random values. Values will change at each script run. It is recommended to always initialize
local arrays, just in case.
Arrays array2D and array2Dt are printed in the log in an illustrative form, as matrices. It is in no way
linked to the fact that we have formatted the initialization lists in the source code in the same manner.
The prices array has the type of the embedded enumeration ENUM_APPLIED_PRICE. Basically, arrays
can be of any type, including structures, function pointers, and other things that we are going to
consider. Since enumerations are based on the int type, the values are displayed by digits, not by the
2.4 Arrays
87
Part 2. Programming fundamentals
names of elements (to obtain the name of a specific element of the enumeration, there is the function
EnumToString, but its mode is not supported in ArrayPrint).
The string with the d array description contains an error: Entity of initial values exceeds the array size.
2.5 Expressions
Expressions are essential elements of any programming language. Whatever applied idea underlies an
algorithm, it is eventually reduced to data processing, that is, to computations. The expression
describes computing some result from one or more predefined values. The values are called operands,
while the actions performed with them are denoted by operations or operators.
As operators that allow manipulating with operands, independent characters or their sequences are
used in expressions, such as '+' for addition or '*' for multiplication. They all form several groups, such
as arithmetic, bitwise, comparison, logic, and some specialized ones.
We have already used expressions in the previous sections of this book, such as to initialize variables. In
the simplest case, the expression is a constant (literal) that is the only operand, while the computation
result is equal to the operand value. However, operands can also be variables, array elements, function
call results (for which the function is called directly from the expression), nested expressions, and other
entities.
All operators substitute (return) their result into the parent expression, directly into the place where
there were operands, which allows combining them making quite complex hierarchic structures. For
example, in the following expression, the result of multiplying variables b by c is added to the value of
variable a, and then the value obtained will be stored in variable v:
v = a + b * c;
In this section, we consider the general principles of constructing and computing various expressions,
as well as the standard set of operators supported in MQL5 for the built-in types. Later on, in the part
dealing with OOP, we will know how operators can be reloaded (redefined) for custom types, i.e.,
structures and classes, which will allow us to use objects in expressions and perform nonstandard
actions with them.
First of all, by the quantity of operands required, operators can be unary and binary. As is clear from
the names, unary ones process one operand, while binary operators process two. In the case of binary,
the operator is always placed between operands. Among unary ones, there are operators that must be
put before the operand and those to be placed after it. For example, the unary minus ('-') operator
allows reversing the sign of the value:
int x = 10;
int y = -x; // -10
At the same time, there is a binary operator for subtraction using the same character, '-'.
2.4 Arrays
88
Part 2. Programming fundamentals
Choosing a correct operator (action) by the compiler in a specific context is determined by the context
of using it in the expression.
Each operator is assigned priority. It determines the order, in which operators will be computed in
complex expressions where there are multiple operators. Higher-priority operators are computed as the
first, while the lowest-priority ones as the last. For instance, in the expression 1 + 2 * 3 there are two
operations (addition and multiplication) and three operands. Since multiplication has a priority higher
than that of addition, the product of 2 * 3 will be found first, and then it will be added to one.
Additionally, each operator is characterized by the associativity. It can be left or right and determines
the order, in which the successive operators having the same priority are executed. For example,
expression 10 - 7 - 1 can purely theoretically be computed in two ways:
• Subtract 7 from 10 and then subtract 1 from the resulting 3, which gives 2; or
• Subtract 1 from 7, which gives 6, and then subtract 6 from 10, resulting in 4.
In the first case, computations were performed from left to right, which corresponds with the left
associativity; since the subtraction operation is left-associative, indeed, the first answer is correct.
The second option of computations corresponds with the right associativity and won't be used.
Let's consider another example where there are priority and associativity involved simultaneously: 11 +
5 * 4 / 2 + 3. Both types of operations, i.e., addition and multiplication, are executed from left to right.
If the priorities were not different, we would get 35, although 24 is the correct answer. Changing
associativity for the right would give us 14.
To explicitly redefine priorities in expressions, parentheses can be used, for instance: (11 + 5) * 4 / (2 +
3). What is enclosed in parentheses is computed earlier, and the intermediate result is substituted in
the expression to be used in other operations. Groups in parentheses can be nested. For more details,
please see section Grouping with Parentheses.
A right-associative operator can be exemplified by the unary operator of logic negation, '!'. Essentially,
its task is to make true from false, and vice versa. Like with other unary operators, associativity means
in this context, what side of the operator the operand must be placed. Symbol '!' is placed before the
operand, i.e., the operand is to the right.
int x = 10;
int on_off = !!x; // 1
In this case, logic negation is performed twice: first time regarding variable x (right '!') and the second
time regarding the result of the preceding negation (left '!'). Such double negation allows transforming
any nonzero value into 1 due to converting into bool and back.
Finally, the last but not the least fine point in processing expressions is the order of computing the
operands. It should be distinguished from the priority that belongs to the operation, not operands. The
order of computing the operands of binary operations is not defined explicitly, which gives the compiler
space to optimize the code and enhance its efficiency. The compiler only guarantees that operands will
be computed before executing the operation.
2.5 Expressions
89
Part 2. Programming fundamentals
There is a limited set of operations, for which the operand evaluation order is defined. Particularly, for
logic AND ('&&') and OR ('||') it is from left to right, and the right part may be omitted if it does not
affect anything due to the value of the left part. But as far as the ternary conditional operator '?:' goes,
the order is even more intricate, since either one or another branch will be calculated upon computing
the first conditions, depending on its trueness. See further sections for more details.
Operand evaluation order is illustrated by the situation where there are several function calls in the
expression. For instance, let 4 functions be used in the expression:
Priority and associativity rules will only be used for the intermediate results of calling these functions,
while the calls themselves can be generated by the compiler in any order it "considers to be necessary"
based on the source code features and compiler settings. For example, functions b and c involved in
multiplication may be called in the order of [b(), c()] or, vice versa, [c(), b()]. If the functions during
being executed may affect the same data, their state will be ambiguous upon the expression
computation.
A similar problem can be seen when working with arrays and increment operators (see Increment and
Decrement).
int i = 0;
int a[5] = {0, 1, 2, 3, 4};
int w = a[++i] - a[++i];
Depending on whether the left or the right difference operand will be computed as the first, we can get
-1 (a[1] - a[2]) or +1 (a[2] - a[1]). Since the MQL5 compiler is ever-improving, there is no guarantee
that the current result (-1) will be retained in the future.
To avoid potential issues, it is recommended not to use an operand repeatedly, if it has already been
modified in the same expression.
In all expressions, there can usually be operands of different types. This leads to the need to cast them
to a certain common type, before performing any actions with them. If there are no explicit typecasts,
MQL5 performs the implicit conversion where necessary. Besides, conversion rules are different for
different type combinations. Explicit and implicit typecasting is discussed in the relevant section.
We have already used this operator for the initialization of variables, which is executed only once,
during creating them. However, assignment allows changing the values of variables in the course of the
algorithm for an arbitrary number of times. For example:
2.5 Expressions
90
Part 2. Programming fundamentals
int z;
int x = 1, y = 2;
z = x;
x = y;
y = z;
Variables x and y were initialized by values 1 and 2, whereupon the auxiliary third variable z and three
assignments were used to exchange values x and y.
The assignment operator, like all operators, returns its result into the expression. This enables writing
the assignments in a sequence.
int x, y, z;
x = y = z = 1;
Here, 1 will first be assigned to variable z, then to variable y, and finally to variable x. Obviously, this
operator is right-associative, because the value being assigned drifts from right to left in the expression.
We can use the assignment as a part of an expression. But, since its priority is lower than those of all
other operators (except for the "comma" one, see Priorities of Operations), it must be enclosed in
parentheses (for more details, please see the section on Grouping with parentheses). This aspect
enables situations where mistypes, such as '=' instead of '==', in expressions lead to not executing the
statements as intended. See the example of such behavior in the section dealing with statement if.
The assignment operator imposes certain limitations on what can be to the left of '=' and what to the
right of it. In programming, these entities aiming to simplify storing are entitled precisely: LValue and
RValue (based on Left and Right).
LValue and RValue
LValue represents an entity, for which memory is allocated and, therefore, a value can be written
in it. Variable and array elements are the known examples of LValue. Upon having studied OOP, we
will get to know another representative of this category: Object, in which the assignment operator
can be reloaded. A mandatory element of LValue is the presence of an identifier.
It should be considered that variables and arrays may be described with the keyword const, and
then they cannot act as LValue, because the modification of constants is prohibited.
RValue is a temporary value used in an expression, such as a literal or value returned due to a
function call or due to computing a fragment of the expression.
Category LValue is of expansive nature, i.e., falling within it allows placing the relevant object to the
left of '=' but does not prohibit using it, on par with RValue, to the right of '='.
Category RValue, over again, is of a limiting nature, i.e., any RValue may only be to the right of '='.
As a certain LValue element is used to the right of '=', its identifier, in fact, denotes its current
contents placed into the expression formula.
However, if an element of LValue is used to the left of '=', its identifier indicates a memory address
(cell) where the new value (expression computation result) should be written.
Different operators have different limitations regarding whether they can be used for the operands
2.5 Expressions
91
Part 2. Programming fundamentals
of LValue or RValue. For example, increment '++' and decrement '--' operators (see Increment and
Decrement) may only be used with LValue.
Here are some examples of what is and is not allowed to do with assignment operators (script
ExprAssign.mq5):
// description of variables
const double cx = 123.0;
int x, y, a[5] = {1};
string s;
// assignment
a[2] = 21; // ok
x = a[0] + a[1] + a[2]; // ok
s = Symbol(); // ok
cx = 0; // const variable may not be changed
// error: 'cx' - constant cannot be modified
5 = y; // 5 — this number (literal)
// error: '5' - l-value required
x + y = 3; // to the left of RValue (expression computation result)
// error: l-value required
Symbol() = "GBPUSD"; // to the left of RValue with the function call result
// error: l-value required
In the column containing examples, e1 and e2 are arbitrary subexpressions. Associativity is marked
with 'L' (left to right) and 'R' (right to left). The number in the first column can be considered as
precedence of executing the operations.
3 * Multiplication e1 * e2 L
3 / Division e1 / e2 L
3 % Division modulo e1 % e2 L
4 + Addition e1 + e2 L
4 - Subtraction e1 - e2 L
Order in the table corresponds with decreasing the priorities: Unary plus and minus are calculated
before multiplication and division, while the latter ones, in turn, before addition and subtraction.
2.5 Expressions
92
Part 2. Programming fundamentals
double a = 3 + 4 * 5; // a = 23
In fact, unary plus does not have any effect in calculations, but can be used for a better visualization of
the expression. Unary minus reverses the sign of its operand.
Arithmetic operations are used for numeric types or those that can be cast to them. The calculation
result is an RValue. In computation, the storage locations of integer operands are often extended up to
the "largest" of the integers used or to int (if all integer types were of a smaller size), as well as cast to
a common type. More details can be found in the section on Typecasting.
bool b1 = true;
bool b2 = -b1;
In this example, variable b1 "expands" to the int type with value 1. Sign reversing gives -1, which in the
reverse typecasting to bool gives true (because -1 is not zero). Using logic type in arithmetic
computations is not welcome.
Dividing integers gives an integer, that is, the fractional part, if any, is omitted. It can be checked using
the script ExprArithmetic.mq5.
int a = 24 / 7; // ok: a = 3
int b = 24 / 8; // ok: b = 3
double c = 24 / 7; // ok: c = 3 (!)
Although variable c is described as double, there are integers in the expression to initialize it; therefore,
the division performed is an integer. To perform a division with a fractional part, at least one operand
must be of real type (the second one will also be cast to it).
Operator '%' calculates the remainder of integer division (it is only applicable to two operands of
integer type).
int x = 11 % 5; // ok: x = 1
int y = 11 % 5.0; // no real number can be used
// error: '%' - illegal operation use
Where operands have different signs, operators '*' and '/' give a negative number. The following rules
apply to operator '%':
In section Characteristics of Arrays, we delved into the idea that a multidimensional array could be
represented by a one-dimensional one due to recalculating the indices of their elements. We also
provided the formula to obtain an index through in a one-dimensional array by the coordinates (column
number X and row number Y at the string length of N) of the two-dimensional array.
index = Y * N + X
Operation '%' allows us to perform a more convenient backward calculation, i.e., find X and Y by the
index-through:
2.5 Expressions
93
Part 2. Programming fundamentals
Y = index / N
X = index % N
If an unpresentable result NaN (Not A Number, such as infinity, square root of a negative number, etc.)
was obtained at some stage during calculating the expression, all subsequent operations with it will also
produce a NaN. It can be distinguished from a normal number using the MathIsValidNumber function
(see Mathematical Functions).
Here, it is subtracted from the NaN (obtained from division) and gives the NaN again.
Addition operation is defined for strings and performs the concatenation, i.e., combining them.
The increment is denoted by two consecutive pluses: '++'. Decrement is denoted by two consecutive
minuses: '--'.
Prefix operators, as the name implies, are written before operand (++x, --x). They change the operand
value, and this new value is involved in the further calculations of the expression.
Postfix operators are written after operand (x++, x--). They substitute the copy of the current operand
value in the expression and then change its value (the new value does not get into the expression).
Simple examples are given in the script ExprIncDec.mq5.
int i = 0, j;
j = ++i; // j = 1, i = 1
j = i++; // j = 1, i = 2
Postfix form may be useful for more compact writing of expressions combining a reference to the
preceding value of the operand and its side modification (two separate statements would be required to
make an alternative record of the same). In all other cases, it is recommended to use the prefix form
(it does not create a temporary copy of the "old" value).
In the following example, the sign is reversed in the array elements consecutively, until the zeroth
element is found. Moving through the array indices is ensured by postfix increment k++ inside the loop
while. Due to postfix, expression a[k++] = -a[k] first updates the kth element and then increases k by
1. Then the assignment result is checked for not being equal to zero (!= 0, see the following section).
int k = 0;
int a[] = {1, 2, 3, 0, 5};
while((a[k++] = -a[k]) != 0){}
// a[] = {-1, -2, -3, 0, 5};
The table below shows the increment and decrement operators in order of priority:
2.5 Expressions
94
Part 2. Programming fundamentals
All increment and decrement operations have a priority higher than arithmetic operations. Prefixes are
of a lower priority than postfixes. In the following example, the "old" value of x is summed up with the
value of y, upon which x is incremented. If the prefix priority were higher, the increment of y would be
performed, upon which the new value, 6, would be summed up with x, and we would get z = 6, x = 0
(previous).
int x = 0, y = 5;
int z = x+++y; // "x++ + y" : z = 5, x = 1
The table below gives all comparison operations and their properties, such as symbols used, priorities,
examples, and associativity.
7 == Equal e1 == e2 L
7 != Not equal e1 != e2 L
The principle of each operation is to compare two operands using the criterion from the column
containing its description. For example, entry "x < y" means checking whether "x is lesser than y".
Correspondingly, the comparison result will be true if x is really lesser than y, and false in all other
cases.
Comparisons work for the operands of any type (for different types, typecasting is performed).
Considering the left associativity and the return of the bool type result, constructing a sequence of
comparisons does not work so obviously. For example, a hypothetic expression to check whether the
value y lies between the values of x and z, could seemingly appear as follows:
2.5 Expressions
95
Part 2. Programming fundamentals
int x = 10, y = 5, z = 2;
bool range = x < y < z; // true (!)
However, such an expression is processed in a different manner. Even the compiler distinguishes it by
the warning: "unsafe use of type 'bool' in operation".
Due to the left associativity, the left condition x < y is checked first, and its result is substituted as a
temporary value of the bool type into the expression that goes as follows: b < z. Then the value of z is
compared to true or false in the temporary variable b. To check whether y ranges between x and z, you
should use two comparison operations combined with the logic operation AND (it will be considered in
the next section).
int x = 10, y = 5, z = 2;
bool range = x < y && y < z; // false
When using the comparing for equality/inequality, the features of the operand types shall be
considered. For instance, floating-point numbers often contain "approximate" values after calculations
(we considered the accuracy of representing double and float in the section Real Numbers). For
example, the sum of 0.6 and 0.3 is not strictly 0.9:
The difference makes 1*10-16, but it is sufficient for the comparison operation to return false.
Therefore, real numbers should be compared for equality/inequality using the greater-/less-then
operators for their difference and acceptable deviation that is sorted out manually, based on the
features of the computation, or a universal one is taken. Recall that for double and float, the embedded
accuracy constants, DBL_EPSILON and FLT_EPSILON, are defined, valid for the value of 1.0. They
must be scaled to compare other values. In script ExprRelational.mq5, one of the possible realizations
of function isEqual is presented to compare real numbers, which considers this aspect.
Here we use the function of obtaining an absolute unsigned value (MathAbs) and the highest of the two
values (MathMax). They will be described in the section Mathematical Functions of Part 4. The absolute
difference between the parameters of function isEqual is compared to the calibrated tolerance in
variable eps using operation '<'.
This function cannot be used to compare with absolute zero, anyway. For this purpose, you can use the
following approach (it will probably require some adaptation to your specific needs):
Strings are compared lexicographically, i.e., letter by letter. The code of each character is compared
to the code of the character in the same position of the second string. Comparison is performed until a
2.5 Expressions
96
Part 2. Programming fundamentals
difference in the codes is found or one of the strings ends. The string ratio will be equal to that of the
first differing characters, or a longer string will be considered greater than the shorter one. Remember
that upper- and lowercase letters have different codes, and strange enough, uppercase ones have
smaller codes than the lowercase ones.
An empty string "" (in fact, it stores one terminal 0) is not equal to the special value of NULL which
means no string.
bool cmp1 = "abcdef" > "abs"; // false, [2]: 's' > 'c'
bool cmp2 = "abcdef" > "abc"; // true, by length
bool cmp3 = "ABCdef" > "abcdef"; // false, by case
bool cmp4 = "" == NULL; // false
Moreover, to compare strings, MQL5 provides some functions that will be described in the section
Working with Strings.
In comparing for equality/inequality, it is not recommended to use bool constants: true or false. The
matter is that, in expressions like v == true or v == false, operand v can be interpreted intuitively as a
logical type, while in fact, it is a number. As it is known, zero value is considered false in numbers, while
all others are interpreted as true (we often want to use it as an indication of some result being present
or absent). However, in this case, typecasting goes backward: true or false are "expanded" to a
numeric type v and actually become equal to 1 and 0, respectively. Such a comparison will have a
result other than the expected one (for example, comparison 100 == true will turn out to be false).
12 || Logical OR e1 || e2 L
Logical NOT transforms true into false and false into true.
Operators AND and OR always compute operands from left to right and, if possible, use the
computational shortcut. If the left operand is equal to false, then operator AND skips the second
operand, because it does not affect anything — the result is already false. If the left operand is equal
to true, then operator OR skips the second operand for the same reason, since the result will, in any
case, be equal to true.
This is often used in programs to prevent from errors in the second (and subsequent) operands. For
example, we can hedge ourselves against the error of accessing a non-existing array element:
2.5 Expressions
97
Part 2. Programming fundamentals
Here we use the built-in function ArraySize that returns the array length. Only if index is smaller than
the length, the element with this index is read and compared with zero.
ArraySize(array) == 0 || array[0] == 0
The condition is true immediately if the array is null. And only if there are elements, the additional
check for the contents will continue.
If the expression consists of multiple operands combined by logical OR, then with the first true (if any)
the total result of true will be obtained immediately. However, if operands are combined by logical AND,
then with the first false the total result of false will be obtained immediately.
Of course, you can combine different operations within one expression, considering their different
priority: Negation is executed first, then the AND-related conditions, and in the end the OR-related
conditions. If another sequence is required, it must be explicitly specified using parentheses.
For example, the following expression without parentheses, A && B || C && D, is in fact equivalent to:
(A && B) || (C && D). For the logical OR to be executed as the first, it should be enclosed in
parentheses: A && (B || C) && D. For more details on using parentheses, see section Grouping with
Parentheses.
Simple examples are given in script ExprLogical.mq5 to check logical operations in practice.
int x = 3, y = 4, z = 5;
bool expr1 = x == y && z > 0; // false, x != y, z does not matter
bool expr2 = x != y && z > 0; // true, both conditions are complied with
bool expr3 = x == y || z > 0; // true, it is sufficient that z > 0
bool expr4 = !x; // false, x must be 0 to get true
bool expr5 = x > 0 && y > 0 && z > 0; // true, all 3 are complied with
bool expr6 = x < 0 || y > 0 && z > 0; // true, y and z are sufficient
bool expr7 = x < 0 || y < 0 || z > 0; // true, z is sufficient
In the string of calculating expr6, the compiler gives the warning: "Check operator precedence for
possible error; use parentheses to clarify precedence".
Logical operations '&&' and '||' should not be mixed with bitwise operations '&' and '|' (considered in
the next section).
All symbols and descriptions of bitwise operators are provided with their associativity and in order of
their priority in the table below.
2.5 Expressions
98
Part 2. Programming fundamentals
9 ^ Bitwise exclusive OR e1 ^ e2 L
10 | Bitwise OR e1 | e2 L
Of the entire group, only the bitwise complement operation '~' is unary, while all others are binary.
In all cases, if the operand size is less than int/uint, it is preliminarily extended to int/uint by adding 0
bits into higher order. Based on the operand type being signed/unsigned, a high-order bit may affect
the sign.
Standard Windows application, Calculator, may help understand the representation of numbers at the
bit level. If you select the Programmer operation mode in the View menu, the groups of toggle buttons
will appear in the program to select representing the number in a hexadecimal (Hex), decimal (Dec),
octal (Oct), or binary (Bin) form. It is the latter one that shows bits. Moreover, you can select the
number size: 1, 2, 4, and 8 bytes. The buttons allow executing all the operations considered: Not ('~'),
And ('&'), Or ('|'), Xor ('^'), Lsh ('<<'), and Rsh ('>>').
Since the Calculator uses signed numbers, negative values may appear when toggling to the decimal
mode (remember that the high-order bit is interpreted as a sign). For convenient analysis, it is
reasonable to exclude the minus that appears, for which purpose it is necessary to select the size in
bytes one grade higher. For example, to check the values within the range up to 255 (uchar, unsigned
one-byte integer), you should select 2 bytes (otherwise, only decimal values through 127 will be
positive, while the others will be displayed in the negative region).
Bitwise complement creates a value, in which the 0-bit is in the place of all 1-bits, while 1-bit is in the
place of 0-bits. For example, the negation of a byte with all zero bits gives a byte with all 1 bits.
Number 50 appears in the bitwise format as '00110010' (byte). Its inversion gives '11001101'.
Unity represented hexadecimally is 0x0001 (for short). Inversion of these bits gives 0xFFFE (see script
ExprBitwise.mq5).
Bitwise AND checks each bit in both operands and in the positions where two set bits (1) are found,
stores the 1-bit into the result. In all other cases (where there is only a set bit in one operand or they
are reset in both places), the 0-bit is written in the result.
Bitwise OR writes 1-bits into the result if they are on the positions where there is a set bit in at least
one of two operands.
Bitwise exclusive OR writes in the result the 1-bits on the positions where there is a set bit in either the
first or second operand, but not in both at the same time. The binary representation of two numbers, X
and Y, and the results of bitwise operations with them are shown below.
2.5 Expressions
99
Part 2. Programming fundamentals
X 10011010 154
Y 00110111 55
X & Y 00010010 18
X | Y 10111111 191
X ^ Y 10101101 173
When writing complex expressions from several different operators, use grouping with parentheses in
order not to become confused with priorities.
Shift operations move bits to the left ('<<') or right ('>>') by the quantity of bits, defined in the second
operand that must be a non-negative integer. As a result, left (for '<<') or right (for '>>') bits are
dropped, since they go beyond the memory cell boundaries. With the left shift, the relevant number of
0 bits are added on the right. With the right shift, either 0 bits are added on the left (if the operand is
unsigned) or the sign bit is reproduced (if the operand is signed). In the latter case, 0 bits are added on
the left for positive numbers and 1 bits for negative ones; i.e., the sign retains.
In the example above, the initial left shift "destroyed" the high-order bits of variable p, while the
subsequent right shift by the same quantity of bits filled them with zeros, which led to decreasing the
value from 0xffc0 to 0x07fe.
Shift size (quantity of bits) must be less than that of the operand type (considering its potential
extension). Otherwise, all initial bits will get lost.
Bitwise operations '&' and '|' should not be mixed with logical operations '&&' and '||' (considered in
the preceding section).
2.5 Expressions
100
Part 2. Programming fundamentals
These operators execute the relevant action for operands e1 and e2, whereupon the result is stored in
e1.
An expression like e1 @= e2 where @ is any operator from the table is approximately equivalent to e1
= e1 @ e2. The word "approximately" emphasizes the presence of some subtle aspects.
First, if the place of e2 is occupied by an expression with an operator having a lower priority than that
of @, e2 is still calculated before that. That is, if the priority is marked with parentheses, we will get e1
= e1 @ (e2).
Second, if there are side modifications of variables in expression e1, they are made only once. The
following example demonstrates this.
Here, arrays a and b contain identical elements and are processed using index variables i and j . At the
same time, the expression for array a uses operation '*=', while that for array b uses the equivalent.
Results are not equal: Both index variables and arrays differ.
Other operators will be useful in problems with bit-level manipulations. Thus, the following expression
can be used to set a specific bit into 1:
ushort x = 0;
x |= 1 << 10;
Here, shift 1 ('0000 0000 0000 0001') is made by 10 bits to the left, which results in obtaining a
number with one set 10th bit ('0000 0100 0000 0000'). Bitwise OR operation copies this bit into
variable x.
2.5 Expressions
101
Part 2. Programming fundamentals
Here, the inversion operation is applied to 1 shifted by 10 bits to the left (which we saw in the
preceding expression), which results in all bits changing their value: '1111 1011 1111 1111'. Bitwise
AND operation resets the zeroed bits (in this case, one) in variable x, while all other bits in x remain
unchanged.
The logical condition must be specified in the first operand 'condition'. This can be an arbitrary
combination of comparison operations and logical operations. Both branches must be present.
If the condition is true, expression expression_ true will be computed, while if it is false, the
expression_ false will be computed.
This operator guarantees that only one of the expressions expression_ true and expression_ false will be
executed.
Types of the two expressions must be identical, otherwise, there will be an attempt to implicitly
typecast them.
Please note that the result of processing expressions in MQL5 always represents an RValue (in C++, if
only LValues are in expressions, then the result of the operator will also be LValue). Thus, the following
code is compiled well in C++, but gives an error in MQL5:
Conditional operators can be nested, that is, it is permitted to use another conditional operator as a
condition or either branch (expression_ true or expression_ false). At the same time, it cannot be always
clear what the conditions relate to (if parentheses are not used to explicitly denote grouping). Let's
consider examples from ExprConditional.mq5.
int x = 1, y = 2, z = 3, p = 4, q = 5, f = 6, h = 7;
int r0 = x > y ? z : p != 0 && q != 0 ? f / (p + q) : h; // 0 = f / (p + q)
In this case, the first logical condition represents comparison x > y. If it is true, the branch with variable
z is executed. If it is false, the additional logical condition p != 0 && q != 0 is checked, with two
expression options, as well.
Below are some more operators, in which logical conditions are written uppercase, while computation
options are lowercase. For simplicity, they all are made variables (from the example above). In reality,
each of the three components may be a richer expression.
For each string, you can track how the result is obtained, which has been shown in the comment.
2.5 Expressions
102
Part 2. Programming fundamentals
Since the operator is right-associative, the compound expression is analyzed from right to left, that is,
the rightmost structure with three operands combined by '?' and ':' becomes the operand of the
external condition written to the left. Then, considering this substitution, the expression is analyzed
from right to left again, and so on, until the final complete upper-level structure '?:' is obtained.
Therefore, the expressions above are grouped as follows (parentheses denote the implicit interpretation
of the compiler; but such parentheses could be added into expressions to visualize the source code,
which approach is actually recommended).
For variable r5, the first condition A ? f : h computes the logical condition for the subsequent expression
and therefore, is transformed into bool. Since A is equal to false, the value is taken from variable h. It is
not equal to 0; therefore, the first condition is considered true. This results in the actuating branch (B ?
x : y), from which the value of variable y is returned, since B is equal to false.
There must be all 3 components (a condition and 2 alternatives) in the operator. Otherwise, the
compiler will generate the error "unexpected token":
In the compiler language, a token is an indivisible fragment of the source code, having its independent
meaning or purpose, such as type, identifier, punctuation character, etc. The entire source code is
divided by the compiler into a sequence of tokens. Signs of the operators considered are tokens, too. In
the code above, there are two symbols '?', and there must be two symbols ':' matching with them, but
it is the only one. Therefore, the compiler "says" that the statement end symbol ';' is premature and
"inquires" what exactly is deficient: "colon sign expected".
Since the conditional operator has a very low priority (13 in the full table, see Priorities of Operations),
it is recommended to enclose it in parentheses. This makes it easier to avoid situations where the
operands of a conditional operator could be "caught" by the neighboring operations having higher
priorities. Fir instance, if we need to calculate the value of a certain variable w via the sum of two
ternary operators, a straightforward approach might appear as follows:
int w = A ? f : h + B ? x : y; // 1
This will work differently than we thought. Due to the higher priority, the sum h + B is considered as a
single expression. Considering its parsing from right to left, this sum appears as a condition and is cast
to the bool type, which is even warned by the compiler as "expression not boolean". Compiler
interpretation can even be visualized by parentheses:
2.5 Expressions
103
Part 2. Programming fundamentals
int v = (A ? f : h) + (B ? x : y); // 9
Deep nesting of conditional operators impacts adversely on the code understandability. Nesting levels
exceeding two or three should be avoided.
2.5.10 Comma
Operator comma that is explicitly denoted as ',' is placed between two expressions computed
independently from left to right. In other words, this operator does not perform any actions itself but
just allows specifying the sequence of two or more expressions within a statement.
Expressions placed right-hand in the sequence can use the results of computing the left-hand
expressions, since they have already been processed.
The operator result is the result of the rightmost expression. The operator has the lowest priority.
Currently, using the operator in MQL5 is limited by the header of the for statement.
Example:
Order of evaluation:
· Expressions are processed from left to right. Thus, the expressions on the right can use the results
of the expressions on the left since they have already been processed.
· The result of the comma operator is the value of the rightmost expression. It's important to note
that the comma operator has the lowest priority, meaning that other operators in the expression
may have higher priorities.
The sizeof operator returns the size of its operand in bytes. Operator syntax: sizeof(x), where x can be
a type or an expression. The expression is not computed in this case, since operator sizeof is executed
at the compilation stage and, in fact, a constant is substituted in its place in the expression.
For fixed-size arrays, the operator returns the total amount of the allocated memory, that is, the
multiplication of the number of elements in all dimensions by the type size in bytes. For dynamic
arrays, it returns the size of an internal structure storing the array properties.
2.5 Expressions
104
Part 2. Programming fundamentals
double array[2][2];
double dynamic1[][1];
double dynamic2[][2];
Print(sizeof(double)); // 8
Print(sizeof(string)); // 12
Print(sizeof("This string is 29 bytes long!")); // 12
Print(sizeof(array)); // 32
Print(sizeof(array) / sizeof(double)); // 4 (quantity of elements)
Print(sizeof(dynamic1)); // 52
Print(sizeof(dynamic2)); // 52
Type double takes up 8 bytes. The size of the string type is 12. These 12 bytes store the service
information we mentioned in the section dealing with type string. This memory is allocated for any
string (even uninitialized). Please note that a string containing a 29-character text is also sized 12.
This is because both an empty string and a string with some contents have an internal structure
intended for storing a reference to memory. To obtain the text length, we should use the StringLen
function.
Fixed-size array size is really computed as the multiplication of the number of elements (2*2=4) by the
double type size (8), a total of 32. As a consequence, an expression like sizeof(array) / sizeof(double)
allows finding out the entity of elements in it.
For dynamic arrays, the internal structure size is 52 bytes. Differences in the descriptions of arrays
dynamic1 and dynamic2 do not affect this value.
Operator sizeof is especially useful to get the sizes of classes and structures.
typename
Operator typename returns a string with the name of the parameter passed to it, which can be a type
or an expression. For arrays, along with the data type keyword, a tag is printed as a pair of parentheses
(or several ones, depending on the array dimensionality).
Print(typename(double)); // double
Print(typename(array)); // double [2][2]
Print(typename(dynamic1)); // double [][1]
Print(typename(1 + 2)); // int
For custom types, such as classes, structures, and others (that we will consider in Part 3), the type
name follows the entity category, such as "class MyCustomType". Moreover, for constants, the "const"
modifier will be added to the string description.
Therefore, to know the short type name consisting of one word, use macro TYPENAME from the
attached file TypeName.mqh.
It can be necessary to learn the type name in the so-called templates that can generate from the
source code similar realizations for different types defined in the parameters of templates.
2.5 Expressions
105
Part 2. Programming fundamentals
In the preceding sections, we have already seen more than a few times that some expressions can
cause unexpected results due to the priorities of operations. To explicitly change the computation
order, we should use parentheses. Part of the expression enclosed in them gets a higher priority as
compared to the environment, without regard to default priorities. Pairs of parentheses can be nested,
but it is not recommended to make more than 3-4 nesting levels. It is better to divide the too complex
expressions into several simpler ones.
Script ExprParentheses.mq5 shows the evolution of placing parentheses within one expression. The
initial intent for it is to set the bit in variable flags using the left-shift operation '<<'. The bit number is
taken from variable offset if it is not zero, or otherwise, as 1 (remember that numbering starts with
zero). Then the obtained value is multiplied by coefficient. No need to search for any applied sense in
this example. However, more sophisticated structures can occur, too.
int offset = 8;
int coefficient = 10, flags = 0;
int result1 = coefficient * flags | 1 << offset > 0 ? offset : 1; // 8
int result2 = coefficient * flags | 1 << (offset > 0 ? offset : 1); // 256
int result3 = coefficient * (flags | 1 << (offset > 0 ? offset : 1)); // 2560
The first version, without parentheses, seems suspicious even to the compiler. It gives a warning that
we have already known: "expression not boolean". The matter is that the ternary conditional operator
has the lowest priority of all operators here. For this reason, the entire left part before '?' is considered
its condition. Inside the condition, calculations are in the following order: Multiplication, bitwise shift,
"more than" comparison, and bitwise OR, which results in an integer. Of course, it can be used as true
or false, but it is desired to "communicate" such intentions to the compiler using explicit typecasting. If
it is absent, the compiler considers the expression suspicious, and not in vain. The first calculation
results in 8. It is incorrect.
Let's add parentheses around the ternary operator. The warning of the compiler will disappear.
However, the expression is still computed wrongly. Since the priority of multiplication is higher than
that of bitwise OR, variables coefficient and flags are multiplied before the bit mask is used, which is
obtained by shifting to the left. The result is 256.
Finally, having added another pair of parentheses, we will get the correct result: 2560.
0 :: Scope resolution n1 :: n2 L
1 () Grouping (e1) L
1 [] Index [e1] L
1 . Dereferencing n1.n2 L
2.5 Expressions
106
Part 2. Programming fundamentals
3 * Multiplication e1 * e2 L
3 / Division e1 / e2 L
3 % Division modulo e1 % e2 L
4 + Addition e1 + e2 L
4 - Subtraction e1 - e2 L
7 == Equal e1 == e2 L
7 != Not equal e1 != e2 L
9 ^ Bitwise exclusive OR e1 ^ e2 L
10 | Bitwise OR e1 | e2 L
12 || Logical OR e1 || e2 L
13 ?: Conditional ternary c1 ? e1 : e2 R
14 = Assignment e1 = e2 R
2.5 Expressions
107
Part 2. Programming fundamentals
15 , Comma e1 , e2 L
As we have seen, square brackets are used to specify the indices of array elements and, therefore,
have one of the highest priorities.
Along with operators that have been considered earlier, there are some still unknown ones here.
We will learn the scope resolution operator '::' within object-oriented programming (OOP). We will also
need the dereferencing operator '.' at the same time. Identifiers of types (classes) and their properties,
not expressions, act as their operands.
Address-taking operator '&' is intended to pass the function parameters by referencing and to obtain
the object addresses in OOP. In both cases, the operator is applied to a variable (LValue).
Type conversion in MQL5 is the process of changing the data type of a variable or expression. MQL5
supports three main types of type conversion: implicit, arithmetic, and explicit.
· Occurs automatically when a variable of one type is used in a context that expects another type.
For example, integer values can be implicitly converted to real values.
2.5 Expressions
108
Part 2. Programming fundamentals
· Gives the programmer control over type conversion. It is done in two forms: C-style ((target)) and
"functional" style (target()). It is used when you need to explicitly instruct the compiler to perform
a conversion between types, for example, when rounding real numbers or when successive type
conversions are required.
Understanding the differences between implicit, arithmetic, and explicit type conversion is crucial for
ensuring the correct execution of operations and avoiding data loss. This knowledge helps programmers
effectively utilize this mechanism in MQL5 development.
We have already seen several rules for implicit type conversion while studying types and variables.
Specifically, if a value of type other than boolean is assigned to a bool variable, then the value 0 is
regarded as false, and all the rest as true. In the more general case, all expressions that assume the
presence of logical conditions are converted to type bool. For example, the first operand of a ternary
conditional operator is always converted into a bool.
But if a value of type bool is assigned to a numeric type, then true becomes 1, and false becomes 0.
When a real number is assigned to an integer type variable, the fractional part is discarded (the
compiler issues a warning). When an integer, on the other hand, is assigned to a variable of real type,
precision can be lost (the compiler also issues a warning). We have already talked about this in the
sections on Integer numbers and Real numbers.
If we have integer and floating point numbers, everything is converted to floating point numbers of the
maximum size used (usually double, unless you explicitly specify float or the numeric literal has a suffix
'f', for example1234.56789f).
For integers of different sizes, there are also conversion rules: they expand if necessary, which means
that they increase to the size of the largest integer type used in the expression (see Arithmetic type
conversions).
In addition to expressions, we often need to implicitly convert types during initialization and
assignment, when the types to the right and left of the '=' sign do not match. The same conversion
rules apply when passing values through function parameters and returning results from functions (for
further details please see the Functions section).
Considering the above, a large number of conversions can be performed in one line of code. If this
causes compiler warnings, it's a good idea to make sure the conversion is intentional and eliminate
warnings by inserting an explicit type conversion.
short s = 10;
long n = 10;
int p = s * n + 1.0;
In this example, when performing a multiplication, the type of the variable s is extended to the type of
the second operand long and an intermediate result of type long is obtained. Because the constant 1.0
is of type double, the result of the product is converted to double before addition. The overall result is
also of type double; however, the variable p is of type int and therefore an implicit conversion from
double to int is performed.
The special types datetime and color are processed according to the rules of integers with lengths of 8
and 4 bytes, respectively. But for date and time, there is a stricter limit on the maximum value -
32535244799, which corresponds to D'3000.12.31 23:59:59'.
Most types can be implicitly converted to and from strings, but the results are not always adequate, so
the compiler issues warnings "implicit conversion from 'number' to 'string'" and "implicit conversion
from 'string' to 'number'" so that the programmer can check them. For example, converting a string to
an integer allows the string to contain only digits and '+'/'-' characters at the beginning. Converting
from a string to a real allows, in addition to numbers, the presence of a dot '.' and notation with
"exponent" ('e' or 'E', e.g. +1.2345e-1). If an unsupported character (for example, a letter) is
encountered in the string, the rest of the string is discarded in full.
For example, the string date and time ("2021.12.12 00:00") cannot be assigned without losses to a
variable of type datetime because datetime is an integer (number of seconds). In this case, reading the
number from the string will end when the first point is reached, i.e. the number will get the value 2021.
This number of seconds corresponds to the 34th minute of the year 1970.
There are special functions for such conversions (see section Data Transformation).
The only direction of implicit and explicit type conversion that is forbidden is from string to bool. The
compiler in such cases shows the error message "cannot implicitly convert type 'string' to 'bool'".
Integer expansion implies conversion of bool, char, unsigned char, short, unsigned short to int (or
unsigned int if int isn't big enough to store specific numbers). Large values can be converted to long
and unsigned long.
If the type of the variable is not able to store the result of the type that was obtained when the
expression was evaluated, the compiler will issue a warning:
double d = 1.0;
int x = 1.0 / 10; // truncation of constant value
int y = d / 10; // possible loss of data due to type conversion
The expression to initialize the variables x and y contains the real number 1.0, so the other operands
(constant 10 in this case) are converted to double, and the result of division will also be of type double.
However, the type of variables is int, and therefore an implicit conversion to it takes place.
Calculation 1.0 / 10 is done by the compiler during compilation and therefore it gets a constant of type
double (0.1). Of course, in practice, it is unlikely that the initializing constant will exceed the size of the
receiving variable. Therefore, the compiler warning "truncation of constant value" can be considered
exotic. It just shows the problem in the most simplified way.
However, as a result of variable-based calculations, similar data loss can also occur. The second
compiler warning we see here ("possible loss of data due to type conversion") occurs much more
frequently. Moreover, the loss is possible not only when converting from real type to integer, but also
vice versa.
As we know, type double cannot accurately represent large integers (although its range of valid values
is much larger than long).
Another warning we might encounter due to type mismatch: "integral constant overflow".
long m1 = 1000000000;
long m2 = m1 * m1; // ok: m2 = 1000000000000000000
long m3 = 1000000000 * 1000000000; // integral constant overflow
// m3 = -1486618624
Integer constants in MQL5 have type int, so the multiplication of million by million is performed taking
into account the range of this type, which is equal to INT_MAX (2147483647). The value
1000000000000000000 causes an overflow, and m3 gets the remainder after dividing this value by
the range (more on this in the sidebar below).
The fact that the receiving variable m3 has type long does not mean that the values in the expression
must be converted to it beforehand. This only happens at the moment of assignment. In order for the
multiplication to be performed according to the rules of long, you need to somehow specify the type
long directly in the expression itself. This can be done with an explicit conversion or by using variables.
In particular, obtaining the same product using a variable m1 of type long (such as m1 * m1) leads to
the correct result in m2.
Signed and unsigned integers
Programs are not always written perfectly, with protection from all possible failures. Therefore,
sometimes it happens that the integer number obtained during the calculations does not fit into the
variable of the selected integer type. Then it gets the remainder of dividing this value by the
maximum value (M) that can be written in the corresponding number of bytes (type size), plus 1. So
for integer types with sizes from 1 to 4 bytes, M + 1 is, respectively, 256, 65536, 4294967296,
and 18446744073709551616.
But there is a nuance for signed types. As we know, for signed numbers, the total range of values is
divided approximately equally between positive and negative areas. Therefore, the new "residual"
value may in 50% of cases exceed the positive or negative limit. In this case, the number turns into
the "opposite": it changes sign and ends up at a distance M from the original one.
It is important to understand that this transformation occurs only due to a different interpretation
of the bit state in the internal representation, and the state itself is the same for signed and
unsigned numbers.
Let's explain this with an example for the smallest integer types: char and uchar.
Since unsigned char can store values from 0 to 255, 256 maps to 0, -1 maps to 255, 300 maps to
44, and so on. If we try to write 300 into a regular signed char, we also get 44, because 44 is in
the range from 0 to 127 (the positive range of char). However, if you set the variables char and
uchar to 3000, the picture will be different. The remainder of 3000 divided by 256 is 184. It ends
up in uchar unchanged. However, for char, the same combination of bits results in -72. It is easy to
check that 184 and -72 differ by 256.
In the following example, it is easy to spot the problem thanks to the compiler warning.
However, if you get an extra large number during the calculation, there will be no warning.
A similar effect can occur when signed and unsigned integer numbers of the same size are used in the
same expression since the signed operand is converted to unsigned. For example:
uint u = 11;
int i = -49;
Print(i + i); // -98
Print(u + i); // 4294967258 = 4294967296 - 38
When two negative integers add up, we get the expected result. The second expression maps the sum
of -38 to the "opposite" unsigned number 4294967258.
Mixing signed and unsigned types in the same expression is not recommended because of these
potential issues.
Besides that, if we subtract something from an unsigned integer, we need to make sure that the result
doesn't come out negative. Otherwise, it will be converted to a positive number and can distort the idea
of the algorithm, in particular, the idea of the while loop which checks the variable for the "greater than
or equal to zero" condition: since unsigned numbers are always non-negative, we can easily get an
infinite loop, i.e. a program hang.
target t = (target)s;
Where target is the name of the target type. Any expression can be a data source s. If any operations
are performed in it, you must enclose the expression in parentheses so that the type conversion applies
to the entire expression.
target t = target(s);
Here, the result of dividing two real numbers is explicitly converted to the type int. Thus, the
programmer confirms their intention to discard the fractional part, and the compiler will not issue
warnings. It should be noted that MQL5 has a group of functions for rounding real numbers in various
ways (see Math functions).
If, on the contrary, you want to perform an operation on integer numbers with a real result, you need
to apply type conversion to the operands (in the expression itself):
int x = 100, y = 7;
double d = (double)x / y; // 14.28571428571429
Converting one of the operands is enough to automatically convert the rest to the same type.
If necessary, you can perform several type conversion operations sequentially. Because the conversion
operation is right-associative, the target types will be applied in order from right to left. In the following
example, we convert the quotient to type float (this conversion allows for a more compact, fewer-
character representation of the value), and then to string. Without an explicit conversion to string, we
would get a compiler warning "implicit number to string conversion".
Don't use explicit type conversion just to avoid a compiler warning. If it has no practical basis, you are
masking a potential error in the program.
2.7 Statements
So far, we've learned about data types, variable declarations, and their use in expressions for
calculations. However, these are only small bricks in the building with which the program can be
compared. Even the simplest program consists of larger blocks that allow you to group related data
processing operations and control the sequence of their execution. These blocks are called statements,
and we have actually already used some of them.
In particular, the declaration of a variable (or several variables) is a statement. Assigning the
expression evaluation result to a variable is also a statement. Strictly speaking, the assignment
operation itself is part of the expression, so it is more correct to call such a statement a statement of
expression. By the way, an expression may not contain an assignment operator (for example, if it
simply calls some function that does not return a value, such as Print("Hello");).
Program execution is the progressive execution of statements: from top to bottom and from left to
right (if there are several statements on one line). In the simplest case, their sequence is performed
linearly, one after the other. For most programs, this is not enough, so there are various control
statements. They allow you to organize loops (repeating calculations) in programs and the selection of
algorithm operation options depending on the conditions.
Statements are special syntactic constructions that represent the source text written according to the
rules. Statements of a particular type have their own rules, but there is something in common.
Statements of all types end with a ';' except for the compound statement. It can do without a
semicolon because its beginning and end are set by a pair of curly brackets. It is important to note that
thanks to the compound statement, we can include sets of statements inside other statements,
building arbitrary hierarchical structures of algorithms.
In this chapter, we will get acquainted with all types of MQL5 control statements, as well as consolidate
the features of declaration and expression statements.
{
[statements]
}
In such a schematic description, any fragment enclosed in semicircular brackets and with the
superscripted opt indicates that it is optional. In this case, there may not be any nested statements
inside the block.
In the following sections, we will see how compound statements are used in combination with other
kinds of statements and what they can contain.
There is one nuance that is worth emphasizing: after the description of the compound statement, the
semicolon ';' is not required. This distinguishes it from all other statements.
The declaration must contain the type and identifier of the element (see Declaring and defining
variables), as well as an optional initial value for initialization. Also, when declaring, additional modifiers
can be specified that change certain characteristics of the element. In particular, we already know the
static and const modifiers, and more will be added soon. Arrays require an additional specification of the
dimension and number of elements (see Description of arrays), while functions require a list of
parameters (for further details please see Functions).
2.7 Statements
114
Part 2. Programming fundamentals
The main difference is the mandatory presence of at least one pair of square brackets (the size inside
them can be indicated or not; depending on that, we get a fixed or dynamically distributed array). In
total, up to 4 pairs of square brackets are allowed (4 is the maximum supported number of
measurements).
In many cases, a declaration can simultaneously act as a definition, i.e. it reserves memory for the
element, determines its behavior, and makes it possible to use it in the program. Specifically, the
declaration of a variable or array is also a definition. From this point of view, a declaration statement
can be called a definition statement all the same, but this has not become a common practice.
Our basic knowledge of functions is enough to reliably assume what their definition should look like:
Please note that this is a definition since this description contains both the external attributes of the
function (interface) and statements that define its internal essence (implementation). The latter is
done with a block of code formed by a pair of curly brackets and immediately following the function
header. As you might guess, this is an example of the compound statement we mentioned in the
previous section. In this case, a terminological tautology is indispensable, since it is perfectly justified:
the compound statement is part of the function definition statement.
A little later, we will learn why and how to separate the interface description from the implementation
and thereby achieve function declaration without defining it. We will also demonstrate the difference
between a declaration and a definition using the class as an example.
The declaration statement makes the new element available by its name in the context of the code
block (see Context, scope, and lifetime of variables) in which the statement is located. Recall that
blocks form the local scope of objects (variables, arrays). In the first part of the book, we encountered
this when describing the greeting function.
In addition to local scopes, there is always a global scope, in which you can also use declaration
statements to create elements that are accessible from anywhere in the program.
If there is no static modifier in the declaration statement and it is located in some local block, then the
corresponding element is created and initialized at the moment the statement is executed (strictly
speaking, memory for all local variables inside the function is allocated, for the sake of efficiency,
immediately upon entering the function, but they are not yet formed at that moment).
For example, the following declaration of the variable i at the beginning of the OnStart function ensures
that such a variable will be created with the specified initial value (0) as soon as the function receives
control (i.e., the terminal will call it because it is the main function of the script).
2.7 Statements
115
Part 2. Programming fundamentals
void OnStart()
{
int i = 0;
Print(i);
Thanks to the declaration in the first statement, the variable i is known and available in the subsequent
lines of the function, in particular, in the second line with the call of the Print function, which displays
the contents of the variable in the log.
The variable j described in the last line of the function will be created just before the end of the function
(this, of course, is meaningless, but clear). Therefore, this variable is not known in all earlier strings of
this function. An attempt to output j to the log using a commented Print call will result in an
"undeclared identifier" compilation error.
Elements declared this way (inside code blocks and without the static modifier) are called automatic,
because the program itself allocates memory for them when entering the block and destroys them
when exiting the block (in our case, after exiting the function). Therefore, the area of memory in which
this happens is called the stack ("last in, first out").
Automatic elements are created in the order in which the declaration statements are executed (first i,
then j ). Destruction is performed in reverse order (first j , then i).
If a variable is declared without initialization and starts to be used in subsequent statements (for
example, to the right of the '=' sign) without first writing a meaningful value into it, the compiler issues
a warning: "possible use of uninitialized variable".
void OnStart()
{
int i, p;
i = p; // warning: possible use of uninitialized variable 'p'
}
If a declaration statement has the static modifier, the corresponding element is created only once when
the statement is executed for the first time, and remains in memory, regardless of exit and possible
subsequent entries and exits in the same block of code. All such static members are removed only
when the program is unloaded.
Despite the increased lifetime, the scope of such variables is still limited to the local context in which
they are defined, and can only be accessed from later statements (located below in the code).
In contrast, declaration statements in the global context create their elements in the same order in
which they appear in the source code, immediately after the program is loaded (before any standard
start function is called, such as OnStart for scripts). Global objects are deleted in reverse order when
the program is unloaded.
2.7 Statements
116
Part 2. Programming fundamentals
The Init function accepts a single parameter v of integer type int, the value of which is returned to the
calling code (return statement).
This allows using it as a wrapper to set the initial value of a variable, for example, for two global
variables:
int k = Init(-1);
int m = Init(-2);
The value of the passed argument gets into the variables k and m by calling the function and returning
from it. However, inside Init, we additionally output the value with Print, and thus we can track how the
variables are created.
Note that we cannot use the Init function in the initialization of global variables above its definition. If
we try to move the k variable declaration above the Init declaration, we get the error "'Init' is an
unknown identifier". This limitation only works for the initialization of global variables, because functions
are also defined globally, and the compiler builds a list of such identifiers in one go. In all other cases,
the order of defining functions in the code is not important, because the compiler first registers them
all in the internal list, and then mutually links their calls from blocks. In particular, you can move the
entire Init function and the declaration of the global variables k and m below the OnStart function - it
will not break anything.
Inside the OnStart function, we will describe several more variables using Init: local i and j , as well as
static n. For simplicity, all variables are given unique values so that they can be distinguished.
void OnStart()
{
Print(k);
int i = Init(1);
Print(i);
// error: 'n' - undeclared identifier
// Print(n);
static int n = Init(0);
// error: 'j' - undeclared identifier
// Print(j);
int j = Init(2);
Print(j);
Print(n);
}
Comments here show erroneous attempts to call the relevant variables before they are defined.
2.7 Statements
117
Part 2. Programming fundamentals
Init: -1
Init: -2
-1
Init: 1
1
Init: 0
Init: 2
2
0
As we can see, the global variables were initialized before the OnStart function was called, and exactly
in the order in which they were encountered in the code. Internal variables were created in the same
sequence as their declaration statements were written.
If a variable is defined but not used anywhere, the compiler will issue a "variable 'name' not used"
warning. This is a sign of a potential programmer error.
Looking ahead, let's say that with the help of declaration/definition statements, not only data elements
(variables, arrays) or functions, but also new user-defined types (structures, classes, templates,
namespaces) that are not yet known to us can be introduced into the program. Such statements can
only be made at the global level, that is, outside of all functions.
It is also impossible to define a function within a function. The following code will not compile:
void OnStart()
{
int Init(const int v)
{
Print("Init: ", v);
return v;
}
int i = 0;
}
The compiler will generate an error: "function declarations are allowed on global, namespace, or class
scope only".
expression ;
The semicolon at the end is important here. Since MQL5 source codes support free formatting, the ';'
is the only delimiter that tells the compiler where the previous statement ended and the next one
began. As a rule, statements are written on separate lines, for example, like this:
2.7 Statements
118
Part 2. Programming fundamentals
If it weren't for the ';', adjacent expressions could silently "stick together" and lead to unintended
results. For example, the expression x = y - 10 * z could well be two: x = y; and -10 * z; (-10 with a unary
minus). How is this possible?
The fact is that it is syntactically permissible to write a statement that actually works in vain, i.e., does
not save the result. Here is another example:
The compiler issues an "expression has no effect" warning. The possibility to construct such
expressions is necessary because the object types, which we will learn in Part 3, allow for the operator
overloading, i.e., we can replace the usual meaning of operator symbols with some specific actions.
Then, if the type of i and j is not int, but some class with an overridden addition operation, such a
notation will have an effect, and the compiler will not issue a warning.
Simple statements can only be written inside compound statements. For example, calling the Print
function outside of a function will not work:
The most relevant, in this case, is the last one: "expressions are not allowed in the global context."
2.7 Statements
119
Part 2. Programming fundamentals
Repeat and select statements consist of a header (each with a different syntax) followed by a
controlled statement. If a managed part needs to specify multiple statements, it uses a compound
statement. This feature is not available for jump statements. They only move the internal pointer,
based on which the program determines which statement is currently to be executed, according to
special rules, which we will discuss in the following sections.
In the simplest case, without control statements, the statements are executed sequentially, one after
the other, as they are written in the code block (in particular, in the body of the main function OnStart
for scripts). If an expression with a call to another function is encountered in a code block, the
program, according to the same linear principle, begins to execute statements inside the called
function, and when they are all executed, it will return to the calling code block, and execution will
continue on the next statement after the function call. Control statements can significantly change this
logic of work.
You can use selection inside loops or vice versa, and the nesting level is unlimited. However, too much
nesting makes the program difficult to understand for the programmer. Therefore, it is recommended
to allocate (transfer) code blocks into functions (one or several): inside each function, it makes sense
to maintain a nesting level of no more than 2-3.
• for loop
• while loop
• do loop
All loops allow one or more statements to be executed a given number of times or until some boolean
condition is met. Executing the contents of a loop once is called an iteration. As a rule, arrays are
processed in loops or periodic repeating actions are performed (usually in scripts or services).
• selection with if
• selection with switch
The former allows you to specify one or more conditions, depending on the truth or falsity of which the
options assigned to them (one or more statements) will be executed. The latter evaluates an expression
of an integer type and selects one of several alternatives based on its value.
• break
• continue
• return
2.7 Statements
120
Part 2. Programming fundamentals
In the title, after the word 'for', the following is indicated in parentheses:
• Initialization: a statement for one-time initialization before the start of the loop;
• Condition: a boolean condition that is checked at the beginning of each iteration, and the loop runs
as long as it is true;
• Expression: formula of calculations performed at the end of each iteration, when all statements in
the loop body have been passed.
All three header components are optional and may be omitted in any combination, including their
absence.
Initialization may include the declaration of variables (along with setting initial values) or the
assignment of values to already existing variables. Such variables are called loop variables. If they are
declared in the header, then their scope and lifetime are limited to the loop.
The loop starts executing if, after initialization, the condition is true, and continues executing for as
long as it is true at the beginning of each subsequent iteration. If during the next check, the condition
is violated, the loop exits, i.e., control is transferred to the statement written after the loop and its
body. If the condition is false before the start of the loop (after initialization), it will never be executed.
The most common form of the for loop has a single loop variable that controls the number of iterations.
In the following example, we calculate the squares of the numbers in the a array.
Then everything repeats, starting from step 2. After exiting the loop, its variable i is destroyed, and an
attempt to access it will cause an error.
The expression for step 4 can be of arbitrary complexity, not just an increment of the loop variable. For
example, to iterate over even or odd elements, one could write i += 2.
2.7 Statements
121
Part 2. Programming fundamentals
Regardless of how many statements make up the body of the loop, it is recommended to write it on a
separate line (lines) from the header. This makes the step-by-step debugging process easier.
Initialization may include multiple variable declarations, but they must be of the same type because
they are one statement. For example, to rearrange elements in reverse order, you can write such a
loop (this is just a demonstration of the loop, there is a built-in function ArrayReverse to reverse the
order in an array, see Copying and editing arrays):
The auxiliary variable temp is created and deleted on each pass of the loop, but the compiler allocates
memory for it only once, as for all local variables, when entering the function. This optimization works
well for built-in types. However, if a custom class object is described in the loop, then its constructor
and destructor will be called at each iteration.
It is acceptable to change the loop variable in the loop body, but this technique is only used in very
exotic cases. It is not recommended to do this, as this may cause errors (in particular, processed
elements can be skipped or execution can get into an infinite loop).
To demonstrate the ability to omit header components, let's imagine the following problem: We need to
find the number of elements of the same array the sum of which is less than 100. To do this, we need a
counter variable k defined before the loop because it must continue to exist after its completion. We will
also create the sum variable to calculate the sum on a cumulative basis.
int k = 0, sum = 0;
for( ; sum < 100; )
{
sum += a[k++];
}
Thus, there is no need to do initialization in the header. In addition, the k counter is incremented using
a postfix increment directly in the expression that calculates the sum (when accessing an array
element). Therefore, we do not need an expression in the title.
At the end of the loop, we print out k and the sum minus the last added element, because it was the
one that exceeded our limit of 100.
Note that we are using a compound block even though there is only one statement in the loop body.
This is useful because when the program grows, everything is already done for adding additional
statements inside the brackets. In addition, this approach guarantees a uniform style for all loops. But
the choice, in any case, is up to the programmer.
In the explicit, maximally abbreviated version, the cycle header might look like this:
2.7 Statements
122
Part 2. Programming fundamentals
for( ; ; )
{
// ... // periodic actions
Sleep(1000); // pause the program for 1 second
}
If there are no statements in the body of such a loop that would interrupt the loop due to some
conditions, it will be executed indefinitely. We'll learn how to break and test conditions in Break jump
and If selection respectively.
Such looping algorithms are usually used in services (they are designed for constant background work)
to monitor the state of the terminal or external network resources. They usually contain statements
that pause the program at a specified interval, for example, using the built-in function Sleep. Without
this precaution, an infinite loop will load 100% of one processor core.
Script StmtLoopsFor.mq5 contains an infinite loop at the end, but it is for demonstration purposes only.
for( ; ; )
{
Comment(GetTickCount());
Sleep(1000); // 1000 ms
// the loop can be exited only by deleting the script at the user's command
// after 3 seconds of waiting we will get the message 'Abnormal termination'
}
Comment(""); // this line will never be executed
In the loop, once per second, the computer's internal timer (GetTickCount) is displayed using the
Comment function: the value is displayed in the upper left corner of the chart. Only the user can
interrupt the loop by deleting the entire script from the chart (the "Delete" button in the Experts
dialog). This code does not check for such user requests to stop inside the loop, although there is a
built-in function IsStopped for this purpose. It returns true if the user has given the command to stop.
In the program, especially if there are loops and long-term calculations, it is desirable to provide for
checking the value of this function and voluntarily terminate the loop and the entire program upon
receipt of true. Otherwise, the terminal will forcibly terminate the script after 3 seconds of waiting
(with output to the "Abnormal termination" log), which will happen in this example.
However, this loop would be better implemented using another repeat statement while. As a rule of
thumb, a for loop should only be used when there is an obvious loop variable and/or a predetermined
number of iterations. In this case, these conditions are not met.
Loop variables are usually integers, although other types are allowed, such as double. This is due to the
fact that the very logic of the loop operation implies the numbering of iterations. In addition, it is
always possible to calculate the necessary real numbers from an integer index, and with greater
accuracy. For example, the following loop iterates over values from 0.0 to 1.0 in increments of 0.01:
2.7 Statements
123
Part 2. Programming fundamentals
In the first case, when adding x += 0.01, the error of floating-point calculations gradually accumulates.
In the second case, each value x is obtained in one operation i * 0.01, with the maximum available
precision.
It is customary to give loop variables the following single-letter names, for example, i, j , k, m, p, q.
Multiple names are required when loops are nested or both forward (increasing) and backward
(decreasing) indexes are calculated within the same loop.
By the way, here is an example of a nested loop. The following code calculates and stores the
multiplication table in a two-dimensional array.
while ( condition )
loop body
The condition is an arbitrary expression of a boolean type. The presence of the condition is mandatory.
If the condition is false before the start of the loop, the loop will never execute.
Unlike C++, MQL5 does not support defining variables in the while loop header.
The while loop is usually used when the number of iterations is not defined. So, an example with the
loop that outputs a computer timer counter every second can be written using a while loop and
checking the stop flag (by calling the IsStopped function) as follows (StmtLoopsWhile.mq5):
2.7 Statements
124
Part 2. Programming fundamentals
while(!IsStopped())
{
Comment(GetTickCount());
Sleep(1000);
}
Comment("");
Also, the while loop is convenient when the loop termination condition can be combined with the
modification of variables in one expression. The next loop is executed until the variable i reaches zero (0
is treated as false).
int i = 5;
while(--i) // warning: expression not boolean
{
Print(i);
}
However, in this case, the header expression is not boolean (and is implicitly converted to false or true).
The compiler generates the relevant warning. It is desirable to always compose expressions taking into
account the expected (according to the rules) characteristics. Below is the correct loop version:
int i = 5;
while(--i > 0)
{
Print(i);
}
The loop can also be used with a simple statement (no block):
Note that a simple statement ends with a semicolon. It also demonstrates that changing the variable
being checked in the header is done inside the loop.
When working with loops, be careful when using unsigned integers. For example, the next loop will never
end, because its condition is always true (in theory, the compiler could issue warnings in such places,
but it does not). After zero, the counter will "turn" into a large positive number (UINT_MAX) and the
loop will continue.
uint i = 5;
while(--i >= 0)
{
Print(i);
}
From the user's point of view, the MQL program will freeze (stop responding to commands), although it
will still consume resources (processor and memory).
2.7 Statements
125
Part 2. Programming fundamentals
2.7.7 Do loop
This loop is similar to the while loop, but its condition is checked after the loop body. Due to this,
controlled statements must be executed at least once.
do
loop body
while ( condition ) ;
Thus, the loop header is separated, and after the logical condition in brackets, there should be a
semicolon. The condition cannot be omitted. When it becomes false, the loop exits.
The following example calculates a sequence of numbers starting from 1, in which each next number is
obtained by multiplying the previous one by the square root of two, the predefined constant M_SQRT2
(StmtLoopsDo.mq5).
double d = 1.0;
do
{
Print(d);
d *= M_SQRT2;
}
while(d < 100.0);
2.7.8 If selection
The if statement has several forms. In its simplest case, it executes the dependent statement if the
specified condition is true:
if ( condition )
statement
If the condition is false, the statement is skipped and the execution immediately jumps to the rest of
the algorithm (subsequent statements, if any).
The statement can be simple or compound. A condition is an expression of a boolean or castable type.
The second form allows you to specify two branches of actions: not only for the true condition
(statement_A) but also for the false (statement_B):
if ( condition )
statement_A
else
statement_B
Whichever of the controlled statements is executed, the algorithm will then continue following the
statements below the if/else statement.
2.7 Statements
126
Part 2. Programming fundamentals
For example, a script can follow a different strategy depending on the timeframe of the chart it is
placed on. For this purpose, it is enough to analyze the value returned by the Period built-in function.
The value is of the ENUM_TIMEFRAMES enum type. If it is less than PERIOD_D1, it means short-term
trading, otherwise, long-term trading (StmtSelectionIf.mq5).
As a statement in the else branch, it is allowed to specify the following operator if, and thus arrange
them into a chain of successive checks. For example, the following fragment counts the number of
capital letters and punctuation symbols (more precisely, non-Latin letters) in a string.
}
Print(capital, " ", punctuation);
The loop is organized through all the characters of the string (numbering starts from 0) and the
StringLen function returns the length of the string. The first if checks each character to see if it
belongs to the range 'A' to 'Z' and, if successful, increments the capital counter by 1. If the character
does not fall into this range, the second if is run, in which the condition for belonging to the range of
lowercase letters (s[i] >= 'a' && s[i] <= 'z') is inverted with '!'. In other words, the condition means that
the character is not in the given range. Given two consecutive checks, if the character is not an
uppercase letter (else) and not a lowercase letter (the second if), we can conclude that the character
is not a letter of the Latin alphabet. In this case, we increment the punctuation counter.
The same checks could be written in a more detailed form, with '{...}' blocks for clarity.
2.7 Statements
127
Part 2. Programming fundamentals
The use of curly brackets helps to avoid logical errors associated which can occur when the
programmer is only guided by indentation in the code. In particular, the most common problem is
called the "hanging" else.
When if statements are nested, sometimes there are fewer else branches than if. Here is one example:
factor = 0.0;
if(mode > 10)
if(mode > 20)
factor = +1.0;
else
factor = -1.0;
The indentation indicates what kind of logic the programmer meant: factor should become +1 when
mode is greater than 20, remain equal to 0 when mode is between 10 and 20, and change to -1
otherwise (mode <= 10). But will the code work that way?
In MQL5, each else is assumed to refer to the nearest previous if (which does not have a else). As a
result, the compiler will treat the statements as follows:
factor = 0.0;
if(mode > 10)
if(mode > 20)
factor = +1.0;
else
factor = -1.0;
So the factor will be -1 in the mode range from 10 to 20, and 0 for mode <= 10. The most interesting
thing is that the program does not produce any formal errors, neither during compilation nor during
execution. And yet it doesn't work correctly.
To eliminate such subtle logical problems allows the placement of curly brackets.
2.7 Statements
128
Part 2. Programming fundamentals
To keep the design consistent, it is desirable to use blocks in all branches of the statement if at least
one block has already been required in it.
When using the loop to check equality, take into account the possibility of a typo when one '=' is
written instead of two characters '=='. This turns the comparison into an assignment, and the assigned
value is analyzed as a logical condition. For example,
// should have been x == y + 1, which would give false and skip the if
if(x = y + 1) // warning: expression not boolean
{
// assigned x = 5 and treated x as true, so if is executed
}
switch ( expression )
{
case constant-expression : statements [break; ]
...
[ default : statements ]
}
The statement header starts with the keyword switch. It must be followed by an expression in
parentheses. The block with curly brackets is also required.
Integer values that can be obtained by evaluating an expression should be specified as constants after
the case keyword. A constant is a literal of any integer types, for example, int (10, 123), ushort
(characters 'A', 's', '*' etc.), or enum elements. Real numbers, variables, or expressions are not
allowed here.
There may be many such case options, or may not be at all, which is indicated by semicircular brackets
with index opt(n). All variants must have unique constants (no repetitions).
For each alternative declared with case, a statement must be written after the colon, which will be
executed if the value of the expression is equal to the corresponding constant. Again, a statement can
2.7 Statements
129
Part 2. Programming fundamentals
One or more of these statements can be followed by the break jump statement.
If there is a break, after executing the previous statements from the case branch, the switch statement
exits, i.e., control is transferred to the statements below switch.
In the absence of break, the statements of the next branch or several branches case continue to be
executed, that is, until the first encountered break or the end of the block switch. This is called "fall-
through".
Thus, the switch statement not only allows splitting the algorithm execution flow into several
alternatives but also combining them, which is not available for the if operator. On the other hand, in
the switch statement, unlike if, you cannot select a range of values as a condition for activating
alternatives.
The default keyword allows you to set the default algorithm variant, that is, for any other expression
values except for constants from all cases. The default option may not be present, or there must be
only one.
The sequence in which case constants and default are listed can be arbitrary.
Even if there is no algorithm for the default branch yet, it is recommended to make it explicitly empty,
i.e. containing break. An empty default will remind you and other programmers that other options exist
but are considered unimportant because otherwise, the default branch would have to signal an error.
Several case variants with different constants can be listed one below the other (or left to right) without
statements, but the last one must have a statement. Such combined cases are indicated on the
diagram by the index (i).
switch(0)
{
}
Let's consider a more complex example with different modes (StmtSelectionSwitch.mq5). In it, the
switch operator is placed inside the loop to show how its work depends on the values of the control
variable i.
2.7 Statements
130
Part 2. Programming fundamentals
switch(i)
{
case -1:
Print("-1: Never hit");
break;
case 1:
Print("Case 1");
factor = 1.5;
break;
case 2: // fall-through, no break (!)
Print("Case 2");
factor *= 2;
case 3: // same statements for 3 and 4
case 4:
Print("Case 3 & 4");
{
double local_var = i * i;
factor *= local_var;
}
break;
case 5:
Print("Case 5");
factor = 100;
break;
default:
Print("Default: ", i);
}
Print(factor);
}
The -1 option will fail because the loop changes the variable i from 0 to 6 (inclusive). When i is 0, the
default branch will trigger. It will also take control when i is equal to 6. All other possible i values are
distributed according to the corresponding case directives. At the same time, there is no break
statement after case 2, and therefore the code for options 3 and 4 will be executed in addition to 2 (in
such cases, it is always recommended to leave a comment that this was done intentionally).
Cases 3 and 4 have a common statement block. But it is also important to note here that if you want
to declare a local variable inside one of the case options, you need to enclose the statements in a
nested compound block ('{...}'). Here, the variable local_ varis defined this way.
It is worth advising that in the default case, there is no break statement. It's redundant because
default is written last in this case. However, many programmers advise inserting break at the end of
any option, even the last one, because it can cease to be the last in the process of subsequent
modifications of the code, and then it is easy to forget to add break, which will probably lead to an
error in the program logic.
2.7 Statements
131
Part 2. Programming fundamentals
If in switch there is no default, and the header expression does not match any of the case constants,
the entire switch is skipped.
As a result of the script execution, we will receive the following messages in the log:
Default: 0
1.0
Case 1
1.5
Case 2
Case 3 & 4
8.0
Case 3 & 4
9.0
Case 3 & 4
16.0
Case 5
100.0
Default: 6
1.0
break ;
When used inside loops, break is usually implemented in one of the branches of the if/else conditional
operator.
Consider a script that prints the current system time counter once per second, but no more than 100
times. It provides for handling the interruption of the process by the user: for this, the function
IsStopped is polled in the conditional operator if and its dependent statement contains break
(StmtJumpBreak.mq5).
2.7 Statements
132
Part 2. Programming fundamentals
int count = 0;
while(++count < 100)
{
Comment(GetTickCount());
Sleep(1000);
if(IsStopped())
{
Print("Terminated by user");
break;
}
}
In the following example, a diagonal matrix is filled in with a times table (the top right corner will remain
filled with zeros).
When the inner loop variable j is greater than the outer loop variable i, the break statement breaks the
inner loop. Of course, this is not the best way to fill the matrix diagonally: it would be easier to loop
over j from 0 to i without any break, but here it demonstrates the presence of equivalent constructions
with break and without break.
Although things may not be so obvious in production projects, it is recommended to avoid the break
operator whenever possible and replace it with additional variables (for example, a boolean variable with
a "telling" name needAbreak), which should be used in terminal expressions in loop headers to break
them in the standard way.
Imagine that two nested loops are used to find duplicate characters in a string. The first loop
sequentially makes each character of the string current and the second runs through the remaining (to
the right) characters.
2.7 Statements
133
Part 2. Programming fundamentals
If the characters at positions i and j match, remember the duplicate character and exit the loop via
break.
It could be assumed that the variable d should contain the letter 'l' after the execution of this
fragment. However, if you place the script on the most popular instrument "EURUSD", the answer will
be 'U'. The thing is that break breaks only the inner loop, and after finding the first duplicate ('ll' in the
word "Hello"), the loop continues on i. Therefore, to exit from several nested loops at once, additional
measures must be taken.
The most popular way is to include in the condition of the outer loop (or all outer loops) a variable that
is filled in the inner loop. In our case, there is already such a variable: d.
Checking d for being equal to 0 will now stop the outer loop after finding the first duplicate. But the
same check can be added to the inner loop, which eliminates the need to use break.
2.7 Statements
134
Part 2. Programming fundamentals
The continue statement breaks the current iteration of the innermost loop containing continue and
initiates the next iteration. The statement can only be used inside for, while and do loops. Execution of
continue inside for results in the next calculation of the expression in the loop header
(increment/decrement of the loop variable), after which the loop continuation condition is checked.
Executing continue inside while or do immediately results in checking the condition in the loop header.
continue ;
It is usually placed in one of the branches of the if/else or switch conditional statement.
For example, we can generate a times table with gaps: when the product of two indexes is odd, the
corresponding array element will remain zero (StmtJumpContinue.mq5).
And here's how you can calculate the sum of the positive elements of an array.
Note that the same loop can be rewritten without continue but with a greater nesting of code blocks:
Thus, operator continue is often used to simplify code formatting (especially if there are several
conditions to pass). However, which of the two approaches to choose is a matter of personal
preference.
2.7 Statements
135
Part 2. Programming fundamentals
The return operator is designed to return control from functions. Given that all executable statements
are inside a particular function, it can be indirectly used to interrupt containing it loops for, while, and
do of any nesting level. It should be taken into account that unlike continue and, especially, break, all
statements following interrupted loops inside the function will also be ignored.
return ([expression]) ;
The need to specify an expression is determined by the function signature (more on this will be
discussed in the relevant section). For a general understanding of how return works in the context of
control statements, let's view an example with the main script function OnStart. Since it is of type void,
i.e. it does not return anything, the operator takes the following form:
return ;
In the section on break, we implemented an algorithm for finding duplicate characters in a string. To
break two nested loops, we not only use break but also modify the condition of the outer loop.
With the return operator, this can be done in a simpler way (StmtJumpReturn.mq5).
void OnStart()
{
string s = "Hello, " + Symbol();
const int n = StringLen(s);
for(int i = 0; i < n; ++i)
{
for(int j = i + 1; j < n; ++j)
{
if(s[i] == s[j])
{
PrintFormat("Duplicate: %c", s[i]);
return;
}
}
}
Print("No duplicates");
}
If equality is found in the if operator, we display the symbol and exit the function. If this algorithm was
in a custom function other than OnStart, we could define a return type for it (for example, ushort
instead of void) and pass the found character using the full form return to the calling code.
Since the double letter 'l' is known to exist in the test string, the statement after the loops (Print) will
not be executed.
2.7 Statements
136
Part 2. Programming fundamentals
An empty statement is used in the program in those places where the syntax requires the presence of a
statement, but the logic of the algorithm instructs to do nothing.
For example, the following while loop is used to find a space in a string. The whole essence of the
algorithm is performed directly in the loop header, so its body must be empty. We could write an empty
block of curly brackets, but an empty statement would also work here. (StmtNull.mq5).
int i = 0;
ushort c;
string s = "Hello, " + Symbol();
while((c = s[i++]) != ' ' && c != 0); // intentional ';' (!)
if(c == ' ')
{
Print("Space found at: ", i);
}
Note that if the semicolon at the end of the while header is omitted (perhaps by accident), then the if
statement will be treated as the body of the loop. As a result, there will be no output to the log by the
Print function. In fact, the program will not work correctly, although without noticeable errors.
The opposite situation is also possible: an extra semicolon after the loop header (where it should not
have been) will "detach" the loop body from the header, i.e. only an empty statement will be executed
in the loop.
In this regard, optional semicolons should be checked in the code, and wherever they are placed
intentionally, leave a comment with explanations.
By the way, from a formal point of view, the empty statement is also used in the for statement when
we omit the initialization expression. In fact, there is always initialization:
The first character ';' is part of an initialization statement, which can be an expression or an empty
statement: both contain the character ';' at the end, with the latter containing nothing but ';'. Thus,
optionality (emptiness) is achieved.
2.8 Functions
A function is a named block with statements. Almost the entire application algorithm of the program is
contained in functions. Outside of functions, only auxiliary operations are performed, such as creating
and deleting global variables.
The execution of statements within a function occurs when we call that function. Some functions, the
main ones, are called automatically by the terminal when various events occur. They are also referred
to as the MQL program entry points or event handlers. In particular, we already know that when we run
a script on a chart, the terminal calls its main function OnStart. In other types of programs, there are
other functions called by the terminal, which we will discuss in detail in the fifth and sixth chapters
covering the trading architecture of the MQL5 API.
In this chapter, we will learn how to define and declare a function, how to describe and pass
parameters to it, and how to return the result of its work from the function.
2.7 Statements
137
Part 2. Programming fundamentals
We will also talk about function overloading, i.e., the ability to provide multiple functions with the same
name, and how this can be useful.
It is allowed to create functions without parameters: then there is no list, and empty brackets are
placed after the function name (they cannot be omitted). Optionally, you can write the void keyword
between the brackets to emphasize that there are no parameters. For example, like this:
void OnStart(void)
{
}
The combination of return type, number and types of parameters in the list is called a function
prototype or signature. Different functions can have the same prototype.
In previous sections, we have already seen function definitions such as OnStart and Greeting. Now let's
try to implement the calculation of Fibonacci numbers as a test function. These numbers are calculated
by the following formula:
f[0] = 1
f[1] = 1
f[i] = f[i - 1] + f[i - 2], i > 1
The first two numbers are 1, and all subsequent numbers are the sum of the previous two. We give the
beginning of the series: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55...
You can calculate the number at a given index using the following function (FuncFibo.mq5).
2.8 Functions
138
Part 2. Programming fundamentals
It takes one parameter n of type int and returns a result of type int. The n parameter has the const
modifier because we are not going to change n inside the function (such an explicit declaration of
restrictions on the "rights" of variables is welcome because it helps avoid random errors).
Local variables prev and result will store the current values of the last two numbers in the series. In the
loop over i we calculate their sum, getting the next number of the sequence. Previously, the old value
result is written to the variable temp, so that after summation, it is transferred to prev.
After executing the loop a given number of times, the result variable contains the desired number. We
return it from the function using the result statement.
The input parameter of a function is also a local variable that will be initialized to the actual value
during the function call. This value is passed "outside" from the statement with the function call.
Parameter names must be unique and must not match local variable names.
The body of a function is a block of code that defines the scope and lifetime of local variables. Their
definition and operation principles were discussed in the sections Declaration/definition statements and
Initialization.
A little later, we will look at the function pointer type, which allows you to create variables that point to
a function with specific characteristics, and then call it not by name, but through this variable.
Continuing the example with the Fibo function, let's call it from the OnStart function. To do this, let's
create a variable f to store the resulting number and in its initialization expression we indicate the name
of the function Fibo and an integer (for example, 10) as an argument, in parentheses.
void OnStart()
{
int f = Fibo(10);
Print(f); // 89
}
We are not required to create a variable to receive a value from a function. Instead, you can call the
function directly from an expression, such as "2*Fibo(10)" or "Print(Fibo(10))". Then its value will be
2.8 Functions
139
Part 2. Programming fundamentals
substituted into the expression at the place of the call. Here, the auxiliary variable f is introduced to
implement the call and return of a value in a separate statement.
For each function call, the compiler generates auxiliary binary code (the programmer does not need to
worry about it). The idea of this code is that before calling the function, it pushes the current position
in the program onto the stack, and after the call is completed, it retrieves it and uses it to return to the
statements following the function call. When one function calls another, that one calls one more
function, the second calls a third, and so on, the return addresses of transitions throughout the
hierarchy of called functions are accumulated on the stack (hence the name stack). As nested function
calls are processed, the stack will be cleared in reverse order. Note that the stack also allocates
memory for the local variables of each function.
Each argument is mapped to the corresponding parameter in the same way that variables are
initialized, with implicit casts if necessary. Before the function starts, all its parameters are guaranteed
to have the specified values. For example, depending on the arguments passed, calls to the Fibo
function can lead to the following effects (described in the comments):
// warnings
double d = 5.5;
Fibo(d); // possible loss of data due to type conversion
Fibo(5.5); // truncation of constant value
Fibo("10"); // implicit conversion from 'string' to 'number'
// errors
Fibo(); // wrong parameters count
Fibo(0, 10); // wrong parameters count
All warnings are about implicit conversions that the compiler performs because the value types do not
match the parameter types. They should be regarded as potential errors and eliminated. The "wrong
parameters count" error occurs when there are too few or too many arguments.
2.8 Functions
140
Part 2. Programming fundamentals
In theory, a function parameter does not have to have a name, i.e. the type alone is sufficient to
describe the parameter. This sounds rather strange because we will not be able to access a parameter
without a name inside the function. However, when creating programs based on some standard
interfaces, sometimes you have to write functions that must correspond to given prototypes. In this
case, some parameters inside the function may be unnecessary. Then, to explicitly indicate this fact,
the programmer can omit their names. For example, the MQL5 API requires the implementation of the
OnDeinit event handler function with the following prototype:
If we don't need the reason parameter in the function code, we can omit it in the description:
The terminal event handling function is usually called by the terminal itself, but if we needed to call a
similar function (with an anonymous parameter) from our code, then we need to pass all the
arguments, regardless of whether the parameters are named or not.
All the cases we've looked at so far are passing by value. This option means that the value of the
argument prepared by the calling code snippet is copied into a new variable, the corresponding input
variable of the function. Otherwise, the argument and input variable are unrelated. All subsequent
manipulations with the variable inside the function do not affect the argument in any way.
To describe a reference parameter, add an ampersand sign '&' on the right of the type. Many
programmers prefer to append an ampersand to a parameter name, thus emphasizing that the
parameter is a reference to the given type. For example, the following entries are equivalent:
When a function is called, a corresponding local variable is not created for a reference parameter.
Instead, the argument specified for this parameter becomes available inside the function under the
name (alias) of the input parameter. Thus, the value is not copied, but used at the same address in
memory. Therefore, modifications to a parameter within a function are reflected in the state of its
associated argument. An important feature follows from this.
You can only specify a variable (LValue, see Assignment operator) as an argument for a reference
parameter. Otherwise, we'll get the "parameter passed as reference, variable expected" error.
• to improve the efficiency of the program by eliminating the copying of the value;
• to pass modified data from a function to the calling code when returning a single value with return
is not enough;
The first point is especially relevant for potentially large variables such as strings or arrays.
To distinguish between the first and second purposes of a reference parameter, the authors of the
function are encouraged to add the const modifier when the parameter inside the function is not
2.8 Functions
141
Part 2. Programming fundamentals
expected to change. This will remind you and make it clear to other developers that passing a variable
inside a function will not lead to side effects.
Not applying the const modifier to reference parameters where possible can lead to problems
throughout the entire function call hierarchy. The fact is that calling such functions will require non-
constant arguments. Otherwise, the error "constant variable cannot be passed as reference" will occur.
As a result, it may gradually turn out that all parameters in all functions should be stripped of the const
modifier for the sake of the code compilability. In fact, this actually expands the scope for potential
bugs with unintentional corruption of variables. The situation should be corrected in the opposite way:
put const wherever return and modification of values are not required.
To compare the ways of passing parameters in the FuncDeclaration.mq5 script, several functions are
implemented: FuncByValue — passing by value, FuncByReference — passing by reference,
FuncByConstReference — passing by constant reference.
void FuncByValue(int v)
{
++v;
// we are doing something else with v
}
In the OnStart function, we call all these functions and observe their effect on i variable used as an
argument. Note that passing a parameter by reference does not change the function call syntax.
2.8 Functions
142
Part 2. Programming fundamentals
void OnStart()
{
int i = 0;
FuncByValue(i); // i cannot change
Print(i); // 0
FuncByReference(i); // i is changing
Print(i); // 1
FuncByConstReference(i); // i cannot change, 1
const int j = 1;
// error
// 'j' - constant variable cannot be passed as a reference
// FuncByReference(j);
FuncByValue(10); // ok
// error: '10' - parameter passed as reference, variable expected
// FuncByReference(10);
}
The literal can only be passed to FuncByValue function, since other functions require a reference, i.e. a
variable, as an argument.
Function FuncByReference cannot be called with the variable j , since the latter is declared as a
constant, and this function declares the ability (or intention) to change its parameter since it is not
equipped with the const modifier. This generates the "constant variable cannot be passed as reference"
error.
The script also describes the Transpose function: it transposes a 2x2 matrix passed as a two-
dimensional array by reference.
Its call from OnStart demonstrates the expected change in the contents of the local array a.
In MQL5, array parameters are always passed as an internal structure of a dynamic array (see the
Characteristics of arrays section). As a consequence, the description of such a parameter must
necessarily have an open size in the first dimension, that is, it is empty inside the first pair of square
brackets.
This does not prevent, if necessary, passing to the function the actual argument, which is an array with
a fixed size (as in our example). However, functions like ArrayResize will not be able to resize or
otherwise reorganize such a masked fixed array.
The sizes of the array in all dimensions except the first must match for both, the parameter and
argument. Otherwise, we will get a "parameter conversion not allowed" error. In particular, the
TransposeVectorfunction is defined in the example:
2.8 Functions
143
Part 2. Programming fundamentals
In addition to passing parameters by value or by reference, there is another option: passing a pointer.
Unlike C++, MQL5 only supports pointers for object types (classes). We will look at this feature in the
third Part.
When calling a function, arguments for such parameters can be omitted. Then their values will be set to
their default values. Such parameters are called optional (optional).
Optional parameters must appear at the end of the parameter list. In other words, if the i-th parameter
is declared with initialization, then all subsequent parameters must also have it. Otherwise, a
compilation error "missing default value for parameter" is shown. Below is a description of a function
with such a problem.
There are two solutions: either the parameter v3 must also have a default value, or the parameter v2
must become mandatory.
You can only omit optional arguments when calling a function from right to left. That is, if the function
has two parameters and both are optional, then when calling, you cannot skip the first one, but specify
the second one. The single value passed will be matched against the first parameter, and the second
will be considered omitted. If both arguments are missing, the empty parentheses are still needed.
Consider the function of finding the maximum number of three. The first parameter is mandatory, the
last two are optional and equal by default to the minimum possible number of type double. Thus, each
of them, in the absence of an explicitly passed value, will certainly be less than (or, in extreme cases,
equal to) all other parameters.
2.8 Functions
144
Part 2. Programming fundamentals
Print(Largest(1)); // ok: 1
Print(Largest(0, -2)); // ok: 0
Print(Largest(1, 2, 3)); // ok: 3
With the help of optional parameters, MQL5 implements the concept of functions with a variable
number of parameters in custom functions.
MQL5 does not support the ellipsis syntax for defining functions with a variable number of parameters,
as C++ does. At the same time, there are built-in functions in the MQL5 API, which are described using
ellipsis and accept a variable number of arbitrary parameters. For example, it is the Print function. Its
prototype looks like this: void Print(argument, ...). Therefore, we can call it with up to 64 arguments
separated by commas (excluding arrays) and it will display them in the log.
To return from an array function, you must use parameters passed by reference (see Value parameters
and reference parameters).
A value is returned using the return statement, in which an expression is specified after the return
keyword. Any of the two forms may be used:
return expression ;
or:
return ( expression ) ;
return ;
The return statement cannot contain any expression inside the void-function: the compiler will generate
an error "'return' - 'void' function returns a value".
For such functions, theoretically, it is not necessary to use return at the end of the block with the
function body. We saw this in the example of the OnStart function.
If the function has a type other than void, then the return statement must be mandatory. If it is not
present, a compilation error "not all control paths return a value" will occur.
int func(void)
{
if(IsStopped()) return; // error: function must return a value
// error: not all control paths return a value
}
It is important to note that a function body can have multiple return statements. In particular, in case
of early exits by condition. Any return statement breaks the execution of the function at the place
where it is located.
2.8 Functions
145
Part 2. Programming fundamentals
If a function must return a value (because it is not of type void), and it is not specified in the return
operator, the compiler will generate an error "function must return a value". The compiler-correct
version of the func function is given below (FuncReturn.mq5).
int func(void)
{
if(IsStopped()) return 0;
return 1;
}
If the return value differs from the specified function type, the compiler will attempt an implicit
conversion. In case the types require explicit conversion, an error will be generated.
To return a value, a temporary variable is implicitly created and made available to the calling code.
After we learn about object types (see the chapter on Classes) and the ability to return pointers to
objects from functions, we'll get back to considering how to pass them safely. Unlike C++, functions in
MQL5 are not capable of returning references. Attempting to declare a function with an ampersand in
the result type results in a "'&' - reference cannot used" error.
The declaration is necessary for the compiler so that it can check in subsequent code fragments how
correctly the function is called by name, passing arguments to it and getting the result.
The entire function definition (including the body) is also a declaration, so there is no need to declare a
function in addition to the definition.
For example, the declaration of the Fibo function above could look like this.
Separate function declarations and definitions are used when building a program from several files with
source text: then the declaration is made in the header file with the extension mqh (see the section
about the #include preprocessor directive ), which is included in files where the function is used, and
the function definition is implemented in only one of the files. Matching of the function signature in the
declaration and definition provides error protection. In other words, a single declaration guarantees the
consistency of changes made to the entire source code
If we declare a function and call it somewhere in the code, but do not provide a fully appropriate
definition for it, the compiler will throw an error: "function 'Name' must have a body". This often
happens when there are typos or inaccuracies either in the declaration or in the definition, as well as in
the process of changing the source codes, when some of the corrections have already been made, and
the other part has most likely been forgotten.
If the function is declared and not used anywhere, the compiler does not require its definition either -
such an element is simply "cut out" from the binary program.
In the Declaration/definition statements section, we considered an example of the Init function (script
StmtDeclaration.mq5), which was used to initialize variables. There, in particular, the problem was
2.8 Functions
146
Part 2. Programming fundamentals
demonstrated that the global variable k cannot be defined before the Init function, since the initial
value k is obtained by calling Init. The compiler through the error "'Init' is an unknown identifier".
Now we know that such a problem can be solved with a declaration. In the FuncDeclaration.mq5 script,
we added the following forward declaration of the Init function before the k variable, and left the Init
definition after k.
// preliminary declaration
int Init(const int v);
// before adding preliminary declaration above
// here was an error: 'Init' is an unknown identifier
int k = Init(-1);
int Init(const int v)
{
Print("Init: ", v);
return v;
}
Now the script compiles normally. Technically, in this case, we could simply move the function above
the variable without a preliminary declaration. We did this to explain the concept. However, there are
cases of mutual dependence of language elements on each other (for example, classes), when it is
impossible to go without a preliminary declaration within the same file.
2.8.8 Recursion
It is allowed to call the same function from statements inside a function. Such calls are called
recursive.
Let's go back to the example of calculating Fibonacci numbers. Following the formula for calculating
each number as the sum of the previous two (except for the first two, which are equal to 1), it is easy
to write a recursive function for calculating Fibonacci numbers.
A recursive function must be able to return control without recursion, as in our case inside the
conditional statement if for indexes 0 and 1. Otherwise, the sequence of function calls could continue
indefinitely. In practice, because unfinished function calls accumulate in a limited area of memory
called the stack (see the Declaration/Definition statements section, and the "Heap" and "Stack"
sidebar in the Describing arrays section), sooner or later the function will terminate with the "Stack
overflow" runtime error. This problem is shown in the FiboEndless function.
2.8 Functions
147
Part 2. Programming fundamentals
Please note that this is not a compilation error. In such a case, the compiler will not even generate a
warning (although, technically it could). The error occurs during script execution. It will be printed to
the Experts journal in the terminal.
Recursion can occur not only when a function is called from the function itself. For example, if the F
function calls the G function which, in turn, calls the F function, this case is an indirect recursion. Thus,
recursion can occur as a result of cyclic calls of any depth.
Functions cannot differ only in their return type. In this case. the overload mechanism is not triggered
and the "function already defined and has different type" error is returned.
If functions of the same name have different numbers of parameters and the "extra" parameters are
declared optional, then the compiler will not be able to determine which one to call. This will generate
the error "ambiguous call to overloaded function with the same parameters".
When an overloaded function is called, the compiler matches the arguments and parameters in the
available overloads. If no exact match is found, the compiler tries to add/remove the const modifier and
to perform numeric type expansion and arithmetic conversion. In the case of object pointers, class
inheritance rules are used.
With a different number of parameters or unrelated parameter types in the same position (such as a
number and a string), the choice is usually clear. However, if the parameter types are to be implicitly
converted from one to another, ambiguity may arise.
2.8 Functions
148
Part 2. Programming fundamentals
Here, the compiler is equally uncomfortable with each of the overloads: for the function double
sum(double v1, double v2) it is necessary to implicitly convert the first argument to double, and for int
sum(int v1, int v2) the second argument in int needs to be converted.
The term 'overload' should be interpreted in the sense that a reused name is "loaded" with "duties"
several times heavier than a regular name used only for one function.
Let's try to overload the function for matrix transposition. We already had an example for a 2x2 array
(see Value parameters and reference parameters). Let's implement the same operation for a 3x3
array. The size of a multidimensional array parameter in higher dimensions (non-zero) changes the
type, i.e. double [][2] is different from double [][3]. Thus, we will overload the old version of the
function:
In the implementation of the new version, it is convenient to use the helper function Swap to exchange
two matrix elements at given indices.
temp = m[i][j];
m[i][j] = m[j][i];
m[j][i] = temp;
}
Now we can call both functions from OnStart using the same notation for arrays of different sizes. The
compiler itself will generate a call to the correct versions.
It is important to note that the const modifier on the parameter, although it changes the prototype of
the function, is not always a sufficient difference for overloading. Two functions of the same name,
which differ only in the presence and absence of const for some parameter, can be considered the
same. This will result in a "function already defined and has body" error. This behavior occurs because,
for value parameters, the const modifier is discarded when the argument is assigned (because a value
2.8 Functions
149
Part 2. Programming fundamentals
parameter, by definition, cannot change the argument in the calling code), and this does not allow one
of several overlapped functions to be selected based on it.
The only difference between the two functions is the const modifiers for the i and j parameters.
Therefore, they are both suitable for calling with arguments of type int and passing by value.
When parameters are passed by reference, overloading with a difference of only const/non-const
attributes succeeds because, for references, the const modifier is important (it changes the type and
eliminates the possibility of implicit conversion). This is demonstrated in the script with a couple of
functions:
void OnStart()
{
// ...
{
int i = 0, j = 1;
SwapByReference(b, i, j);
}
{
const int i = 0, j = 1;
SwapByReference(b, i, j);
}
}
They are left as almost empty stubs, in which the signature of each function is printed using the
Print(_ _ FUNCSI_ _ )call. This makes it possible to ensure that the appropriate version of the function is
called depending on the const attribute of the arguments.
2.8 Functions
150
Part 2. Programming fundamentals
The function_ type identifier defines a type name that becomes a synonym (alias) for a pointer to any
function that returns a value of the given type function_ result_ type and accepts a list of input
parameters (list_ of_ input_ parameters).
For example, we can have 2 functions with the same prototypes (two input parameters of type double
and the result type is also double) that perform different arithmetic operations: addition and
subtraction (FuncTypedef.mq5).
This entry introduces the Calc type into the program, with which you can define a variable/parameter
for storing/passing a reference to any function with such a prototype, including both functions plus and
minus. This type is a pointer because the character '*' (*Calc) is used in the description. We will learn
more about the features of the asterisk as applied to pointers when studying OOP.
It is convenient to use such a class of pointers to create custom algorithms that can "on the fly" call
different functions corresponding to the alias, depending on the input data.
Its first parameter is declared with the Calc type. Thanks to this, we can pass an arbitrary function with
a suitable prototype to it and, as a result, perform some operation, the essence of which the calculator
function itself does not know about. It does this by delegating the call to a pointer: ptr(v1, v2). Because
ptr is a function pointer, this syntax not only resembles a function call but actually calls the function
that the pointer holds.
Note that we pre-check the ptr parameter against the special value NULL (NULL is the equivalent of
zero for pointers). The fact is that the pointer may not point anywhere, that is, it may not be initialized.
So, in the script, we have a global variable described:
Calc calc;
It has no pointers. If it weren't for the "protection" against NULL, calling calculator with an "empty"
pointer calc would result in a run-time error "Invalid function pointer call ".
2.8 Functions
151
Part 2. Programming fundamentals
Calls to the calculator function with different pointers in the first parameter will give the following
results (shown in the comments):
void OnStart()
{
Print(calculator(plus, 1, 2)); // 3
Print(calculator(minus, 1, 2)); // -1
Print(calculator(calc, 1, 2)); // 0
}
Note that if there is no explicit initialization, all function pointers are filled with zero values. This applies
to both global and local variables of a given type.
A pointer type defined with typedef can be returned from functions, for example:
In addition, the type of function pointers is often used for callback functions (callback, see
FuncCallback.mq5). Suppose we have a DoMath function that performs lengthy calculations (probably, it
is implemented in a separate library). In terms of user interface convenience and friendliness, it would
be great to show the user a progress indication. For this purpose, you can define a special type of
function pointer for notifications about the percentage of work completed (ProgressCallback), and add a
parameter of this type to the DoMath function. In the DoMath code, you should periodically call the
passed function:
// long calculations
}
}
Then the calling code can define the required callback function, pass a pointer to it to DoMath and
receive updates as the calculation progresses.
2.8 Functions
152
Part 2. Programming fundamentals
void OnStart()
{
double data[] = {0};
DoMath(data, MyCallback);
}
Function pointers work only with custom functions defined in MQL5. They cannot point to built-in
functions of the MQL5 API.
2.8.11 Inlining
In order to improve code efficiency, modern compilers often use the following trick. When generating
executable code, some function calls are replaced directly by the function body (its statements). This
technique is called inlining. This speeds up the operation by avoiding the overhead associated with the
organization of the call and return from the function. From a programmer's point of view, inlining
doesn't change anything.
MQL5 supports inlining by default. If necessary, it can be disabled, but only in code profiling mode. The
inline keyword is reserved in MQL5 for compatibility with C++ source codes. Its presence or absence
before the function definition does not affect the generated program.
2.9 Preprocessor
Up to this moment, we have been studying MQL5 programming, assuming that source codes are
processed by the compiler, which converts their textual representation into binary (executable by the
terminal). However, the first tool that reads and, if necessary, converts source codes is the
preprocessor. This utility built into MetaEditor is controlled by special directives inserted directly into
the source code. It can solve a number of problems that programmers face when preparing source
codes.
Similarly to the C++ preprocessor, MQL5 supports the definition of macro substitutions (#define),
conditional compilation (#ifdef) and inclusion of other source files (#include ). In this chapter, we will
explore these possibilities. Some of them have limitations compared to C++.
In addition to the standard directives, the MQL5 preprocessor has its own specific ones, in particular, a
set of MQL program properties (#property), and functions import from separate EX5 and DLLs
(#import). We will address them in the fifth, sixth and seventh parts when studying various types of
MQL programs.
All preprocessor directives begin with a hash sign '#' followed by a keyword and additional parameters,
the syntax of which depends on the type of directive.
It is recommended to start a preprocessor directive from the very beginning of the line, or at least
after a whitespace indent (if the directives are nested). Inserting a directive inside source code
statements is considered a bad programming style (unlike MQL5, the C++ preprocessor does not allow
this at all).
2.8 Functions
153
Part 2. Programming fundamentals
Preprocessor directives are not language statements and should not be terminated with a ';'. Directives
usually continue to the end of the current line. In some cases, they can be extended in a special way
for the following lines, which will be discussed separately.
The directives are executed sequentially, in the same order in which they occur in the text and taking
into account the processing of previous directives. For example, if another file is connected to a file
using the #include directive and a substitution rule is defined in the included file using #define, then this
rule starts working for all subsequent lines of code, including the header files included later.
Splitting source code into multiple files is a common practice when writing complex programs. Such
programs are built on a modular basis so that each module/file contains logically related code that
solves one or more related tasks.
Include files are also used to distribute libraries (sets of ready-made algorithms). The same library can
be included in different programs. In this case, the library update (the update of its header file) will be
automatically applied in all programs during their next compilation.
If the main files of MQL programs must have the mq5 extension, then the include files commonly have
the extension mqh ('h' at the end of the word means "header"). At the same time, it is permissible to
use the #include directive for other types of text files, for example, *.txt (see below). In any case, when
a file is included, the final program combined from the main mq5 file and all headers must still be
syntactically correct. For example, including a file with binary information (like a png image) will break
the compilation.
#include <file_name>
#include "file_name"
In the first one, the file name is enclosed in angle brackets. The compiler searches for such files in the
terminal data directory in the MQL5/Include/ subfolder.
For the second one, with the name in quotes, the search is performed in the same directory which
contains the current file that uses the #include statement.
In both cases, the file can be located in subfolders within the search directory. In this case, you should
specify the entire relative hierarchy of folders before the file name in the directive. For example, along
with MetaTrader 5, there are many commonly used boot files, among which is DateTime.mqh with a set
of methods for working with date and time (they are designed as structures, the language constructs
that we will discuss in Part 3 devoted to OOP). The DateTime.mqh file is located in the Tools folder. To
include it in your source code, you should use the following directive:
#include <Tools/DateTime.mqh>
To demonstrate how to include a header file from the same folder as the source file with the directive,
let's consider the file Preprocessor.mq5. It contains the following directive:
2.9 Preprocessor
154
Part 2. Programming fundamentals
#include "Preprocessor.mqh"
An include file can, in turn, include other files. In particular, inside Preprocessor.mqh there is the
following code:
double array[] =
{
#include "Preprocessor.txt"
};
It means that the contents of the array are initialized from the given text file. If we look inside
Preprocessor.txt, we will see the text that complies with the array initialization syntax rules:
1, 2, 3, 4, 5
Thus, it is possible to collect source code from custom components, including generating it using other
programs.
Note that if the file specified in the directive is not found, the compilation will fail.
The order in which multiple files are included determines the order in which the preprocessor directives
in them are processed.
In addition, there is a #undef directive to undo any of the previous #define definitions. If #undef is not
used, each defined macro is valid until the end of source compilation.
Macros are registered and then used in code by name, following the rules of identifiers. By convention,
macro names are written in capital letters. Macro names can overlap the names of variables, functions,
and other elements of the source code. Purposeful use of this fact allows the flexibility to change and
generate source code on the fly. However, an unintentional coincidence of a macro name with a
program element will result in errors.
The principle of operation of both forms of macro substitutions is the same. Using the #define directive,
an identifier is introduced, which is associated with a certain piece of text – a definition. If the
preprocessor finds a given identifier later in the source code, it replaces it with the text associated with
it. We emphasize that the macro name can be used in compiled code only after registration (this is
similar to the variable declaration principles, but only at the compilation stage).
Replacing a macro name with its definition is called expansion. The analysis of the source code occurs
progressively and by one line in a pass, but the expansion in each line can be performed an arbitrary
number of times, as in a loop, as long as macro names are found in the result. You cannot include the
same name in a macro definition: when substituting, such a macro will result in an "unknown identifier"
error.
In Part 3 of the book, we'll learn about templates, which also allow you to generate (or, in fact,
replicate) source code, but with different rules. If there are both, macro substitution directives and
2.9 Preprocessor
155
Part 2. Programming fundamentals
templates in the source code, the macros are expanded first, and then the code is generated from the
templates.
The text starts after the identifier and continues to the end of the current line. The identifier and text
must be separated by an arbitrary number of spaces or tabs. If the required sequence of characters is
too long, then for readability you can split it into several lines by putting a backslash character '\' at
the end of the line.
The text can consist of any language constructs: constants, operators, identifiers, and punctuation
marks. If you substitute macro_ identifier instead of the found constructs in the source code, all of them
will be included in the compilation.
1. Flag declarations, which are then used for conditional compilation checks;
2. Named constant declarations;
3. Abbreviated notation of common statements.
The first point is characterized by the fact that nothing needs to be specified after the identifier - the
presence of a directive with a name is already enough for the corresponding identifier to be registered
and can be used in conditional directives #ifdef/ifndef. For them, it is only important whether the
identifier exists or not, i.e. it works in the flag mode: declared / not declared. For example, the following
directive defines the DEMO flag:
#define DEMO
It can then be used, say, to build a demo version of the program from which certain functions are
excluded (see the example in the conditional compilation section).
The second way to use a simple directive allows you to replace the "magic numbers" in the source
code with friendly names. "Magic numbers" are constants inserted into the source text, the meaning of
which is not always clear (because a number is just a number: it is desirable to at least explain it in a
comment). In addition, the same value can be scattered throughout different parts of the code, and if
the programmer decides to change it to another, then he will have to do this in all places (and hope
that he did not miss anything).
2.9 Preprocessor
156
Part 2. Programming fundamentals
With a named macro, these two problems are easily solved. For example, a script can prepare an array
with Fibonacci numbers to a certain maximum depth. Then it makes sense to define a macro with a
predefined array size and use it in the description of the array itself (Preprocessor.mq5).
#define MAX_FIBO 10
int fibo[MAX_FIBO]; // 10
void FillFibo()
{
int prev = 0;
int result = 1;
If the programmer subsequently decides that the size of the array needs to be increased, it is enough
for him to do this in one place - in the #define directive. Thus, the directive actually defines a certain
parameter of the algorithm, which is "hardwired" into the source code and is not available for user
configuration. The need for this arises quite often.
The question may arise how defining through #define differs from a constant variable in the global
context. Indeed, we could declare a variable with the same name and purpose, and even preserve the
uppercase letters:
However, in this case, MQL5 will not allow defining an array with the specified size, since only constants
are allowed in square brackets, i.e. literals (and a constant variable, despite its similar name, is not a
constant). To solve this problem, we could define an array as dynamic (without specifying a size first)
and then allocate memory for it using the ArrayResize function - passing a variable as a size is not
difficult here.
An alternative way to define a named constant is provided by enums, but is limited to integer values
only. For example:
enum
{
MAX_FIBO = 10
};
The search for macro names in source texts for replacement is performed taking into account the
syntax of the language, that is, indivisible elements, such as variable identifiers or string literals, will
remain unchanged, even if they include a substring that matches one of the macros. For example, given
2.9 Preprocessor
157
Part 2. Programming fundamentals
the macro XYZ below, the variable XYZAXES will be kept as it is, and the name XYZ (because it is
exactly the same as the macro) will be changed to ABC.
Macro substitutions allow you to embed your code in the source code of other programs. This
technique is usually used by libraries that are distributed as mqh header files and connected to
programs using the #include directives.
In particular, for scripts, we can define our own library implementation of the OnStartfunction, which
must perform some additional actions without affecting the original functionality of the program.
void OnStart()
{
Print("OnStart wrapper started");
// ... additional actions
_OnStart();
// ... additional actions
Print("OnStart wrapper stopped");
}
Then the original function OnStart (in Preprocessor.mq5) will be renamed by the preprocessor in the
source code to _ OnStart (it is understood that this identifier is not used anywhere else for some other
purpose). And the new version of OnStart from the header calls _ OnStart, "wrapping" it into additional
statements.
The third common way to use the simple #define is to shorten the notation of language constructs. For
example, the title of an infinite loop can be denoted with one word LOOP:
LOOP
{
// ...
Sleep(1000);
}
This method is also the main technique for using the #define directive with parameters (see below).
2.9 Preprocessor
158
Part 2. Programming fundamentals
Such a macro has one or more parameters in parentheses. Parameters are separated by commas.
Each parameter is a simple identifier (often a single letter). Moreover, all parameters of one macro
must have different identifiers.
It is important that there is no space between the identifier and the opening parenthesis, otherwise the
macro will be treated as a simple form in which the replacement text starts with an opening
parenthesis.
After this directive is registered, the preprocessor will search the source codes for lines of the form:
macro_identifier(expression,...)
Arbitrary expressions can be specified instead of parameters. The number of arguments must match
the number of macro parameters. All found occurrences will be replaced with text_ with_ parameters, in
which, in turn, the parameters will be replaced with the passed expressions. Each parameter can occur
several times, in any order.
For example, the following macro finds the maximum of two values:
Macro substitution will work for any data type (for which the operations applied inside the macro are
valid).
However, substitution can also have side effects. For example, if the actual parameter is a function call
or statement that modifies the variable (say, ++x), then the corresponding action can be performed
multiple times (instead of the intended one time). In the case of MAX, this will happen twice: during the
comparison and when getting values in one of the branches of the '?:' operator. In this regard, it makes
sense to convert such macros into functions whenever possible (especially considering that in MQL5
functions are automatically inlined).
There are parentheses around the parameters and around the entire macro definition. They are used to
ensure that the substitution of expressions as parameters or the macro itself inside other expressions
does not distort the computing order due to different priorities. Let's say the macro defines the
multiplication of two parameters (not yet enclosed in parentheses):
#define MUL(A,B) A * B
Then the use of the macro with the following expressions will produce unexpected results:
2.9 Preprocessor
159
Part 2. Programming fundamentals
You can specify another macro as a macro parameter. In addition, you can also insert other macros in
a macro definition. All such macros will be replaced sequentially. For example:
#define SQ3(X) (X * X * X)
#define ABS(X) MathAbs(SQ3(X))
#define INC(Y) (++(Y))
Then the following code will print 504 (MathAbs is a built-in function that returns the modulus of a
number, i.e. without a sign):
int x = -10;
Print(ABS(INC(x)));
// -> ABS(++(Y))
// -> MathAbs(SQ3(++(Y)))
// -> MathAbs((++(Y))*(++(Y))*(++(Y)))
// -> MathAbs(-9*-8*-7)
// -> 504
In the variable x, the value -7 will remain (due to the triple increment).
A macro definition can contain unmatched parentheses. This technique is used, as a rule, in a pair of
macros, one of which should open a certain piece of code, and the other should close it. In this case,
unmatched parentheses in each of them will become matched. In particular, in standard library files
available in the MetaTrader 5 distribution package, in Controls/Defines.mqh, the EVENT_MAP_BEGIN
and EVENT_MAP_END macros are defined. They are used to form the event processing function in
graphical objects.
The preprocessor reads the entire source text of the program line by line, starting from the main mq5
file and inserting the texts from the header files encountered in place. By the time any line of code is
read, a certain set of macros that are already defined is formed. It does not matter in which order the
macros were defined: it is quite possible that one macro refers in its definition to another, which was
described both above and below in the text. It is only important that in the line of source code where
the macro name is used, the definitions of all referenced macros are known.
Consider an example.
Here, the NEG macro uses the SQN and TEN macros, which are described below it. And this does not
prevent us from successfully using it in the code after all three #define-s.
2.9 Preprocessor
160
Part 2. Programming fundamentals
· a single hash symbol '#' before the name of a macro parameter turns the contents of that
parameter into a string; it is allowed only in function macros;
· a double hash symbol '##' between two words (tokens) combines them, and if the token is a macro
parameter, then its value is substituted, but if the token is a macro name, it is substituted as is,
without expanding the macro; if as a result of "gluing" another macro name is obtained, it is
expanded;
It calls the Print function, in which the passed expression is displayed as a string thanks to #A, and
after the sign "equal", the actual value of A is printed.
With it, we can actually generate a call to the SQN macro defined above:
Print(COMBINE(SQ,N,2)); // 4
The literals SQ and N are concatenated, after which the macro SQN expands to ((2)*(2)) and produces
the result 4.
The following macro allows you to create a variable definition in code by generating its name given the
parameters of the macro:
VAR(int, 3);
int var3 = 3;
Concatenation of tokens allows the implementation of a loop shorthand over the array elements using a
macro.
2.9 Preprocessor
161
Part 2. Programming fundamentals
Substitutions registered with #define can be undone if they are no longer needed after a particular
piece of code. For these purposes, the #undef directive is used.
#undef macro_identifier
In particular, it is useful if you need to define the same macro in different ways in different parts of the
code. If the identifier specified in #define has already been registered somewhere in earlier lines of
code (by another #define directive), then the old definition is replaced with the new one, and the
preprocessor generates the "macro redefinition" warning. The use of #undef avoids the warning while
explicitly indicating the programmer's intention not to use a particular macro further down the code.
Name Description
__COUNTER__ Counter (each mention in the text during macro expansion results in an
increase of 1)
2.9 Preprocessor
162
Part 2. Programming fundamentals
Name Description
#ifdef macro_identifier
statements
#endif
If a macro with the specified identifier is defined above in the code using #define, then this code
fragment will participate in compilation. Otherwise, it is excluded. In addition to the macros defined in
the application code, the environment provides a set of predefined constants, in particular, the
_RELEASE and _DEBUG flags (see section Predefined constants): their names can also be checked in
conditional compilation directives.
The extended form #ifdef allows the specification of two pieces of code: the first will be included if the
macro identifier is defined, and the second if it is not. To do this, a fragment separator #else is inserted
between #ifdef and #endif.
#ifdef macro_identifier
statesments_true
#else
statements_false
#endif
The #ifndef directive works similarly, but fragments are included and excluded according to the reverse
logic: if the macro specified in the header is not defined, the first fragment is compiled, and if it is
defined, the second fragment is compiled.
For example, depending on the presence of the DEMO macro substitution, we may or may not call the
function for calculating Fibonacci numbers.
#ifdef DEMO
Print("Fibo is disabled in the demo");
#else
FillFibo();
#endif
In this case, if the DEMO mode is enabled, instead of calling the function, a message would be displayed
in the log, but since in the Preprocessor.mq5 script and all the included files there is no #define DEMO
definition, compilation proceeds according to branch #else, that is, the call to the FillFibo function gets
into the executable ex5 file.
2.9 Preprocessor
163
Part 2. Programming fundamentals
#ifdef _DEBUG
Print("Debugging");
#else
#ifdef _RELEASE
Print("Normal run");
#else
Print("Undefined mode!");
#endif
#endif
The key is one of the properties listed in the following table, in the first column. The second column
specifies how the value will be interpreted.
Property Value
version String with the program version number (for the MQL5 Market, it must
be in the "X.Y" format, where X and Y are integers corresponding to the
major and minor build numbers)
icon String, path to the file with the program logo in ICO format
stacksize Integer specifying the size of the stack in bytes (by default it is from 4 to
16 MB, depending on the type of program and environment, 1 MB =
1024*1024 bytes); if necessary, the size increases up to 64 MB
(maximum)
All aforementioned string properties are the source of information for the program's properties dialog,
which opens when it starts. However, for scripts, this dialog is not displayed by default. To change this
behavior, you must additionally specify the #property script_ show_ inputs directive. In addition,
information about the rights is displayed in a tooltip when hovering the mouse cursor over the program
in the MetaTrader 5 Navigator.
The copyright, link, and version properties have already been seen in all the previous examples in this
book.
2.9 Preprocessor
164
Part 2. Programming fundamentals
The stack size stacksize is a recommendation: if the compiler finds local variables (usually arrays) in
the source code that exceed the specified value, the stack will be automatically increased during
compilation, but up to no more than 64 MB. If the limit is exceeded, the program will not even be able
to start: in the log (tab Log, and not Experts) the error "Stack size of 64MB exceeded. Reduce the
memory occupied by local variables" will occur.
Please note that such analysis and launch prevention only take into account a fixed snapshot of the
program at the time of launch. In the case of recursive function calls, the stack memory consumption
can increase significantly and lead to a stack overflow error, but already at the program execution
stage. For more information about the stack, see the note in Describing arrays.
The #property directives work only in the compiled mq5 file, and are ignored in all those included with
#include.
2.9 Preprocessor
165
Part 3. Object Oriented Programming
One of these technologies, implemented at the level of many programming languages, is called Object-
Oriented, and the programming style based on it is called Object-Oriented Programming (OOP),
respectively. The MQL5 programming language also supports it and therefore belongs to the family of
object-oriented languages, like C++.
From the name of the technology, it can be concluded that it is organized around objects. Essentially,
an object is a variable of a user-defined type, i.e., a type defined by a programmer using MQL5 tools.
The opportunity to create types that model the subject area makes programs more understandable and
simplifies their writing and maintenance.
In MQL5, there are several methods to define a new type, and each method is characterized by some
features that we will describe in the relevant sections. Depending on the method of description, user-
defined types are divided into classes, structures, and associations. Each of them can combine data
and algorithms, i.e., describe the state and behavior of applied objects.
In Part 1 of the book, we brought up the quote from one of the fathers of programming, Nicklaus Wirth,
that programs are a symbiosis of algorithms and data structures. So, the objects are essentially mini-
programs – each is responsible for solving its own, albeit small, but logically complete task. By
composing objects into a single system, you can build a service or product of arbitrary complexity.
Thus, with the OOP we get a new interpretation of the principle of "divide and conquer".
OOP should be thought of as a more powerful and flexible alternative to the procedural programming
style we explored in Part Two. At the same time, both approaches should not be contrasted: if
necessary, they can be combined, and in the simplest tasks, OOP can be left aside.
So, in this third Part of the book, we will study the basics of OOP and the possibilities of their practical
implementation in MQL5. In addition, we will talk about templates, interfaces, and namespaces.
MQL5 Programming for Traders – Source Codes from the Book. Part 3
Examples from the book are also available in the public project \MQL5\Shared Projects\MQL5Book
The choice between structures and classes in the implementation of the algorithm is traditionally based
on the requirements for access to the elements of the object and the presence of internal business
166
Part 3. Object Oriented Programming
logic. If a simple container with structured data is needed and its state does not need to be checked
for correctness (in programming this is called an "invariant"), then a structure will do just fine. If you
want to restrict access and support writing and reading according to some rules (which are formalized
in the form of functions assigned to the object, which we will discuss later), then it is better to use
classes.
MQL5 has built-in types of structures that describe entities that are in demand for trading, in
particular, rates (MqlRates), ticks (MqlTick), date and time (MqlDateTime), trade requests
(MqlTradeRequest), requests' results (MqlTradeResult) and many others. We will talk about them in
Part 6 of this book.
In reality, there may be more parameters and it won't be easy to pass them to the function as a list.
Moreover, based on the results of several calculations, it makes sense to save some of the best settings
in some kind of array. Therefore, it is convenient to represent a set of parameters as a single object.
The description of the structure with the same variables looks as follows:
struct Settings
{
datetime start;
int barNumber;
ENUM_APPLIED_PRICE price;
int components;
};
The description starts with the keyword struct followed by the identifier of our choice. This is followed
by a block of code in curly brackets, and inside it are descriptions of variables included in the structure.
Additionally, these are called fields or members of a structure. There is a semicolon after the curly
brackets since the whole notation is a statement defining a new type, and ';' is required after
statements.
Once the type is defined, we can apply it in the same way as built-in types. In particular, the new type
allows you to describe variable Settings in the program in the usual way.
Settings s;
It is important to note that a single structure description allows you to create an arbitrary number of
structure variables and even arrays of this type. Each structure instance will have its own set of
elements, and they will contain independent values.
To access members of a structure, a special dereference operator is provided — the dot character '.'.
To the left of it should be a variable of structure type, and to the right — an identifier of one of the
fields available in it. Here's how you can assign a value to a structure element:
void OnStart()
{
Settings s;
s.start = D'2021.01.01';
s.barNumber = 1000;
s.price = PRICE_CLOSE;
s.components = 8;
}
There is a more convenient way to fill in the structure which is the aggregate initialization. In this case,
the sign '=' is written to the right of the structure variable, followed by a comma-separated list of initial
values of all fields in curly brackets.
The types of the value must match the corresponding element types. It is allowed to specify fewer
values than the number of fields: then the remaining fields will receive zero values.
Note that this method only works when the variable is initialized, at the time of its definition. It is
impossible to assign the contents of an already existing structure in this way, we will get a compilation
error.
Settings s;
// error: '{' -