
Расширенные переменные и типы данных в MQL5
Введение
MQL5 — это язык программирования самой популярной торговой платформы MetaTrader 5, весьма богатой на инструменты создания торговой системы любой сложности. Наша цель как разработчиков — понять, как использовать эти инструменты и концепции для достижения цели разработки.
Мы уже упоминали основы простых переменных и типов данных в MQL5 в статье "Как и зачем разрабатывать собственную систему для алгоритмической торговли". Кроме того, мы рассмотрели использование MQL5 для написания торговых приложений, а также переменные с точки зрения их определения, типов и способов применения. Мы также узнали о простых типах данных, таких как integer, float, double, string и bool. Мы также упомянули входные переменные и то, как их можно использовать, чтобы дать пользователю возможность устанавливать свои предпочтения в программе.
В статье "Работаем с датами и временем в MQL5" мы подробно рассмотрели тип данных Datetime, без которого не обходится ни одно торговое приложение.
В этой статье мы рассмотрим новые переменные и типы данных в MQL5, а также их применение при разработке торгового программного обеспечения MQL5. Мы узнаем больше о некоторых продвинутых концепциях переменных и типов данных и рассмотрим их в следующих темах:
- Константы: идентификаторы фиксированных значений.
- Массивы: переменные любого типа с несколькими значениями.
- Перечисления: целочисленные списки констант с целочисленными значениями.
- Структуры: набор связанных переменных разных типов.
- Приведение типов: преобразование одного типа значения в другой.
- Локальные переменные: локально объявленные переменные внутри функций.
- Глобальные переменные: глобально объявленные переменные вне функций.
- Статические переменные: объявленные локальные переменные, которые сохраняют свои значения в памяти.
- Предопределенные переменные.
Мы подробно рассмотрим каждую тему, включая многочисленные примеры, чтобы углубить наше понимание и иметь возможность эффективно использовать все упомянутые концепции. Программирование — это наука, в которой очень важна практика, поэтому важно пробовать и применять полученные знания, чтобы понять, как можно использовать каждую концепцию как часть общего кода для создания эффективной торговой системы.
Константы
В этой части мы углубимся в концепцию констант в программировании и MQL5, чтобы понять, почему нам нужно использовать переменные этого типа. Постоянная переменная также называется доступной только для чтения или именованной константой. Ее нельзя изменить и мы не можем присвоить ей новое значение после первой инициализации. Если код попытается это сделать, это приведет к ошибке. Понятие константы поддерживается большинством языков программирования, включая MQL5. У нас есть два метода определения константы в нашем программном обеспечении:
- Глобальные константы
- Спецификатор const
Глобальные константы:
Эту глобальную константу можно глобально определить с помощью директивы препроцессора #define в начале нашей программы, затем указываем идентификатор, а после этого присваиваем значение константы. Значение константы может быть любым типом данных, например строковым, и в нашем программном обеспечении нам нужно закодировать следующее:
#define Identifier_Name constant_value
Как мы видим в предыдущем коде, у нас есть директива препроцессора #define, которая указывает на объявление константы. Следующий пример позволяет лучше понять этот метод:
В глобальной области видимости мы сначала используем директиву препроцессора #define, чтобы объявить, что у нас есть константа, идентификатор — PLATFORM_NAME, а постоянное значение идентификатора — строковый тип данных "MetaTrader 5".
#define PLATFORM_NAME "MetaTrader 5"
В нужной функции мы можем, например, сказать, что напечатаем идентификатор в OnStart()
void OnStart() { Print("The trading platform name is: ", PLATFORM_NAME); }
При компиляции и запуске программного обеспечения мы можем найти результат в виде сообщения на вкладке "Эксперты", как показано на следующем скриншоте.
Как мы уже упоминали, значение константы MetaTrader 5 невозможно изменить.
Спецификатор const:
В соответствии с этим методом мы можем объявить константу, используя спецификатор const перед переменной и ее типом данных. Этот спецификатор объявляет, что это будет константа и ее нельзя изменить. Ниже показано, как мы можем это сделать.
const int varName = assignedVal
Ниже приведен простой пример объявления константы этим методом.
const int val = 1;
Если нам нужно напечатать значение этой переменной
Print("constant val is: ", val);
Мы можем найти результат, аналогичный следующему:
Как я уже упоминал, если мы попытаемся обновить переменную (val), то получим следующую ошибку, поскольку она является константой и не может быть обновлена.
Чтобы проиллюстрировать разницу между обычной переменной и константой, мы можем использовать следующий пример, где у нас есть два значения: константа и обычная переменная.
const int val = 1; int val2 = 2; Print("constant val is: ", val); Print("val 2 is: ", val2);Результат при запуске программы представлен ниже.
Теперь попытаемся обновить val2 другим значением, равным 4, с помощью этого кода.
val2 = 4;
Если мы затем снова распечатаем эти два значения, результат будет следующим:
Как видим, значение val2 меняет значение с 2 на 4.
Массивы
Массив - базовое понятие любого языка программирования. Массив — это переменная, в которой мы можем хранить множество значений любого типа данных. Мы можем представить массив в виде списка с индексами и соответствующими значениями. Когда нам нужно получить доступ к определенному значению, мы можем сделать это путем индексации.
Индексация массива начинается с нуля, поэтому максимальный индекс равен результату уменьшения размера массива на одно значение. Если у нас есть массив с пятью значениями, его индексы равны (0, 1, 2, 3, 4), а максимальное значение, как видим, равно 4, что является результатом (5-1).
Если нам нужно получить доступ к значению определенного индекса, мы ссылаемся на него, указывая его индекс в квадратных скобках []. Это то, что мы подразумеваем под доступом посредством индексации.
У нас также есть статический и динамический массивы. Разница между ними с точки зрения размера массива заключается в том, что статический массив имеет фиксированный размер и не может быть изменен, динамический массив не имеет размера. Его можно использовать, если нам нужно изменить размер массива вместо использования статического.
Объявление статического массива
Если нам нужно объявить новый статический массив, мы можем сделать это, используя следующий пример.
int newArray[5]; newArray[0] = 1; newArray[1] = 2; newArray[2] = 3; newArray[3] = 4; newArray[4] = 5; Print("newArray - Index 0 - Value: ", newArray[0]); Print("newArray - Index 1 - Value: ", newArray[1]); Print("newArray - Index 2 - Value: ", newArray[2]); Print("newArray - Index 3 - Value: ", newArray[3]); Print("newArray - Index 4 - Value: ", newArray[4]);
После запуска программы мы увидим следующий результат.
Мы можем получить тот же результат, объявив массив с использованием следующего сокращенного кода для большей эффективности, а также присвоив значения массиву внутри скобок {} и разделив их запятой (,).
int newArray[5] = {1, 2, 3, 4, 5}; Print("newArray - Index 0 - Value: ", newArray[0]); Print("newArray - Index 1 - Value: ", newArray[1]); Print("newArray - Index 2 - Value: ", newArray[2]); Print("newArray - Index 3 - Value: ", newArray[3]); Print("newArray - Index 4 - Value: ", newArray[4]);
Если мы выполним данный код, мы получим уже показанные выше сообщения.
Объявление динамического массива
Как уже упоминалось, динамический массив — это массив без фиксированного размера, размер которого можно изменить. Этот тип массива используется для хранения таких данных, как цена и значение индикатора, поскольку эти данные являются динамическими.
Ниже показано, как можно объявить новый динамический массив.
double myDynArray[]; ArrayResize(myDynArray,3); myDynArray[0] = 1.5; myDynArray[1] = 2.5; myDynArray[2] = 3.5; Print("Dynamic Array 0: ",myDynArray[0]); Print("Dynamic Array 1: ",myDynArray[1]); Print("Dynamic Array 2: ",myDynArray[2]);
Как мы видим в предыдущем коде, мы объявили массив с пустой квадратной скобкой, затем использовали функцию ArrayResize для изменения размера массива. Результат следующий.
У нас есть три напечатанных сообщения для каждого индекса и соответствующее значение. Мы определили одномерные массивы, но мы также можем использовать многомерные массивы - два, три или четыре.
Многомерные массивы
Многомерные массивы можно рассматривать как вложенные массивы или массивы внутри массивов. Например, у нас есть два массива, каждый из которых имеет два элемента. Посмотрим, как мы можем закодировать этот тип массива следующим образом:
Объявление двумерных массивов с двумя элементами
double newMultiArray[2][2];
Присвоение значений или элементов первому массиву с индексом 0
newMultiArray[0][0] = 1.5; newMultiArray[0][1] = 2.5;
Присвоение значений или элементов второго массива с индексом 1
newMultiArray[1][0] = 3.5; newMultiArray[1][1] = 4.5;
Печать значений двух массивов и их элементов или значений
Print("Array1 - Index 0 - Value: ", newMultiArray[0][0]); Print("Array1 - Index 1 - Value: ", newMultiArray[0][1]); Print("Array2 - Index 0 - Value: ", newMultiArray[1][0]); Print("Array2 - Index 1 - Value: ", newMultiArray[1][1]);
После запуска программы мы увидим следующий результат.
Как видим, у нас есть массив 1 с двумя значениями 1.5 и 2.5, а также Массив 2 с двумя значениями 3.5 и 4.5.
Когда мы объявляем многомерные массивы, мы можем оставить пустым только первое измерение, как в следующем примере.
double newMultiArray[][2];
Затем мы можем передать его, используя переменную, аналогичную следующей:
int array1 = 2; ArrayResize(newMultiArray, array1);
После того, как мы скомпилировали и запустили приложение, мы получим тот же результат, что и раньше.
Перечисления
Мы можем представить перечисления как наборы данных или списки констант/элементов, которые можно использовать для описания связанных понятий. Перечисления могут встроенными и пользовательскими. Встроенные перечисления предопределены в MQL5, и мы можем вызывать и использовать их в нашей программе. Пользовательские перечисления вводятся в соответствии с нашими потребностями.
Встроенные перечисления можно найти в справочнике MQL5, например, ENUM_DAY_OF_WEEK. Мы также можем установить пользовательские перечисления, чтобы использовать их позже в созданных нами приложениях.
Прежде всего мы используем ключевое слово enum для определения перечисления, имя типа перечисления и список значений связанных типов данных, разделенных запятыми.
enum name of enumerable type { value 1, value 2, value 3 };
Например, создадим именованное перечисление workingDays, которое будет типом. Переменные для него я объявлю позже
enum workingDays
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
};
Теперь нам нужно объявить новую связанную переменную типа workingDays, присвоить сегодняшнее значение (today) среди определенных в списке и распечатать эту переменную.
workingDays toDay;
toDay = Wednesday;
Print(toDay);
Мы можем найти сообщение (2) для среды (Wednesday) как рабочего дня в списке, начиная с 0 для понедельника (Monday) и до 4 для пятницы (Friday), как показано на следующем скриншоте.
Мы также можем определить другой начальный номер, который будет назначен вместо (0), присвоив значению необходимый номер, например, указав, что понедельник = 1.
Структуры
Нам нужно объявить несколько различных типов данных для связанных переменных, и в этом случае нам поможет структура. Члены структуры могут иметь любой тип данных, в отличие от перечисления, члены которого могут быть только одного типа. Аналогично перечислениям, в MQL5 есть предопределенные структуры, такие как, например, MqlTick, но мы можем создавать свои собственные.
Допустим, нам нужно создать структуру для tradeInfo. В качестве членов у нас есть символ, цена, стоп-лосс, тейк-профит и время торговли. Они имеют разные типы данных: символ представляет собой строку, а цена, стоп-лосс и тейк-профит — значение типа double. В этом случае нам нужно использовать структуры для выполнения нашей задачи:
Используем ключевое слово struct, чтобы объявить нашу собственную структуру.
struct tradeInfo { string symbol; double price; double stopLoss; double takeProfit; };
Затем мы можем объявить новый торговый объект типа tradeInfo и присвоить значения членам этой структуры, обратившись к ним с помощью (.) после имени объекта следующим образом
tradeInfo trade; trade.symbol = "EURUSD"; trade.price = 1.07550; trade.stopLoss = 1.07500; trade.takeProfit = 1.07700;
Мы можем распечатать члены структуры с их значениями, как показано ниже
Print("Symbol Trade Info: ", trade.symbol); Print("Price Trade Info: ", trade.price); Print("SL Trade Info: ", trade.stopLoss); Print("TP Trade Info: ", trade.takeProfit);
Выведем сообщение, как на следующем скриншоте.
Как можно видеть на предыдущем снимке экрана, именно такие назначенные значения нам и нужны, поэтому мы можем поэкспериментировать с ними для достижения наших целей.
Приведение типов
Под приведением типов понимается преобразование значение переменной из одного типа данных в другой. Этот процесс может привести к неожиданным результатам.
Когда мы приводим или копируем числовое значение из одной переменной в другую, мы можем столкнуться с потерей данных, когда этот процесс происходит между разными типами.
Ниже приведены примеры, когда данные не потеряны или потеряна часть данных:
Мы копируем целочисленное значение в значение long. В этом случае потери данных не произойдет, поскольку приведение выполняется от меньших переменных к большим. Когда мы копируем целое число в double, также копируется целое число и значение после десятичной точки равна нулю, поэтому нет данных, которые можно было бы потерять. Если же мы, наоборот, скопируем значение double в целочисленную переменную, значение после десятичной дроби будет потеряно (усечено).
Давайте рассмотрим примеры кода. Сначала мы увидим приведение данных без потерь:
int price = 3 - 1; double newPrice = price + 1; Print ("The price is ", newPrice);
Как видим, тип переменной (цена) — int, затем мы приводим ее к переменной double и печатаем ее. Предполагается, что потери данных не возникнет, поскольку мы выполняем приведение от меньшего типа к большему. Ниже приводится отображаемое сообщение.
Но когда мы производим обратную операцию, мы увидим совсем другую картину.
double price = 3.50 - 1; int newPrice = price + 1; Print ("The price is ", newPrice);
Тип значения - double и значение цены равно 3.50. Приведем его к переменной int (newPrice) и распечатаем. Мы теряем данные после десятичной точки, которая является значением (.50), и результатом будет (3).
Компилятор предупредит нас, если мы приведем значение большего типа к переменной меньшего типа. Мы сами решаем, игнорировать потерю данных или нет, в зависимости от наших целей и важности потерянных данных. Ниже приведен пример этого предупреждения:
Мы можем округлить значение, добавив (int) перед переменной double, чтобы отобразить предупреждение компилятора.
Локальные переменные
Представим, что в нашем программном обеспечении есть глобальная и локальная области видимости. Глобальная область видимости заключается в том, что мы можем получить доступ к любому месту в нашем приложении (мы рассмотрим понятие глобальной области видимости с точки зрения переменных в следующем разделе), но локальная область может быть доступна только в той области видимости, в которой она объявлена.
Если у нас есть функция и внутри этой функции есть переменные, которые объявляются только на уровне функции, эти переменные считаются локальными. Таким образом, они могут быть доступны во время работы функции, но не будут доступны после выхода из функции.
Для наглядности рассмотрим следующий пример:
void myFunc() { int var = 5; Print(var); }
Как видим, у нас есть функция myFunc. Эту функцию можно вызывать где угодно. При вызове этой функции, мы видим локальную переменную var. Эта переменная может работать только внутри функции, но после выхода из функции она недоступна.
Итак, при вызове функции мы видим, что результат равен 5:
Если мы попытаемся получить доступ к локальной переменной var извне функции, компилятор выдаст ошибку undeclared identifier (необъявленный идентификатор):
То же самое происходит, если внутри функции есть вложенные уровни или области видимости: каждая объявленная переменная будет доступна только в своей области видимости.
Рассмотрим это на следующем примере:
void myFunc() { int var = 5; if(var == 5) { int newVar = 6 ; Print(newVar); } }
Как видим, мы вложили if для сравнения значения var с 5. При true мы объявим newVar со значением 6 и распечатаем его. В результате мы получаем 6, поскольку условие истинно.
newVar представляет собой новую локальную переменную внутри области действия оператора if. Она недоступна за ее пределами, как и любая другая локальная область видимости. Любое объявление переменной с тем же именем, что и у локальной переменной, переопределит предыдущее и будет введено в более высокую область видимости.
Итак, концепция очень проста: любая локальная переменная будет доступна на своем уровне или в области видимости внутри функции. Но что нам делать, если нам нужно получить доступ к переменной в любом месте нашего приложения? Здесь в игру вступает глобальная переменная.
Глобальные переменные
В отличие от локальных переменных, мы можем получить доступ к глобальным переменным из любого места нашего приложения. Когда нам нужно объявить глобальные переменные, это нужно делать в глобальной области видимости программного обеспечения вне какой-либо функции, чтобы мы могли использовать или вызывать их в любом месте приложения. Эти типы переменных можно использовать, когда нам нужно объявить переменную, которая будет многократно использоваться многими функциями.
Глобальные переменные определяются в начальной части приложения после входных переменных. В отличие от локальных переменных, если мы попытаемся объявить глобальные переменные внутри блока, это приведет к ошибке компиляции "Declaration of variable hides global declaration" (объявление переменной скрывает глобальное объявление):
Мы можем обновлять глобальные переменные в любой точке приложения, как в следующем примере:
int stopLoss = 100; void OnStart() { Print("1st Value: ", stopLoss); addToSL(); } void addToSL() { stopLoss = stopLoss + 50; Print("Updated Value: ", stopLoss); }
Как видим, мы объявили переменную stopLoss глобально в верхней части программы, распечатали назначенное значение, вызвали функцию addToSL(), которая добавила 50 к первому назначенному значению, а затем распечатали обновленное значение. После компиляции и запуска приложения мы увидим следующие сообщения:
Как видим, наше первоначальное значение переменной равно 100, а при обновлении путем добавления 50 в функции addToSL() оно становится равным 150.
Статические переменные
Статические переменные являются локальными переменными, но они сохраняют свои значения в памяти, даже если приложение покинуло их область видимости. Их можно объявить в блоке функций или локальных переменных.
Мы можем сделать это, используя ключевое слово static перед именем переменной и присвоив ее значение.
void staticFunc() { static int statVar = 5; statVar ++; Print(statVar); }
Вызовем функцию staticFunc()
staticFunc();
Здесь переменная сохранит свое значение (5) в памяти, и каждый раз, когда мы вызываем функцию, на выходе будет 6 (5+1). Ниже приведен скриншот вывода:
Мы можем видеть значение 6 на вкладке "Эксперты".
Предопределенные переменные
При создании кода нам может потребоваться постоянно писать много строк, чтобы сделать что-то часто используемое. Во многих языках программирования есть предопределенные переменные или даже функции. Мы можем легко их использовать без необходимости заново переписывать весь код. Нам нужно лишь знать, какое ключевое слово нужно использовать.
В MQL5 имеется множество предопределенных переменных, и мы можем получить доступ к их значениям, как в примерах ниже:
- _Symbol - текущий символ графика.
- _Point - значению пункта текущего символа: 0,00001 для пяти цифр текущего символа и 0,001 для трех цифр текущего символа.
- _Period - текущий период или таймфрейм символа.
- _Digits - количество знаков после запятой (пять или три).
- _LastError - значение последней ошибки.
- _RandomSeed - текущее состояние генератора псевдослучайных чисел.
- _AppliedTo - тип данных для расчета индикатора.
Эти и другие предопределенные переменные можно найти в одноименном разделе документации.
Заключение
Мы рассмотрели тонкости MQL5, уделив особое внимание основным понятиям программирования, таким как типы данных, переменные и другие ключевые элементы, имеющие решающее значение для разработки современных приложений. Овладение MQL5 требует понимания как базовых, так и продвинутых аспектов, при этом практика имеет основополагающее значение для повышения уровня профессионализма. Вы можете прочитать другие мои статьи, если хотите узнать больше о программировании или разработке торговых систем на основе таких популярных технических индикаторов, как скользящие средние, RSI и других.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/14186





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Ознакомьтесь с новой статьей: Расширенные переменные и типы данных в MQL5.
Автор: Мохамед Абдельмаабуд
Спасибо, что поделились информацией. Я постараюсь написать о том, что вы упомянули, как можно больше.
Ключевое слово const действует иначе, чем #define "constant".
Кстати, мне нравится, как реализованы константы в C# (компилятор заменяет их на литеральные значения).
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/constants
На самом деле, когда компилятор встречает идентификатор константы в исходном коде C#, он подставляет буквенное значение непосредственно в код промежуточного языка (IL), который он производит. Поскольку во время выполнения с константой не ассоциируется адрес переменной, const-поля не могут передаваться по ссылке и не могут выступать в качестве l-значения в выражении.