Основы программирования на MQL5: Глобальные переменные терминала MetaTrader 5

Dmitry Fedoseev | 11 октября, 2016

Содержание

Введение

Глобальные переменные терминала (рис. 1) — уникальная особенность терминала MetaTrader и языка MQL.


Рис. 1. Фрагмент кода с использованием глобальных переменных

Не следует путать глобальные переменные терминала со всем известными глобальными переменными программы (рис. 2) и пытаться найти им аналогию в других языках программирования, если вы с ними знакомы.


Рис. 2. Фрагмент кода эксперта MovingAverage из примеров терминала, глобальные переменные программы выделены красной областью

Может быть, именно потому что аналогии глобальным переменным терминала нет в других языках программирования, они не пользуются большим вниманием у новичков, изучающих MQL5. Возможно, у них просто нет представления о том, как и для чего их можно использовать, или же само их использование кажется трудоемким из-за довольно громоздких имен у функций по их использованию: GlobalVariableSet(), GlobalVariableGet(), GlobalVariableCheck() и т.п.

Основная особенность и отличие глобальных переменных терминала в том, что они сохраняют свои значения даже после закрытия терминала. Именно поэтому они являются очень удобным и быстрым средством для хранения важных данных и становятся почти незаменимыми при разработке надежных экспертов со сложным взаимодействием между ордерами. Когда вы освоите работу с глобальными переменными терминала,  вам будет сложно представить себе создание экспертов на MQL5 без их использования.

В этой статье на практических примерах мы изучим все функции по работе с глобальными переменными, рассмотрим их особенности, разберем различные приемы их использования и напишем класс, значительно упрощающий и ускоряющий работу с ними.

В документации по MQL5 раздел с функциями для работы с глобальными переменными расположен по следующей ссылке

Просмотр глобальных переменных в терминале

В терминале выполните команду: Главное меню — Сервис — Глобальные переменные, в результате откроется окно "Глобальные переменные" (рис. 3).

 
Рис. 3. Окно "Глобальные переменные" в терминале. 

В основном вся работа с глобальными переменными выполняется программно. Но в некоторых случаях, при отладке экспертов, это окно может быть полезным. Окно позволяет не только просматривать все глобальные переменные терминала, но и редактировать их: изменять имена или значения. Для изменения переменной необходимо щелкнуть на поле с ее именем или со значением. Также это окно позволяет создавать новые переменные: для этого щелкните на кнопке "Добавить", расположенной в правом верхнем углу окна. Ниже кнопки "Добавить" расположена кнопка "Удалить", позволяющая удалять глобальные переменные. Для удаления надо выделить переменную щелкнув на строке с ней, после чего кнопка активизируется и ее можно будет нажать. Потренируйтесь создавать, менять и удалять глобальные переменные. Только будьте аккуратны: если в окне уже существовали какие-то переменные, не трогайте их, ведь они могут быть нужны какому-либо запущенному у вас эксперту.

Глобальная переменная имеет три атрибута: имя, значение и время. В поле "Время" фиксируется время последнего обращения к переменной. По истечению 4 недель после последнего обращения она будет удалена автоматически. Это очень удобная особенность глобальных переменных, ведь вам можно совершенно не беспокоиться об их удалении. Однако же, если переменные хранят важные данные, к ним необходимо иногда обращаться, чтобы продлить срок их существования.  

Создание глобальной переменной терминала

Специальных действий по созданию переменной выполнять не требуется, ее создание происходит автоматически при присвоении ей значения. Если переменная с с данным именем уже существовала, ее значение будет обновлено, если не существовала — она будет создана. Для присвоения переменной значения используется функция GlobalVariableSet(). В функцию передается два параметра: имя переменной (строка) и ее значение (тип double). Попробуем создать переменную. Откройте редактор MetaEditor, создайте скрипт, в его функцию OnStart() напишите следующий код:

GlobalVariableSet("test",1.23);

 Выполните скрипт, потом откройте окно глобальных переменных из терминала, в нем должна появиться новая переменная с именем "test" и значением 1.23 (рис. 4).

 
Рис. 4. Фрагмент окна глобальных переменных с новой переменной "test"

Код этого примера можно найти в приложении к статье в скрипте с именем "sGVTestCreate".  

Получение значения глобальной переменной

При выполнении предыдущего примера скрипт, создающий переменную, завершил свою работу, но сама переменная продолжает существовать. Посмотрим ее значение. Для получения значения используется функция GlobalVariableGet(). Существует два варианта вызова функции. В первом варианте в функцию передается один параметр — имя. Возвращает функция значение типа double:

double val=GlobalVariableGet("test");
Alert(val);

При выполнении этого кода откроется окно со значением 1.23. В приложении этот пример расположен в скрипте с именем "sGVTestGet1".

Во втором варианте в функцию передаются два параметра: имя и переменная double для значения (второй параметр передается по ссылке), сама же функция возвращает true или false в зависимости от успешности ее работы:

double val;
bool result=GlobalVariableGet("test",val);
Alert(result," ",val);

В результате работы этого примера откроется окно с сообщением "true 1.23".

Если попробовать получить значение несуществующей переменной, функция вернет false и значение 0. Немного изменим код предыдущего примера: переменной val при объявлении присвоим значение 1.0 и попробуем получить значение несуществующей  переменной "test2":

double val=1.0;
bool result=GlobalVariableGet("test2",val);
Alert(result," ",val);

В результате работы этого примера откроется окно с сообщением "false 0.0". В приложении этот пример расположен в скрипте с именем "sGVTestGet2-2".

При вызове функции по первому варианту, если указать имя несуществующей переменной, также получим значение 0.0, но при этом будет получена ошибка. Используя первый вариант вызова функции и проверку ошибки, можно получить аналог второго варианта функции:

ResetLastError();
double val=GlobalVariableGet("test2");
int err=GetLastError();
Alert(val," ",err);

В результате работы этого кода (в приложении пример находится в скрипте "sGVTestGet1-2") откроется сообщение "0.0 4501". 0.0 — это значение, 4501 — код ошибки — "Глобальная переменная клиентского терминала не найдена". Ошибка является не критической, а скорее информационной. Если алгоритм допускает, можно обращаться к несуществующей переменной. Например, для отслеживания максимального эквити:

GlobalVariableSet("max_equity",MathMax(GlobalVariableGet("max_equity",AccountInfoDouble(ACCOUNT_EQUITY)));

Данный код будет работать правильно, даже если переменная "max_equity" не существует. Сначала функцией MathMax() выполняется выбор максимального значения между фактическим значением эквити и ранее сохраненным в переменной "max_equity", поскольку переменная не существует, получаем фактическое значение эквити.

Имена глобальных переменных

Как уже можно было понять, имя глобальной переменной терминала является строкой. Нет никаких ограничений по используемым для имен символам и их порядку. Любые знаки, которые можно набрать на клавиатуре, могут использоваться в именах, в том числе и пробелы, и знаки, запрещенные в именах файлов. Но тем не менее, хотелось бы порекомендовать выбирать для переменных простые и легко читаемые имена, включающие буквенные, цифровые символы и знак подчеркивания, по аналогии с обычными переменными.

Есть только одно существенное ограничение, требующее аккуратности при выборе имен глобальным переменным — это длина имени: не больше 63 знаков.

Проверка существования переменной

Для проверки, существует ли переменная, используется функция GlobalVariableCheck(). В функцию передается один параметр — имя переменной. Если переменная существует, функция вернет true, иначе false. Проверим существование переменных "test" и "test2":

bool check1=GlobalVariableCheck("test");
bool check2=GlobalVariableCheck("test2");
Alert(check1," ",check2);

В приложении этот пример находится в скрипте "sGVTestCheck". В результате работы скрипта получим сообщение: "true false" — переменная "test" существует, "test2" не существует.

В некоторых случаях обязательно нужна проверка на существование переменной, например, при отслеживании минимального эквити. Если в приведенном выше примере по отслеживанию максимального эквити заменить функции MathMax() на MathMin(), он будет работать неправильно, и в переменной всегда будет 0.

В этом случае поможет проверка на существование переменной:

if(GlobalVariableCheck("min_equity")){
   GlobalVariableSet("min_equity",MathMin(GlobalVariableGet("min_equity"),AccountInfoDouble(ACCOUNT_EQUITY)));
}
else{
   GlobalVariableSet("min_equity",AccountInfoDouble(ACCOUNT_EQUITY));

Если переменная существует, выбираем наименьшее значение функцией MathMin(), а если не существует, то сразу присваиваем значение эквити.

Время глобальной переменной

Время глобальной переменной, которое мы уже видели на рис. 3, можно получить при помощи функции GlobalVariableTime(). В функцию передается один параметр — имя переменной. Возвращает функция значение типа datetime:

datetime result=GlobalVariableTime("test");
Alert(result);

В приложении код этого примера находится в скрипте с именем  "sGVTestTime". Значение атрибута времени переменной меняется только при обращении к ее значению, то есть при использовании функций GlobalVariableSet() и GlobalVariableGet() никакие другие функции не меняют значение времени переменной. Если изменить переменную вручную через окно "Глобальные переменные", ее время также меняется (и при изменении ее значения, и при изменении ее имени).

Хотелось бы еще раз напомнить: переменная существует в течение 4 недель с момента последнего к ней обращения, а потом автоматически удаляется терминалом. 

Просмотр всех глобальных переменных

Иногда бывает нужно найти глобальную переменную, не зная ее точного имени. Может быть известно начало имени переменной, но неизвестно окончание, например: "gvar1", "gvar2" и т.п. Для поиска таких переменных необходимо делать перебор всех глобальных переменных терминала и проверять их имена. Для этого пригодятся функции: GlobalVariablesTotal() и GlobalVariableName(). Функция GlobalVariablesTotsl() возвращает общее количество глобальных переменных. Функция GlobalVariableName() возвращает имя переменной по ее индексу, в функцию передается один параметр типа int. Сначала просмотрим все переменные, выведем в окно сообщений их имена и значения:

   Alert("=== Начало ===");
   int total=GlobalVariablesTotal();
   for(int i=0;i<total;i++){
      Alert(GlobalVariableName(i)," = ",GlobalVariableGet(GlobalVariableName(i)));
   }

В результате работы этого примера откроется окно сообщений с именами всех переменных и их значениями (рис. 5). В приложении этот пример находится в скрипте с именем "sGVTestAllNames".

 
Рис. 5. Окно сообщений со списком всех глобальных переменных терминала

Для того, чтобы просмотреть переменные с определенными признаками в именах, добавим дополнительную проверку. В следующем примере выполняется проверка того, чтобы имя переменной начиналось с "gvar", (в приложении этот пример находится в скрипте с именем "sGVTestAllNames2"):

   Alert("=== Начало ===");
   int total=GlobalVariablesTotal();
   for(int i=0;i<total;i++){
      if(StringFind(GlobalVariableName(i),"gvar",0)==0){
         Alert(GlobalVariableName(i)," = ",GlobalVariableGet(GlobalVariableName(i)));
      }
   }

Проверка выполняется при помощи функции StringFind(). Если вы желаете улучшить свои навыки при работе со строковыми функциями, обратите внимание на статью Основы программирования на MQL5 - Строки.

Удаление глобальных переменных

Для удаления одной глобальной переменной применяется функция GlobalVariableDel(), в которую передается один параметр — имя переменной. Удалим созданную ранее переменную "test" (скрипт "sGVTestDelete" в приложении):

GlobalVariableDel("test");

Для проверки результата работы этого примера можно использовать скрипт "sGVTestGet2-1" или "sGVTestGet2-2", а можно открыть окно глобальных переменных.

С удалением одной переменной все просто, но чаще всего бывает нужно удалить не одну переменную, а несколько. Для этого подойдет функция GlobalVariablesDeleteAll(). В функцию передаются два необязательных параметра. Если вызвать функцию без параметров, будут удалены абсолютно все глобальные переменные. Обычно возникает необходимость удалить только некоторую группу переменных, имеющих одинаковый префикс (начало имени). Первый параметр функции используется для указания префикса. Поэкспериментируем с этой функцией. Сначала создадим несколько переменных с различными префиксами:

   GlobalVariableSet("gr1_var1",1.2);
   GlobalVariableSet("gr1_var2",3.4);   
   GlobalVariableSet("gr2_var1",5.6);
   GlobalVariableSet("gr2_var2",7.8);  

Этот код создает четыре переменных: две с префиксом "gr1_" и две с префиксом "_gr2". В приложении этот пример находится в скрипте с именем "sGVTestCreate4". Убедимся в результате работы этого скрипта, запустив скрипт "sGVTestAllNames" (рис. 6).

 
Рис. 6. Переменные, созданные скриптом "sGVTestCreate4"

Теперь удалим их, но не все, а только начинающиеся с префикса "gr1_" (скрипт "sGVTestDeleteGroup" в приложении):

GlobalVariablesDeleteAll("gr1_");

После выполнения этого кода еще раз посмотрим все переменные скриптом "sGVTestAllNames" (рис. 7). В результате мы снова увидим список всех переменных, кроме пары переменных, начинающихся с "gr1_".

 
Рис. 7. Группа переменных с префиксом "gr1_" удалена

Второй параметр функции GlobalVariableDeleteAll() используется, если нужно удалить только старые переменные. В этом параметре указывается дата.  Если дата последнего обращения к переменной меньше указанной, то переменная будет удалена. Обратите внимание: удаляются переменные, имеющие меньшее время, а не меньшее или равное). При этом дополнительно можно выбирать переменные по префиксу. Если выбор по префиксу не нужен, первым параметром указывается значение по умолчанию NULL:

GlobalVariablesDeleteAll(NULL,StringToTime("2016.10.01 12:37"));

На практике удаление переменных по времени может потребоваться только для решения какой-нибудь очень редкой и необычной задачи, так что ограничимся только теоретическим рассмотрением второго параметра.

Функция GlobalVariablesFlush

При закрытии терминала автоматически выполняется сохранение глобальных переменных в файл, из которого терминал загружает их назад при запуске. Сами тонкости этого процесса (имя файла, формат сохранения данных и т.п.) при использовании глобальных переменных знать не обязательно.

В случае аварийного завершения работы терминала может произойти потеря глобальных переменных. Чтобы избежать такой неприятности, существует функция GlobalVariableFlush(). Функция производит принудительное сохранение глобальных переменных. После установки значений функцией GlobalVariableSet() или после удаления переменных достаточно просто вызывать функцию GlobalVariableFlush(), функция вызывается без параметров: 

   GlobalVariableSet("gr1_var1",1.2);
   GlobalVariableSet("gr1_var2",3.4);   
   GlobalVariableSet("gr2_var1",5.6);
   GlobalVariableSet("gr2_var2",7.8);   
   
   GlobalVariablesFlush();

В приложении этот код находится в файле "sGVTestFlush". 

Хотелось бы продемонстрировать эффект от действия функции GlobalVariableFlush(), однако в результате экспериментов по аварийному закрытию терминала так и не удалось получить пропадания глобальных переменных. Выполнялось завершение работы терминала через диспетчер задач, вкладку "Процессы". Возможно, глобальные переменные пропадут при резком обесточивании компьютера. В настоящее время резкое отключение питания компьютера — явление нечастое, ведь подавляющее большинство пользователей — обладатели ноутбуков, на стационарных компьютерах используются устройства бесперебойного питания, а если терминал работает на специальном выделенном сервере, то там тем более приняты все меры по обеспечению бесперебойного питания. Так что и без функции GlobalVariableFlush() глобальные переменные являются достаточно надежным средством сохранения данных.    

Временная переменная, функция GlobalVariableTemp

Функция GlobalVariableTemp() выполняет создание временной глобальной переменной (которая будет существовать только до завершения работы терминала). За несколько лет разработки экспертов на MQL5 у автора статьи ни разу не возникало необходимости в использовании такой переменной. Более того, само явление временной глобальной переменой противоречит основному принципу, ради которого они используются — для длительного хранения данных независимо от перезапуска терминала. Но поскольку функция существует в языке MQL5, уделим ей немного внимания, на случай, если она всё-таки кому-нибудь пригодится.

При вызове функции в нее передается один параметр — имя переменной. Если переменой с таким именем не существует, то будет создана временная переменная со значением 0. После этого ей нужно присвоить значение функцией GlobalVariableSet() и дальше пользоваться ею как обычно. Если же переменная уже существовала (была создана ранее функцией GlobalVariableSet()), она не будет преобразована во временную:

   GlobalVariableSet("test",1.2); // установка переменной значения, чтобы убедиться в том, что переменная существует
   GlobalVariableTemp("temp"); // создание временной переменной
   Alert("Значение переменной temp сразу после создания - ",GlobalVariableGet("temp"));
   GlobalVariableSet("temp",3.4); // установка значения временной переменной
   GlobalVariableTemp("test"); // попытка преобразовать переменную "test" во временную

В приложении данный пример находится в файле "sGVTestTemp". После запуска скрипта откройте окно глобальных переменных, в нем должна быть переменная "temp" со значением 3.4 и "test" со значением 1.2. Закройте окно глобальных переменных, перезапустите терминал и снова откройте окно. Переменная "test" сохранится, а "temp" исчезнет.

Изменение переменной по условию, функция GlobalVariableSetOnCondition

Осталось рассмотреть последнюю и, на мой взгляд, самую интересную функцию: GlobalVariableSetOnCondition(). В функцию передается три параметра: имя, новое значение и проверочное значение. Если переменная имеет значение, равное проверочному, ей устанавливается новое значение и функция возвращает true, иначе она возвращает false (кроме того, false возвращается и при отсутствии переменной).

По принципу своей работы функция идентична следующему коду:

   double check_value=1;
   double value=2;

   if(GlobalVariableGet("test")==check_value){
      GlobalVariableSet("test",value);
      return(true);
   }
   else {
      return(false);
   }

Если значение глобальной переменной "test" равно значению check_value, ей устанавливается значение value и возвращается значение true, иначе — возвращается значение false. Переменной check_value по умолчанию присвоено значение 1, чтобы в случае отсутствия глобальной переменной "test" конструкция возвращала значение false.

Основное назначение функции GlobalVariableSetOnCondition() — обеспечение последовательного выполнения нескольких экспертов. Поскольку современная операционная система является многозадачной, а каждый эксперт представляет собой отдельный поток, нет гарантии, что один эксперт за раз выполнит всю свою работу от начала до конца, затем это же сделает второй, и т.д.

Кто раньше имел дело с терминалом MetaTrader 4, могут помнить такое явление, как занятость торгового потока. Сейчас несколько экспертов могут одновременно отсылать торговые заявки на сервер, и они будут исполнены, а тогда в один момент только один эксперт мог отсылать заявку. Если в терминале работало несколько экспертов, при попытках выполнения рыночных операций часто возникала ошибка занятости торгового потока. При открытии и закрытии ордеров ошибка не доставляла неприятностей, правильно написанный эксперт повторит попытку закрытия или открытия позиции, да и само совпадение моментов открытия и закрытия у разных экспертов — нечастое явление. Если же у нескольких экспертов активизировалась функция трейлинг-стопа (имеется в виду функция в эксперте, а не стандартная функция терминала), на одном тике только у одного эксперта удавалось выполнить модификацию стоп-лосса, а это уже становилось проблемой. Несмотря на отсутствие данной проблемы в настоящее время, все равно могут возникнуть задачи, требующие последовательного выполнения некоторых экспертов. 

Для обеспечения последовательной работы группы экспертов мы и используем описываемую глобальную переменную. В начале выполнения функции OnTick() присвоим переменной значение value, после этого другие эксперты, выполнив проверку значения переменной и увидев в ней значение value, поймут, что какой-то эксперт уже работает, и прервут выполнение своих функций OnTick() или войдут в ожидающий цикл. После завершения всех действий эксперта мы присвоим переменной значение check_value, и теперь уже другой советник может начать выполнение своей функции OnTick(), и т.д.

В принципе, вышеприведенный код подходит для решения данной задачи, но он не дает гарантии, что после выполнения строки:

if(GlobalVariableGet("test")==check_value){

будет сразу выполнена строка: 

GlobalVariableSet("test",value);

Вполне возможно, что между ними "вклинится" другой эксперт, "увидит", что переменная имеет значение check_value, начнет свою работу, выполнит часть ее, но в это время продолжит работу первый эксперт. Таким образом параллельно фактически будут работать два эксперта. Для решения этой проблемы и нужна функция GlobalVariableSetOnCondition(). Как написано в документации, "функция обеспечивает атомарный доступ к глобальной переменной". Атомарный означает "неделимый". Это значит, что между проверкой значения переменной и установкой ей нового значения гарантированно не вклинится никакая другая программа.

Один неудобный момент при использовании функции состоит в том, что она сама не создает переменную при ее отсутствии. Значит, нужно выполнять дополнительную проверку (желательно делать это при инициализации эксперта) и создавать переменную. 

Напишем двух советников для эксперимента. Оба эксперта совершенно идентичны, в начале функции OnTick() открывается окно с с сообщением "Начало EA1", делается пауза в три секунды (при помощи функции Sleep()), в конце выводится сообщение "Окончание EA1":

void OnTick(){
   Alert("Начало EA1");   
   Sleep(3000);   
   Alert("Окончание EA1");
}

Второй эксперт идентичен, только сообщения будут другими: "Начало EA2" и "Окончание EA2". В приложении эксперты имеют имена "eGVTestEA1" и "eGVTestEA2". Откройте в терминале два одинаковых графика и прикрепите на них экспертов. По окну сообщений увидим, что эксперты начинают и заканчивают работу одновременно, то есть работают параллельно (рис. 8).


Рис. 8. Сообщения экспертов о начале из окончании выполнения функции OnTick()

Теперь применим функцию GlobalVariableSetOnCondition() для обеспечения последовательной работы экспертов. Доработка будет совершенно идентична для обеих экспертов, поэтому будем писать код в подключаемом файле. В приложении файл имеет имя "GVTestMutex.mqh".

Рассмотрим функции файла "GVTestMutex.mqh". При инициализации эксперта нужно проверить существование глобальной переменной, создать ее, если надо (функция Mutex_Init()), в функцию будет передаваться один параметр — имя переменной:

void Init(string name){
   if(!GlobalVariableCheck(name)){
      GlobalVariableSet(name,0);
   }
}

Второй функцией будет функция проверки: Mutex_Check(). В функции будет выполняться цикл с ожиданием освобождения глобальной переменной. Как только переменная освободится, функция вернет true, и эксперт продолжит выполнение своей функции OnTick(). Если же дождаться освобождения переменной за заданное время не удастся, функция вернет false, при этом надо будет прервать выполнение функции OnTick():

bool Mutex_Check(string name,int timeout){   
   datetime end_time=TimeLocal()+timeout; // время окончания ожидания
   while(TimeLocal()<end_time){ // цикл в течение заданного времени
      if(IsStopped()){
         return(false); // если эксперт снимают с графика
      }
      if(GlobalVariableSetOnCondition(name,1,0)){ 
         return(true);
      }
      Sleep(1); // небольшая пауза
   }   
   return(false); // не удалось дождаться освобождения
}

В функцию передается имя глобальной переменной и время ожидания в секундах.

Третья функция — Mutex_Release(), в ней глобальной переменной устанавливается значение 0, то есть "отпускание", чтобы другие эксперты могли начать свою работу:

void Mutex_Release(string name){
   GlobalVariableSet(name,0);
}

Сделаем копию одного эксперта, подключим к нему файл и добавим в него вызов функций. Имя переменной будет "mutex_test", функцию Mutex_Check() будем вызывать с таймаутом 30 сек, ниже приведен полный код эксперта:

#include <GVTestMutex.mqh>

int OnInit(){
   Mutex_Init("mutex_test");
   return(INIT_SUCCEEDED);
}

void OnTick(){

   if(!Mutex_Check("mutex_test",30)){
      return;
   }

   Alert("Начало EA1");
   Sleep(3000);
   Alert("Окончание EA1");

   Mutex_Release("mutex_test");

}

Сделаем копию этого эксперта и изменим текст выводимых сообщений. В приложении эксперты имеют имена "eGVTestEA1-2" и "eGVTestEA2-2". Запустим этих экспертов на двух одинаковых графиках и увидим, что теперь они работают по очереди (рис. 9).

 
Рис. 9. Эксперты работают по очереди

Обратите внимание на параметр тайм-аута: необходимо выставлять такое время, которое заведомо значительно больше времени работы всех экспертов в группе. Может произойти так, что какой-то эксперт оказался снятым с графика в процессе выполнения функции OnTick() но функция Mutex_Release() не выполнилась. В этом случае уже никакие эксперты не дождутся своей очереди. Поэтому на случай истечения тайм-аута необходимо установить глобальной переменной значение 0 или же каким-то другими способом следить за этим. Это зависит от конкретной задачи — допустимо ли иногда параллельное выполнение экспертов, или же необходима только последовательная их работа.

Класс для удобной работы с глобальными переменными

Для обеспечения удобной работы с глобальными переменными необходимо уделить внимание следующим пунктам.

  1. Нужны уникальные имена переменных для каждого экземпляра эксперта.
  2. Необходимо обеспечить отличие имен переменных при работе в тестере от имен переменных при работе на счете.
  3. Если эксперт работает в тестере, то по завершению каждого одиночного тестирования эксперт должен удалять все переменные, которые он создал в процессе этого тестирования.
  4. Обеспечить более удобный вызов функций по работе с глобальными переменными, чтобы имена функций были более короткими.
При использовании глобальных переменных в эксперте можно условно выделить два типа переменных: общие и привязанные к ордерам. Общие переменные используются для хранения какой-то общей информации, касающейся работы эксперта: например, время наступления какого-то события, максимальную прибыль группы позиций и т.п. Переменные, привязанные к ордерам, содержат дополнительную информацию, касающуюся только одного ордера (или позиции): например, индекс ордера в ряду прогрессии лотов, цену открытия при запросе и т.п. Каждый ордер имеет свой уникальный номер — тикет, поэтому достаточно сформировать имя из тикета ордера и части, определяющей наименование данных, сохраняемых в переменной (например "index", "price"). Тикет ордера — это переменная типа ulong, его максимальная длина 20 знаков, максимальная длина переменной — 63 знака, так что в нашем распоряжении остается еще 43 знака.

С формированием имен для общих переменных дело обстоит немного сложнее. Произведем грубую оценку возможной длины переменной. Первый признак, по которому можно отделить переменные одного эксперта от другого, — это имя эксперта, допустим, из 20 знаков. Одинаковые эксперты могут работать на разных символах. Значит, второй уникальный признак — это символ (еще 4 знака). Эксперты, работающие на одном символе, отличаются идентификаторами ордеров — магическими номерами, они имеют тип ulong (максимальная длина — 20 знаков). Из одного терминала может выполняться переключение между счетами, номер счета — это переменная типа long (максимальная длина — 19 знаков). Итак, в сумме мы получаем 63 знака. Но это, согласно заданным условиям, вся разрешенная длина переменной, а ведь это только длина префикса!

Значит, придется чем-то пожертвовать. Будем придерживаться правила: один терминал работает только с одним счетом. Если у вас несколько счетов, для каждого устанавливайте свой экземпляр терминала. Значит, мы можем избавиться от номера счета, при этом максимальный размер префикса сокращается до 43 знаков, получаем 20 свободных знаков. Можно придерживаться еще одного правила: не использовать длинные магические номера. Наконец, нелишним будет и уделять внимание именам экспертов, давать им более короткие имена. Имя глобальной переменной, формируемое из имени эксперта, символа и магика можно считать приемлемым. Не исключено, что кто-то сможет придумать для себя более удобный способ формирования имен, но в этой статье будем придерживаться данного способа.

Начнем писать класс. Имя класса будет "CGlobalVariables", имя файла "CGlobalVariables.mqh" в приложении. В разделе private объявим две переменных для префиксов, одну — для общих переменных, вторую — для привязанных к ордерам:

class CGlobalVariables{
   private:
      string m_common_prefix; // префикс общих переменых
      string m_order_prefix; // префикс ордерных переменных
   public:
      // конструктор
      void CGlobalVariables(){}
      // деструктор
      void ~CGlobalVariables(){}
}

В секции public создадим метод Init(). Метод будет вызываться при инициализации эксперта, в него будет передаваться два параметра: символ и магический номер. В этом методе будут формироваться префиксы. Префиксы ордерных переменных просты, нужно только отделить переменные эксперта, работающего на счете от эксперта, работающего в тестере. Значит, ордерные переменные на счете будут начинаться с префикса "order_", а в тестере — с "tester_order_". К префиксу общих переменных в тестере добавим только "t_" (они сами по себе уникальны, плюс к тому надо экономить на количестве символов). Еще при инициализации в тестере необходимо выполнить удаление старых глобальных переменных. Конечно, их надо будет удалять и при деинициализации, но кто знает, как могло быть завершено тестирование: вдруг переменные остались. Пока просто создадим метод DeleteAll() и вызовем его. Расположить этот метод лучше в секции private, код для него мы напишем позже. Далее приведен код метода Init():

void Init(string symbol,int magic){
   m_order_prefix="order_";
   m_common_prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"+symbol+"_"+IntegerToString(magic)+"_";
   if(MQLInfoInteger(MQL_TESTER)){
      m_order_prefix="tester_"+m_order_prefix;
      m_common_prefix="t_"+m_common_prefix;
      DeleteAll();
   }         
}

Сразу добавим метод, возвращающий префикс общих переменных, вдруг он пригодится для какой-то особой работы с глобальными переменными:

string Prefix(){
   return(m_common_prefix);
} 

Добавим основные методы: для проверки, установки, получения значения и удаления отдельных переменных. Поскольку у нас два префикса, то для каждой функции будет два метода с перегрузкой имен (по две функции с одинаковыми именами, но разным набором параметров). В одну группу методов будет передаваться только один параметр — имя переменой, это методы для общих переменных. В другую группу функций будет передаваться тикет и имя переменой, это методы для ордерных переменных:

// для общих переменных
bool Check(string name){
   return(GlobalVariableCheck(m_common_prefix+name));
}
void Set(string name,double value){
   GlobalVariableSet(m_common_prefix+name,value);      
}      
double Get(string name){
   return(GlobalVariableGet(m_common_prefix+name));
} 
void Delete(string name){
   GlobalVariableDel(m_common_prefix+name); 
}
// для ордерных переменных
bool Check(ulong ticket,string name){
   return(GlobalVariableCheck(m_order_prefix+IntegerToString(ticket)+"_"+name));
}
void Set(ulong ticket,string name,double value){
   GlobalVariableSet(m_order_prefix+IntegerToString(ticket)+"_"+name,value);      
}      
double Get(ulong ticket,string name){
   return(GlobalVariableGet(m_order_prefix+IntegerToString(ticket)+"_"+name));
} 
void Delete(ulong ticket,string name){
   GlobalVariableDel(m_order_prefix+IntegerToString(ticket)+"_"+name); 
} 

Вернемся к методу DeleteAll(), напишем код для удаления переменных по префиксам:

GlobalVariablesDeleteAll(m_common_prefix);
GlobalVariablesDeleteAll(m_order_prefix);  

Это удаление надо будет выполнять в тестере по завершению тестирования,  поэтому добавим метод Deinit() который будет вызываться при деинициализации эксперта:

 void Deinit(){
    if(MQLInfoInteger(MQL_TESTER)){
        DeleteByPrefix();
    }
 }

Для повышения надежности глобальных переменных необходимо использовать функцию GlobalVariablesFlush(), поэтому добавим еще один метод с этой функцией. Вызывать метод класса гораздо проще, чем писать длинное имя функции (выполнение требований пункта 4):

void Flush(){
   GlobalVariablesFlush();
} 

Иногда бывает нужно объединить общие переменные в группы путем добавления к ним дополнительных префиксов и в процессе работы эксперта удалять эти группы. Добавим еще один метод DeletByPrefix():

void DeleteByPrefix(string prefix){
   GlobalVariablesDeleteAll(m_common_prefix+prefix);
}

В результате получен достаточный минимум функциональности класса, но который позволит решить до 95% задач по работе с глобальными переменными.

Для использования класса к эксперту необходимо подключить файл:

#include <CGlobalVariables.mqh>

Создать объект:

CGlobalVariables gv;

При инициализации эксперта вызвать метод Init() передавая ему символ и магический номер:

gv.Init(Symbol(),123);

При деинициализации вызвать метод Deinit(), чтобы из тестера переменные были удалены:

gv.Deinit();

После этого, в процессе разработки эксперта, остается только пользоваться методами Check(), Set(), Get(), Delete() передавая им только уникальную часть имени переменной, например:

   gv.Set("name1",123.456);
   double val=gv.Get("name1");

В результате работы этого эксперта в списке глобальных переменных терминала можно увидеть переменную с именем "eGVTestClass_GBPJPY_123_name1", рис. 10.

 
Рис. 10. Фрагмент окна глобальных переменных с переменной, созданной при помощи класса CGlobalVariables

Длина переменной 29 символов, значит, имеется очень большой запас, и при использовании данного класса можно быть вполне свободным при выборе имен для переменных.  Для ордерных переменных еще надо передавать тикет ордера, но уже без необходимости каждый раз формировать полное имя и вызывать функцию IntegerToSTring() для преобразования тикета в строку, что значительно упрощает использование глобальных переменных. Пример использования класса находится в приложении в эксперте с именем "eGVTestClass".

Можно немного изменить класс, чтобы пользоваться им было еще проще. Доработаем конструктор и деструктор класса. В конструктор добавим вызов метода Init() и соответственно параметры, в деструктор добавим вызов метода Deinit():

void CGlobalVariables(string symbol="",int magic=0){
   Init(symbol,magic);
}
// деструктор
void ~CGlobalVariables(){
   Deinit();
}

После этого нет необходимости вызывать методы Init() и Deinit(), достаточно указать символ и магический номер при создании экземпляра класса:

CGlobalVariables gv(Symbol(),123);

Заключение

В статье подробно рассмотрены все функции по работе с глобальными переменными терминала, в том числе очень подробно рассмотрена функция GlobalVariableSetOnCondition(). Создан класс, значительно упрощающий использование глобальных переменных при создании экспертов. Конечно, класс этот включает в себя не все возможности по работе с глобальными переменными, а только абсолютно необходимые и наиболее часто используемые. В случае интереса к подходу по работе с глобальными переменными, изложенному в статью вы сможете доработать предложенный класс или создать свой. 

Файлы приложения

  • sGVTestCreate — создание переменной.
  • sGVTestGet1 — первый вариант получения значения.
  • sGVTestGet2  — второй вариант получения значения.
  • sGVTestGet1-2 — первый вариант получения значения несуществующей переменной.
  • sGVTestGet2-2 — второй вариант получения значения несуществующей переменной.
  • sGVTestCheck — проверка существования переменной.
  • sGVTestTime — получение времени переменной.
  • sGVTestAllNames — получение списка имен всех переменных.
  • sGVTestAllNames2 — получение списка имен с заданным префиксом.
  • sGVTestDelete — удаление переменной.
  • sGVTestCreate4 — создание четырех переменных (две группы по две переменные).
  • sGVTestDeleteGroup — удаление одной группы переменных.
  • sGVTestFlush — принудительное сохранение переменных.
  • sGVTestTemp — создание временной переменной.
  • eGVTestEA1, eGVTestEA2 — демонстрация параллельной работы экспертов.
  • GVTestMutex.mqh — функции для создания мьютекса.
  • eGVTestEA1-2, eGVTestEA1-2 — демонстрация последовательной работы экспертов.
  • CGlobalVariables.mqh — класс CGlobalVariables для работы с глобальными переменными.
  • eGVTestClass — эксперт с примером использования класса CGlobalVariables.