Русский

Учебник по MQL4  Переменные  Переменные GlobalVariables

Переменные GlobalVariables


В клиентском терминале может работать несколько прикладных программ одновременно. В ряде случаев возникает необходимость передать какие-то сведения из одной программы в другую. Специально для этого в языке MQL4 созданы глобальные переменные клиентского терминала.

Глобальная переменная клиентского терминала - переменная, значение которой доступно из всех прикладных программ, запущенных на клиентском терминале (сокращённо - GV-переменная).


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

Свойства GV-переменных


В отличие от других переменных, GV-переменная может быть не только создана из любой программы, но и удалена. Значение GV-переменной сохраняется на жёстком диске компьютера и после закрытия клиентского терминала. Однажды объявленная GV-переменная существует в клиентском терминале в течение 4 недель с момента последнего обращения. Если в течение указанного срока ни одна из программ не обращалась к GV-переменной, то она удаляется клиентским терминалом. GV-переменная может иметь только тип double.

Функции для работы с GV-переменными


Для работы с GV-переменными в MQL4 имеются ряд функций (см. также Глобальные переменные GlobalVariables). Рассмотрим те из них, которые понадобятся нам в последующих примерах.

Функция GlobalVariableSet()

datetime GlobalVariableSet( string name, double value)

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

Параметры:

name - Имя глобальной переменной.

value - Новое числовое значение.


Функция GlobalVariableGet()

double GlobalVariableGet( string name)

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

Параметры:

name - Имя глобальной переменной.


Функция GlobalVariableDel()

bool GlobalVariableDel( string name)

Функция удаляет глобальную переменную. При успешном удалении функция возвращает TRUE, иначе FALSE. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetLastError().

Параметры:

name - Имя глобальной переменной.


Чтобы продемонстрировать насколько удобным и полезным может быть использование GV-переменных, решим следующую задачу:

Задача 24. В терминале одновременно работают несколько экспертов. Сумма депозита составляет $10 000. Общая стоимость всех открытых ордеров не должна превышать 30% от суммы депозита. Каждому эксперту должно быть выделено равное количество денежных средств. Составить программу эксперта, содержащую расчёт суммы, выделенной для торговли.

Собственно расчёт суммы, которая выделяется эксперту для торговли, не представляет никакой сложности. Однако, для того, чтобы такой расчёт выполнить, необходимо знать, какое количество экспертов одновременно запущено в клиентском терминале. В MQL4 не существует функции, которая помогла бы ответить на этот вопрос. Единственная возможность для подсчёта количества одновременно запущенных программ состоит в том, чтобы каждая из программ заявила о себе путём изменения значения некоторой GV-переменной. В дальнейшем, все программы, заинтересованные в такой информации, могут обратиться к этой переменной и по её значению судить о текущем положении дел.

Здесь нужно заметить, что в общем случае не каждая программа предназначена для решения подобной задачи. Если в клиентском терминале появится эксперт, не заявляющий о своём присутствии, то, естественно, он никак не будет учтён. Поэтому в данном случае сама постановка задачи предполагает использование только тех экспертов, которые содержат необходимый код - и для изменения значения GV-переменной, и для последующего считывания значения этой переменной.

Ниже представлен эксперт, демонстрирующий использование GV-переменных (globalvar.mq4), который может быть использован для решения Задачи 24:

//--------------------------------------------------------------------
// globalvar.mq4
// Предназначен для использования в качестве примера в учебнике MQL4.
//--------------------------------------------------------------------
int    Experts;                                 // Колич. экспертов
double Depo=10000.0,                            // Заданный депозит
       Persent=30,                              // Заданный процент    
       Money;                                   // Искомые средства
string Quantity="GV_Quantity";                  // Имя GV-переменной
//--------------------------------------------------------------------
int init()                                      // Спец. функция init
  {
   Experts=GlobalVariableGet(Quantity);         // Получим тек. знач.
   Experts=Experts+1;                           // Колич. экспертов
   GlobalVariableSet(Quantity, Experts);        // Новое значение
   Money=Depo*Persent/100/Experts;                // Средства для эксп.
   Alert("Для эксперта в окне ", Symbol()," выделено ",Money);
  
return;                                      // Выход из init()
  }
//--------------------------------------------------------------------
int start()                                     // Спец. функция start
  {
   int New_Experts= GlobalVariableGet(Quantity);// Новое колич. эксп.
   if (Experts!=New_Experts)                    // Если изменилось
     {
      Experts=New_Experts;                      // Теперь текущ. такое
      Money=Depo*Persent/100/Experts;             // Новое знач. средств
      Alert("Новое значение для эксперта ",Symbol(),": ",Money);
    
}
   /*
   ...
   Здесь долен быть указан основной код эксперта,
   в котором используется значение переменной Money
   ...
   */

   return;                                      // Выход из start()
  }
//--------------------------------------------------------------------
int deinit()                                    // Спец. ф-ия deinit
  {
   if (Experts ==1)                             // Если эксперт один..
      GlobalVariableDel(Quantity);              //..удаляем GV-перемен
   else                                         // А иначе..
      GlobalVariableSet(Quantity, Experts-1);   //..уменьшаем на 1
   Alert("Эксперт выгружен из окна ",Symbol()); // Сообщ. о выгрузке
   return;                                      // Выход из deinit()
  }
//--------------------------------------------------------------------

Этот эксперт содержит три специальные функции. Кратко напомним, что все специальные функции запускаются на исполнение клиентским терминалом: функция init() - при присоединении эксперта к окну финансового инструмента, функция deinit() - при выгрузке эксперта из окна, а функция start() - потиково. В головной части программы объявлены глобальные переменные (областью видимости этих переменных является программа).

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

string Quantity = "GV_Quantity";                // Имя GV-переменной
Обратите внимание, имя глобальной переменной клиентского терминала может быть вычислено в исполняющейся программе (имена всех других видов переменных задаются программистом на этапе создания кода).

Разберём подробно, как изменяется и обрабатывается значение переменной Quantity в процессе выполнения программы. Прежде всего, эксперт, присоединяемый к окну финансового инструмента, должен заявить о своём присутствии, чтобы другие эксперты, работающие в терминале, могли об этом событии узнать. Это необходимо сделать как можно раньше (как можно ближе к тому моменту, когда эксперт присоединён). Наиболее подходящее для этого место в программе - специальная функция init(). В первой строке этой функции эксперт запрашивает текущее значение переменной Quantity, для этого используется функция GlobalVariableGet():

   Experts = GlobalVariableGet(Quantity);       // Получим тек. знач.

Теперь значение GV-переменной Quantity, каким бы оно ни было на момент присоединения эксперта, должно быть увеличено на 1. Это означает, что только что присоединяемый эксперт увеличил общее количество экспертов, одновременно работающих в терминале, на один:

   Experts = Experts+1;                         // Колич. экспертов

Глобальная переменная Experts используется в программе для удобства. Её значение недоступно другим экспертам. Чтобы изменить значение GV-переменной Quantity, необходимо воспользоваться функцией GlobalVariableSet(), устанавливающей новое значение GV-переменной:

   GlobalVariableSet(Quantity, Experts);        // Новое значение

Это означает, что GV-переменной Quantity присвоено новое значение Experts. Теперь это новое значение GV-переменной доступно всем работающим в терминале программам. Вслед за этим рассчитывается и искомая сумма, выделенная для торговли только что присоединённому эксперту, и делается сообщение (здесь сообщения необходимы только для того, чтобы проиллюстрировать в какой момент времени в каком эксперте осуществляются те или иные события; в реально работающей программе сообщения используются по необходимости).

   Money = Depo*Persent/100/Experts;              // Средства для эксп.
   Alert("Для эксперта в окне ", Symbol()," выделено ",Money);

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

Теперь, в рамках решения поставленной задачи, целью эксперта является отслеживание текущего количества присоединённых экспертов - ведь могут быть присоединены или выгружены другие эксперты, а значит, общее количество одновременно работающих экспертов может измениться. В зависимости от этого наш эксперт должен пересчитать сумму средств, выделяемых ему по условию задачи. Поэтому, первое, что делает эксперт на каждом новом тике, - это запрашивает новое значение GV-переменной Quantity:

   int New_Experts= GlobalVariableGet(Quantity);// Новое колич. эксп.

и если это новое значение New_Experts не совпадает с последним известным значением Experts, то новое известное значение теперь учитывается как текущее, выполняется пересчёт средств, выделяемых эксперту для торговли (Money), а также делается соответствующее сообщение:

   if (Experts != New_Experts)                  // Если изменилось
     {
      Experts = New_Experts;                    // Теперь текущ. такое
      Money = Depo*Persent/100/Experts;           // Новое знач. средств
      Alert("Новое значение для эксперта ",Symbol(),": ",Money);
    
}

Если же переменные New_Experts и Experts совпадают, то этот расчёт не производится, а в дальнейшем коде эксперта (в функции start()) используется ранее вычисленное значение переменной Money. Так, в зависимости от ситуации, на каждом тике высчитывается новое или сохраняется имеющееся значение Money.

На этапе выгрузки каждый эксперт, принимающий участие в вычислениях в рамках Задачи 24, должен сообщить другим экспертам, что он выгружен, т.е. количество одновременно работающих экспертов уменьшилось. Более того, если этот эксперт последний, то необходимо удалить и саму GV-переменную. О выгрузке эксперта однозначно свидетельствует факт исполнения специальной функции deinit(), поэтому соответствующий код должен быть расположен именно в этой функции:

int deinit()                                    // Спец. ф-ия deinit
{
if (Experts ==1) // Если эксперт один..
GlobalVariableDel(Quantity); //..удаляем GV-перемен
else // А иначе..
GlobalVariableSet(Quantity, Experts-1); //..уменьшаем на 1
Alert("Эксперт выгружен из окна ",Symbol()); // Сообщ. о выгрузке
return; // Выход из deinit()
}

Все вычисления в специальной функции deinit() производятся в рамках одного оператора if. Если количество экспертов равно 1, т.е. этот эксперт последний, то исполняется удаление GV-переменной с помощью функции GlobalVariableDel(), а во всех остальных случаях (т.е. когда количество экспертов больше 1) с помощью функции GlobalVariableSet() для переменной Quantity устанавливается новое значение, которое на 1 меньше текущего. Эксперты, оставшиеся загруженными, в начале исполнения специальной функции start(), отследят новое значение переменной Quantity и пересчитают искомое значение переменной Money.

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

   Experts = GlobalVariableGet(Quantity);       // Получим тек. знач.
   int New_Experts= GlobalVariableGet(Quantity);// Новое колич. эксп.

Рекомендуется скомпилировать и запустить на исполнение globalvar.mq4 в нескольких окнах различных валютных инструментов. В зависимости от последовательности событий, в окне функции Alert можно наблюдать соответствующие сообщения, например, такие:


Рис. 55. Сообщения в окне функции Alert в результате последовательной загрузки и выгрузки
эксперта globalvar.mq4 в окна трёх различных финансовых инструментов.

В клиентском терминале имеется возможность открыть панель "Глобальные переменные", на которой в режиме реального времени можно увидеть все открытые на текущий момент GV-переменные и их значения. Эта панель доступна через меню клиентского терминала Сервис >> Глобальные переменные (клавиша F3):


Рис. 56. Состояние панели глобальных переменных клиентского терминала в момент, когда
в клиентском терминале было одновременно запущено три эксперта globalvar.mq4.

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

Ошибки при использовании GV-переменных


Если запустить эксперт globalvar.mq4 в окнах нескольких финансовых инструментов и не торопясь последовательно отслеживать все события, то легко убедиться, что представленный код успешно работает. Однако это происходит только в том случае, если паузы между событиями достаточно большие. Обратите внимание на условие оператора if в функции deinit():

   if (Experts ==1)                             // Если эксперт один..

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



Рис. 57. Выгрузка советника из окна EUR/USD до третьего тика.

На Рис. 57 представлено развитие событий, касающихся значения глобальной переменной клиентского терминала Quantity. Проследим, как будет изменяться это значение в зависимости от происходящих событий. Пусть запуск на исполнение эксперта в окне EUR/USD произошел в момент t 0. В этот момент GV-переменной Quantity ещё не существует. В период t 0 - t 1 исполняется специальная функция init() эксперта, в результате чего создаётся GV-переменная Quantity, значение которой в момент t 1 равно 1. Последующие тики по валютному инструменту EUR/USD запускают на исполнение специальную функцию start(). Однако в период t 0 - t 6 в клиентском остаётся всего один эксперт, поэтому значение значение GV-переменной Quantity не изменяется.

В момент t 6 в окно финансового инструмента GBP/USD присоединяется второй эксперт. В результате исполнения его функции init() значение GV-переменной Quantity изменяется и в момент t 7 становится равным 2. После этого в момент t 8 в окно USD/CHF присоединяется третий эксперт, в результате чего в момент t 9 значение GV-переменной Quantity становится равным 3.

В момент t 10 трейдер принимает решение о выгрузке советника из окна EUR/USD. Обратите внимание, последний раз переменная Experts в эксперте, работающем в этом окне, была изменена в период исполнения функции start(), запущенной на втором тике, т.е. в период t 4 - t 5. В момент t 10 значение переменной Experts в эксперте, работающем в окне EUR/USD, остаётся равным 1. Поэтому при исполнении специальной функции deinit() этого эксперта GV-переменная Quantity будет удалена в результате исполнения строк:

   if (Experts ==1)                             // Если эксперт один..
GlobalVariableDel(Quantity); //..удаляем GV-перемен

Таким образом, несмотря на то, что в клиентском терминале остаются подключёнными два эксперта, GV-переменная удалена! Нетрудно понять, какие последствия это событие будет иметь для расчётов в работающих экспертах. При исполнении функции start() в этих экспертах будет установлено, что текущее значение переменной New_Experts равно нулю, поэтому и новое текущее значение переменной Experts также обнулится. В результате окажется, что значение переменной Money вычислить не представляется возможным, т.к. в формуле, используемой для расчёта, переменная Experts стоит в знаменателе. Таким образом, дальнейшие расчёты в экспертах пойдут по ложному пути со всеми вытекающими последствиями.

Кроме того, при исполнении функции deinit() экспертов (при выгрузке из окон GBP/USD и USD/CHF) GV-переменная будет снова открыта, но получит значение -1 (минус единица) после выгрузки одного из них, и -2 при выгрузке последнего. Это приведёт к получению отрицательного значения переменной Money. Важным также является то, что после выгрузки всех экспертов GV-переменная Quantity останется открытой в клиентском терминале и в будущем окажет влияние на работу всех экспертов, интересующихся её значением.

Возможен и другой сценарий развития событий. На Рис. 58 показано как изменится значение GV-переменной в случае, если перед выгрузкой первого эксперта успеет случиться ещё один тик.



Рис. 58. Выгрузка советника из окна EUR/USD после третьего тика.


События, отраженные на Рис. 58, в период t 0 - t 9 полностью совпадают с событиями, отражёнными на Рис. 57. Согласно представленной диаграмме, в момент t 12 по финансовому инструменту EUR/USD поступает третий тик, в результате чего при исполнении специальной функции start() значение переменной Experts изменится и станет равным 3. Это значит, что при выгрузке эксперта из окна EUR/USD, в результате исполнения функции deinit() значение GV-переменной Quantity будет установлено равным 2, т.е. правильно отражающим количество экспертов, оставшихся в работе.

На основании этих рассуждений можно сделать вывод, что эксперт globalvar.mq4 составлен некорректно. Алгоритмическая ошибка в данном случае состоит в том, что для анализа ситуации в функции deinit() используется значение переменной Experts, не отражающей действительное количество одновременно работающих экспертов во всех без исключения случаях. Для случая, показанного на Рис. 58, значение переменной Experts оказывается справедливым, а для случая, представленного на Рис. 57, - нет. Таким образом, общий результат работы эксперта находится в зависимости от случайных событий, а именно, от последовательности прихода тиков по финансовым инструментам, с которыми работает эксперт.

В данном случае исправить ошибку просто. Достаточно обновить значение переменной Experts перед анализом (перед исполнением оператора if):

int deinit()                                    // Спец. ф-ия deinit
{
Experts = GlobalVariableGet(Quantity); // Получим тек. знач.
if (Experts ==1) // Если эксперт один..
GlobalVariableDel(Quantity); //..удаляем GV-перемен
else // А иначе..
GlobalVariableSet(Quantity, Experts-1); //..уменьшаем на 1
Alert("Эксперт выгружен из окна ",Symbol()); // Сообщ. о выгрузке
return; // Выход из deinit()
}

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

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