Выдерживаем паузу между торговыми операциями
В клиентском терминале MetaTrader 4 в отличии от MetaTrader 3 нет жесткого ограничения на паузу между торговыми операциями, совершаемых советниками. Поэтому теоретически можно совершать хоть десятки сделок в секунду. Однако чрезмерное злоупотребление этим приведет к тому, что Ваш счет будет заблокирован для торговли.
Это произойдет или потому, что сервер подумает, что такое количество запросов в секунду может осуществлять только злоумышленник, который ставит перед собой лишь цель "завалить" торговый сервер. Или потому, что сотнями запросами в минуту Вы разъярите дилера - сотрудника дилингового центра, которому приходится обрабатывать Ваши запросы.
Считается правилом хорошего тона не совершать торговые операции чаще, чем 1 раз в 5-10 секунд. Конечно, в любом правиле могут быть исключения и иногда Вам крайне необходимо совершить операции с меньшим временным лагом, но старайтесь этой возможностью не злоупотреблять.
Старайтесь выдерживать паузу минимум в 5 секунд. В этом Вам поможет моя функция WaitBeforeTransaction()
//+--------------------------------------------------------------------------------------------------+
//| Фунцкия WaitBeforeTransaction выдерживает паузу Secs секунд |
//| между торговыми операциями эксперта (по умолчанию 5 секунд) |
//| |
//| Возвращает: |
//| 1 - если пауза выдержена без ошибок |
//| 0 - если эксперт был остановлен |
//| -1 - если произошла какая-то ошибка |
//+-------------------------------------------------------------------------------------------------+
int WaitBeforeTransaction(int Secs = 5)
{
// если режим тестирования, то ждать необязательно
if (IsTesting()) return(1);
// если глобальная переменная LastTradeTime не существует,
// то создать ее
if (!GlobalVariableCheck("LastTradeTime"))
{
// Если произошла какя-то ошибка при вызове функции
// GlobalVariableCheck(), выйдем с ошибкой
if (GetLastError()!=0)
{
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при проверке глобальной переменной LastTradeTime");
return(-1);
}
// глобальная переменная не существует, создадим ее
if (GlobalVariableSet("LastTradeTime", 1)==0)
{
// произошла ошибка при создании глобальной переменной
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при создании глобальной переменной LastTradeTime");
return(-1);
}
// глобальная переменная успешно создана
Print("WaitBeforeTransaction(): глобальная переменная ",
"LastTradeTime создана");
}
// получим время последней операции
datetime LastTradeTime;
LastTradeTime = GlobalVariableGet("LastTradeTime");
// если произошла ошибка (равна нулю), то выходим с ошибкой
if (LastTradeTime==0)
{
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при чтении глобальной переменной LastTradeTime");
return(-1);
}
// ждем Secs секунд
while(true)
{
// если эксперт остановлен, выйдем со значением 0
if (IsStopped())
{
Print("WaitBeforeTransaction(): эксперт остановлен. Выходим...");
return(0);
}
// если прошло меньше Secs секунд, то ждем
if ((LocalTime()-LastTradeTime)<Secs)
{
// пауза 0.1 секунда
Sleep(100);
continue;
}
// т.к. прошло больше Secs секунд, то попробуем изменить значение
// глобальной переменной LastTradeTime на текущее время
// используем функцию GlobalVariableSetOnCondition(), чтобы выявить
// ошибку, если уже за время ожидания другой эксперт успел совершить
// сделку и изменил значение глобальной переменной
if (GlobalVariableSetOnCondition("LastTradeTime", LocalTime(), LastTradeTime))
{
// за время ожидания глобальная переменная не изменилась, поэтому
// удалось установить ее новое значение
// пауза выдержена, обновим котировки и выйдем без ошибки
RefreshRates();
return(1);
}
else
{
// не удалось изменить значение глобальной переменной, т.к. другой эксперт
// успел совершить сделку раньше и установил новое значение переменной
// поэтому получим текущее значение глобальное переменной и продолжим ожидание
LastTradeTime = GlobalVariableGet("LastTradeTime");
// если произошла ошибка (равна нулю), то выходим с ошибкой
if (LastTradeTime==0)
{
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при чтении глобальной переменной LastTradeTime");
return(-1);
}
}
}
}
В следующих выпусках я разберу подробно, как работает эта функция. Параллельно мы сможем изучить неизвестные нам функции.
Определение режима тестирования на истории с помощью функции IsTesting()
В прошлом выпуске я написал функцию WaitBeforeTransaction(), которая выдерживает паузу между двумя последовательными торговыми операциями. В коде этой функции встречается очень много незнакомых функций. В этом и последующих выпусках я планирую очень кратко о них рассказать.
Сам исходный код функции обладает достаточным количеством комментариев, и я уверен, что логика реализации этой функции Вам станет абсолютно понятна после изучения следующих функций:
- IsTesting()
- GlobalVariableCheck()
- GlobalVariableSet()
- GlobalVariableGet()
- LocalTime()
Начнем с рассмотрения функции IsTesting().
bool IsTesting()
Функция IsTesting() возвращает true, если эксперт работает в режиме тестирования на исторических данных, и false - если работа ведется на демо- или реальном счете.
Дело в том, что необязательно сразу заставлять эксперта торговать на демо- или реальном счете. Гораздо эффективнее вначале оттестировать эксперта на исторических данных, которые есть в клиентском терминале. После этого с определенной точностью станет понятно, на что способен этот эксперт.
Основное преимущество тестов на истории - быстрота. Вам не надо ждать месяцы или годы, чтобы оттестировать своего эксперта на реальных котировках. Вы просто "прогоняете" советника по истории. На это у Вас уходит лишь несколько минут. Однако если Вы будете каждый раз ждать 10 секунд между торговыми операциями, то Вы потеряете это преимущество. Именно поэтому в коде нашей функции присутствует строка:
if (IsTesting()) return(1);
Т.е. мы выходим из функции, если эксперт находится в режиме тестирования на историчексих данных.
В следующем выпуске я расскажу о глобальных переменных и о функции GlobalVariableCheck().
GlobalVariableCheck(): проверка существования глобальной переменной
В этом и последующих выпусках я хочу рассказать о глобальных переменных и о функциях, с ними связанных.
Для человека, имеющего уже опыт написания экспертов, выражение "глобальные переменные" может внести небольшую путанницу. Дело в том, что на самом деле существует два вида "глобальных переменных":
- переменные, которые видны из любой функции ОДНОГО эксперта (т.е. являются глобальными в пределах этого эксперта); и
- переменные, которые являются общими для ВСЕХ экспертов.
О глобальных переменных первого типа - тех, которые являются общими для всех функций ОДНОГО эксперта, - я рассказывал ранее, в выпуске "Глобальные переменные".
Сейчас же речь пойдет именно о глобальных переменных второго типа - общих для ВСЕХ экспертов.
Глобальная переменная второго типа - это переменная, к которой можно обратиться из любого эксперта. Если в течение четырех недель не было сделано попытки прочесть значение глобальной переменной или записать в нее новое значение, то глобальная переменная автоматически удаляется клиентским терминалом.
Для работы с глобальными переменными используются следующие функции:
- GlobalVariableCheck()
- GlobalVariableDel()
- GlobalVariableGet()
- GlobalVariableName()
- GlobalVariableSet()
- GlobalVariableSetOnCondition()
- GlobalVariablesDeleteAll()
- GlobalVariablesTotal()
Вначале я хотел бы рассказать о функции GlobalVariableCheck().
bool GlobalVariableCheck(string name)
Эта функция возвращает true, если глобальная переменная с именем name существует, и false, если такой переменной нет.
В нашей функции WaitBeforeTransaction() мы храним время и дату совершения последней торговой операции в глобальной переменной "LastTradeTime".
Перед тем, как узнать время последней операции, прочитав значение этой переменной, мы проверяем, существует ли она вообще:
// если глобальная переменная LastTradeTime не существует,
// то создать ее
if (!GlobalVariableCheck("LastTradeTime"))
{
... создаем переменную ...
}
Проверять существование глобальной переменной и создавать ее в случае отсутствия можно в двух местах: при инициализации эксперта - в функции init() - или на каждом тике - в функции start().
Несмотря на то, что на первый взгляд напрашивается осуществлять эти действия в функции init(), я бы советовал Вам делать это все-таки в функции start(). Дело в том, что если пользователь при работающем эксперте удалит глобальную переменную, а проверка на ее существование делается только при инициализации эксперта, то дальнейшее поведение такого советника сложно спрогнозировать. Скорее всего, такой советник просто перестанет торговать и начнет выдавать ошибки при обращении к удаленной глобальной переменной.
В следующем выпуске я расскажу о том, как установить новое значение глобальной переменной или создать ее с помощью функции GlobalVariableSet().
GlobalVariableSet() - установка нового значения глобальной переменной
В прошлом выпуске я рассказал о том, как проверить наличие глобальной переменной с помощью функции GlobalVariableCheck().
Предположим, что ее не существует или же нам надо изменить ее значение. В этом случае надо использовать функцию GlobalVariableSet().
datetime GlobalVariableSet(string name, double value)
Функция GlobalVariableSet() устанавливает новое значение value глобальной переменной с именем name и в случае успеха возвращает время последнего доступа к глобальной переменной. Если произошла какая-то ошибка, то функция возвращает 0. Код ошибки, как обычно, можно получить с помощью функции GetLastError().
Если глобальная переменная с именем name отсутствовала, то она создается и она принимает значение value.
Пример использования функции GlobalVariableSet() можно найти в написанной нами функции WaitBeforeTransaction():
// если глобальная переменная LastTradeTime не существует,
// то создать ее
if (!GlobalVariableCheck("LastTradeTime"))
{
// Если произошла какя-то ошибка при вызове функции
// GlobalVariableCheck(), выйдем с ошибкой
if (GetLastError()!=0)
{
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при проверке глобальной переменной LastTradeTime");
return(-1);
}
// глобальная переменная не существует, создадим ее
if (GlobalVariableSet("LastTradeTime", 1)==0)
{
// произошла ошибка при создании глобальной переменной
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при создании глобальной переменной LastTradeTime");
return(-1);
}
// глобальная переменная успешно создана
Print("WaitBeforeTransaction(): глобальная переменная ",
"LastTradeTime создана");
}
В следующем выпуске я продолжу рассказывать о функциях для работы с глобальными переменными.
GlobalVariableSetOnCondition() - установка нового значения глобальной переменной, если текущее ее значение равно заданному значению
В прошлом выпуске я рассказал о функции GlobalVariableSet(), с помощью которой можно присвоить новое значение глобальной переменной.
К счастью, создатели языка MetaQuotes Language 4 являются профессиональными программистами и заранее могут предугадать, что может понадобиться трейдеру при написании экспертов. Я им очень благодарен, что они включили в список функций языка MetaQuotes Language 4 функцию GlobalVariableSetOnCondition().
Давайте же рассмотрим, чем так полезна эта функция.
bool GlobalVariableSetOnCondition(string name, double value, double check_value)
Прежде всего эта функция проверяет, существует ли глобальная переменная с именем name. В документации по MetaQuotes Language 4 написано, что если такой глобальной переменной нет, то функция возвращает false и генерит ошибку ERR_GLOBAL_VARIABLE_NOT_FOUND (4058), которую можно получить с помощью функции GetLastError().
Однако при экспериментах с функцией GlobalVariableSetOnCondition() мне не удалось добиться того, чтобы функция вернула ошибку ERR_GLOBAL_VARIABLE_NOT_FOUND (4058). Почему-то даже в случае отсутствия глобальной переменной функция возвращает false и код ошибки ERR_NO_ERROR (т.е. 0). Вот кусок кода, который я использовал для этой цели:
string name = "MyGlobalVariable";
double value = 1;
double check_value = 2;
if (GlobalVariableSetOnCondition(name, value, check_value))
{
Print("Глобальная переменная ", name, " существует.",
" Ее значение изменено с ", check_value, " на ", value);
}
else
{
int Err=0;
Err = GetLastError();
Print(Err);
switch (Err)
{
case 0: Print("Глобальная переменная ", name," изменена не была",
" т.к. ее значение не равно ", check_value); break;
case 4058: Print("Глобальной переменной ", name,
" не существует"); break;
default: Print("Неизвестная ошибка: ", Err);
}
}
Если это баг, то думаю, что в следующих версиях MetaTrader 4 его исправят.
Вернемся к описанию функции GlobalVariableSetOnCondition(). Если же глобальная переменная name существует, то функция не изменит ее текущего значения и вернет false, если текущее значение этой глобальной переменной не равно check_value. Если потом запросить код последней ошибки, то функция GetLastError() вернет ERR_NO_ERROR (т.е. 0), т.к. реально никакой ошибки не было.
Если же текущее значение глобальной переменной name равно значению check_value, то функция GlobalVariableSetOnCondition() присвоит этой глобальной переменной новое значение: value.
В следующем выпуске я расскажу о примере использования функции GlobalVariableSetOnCondition().
Пример использования функции GlobalVariableSetOnCondition()
В прошлом выпуске я рассказал о функции GlobalVariableSetOnCondition().
В этом же выпуске я хотел бы рассмотреть пример ее практического использования.
//+----------------------------------------------------------------------------------------+
//| int StartTrading() |
//| |
//| Функция занимает торговый поток |
//| Функция возвращает: |
//| 0 - если можно торговать |
//| 1 - если работа эксперта была остановлена |
//| 2 - если торговля экспертов запрещена на уровне настроек|
//| клиентского терминала |
//+----------------------------------------------------------------------------------------+
#define GLOB_VAR_NAME "TradeIsAllowed"
int StartTrading()
{
// если советник работает в режиме тестирования, то просто выйдем
if (IsTesting()) return(0);
int LastError;
while (!IsStopped())
{
// Если торговля экспертов запрещена на уровне настроек
// клиентского терминала, то выйдем и вернем 2
if (!IsExpertEnabled()) return(2);
// проверим, существует ли глобальная переменная
if (GlobalVariableCheck(GLOB_VAR_NAME)) break;
// если произошла ошибка при проверке глобальной переменной,
// то сообщим об этом в логах и выждем 0.1 секунды
LastError = GetLastError();
if (LastError!=0)
{
Print("StartTrading(): ошибка ", LastError,
" при проверке наличия глобальной переменной ", GLOB_VAR_NAME);
Sleep(100);
continue;
}
// глобальная переменная не существует - создадим ее
if (GlobalVariableSet(GLOB_VAR_NAME, 0)>0) break;
// произошла ошибка при создании переменной - запишем в лог
LastError = GetLastError();
Print("StartTrading(): ошибка ", LastError,
" при создании глобальной переменной ", GLOB_VAR_NAME);
Sleep(100);
}
// глобальная переменная существует или же эксперт был остановлен
// В цикле проверяем, как изменилась ситуация
while (!IsStopped())
{
// Если торговля экспертов запрещена на уровне настроек
// клиентского терминала, то выйдем и вернем 2
if (!IsExpertEnabled()) return(2);
// Если удалось изменить значение глобальной переменной, то обновим данные
// о текущих курсах и вернем 0
if (GlobalVariableSetOnCondition(GLOB_VAR_NAME, 1, 0))
{
RefreshRates();
return(0);
}
// Если дошли до момента, то ситуация не изменилась
// Поэтому делаем паузу в 0.1 секунду
Sleep(100);
}
// Т.к. вышли из цикла, то работа эксперта была остановлена
// Вернем 1
return(1);
}
//+----------------------------------------------------------------------------------------+
//| int StopTrading() |
//| |
//| Функция разрешает торговать следующему эксперту |
//| Функция возвращает: |
//| 0 - если другому эксперту можно торговать |
//| 1 - если работа эксперта была остановлена |
//| 2 - если торговля экспертов запрещена на уровне настроек|
//| клиентского терминала |
//+----------------------------------------------------------------------------------------+
#define GLOB_VAR_NAME "TradeIsAllowed"
int StopTrading()
{
// если советник работает в режиме тестирования, то просто выйдем
if (IsTesting()) return(0);
int LastError;
while (!IsStopped())
{
// Если торговля экспертов запрещена на уровне настроек
// клиентского терминала, то выйдем и вернем 2
if (!IsExpertEnabled()) return(2);
// Если удалось изменить значение глобальной переменной, то обновим данные
// о текущих курсах и вернем 0
if (GlobalVariableSet(GLOB_VAR_NAME, 0)>0)
{
return(0);
}
// при сбросе значения глобальной переменной произошла какая-то ошибка
LastError = GetLastError();
Print("StopTrading(): ошибка ", LastError,
" при сбросе значения глобальной переменной ", GLOB_VAR_NAME);
// делаем паузу в 0.1 секунду
Sleep(100);
}
// Т.к. вышли из цикла, то работа эксперта была остановлена
// Вернем 1
return(1);
}
Сегодня я не буду говорить о том, где и как используются эти две функции. Об этом мы поговорим завтра, в следующем выпуске.
Функции StartTrading() и StopTrading()
В прошлом выпуске я привел исходные коды двух функций: StartTrading() и StopTrading().
В этом выпуске я расскажу о том, что делают эти функции и как использовать.
В выпуске "Как избежать ошибок, если несколько экспертов торгуют одновременно" я приводил исходный код функции WaitUntilTradingIsAllowed(), которая ждет, пока освободится торговый поток, т.е. когда другой эксперт закончит торговать. После этого функция WaitUntilTradingIsAllowed() возвращает управление эксперту, из которого она была вызвана. Это будет сигналом того, что торговый поток освободился и можно торговать.
Теоретически возможна ситуация, когда сразу несколько экспертов ждут освобождения торгового потока и в момент его освобождения в каждом из них функция WaitUntilTradingIsAllowed() завершится практически одновременно. В этом случае сразу несколько экспертов попытается совершить торговую операцию в один и тот же момент времени. В результате только у одного эксперта это получится, а остальные эксперты вернут ошибку ERR_TRADE_CONTEXT_BUSY (146).
Для того, чтобы избежать этого, есть очень простой (благодаря наличию функции GlobalVariableSetOnCondition) выход. Мы создаем объект (глобальную переменную "TradeIsAllowed"), который будет иметь значение 0, если торговый поток свободен и торговать можно, и значение 1, если другой эксперт в этот момент уже торгует.
Таким образом, перед тем, как совершить торговую операцию, надо вызвать функцию StartTrading(). Вначале эта функция проверит, существует ли глобальная переменная с именем "TradeIsAllowed". Если она не существует, то создаст ее и присвоит ей значение 0 ("торговать можно"). Если же такая глобальная переменная уже существует, то мы сразу переходим к проверке ее значения.
Мы пытаемся в бесконечном цикле изменить значение глобальной переменной "TradeIsAllowed" на 1. При этом мы это делаем, используя функцию GlobalVariableSetOnCondition():
// Если удалось изменить значение глобальной переменной, то обновим данные
// о текущих курсах и вернем 0
if (GlobalVariableSetOnCondition(GLOB_VAR_NAME, 1, 0))
{
RefreshRates();
return(0);
}
Эта функция изменит значение глобальной переменной на 1 ("торговать другим нельзя") только в том случае, если ее текущее значение равно 0 ("торговый поток свободен"). Таким образом, если у нас несколько экспертов ждут освобождения торгового потока, то изменить глобальную переменную сможет только один из них. Остальные это сделать не смогут, т.к. в момент вызова ими функции GlobalVariableSetOnCondition() значение переменной уже будет не равно 0. В этом случае другие эксперты "заснут" на 0.1 секунды (команда Sleep) и будут повторять свои попытки дождаться освобождения торгового потока снова и снова, до тех пор, пока цель не будет достигнута или пока эксперт не будет остановлен (см. функцию IsStopped) или торговля экспертов не будет запрещена на уровне настроек терминала (см. функцию IsExpertEnabled).
После того, как Ваш эксперт, который вызвал функцию StartTrading(), закончит торговать, не забудьте сразу же вызвать функцию StopTrading().
Эта функция просто устанавливает значение глобальной переменной "TradeIsAllowed" в 0 ("торговый поток свободен").
Пример использования функций StartTrading() и StopTrading() в эксперте:
...
StartTrading();
// мы захватили торговый поток - можно и поторговать
OrderSend(Symbol(),OP_BUY,1,Ask,3,Bid-25*Point,Ask+25*Point,"My order #"+counter,16384,0,Green);
// освободим за собой торговый поток
StopTrading();
...
Если же Вы забудете вызвать функцию StopTrading(), то значение глоабльной переменной "TradeIsAllowed" останется равной 1, и ни один эксперт не сможет больше торговать.
В следующем выпуске я расскажу о том, как получить значение глобальной переменной с помощью функции GlobalVariableGet().
GlobalVariableGet() - получение значения глобальной переменной
В прошлых выпусках я рассказал о том, как:
- проверить существование глобальной переменной с помощью функции GlobalVariableCheck();
- установить новое значение глобальной переменной - GlobalVariableSet();
- изменить значение глобальной переменной только в том случае, если старое значение равно определенному значению - функция GlobalVariableSetOnCondition().
В этом же выпуске я расскажу о функции GlobalVariableGet(), с помощью которой можно получить значение глобальной переменной.
double GlobalVariableGet(string name)
Функция GlobalVariableGet() возвращает значение глобальной переменной name или 0 в случае какой-нибудь ошибки:
double gv;
gv = GlobalVariableGet("LastTradePrice");
int err = GetLastError();
if (err!=0) Print("GlobalVariableGet(): ошибка ", err);
Прмеров использования функции GlobalVariableGet() можно привести бесконечное количество. Даже в предыдущих наших примерах мы ее уже часто использовали. Например, в нашей функции WaitBeforeTransaction().
Эта функция позволяет Вам выдерживать паузу между торговыми операциями:
//+--------------------------------------------------------------------------------------------------+
//| Фунцкия WaitBeforeTransaction выдерживает паузу Secs секунд |
//| между торговыми операциями эксперта (по умолчанию 5 секунд) |
//| |
//| Возвращает: |
//| 1 - если пауза выдержена без ошибок |
//| 0 - если эксперт был остановлен |
//| -1 - если произошла какая-то ошибка |
//+-------------------------------------------------------------------------------------------------+
int WaitBeforeTransaction(int Secs = 5)
{
// если режим тестирования, то ждать необязательно
if (IsTesting()) return(1);
// если глобальная переменная LastTradeTime не существует,
// то создать ее
if (!GlobalVariableCheck("LastTradeTime"))
{
// Если произошла какя-то ошибка при вызове функции
// GlobalVariableCheck(), выйдем с ошибкой
if (GetLastError()!=0)
{
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при проверке глобальной переменной LastTradeTime");
return(-1);
}
// глобальная переменная не существует, создадим ее
if (GlobalVariableSet("LastTradeTime", 1)==0)
{
// произошла ошибка при создании глобальной переменной
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при создании глобальной переменной LastTradeTime");
return(-1);
}
// глобальная переменная успешно создана
Print("WaitBeforeTransaction(): глобальная переменная ",
"LastTradeTime создана");
}
// получим время последней операции
datetime LastTradeTime;
LastTradeTime = GlobalVariableGet("LastTradeTime");
// если произошла ошибка (равна нулю), то выходим с ошибкой
if (LastTradeTime==0)
{
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при чтении глобальной переменной LastTradeTime");
return(-1);
}
// ждем Secs секунд
while(true)
{
// если эксперт остановлен, выйдем со значением 0
if (IsStopped())
{
Print("WaitBeforeTransaction(): эксперт остановлен. Выходим...");
return(0);
}
// если прошло меньше Secs секунд, то ждем
if ((LocalTime()-LastTradeTime)<Secs)
{
// пауза 0.1 секунда
Sleep(100);
continue;
}
// т.к. прошло больше Secs секунд, то попробуем изменить значение
// глобальной переменной LastTradeTime на текущее время
// используем функцию GlobalVariableSetOnCondition(), чтобы выявить
// ошибку, если уже за время ожидания другой эксперт успел совершить
// сделку и изменил значение глобальной переменной
if (GlobalVariableSetOnCondition("LastTradeTime", LocalTime(), LastTradeTime))
{
// за время ожидания глобальная переменная не изменилась, поэтому
// удалось установить ее новое значение
// пауза выдержена, обновим котировки и выйдем без ошибки
RefreshRates();
return(1);
}
else
{
// не удалось изменить значение глобальной переменной, т.к. другой эксперт
// успел совершить сделку раньше и установил новое значение переменной
// поэтому получим текущее значение глобальное переменной и продолжим ожидание
LastTradeTime = GlobalVariableGet("LastTradeTime");
// если произошла ошибка (равна нулю), то выходим с ошибкой
if (LastTradeTime==0)
{
Print("WaitBeforeTransaction(): ошибка ",GetLastError(),
" при чтении глобальной переменной LastTradeTime");
return(-1);
}
}
}
}
В следующем выпуске я расскажу о том, как можно удалить глобальную переменную - о функции GlobalVariableDel().
GlobalVariableDel() - удаление глобальной переменной
В этом выпуске я расскажу о том, как удалить глобальную переменную. Для этого используется функция GlobalVariableDel():
bool GlobalVariableDel(string name)
Функция GlobalVariableDel() удаляет переменную name и возвращает true в случае успешного удаления переменной и false - в случае какой-то ошибки. Код ошибки можно получить с помощью функции GetLastError().
Функция GlobalVariableDel() очень часто применяется при работе с глобальными переменными, поэтому можно привести огромное количество примеров ее использования. Например, очень часто перед трейдером стоит задача сохранения массивов данных (список тикеров позиций, открытых этим экспертом, и т.д.) в глобальных переменных.
Конечно, можно все данные хранить в переменных эксперта, но при перезапуске клиентского терминала они будут потеряны, поэтому лучше критические данные, которые нельзя потерять ни в коем случае, хранить не в памяти, а сразу в глобальных переменных.
Функции для работы с такими массивами:
- AddItem()
- DeleteItem()
- Count()
- Search()
- BSearch
- Sort()
Однако перед тем, как написать эти функции, я хотел бы рассказать о реализации "критических секций" в советнике.
Дело в том, что если в момент выполнения этих функций будет осуществлен доступ к массивам данных, хранимых в глобальных переменных, из нескольких советников одновременно, то результат будет непредсказуемым и в большинстве случаев данные будут испорчены.
Для того, чтобы избежать этого, мы создадим объект "критическая секция", который может находиться в двух состояниях:
1. "зеленый свет" (-1); и
2. "красный свет" (1).
В каждый конкретный момент только один советник может получить доступ к данным. Пока советник получает данные, будет гореть "красный свет" и другие советники будут ждать "зеленового света". Если горит "зеленый свет", то дорога свободна, т.к. никто в данный момент к данным не обращается.
О реализации "критической секции" на MQL4 я расскажу в следующем выпуске, а уже после этого мы начнем создавать функции доступа к массивам даным, хранимых в глобальных переменных.
"Критическая секция": разграничение доступа к ресурсу
В этом выпуске я расскажу о способе организации "критической секции" в советнике.
Дело в том, что если в момент выполнения этих функций будет осуществлен доступ к массивам данных, хранимых в глобальных переменных, из нескольких советников одновременно, то результат будет непредсказуемым и в большинстве случаев данные будут испорчены.
Для того, чтобы избежать этого, мы создадим объект "критическая секция", который может находиться в двух состояниях:
1. "зеленый свет" (-1); и
2. "красный свет" (1).
В каждый конкретный момент только один советник может получить доступ к данным. Пока советник получает данные, будет гореть "красный свет" и другие советники будут ждать "зеленового света". Если горит "зеленый свет", то дорога свободна, т.к. никто в данный момент к данным не обращается.
Напишем две функции:
- Lock() - вызываем ее перед началом работы с ресурсом. Функция ждет "зеленового света" и меняет его на "красный".
- Unlock() - обязательно вызываем ее после окончания работы с ресурсом, чтобы снова зажечь "зеленый свет".
Вот исходный код этих функций:
//+------------------------------------------------------------------+
//| Lock() |
//| |
//| Возвращает: |
//| 0 - если "критическая секция" успешно |
//| заблокирована |
//| 1 - в случае ошибки |
//| 2 - эксперт остановлен |
//| 3 - по таймауту (слишком долго ждали) |
//+------------------------------------------------------------------+
int Lock(string GlobVarName, int timeout = 0)
{
string critical_section = GlobVarName+"Lock";
// проверим, существует ли переменная critical_section
if (!GlobalVariableCheck(critical_section))
{
if (GetLastError()!=0) return(1);
// переменная не существует, создадим ее
if (GlobalVariableSet(critical_section, -1.0)==0) return(1);
// переменная создана
}
int StartTime = GetTickCount();
// ждем "зеленового света"
while (true)
{
// проверить, не загорелся ли "зеленый свет"
if (GlobalVariableGet(critical_section)==-1.0)
{
// "зеленый свет" загорелся, зажигаем "красный свет"
if (GlobalVariableSetOnCondition(critical_section, 1.0, -1.0)) return(0);
// нас опередили, поэтому ждем "зеленового света"
}
// проверим, не остановлен ли эксперт
if (IsStopped()) return(2);
// таймаут не истек?
if (timeout!=0)
{
if ((GetTickCount()-StartTime)>timeout*1000) return(3);
}
// спим 0.1 секунды
Sleep(100);
}
}
//+------------------------------------------------------------------+
//| Unlock() |
//| |
//| Возвращает: |
//| 0 - если "критическая секция" успешно |
//| разблокирована |
//| 1 - в случае ошибки |
//| 2 - эксперт остановлен |
//| 3 - по таймауту (слишком долго ждали) |
//+------------------------------------------------------------------+
int Unlock(string GlobVarName, int timeout = 0)
{
string critical_section = GlobVarName+"Lock";
// проверим, существует ли переменная critical_section
if (!GlobalVariableCheck(critical_section))
{
if (GetLastError()!=0) return(1);
// переменная не существует, создадим ее
if (GlobalVariableSet(critical_section, -1.0)==0) return(1);
// переменная создана, поэтому выходим
return(-1.0);
}
int StartTime = GetTickCount();
// бесконечный цикл
while (true)
{
// пытаемся установить "зеленый свет"
if (GlobalVariableSetOnCondition(critical_section, -1.0, 1.0)) return(0);
// проверим, не остановлен ли эксперт
if (IsStopped()) return(2);
// таймаут не истек?
if (timeout!=0)
{
if ((GetTickCount()-StartTime)>timeout*1000) return(3);
}
// спим 0.1 секунды
Sleep(100);
}
}
В следующем выпуске я разберу код этих функций.
"Критическая секция": функции Lock() и Unlock()
В прошлом выпуске я начал рассказывать о критических секциях и привел исходный код функций, с помощью которых эти критические секции реализуются: Lock() и Unlock().
В этом выпуске я хотел остановится на этих функциях подробнее.
Напомню, что критические секции используются для того, чтобы в каждый момент времени только один эксперт имел доступ к ресурсу (например, к переменной).
Критическая секция - это объект, который может находиться только в двух состояниях:
1. "зеленый свет" (-1); и
2. "красный свет" (1).
Перед использованием ресурса мы должны вызвать функцию Lock(). В качестве объекта "критическая секция" мы будем использовать глобальную переменную, имя которой содержится в переменной critical_section.
Первым делом в функции Lock() мы проверяем, существует ли "объект", т.е. глобальная переменная. Если она не существует, то мы ее создаем:
// проверим, существует ли переменная critical_section
if (!GlobalVariableCheck(critical_section))
{
if (GetLastError()!=0) return(1);
// переменная не существует, создадим ее
if (GlobalVariableSet(critical_section, -1.0)==0) return(1);
// переменная создана
}
В случае каких-то ошибок вернем 1. При создании же переменной присвоим ей значение -1.0 ("зеленый свет").
Далее в течение timeout секунд в цикле пытаемся дождаться "зеленого света". По умолчанию ждем бесконечно (если параметр timeout равен нулю).
Чтобы не "зависнуть" в этом цикле, мы делаем ряд проверок. Прежде всего мы проверяем, не зажегся ли "зеленый свет". А если он зажегся, то пытаемся установить "красный свет":
// проверить, не загорелся ли "зеленый свет"
if (GlobalVariableGet(critical_section)==-1.0)
{
// "зеленый свет" загорелся, зажигаем "красный свет"
if (GlobalVariableSetOnCondition(critical_section, 1.0, -1.0)) return(0);
// нас опередили, поэтому ждем "зеленового света"
}
Далее проверяем, не был ли остановлен эксперт:
// проверим, не остановлен ли эксперт
if (IsStopped()) return(2);
Также проверим, не истекли ли наши timeout секунд:
// таймаут не истек?
if (timeout!=0)
{
if ((GetTickCount()-StartTime)>timeout*1000) return(3);
}
Если же пока нет повода прервать бесконечный цикл, то мы "спим" 0.1 секунды (функция Sleep) и повторяем все сначала.
Функция Unlock() работает по аналогии: проверяем, существует ли глобальная переменная и создаем ее, если надо:
// проверим, существует ли переменная critical_section
if (!GlobalVariableCheck(critical_section))
{
if (GetLastError()!=0) return(1);
// переменная не существует, создадим ее
if (GlobalVariableSet(critical_section, -1.0)==0) return(1);
// переменная создана, поэтому выходим
return(0);
}
Далее в бесконечном цикле мы пытаемся установить "зеленый свет" и если нам это удалось, то выходим:
// бесконечный цикл
while (true)
{
// пытаемся установить "зеленый свет"
if (GlobalVariableSetOnCondition(critical_section, -1.0, 1.0)) return(0);
...
}
Естественно, что также в цикле мы проверяем, не остановлен ли эксперт:
// проверим, не остановлен ли эксперт
if (IsStopped()) return(2);
И проверяем, не истек ли таймаут:
if (timeout!=0)
{
if ((GetTickCount()-StartTime)>timeout*1000) return(3);
}
Если нет повода выйти из цикла, то спим 0.1 секунды и повторяем все снова:
Sleep(100);
Пример использования функций Lock() и Unlock():
// вызываем Lock() перед началом работы с ресурсом
Lock();
// работаем с ресурсом
// ...
// вызываем Unlock() сразу же после завершения работы с ресурсом
Unlock();
В следующем выпуске я продолжу изучение функций для работы с глобальными переменными.
GlobalVariablesTotal() - количество глобальных переменных
В прошлых выпусках я рассказал об очень полезных функциях, предназначенных для работы с глобальными переменными:
- GlobalVariableCheck()
- GlobalVariableSet()
- GlobalVariableSetOnCondition()
- GlobalVariableGet()
- GlobalVariableDel()
Рассмотренных ранее функций вполне достаточно, чтобы выполнить любое действие с глобальной переменной, если известно ее имя. Однако иногда Вы можете не знать имени глобальной переменной и Вам захочется ее найти среди других переменных.
В этом Вам помогут функции GlobalVariablesTotal() и GlobalVariableName().
В этом выпуске я расскажу о функции GlobalVariablesTotal():
int GlobalVariablesTotal()
Эта функция возвращает общее количество глобальных переменных. Это значение Вы в дальнейшем можете использовать в качестве верхней границы в цикле, в котором Вы перебираете все имеющиеся глобальные переменные с помощью функции GlobalVariableName().
Об этой функции и о примере использования функции GlobalVariablesTotal() я расскажу в следующем выпуске.
GlobalVariableName(): имя глобальной переменной
В этом выпуске я расскажу о функции GlobalVariableName():
string GlobalVariableName(int index)
Функция GlobalVariableName() возвращает имя глобальной переменной с номером index. Порядковый номер переменной должен быть большим или равным нулю, но меньше, чем значение, возвращаемое функцией GlobalVariablesTotal().
Например, мы можем вывести имена всех глобальных переменных:
int index;
string gv_name;
Print("Всего глобальных переменных: ", GlobalVariablesTotal());
for (index=0; index<GlobalVariablesTotal();index++)
{
gv_name = GlobalVariableName(index);
if (GetL