- •Внешние переменные
- •Глобальные переменные
- •Формальные параметры функций
- •Операции отношения
- •Логическая операция и (&&)
- •Побитовые операции
- •Объединение арифметических и побитовых операций с операцией присваивания
- •Формат оператора цикла while:
- •Формат оператора цикла for:
- •Оператор досрочного завершения цикла break
- •Формат оператора switch:
- •Формат функции OrderSelect():
- •IsStopped(): как проверить, что эксперт был остановлен
- •Выдерживаем паузу между торговыми операциями
- •Index - индекс элемента
- •Реализация трейлинг стопа в эксперте
- •Функция iAlligator() - Аллигатор Билла Вильямса (Alligator)
- •IGator() - определение периодов сна и бодроствования Аллигатора с помощью Gator Oscillator
- •Функция iGator()
- •IFractals(): фракталы б.Вильямса - первое измерение рынка
- •IAo(): Волшебный Осциллятор Билла Вильямса (Awesome Oscillator - ao) - второе измерение рынка
- •Сигналы Волшебного осциллятора (Awesome Oscillator)
Выдерживаем паузу между торговыми операциями
В клиентском терминале 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).
В каждый конкретный момент только один советник может получить доступ к данным. Пока советник получает данные, будет гореть "красный свет" и другие советники будут ждать "зеленового света". Если горит "зеленый свет", то дорога свободна, т.к. никто в данный момент к данным не обращается.
О реализации "критической секции" на MQL4 я расскажу в следующем выпуске, а уже после этого мы начнем создавать функции доступа к массивам даным, хранимых в глобальных переменных.
"Критическая секция": разграничение доступа к ресурсу
В этом выпуске я расскажу о способе организации "критической секции" в советнике.
Дело в том, что если в момент выполнения этих функций будет осуществлен доступ к массивам данных, хранимых в глобальных переменных, из нескольких советников одновременно, то результат будет непредсказуемым и в большинстве случаев данные будут испорчены.
Для того, чтобы избежать этого, мы создадим объект "критическая секция", который может находиться в двух состояниях:
"зеленый свет" (-1); и
"красный свет" (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).
Перед использованием ресурса мы должны вызвать функцию 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 (GetLastError()==0)
Print("Переменная с номером ", index, " имеет имя ", gv_name);
else
Print("Ошибка при получении имени глобальной переменной с номером ", index);
}
Предположим, что мы храним какую-то нестандартную информацию о конкретной позиции или ордере в глобальных переменных. В качестве имени глобальной переменной мы используем тикер ордера. Т.е. если нам надо сохранить информацию (например, баланс торгового счета в момент открытия этого ордера) об ордере #156777, то мы создаем глобальную переменную с именем #156777 и присваиваем ей значение текущего баланса.
Предположим, нам надо вывести список ордеров, которые были открыты, когда баланс был меньше 10000.
int index;
string name;
// пробежимся по всем глобальным переменным
for (index=0; index<GlobalVariablesTotal();index++)
{
name = GlobalVariableName(index);
if (GetLastError()!=0)
{
Print("Ошибка при получении имени глобальной переменной с номером ", index);
continue;
}
// убедимся, что имя глобальной переменной начинается с #
if (StringSubstr(name, 0, 1)!="#") continue;
// убедимся, что после # идут одни цифры
int i;
bool is_numeric;
is_numeric = true;
for (i=1; i<StringLen(name);i++)
{
if ((StringGetChar(name, i)<48) // 48 - код '0'
||(StringGetChar(name, i)>57)) // 57 - код '9'
{
is_numeric = false;
break;
}
}
if (!is_numeric) continue;
// проверим значение баланса
if (GlobalVariableGet(name)<10000)
{
Print("Ордер ", name, " был открыт, когда баланс был меньше 10000");
}
}
В этом коде есть несколько незнакомых функций для работы со строками:
string StringSubstr(string text, int start, int length=0) - возвращает подстроку строки text начиная с позиции start и длиной length.
int StringLen(string text) - возвращает число символов в строке text.
int StringGetChar(string text, int pos) - возращает код символа, расположенного в позиции pos строки text.
Все эти функции я подробно рассмотрю в следующих выпусках, но сначала я расскажу о функции GlobalVariablesDeleteAll().
GlobalVariablesDeleteAll() - удаление глобальных переменных
В прошлых выпусках мы рассмотрели огромное количество функций, которые используются при работе с глобальными переменными, а также я привел большое количество полезных примеров практического использования этих функций.
Фактически только одна функция осталась неописанной - GlobalVariablesDeleteAll().
int GlobalVariablesDeleteAll(string prefix_name=NULL)
Функция GlobalVariablesDeleteAll() удаляет глобальные переменные. Если она вызывается без параметров или в качестве параметра передается NULL, то функция удаляет все переменные. Если же в качестве параметра prefix_name передана строка, то функция удаляет все переменные, имя которых начинается с этой строки.
Функция возвращает количество удаленных переменных.
Пример использования функции GlobalVariablesDeleteAll():
// удалим все глобальные переменные
Print ("Удалено ", GlobalVariablesDeleteAll(), " глобальных переменных");
В предыдущих выпусках я рассказал о всех функциях, которые применяются для работы с глобальными переменными. Думаю, что будет полезно привести несколько практических примеров использования этих функций.
Очень часто перед трейдером стоит задача сохранения массивов данных (список тикеров позиций, открытых этим экспертом, и т.д.) в глобальных переменных.
Конечно, можно все данные хранить в переменных эксперта, но при перезапуске клиентского терминала они будут потеряны, поэтому лучше критические данные, которые нельзя потерять ни в коем случае, хранить не в памяти, а сразу в глобальных переменных.
В следующем выпуске я начну рассмотрение способов реализации глобальных массивов.
Реализация глобального массива: обзор задачи
В предыдущих выпусках я рассказал о всех функциях, которые применяются для работы с глобальными переменными. Думаю, что будет полезно привести несколько практических примеров использования этих функций.
Очень часто перед трейдером стоит задача сохранения массивов данных (список тикеров позиций, открытых этим экспертом, и т.д.) в глобальных переменных.
Конечно, можно все данные хранить в переменных эксперта, но при перезапуске клиентского терминала они будут потеряны, поэтому лучше критические данные, которые нельзя потерять ни в коем случае, хранить не в памяти, а сразу в глобальных переменных.
Функции для работы с такими массивами:
AddItem()
DeleteItem()
GetItem()
Count()
Search()
BSearch
Sort()
В каждой из этих функций мы также будем использовать "критические секции", рассмотренные ранее. А иначе не избежать ситуации, когда данные будут испорчены, если в момент выполнения этих функций будет осуществлен доступ к ним из нескольких советников одновременно.
Итак, давайте попробуем описать, в каком формате у нас будет храниться глобальный массив.
Предположим, что наш глобальный массив имеет имя, которое содержится в переменной global_array_name.
Тогда:
Количество элементов массива будет находится в глобальной переменной global_array_name+"Count".
Глобальная переменная с именем global_array_name+"Lock" используется в качестве критической секции при вызове функций для работы с этим массивом.
Элементы массива будут находится в переменных global_array_name+"1", global_array_name+"2" и т.д.
Если в переменной global_array_name+"IsSorted" находится ненулевое значение, то массив отсортирован. В противном случае - неотсортированный.
Теперь мы знаем достаточно, чтобы создать необходимые функции для работы глобальным массивом. В следующем выпуске я расскажу о функции Count().
Глобальные массивы: функция Count()
В прошлом выпуске я расскал о структуре хранения данных в глобальном массиве. Теперь мы знаем достаточно, чтобы начать реализовывать функции для работы с массивом:
AddItem()
DeleteItem()
GetItem()
Count()
Search()
BSearch
Sort()
В каждой из вышеперечисленых функций будем использовать "критические секции". Перед каждым обращением к данным мы будем "локировать" критическую секцию с помощью вызова функции Lock(). По окончании работы с массивом "разлочим" критическую секцию с помощью функции Unlock().
Сами критические секции и функции для работы с ними подробно рассмотрены в выпуске "Критическая секция": разграничение доступа к ресурсу".
Вернемся к задуманной нами реализации глобальных массивов. Напишем функцию Count() для получения числа элементов в массиве:
//+--------------------------------------------------------------------------------------------------+
//| Функция Count() |
//| |
//| Возвращает количество элементов массива или -1 в случае ошибки |
//+--------------------------------------------------------------------------------------------------+
int Count(string array)
{
// Залокируем критическую секцию
string critical_section = array+"Lock";
if (Lock(critical_section)!=0) return(-1);
// Количество элементов массива хранится в переменной с именем,
// равным имя массива + "Count"
string gv;
gv = array+"Count";
int err;
// Если глобальная переменная не существует, то элементов нет,
// поэтому вернем 0
if (!GlobalVariableCheck(gv))
{
err = GetLastError();
if (err!=0)
{
// Разлокировать критическую секцию
Unlock(critical_section);
// Вывести сообщение об ошибке и выйти
Print("Count()->GlobalVariableCheck(): ошибка ", err);
return(-1);
}
else
{
// Разлокировать критическую секцию
Unlock(critical_section);
// Выйти
return(0);
}
}
// Глобальная переменная существует, поэтому вернем ее значение
int result;
result = GlobalVariableGet(gv);
err = GetLastError();
if (err!=0)
{
// Разлокировать критическую секцию
Unlock(critical_section);
// Вывести сообщение об ошибке и выйти
Print("Count()->GlobalVariableGet(): ошибка ", err);
return(-1);
}
// Разлокировать критическую секцию
Unlock(critical_section);
// Выйти
return(result);
}
Код этой функции крайне понятен и достаточно подробно откомментирован, поэтому я не вижу причин его разбирать более подробно. Но если у Вас появятся какие-то вопросы, то я с удовольствием на них отвечу.
В следующем выпуске я расскажу о функции AddItem().
Глобальные массивы: функция AddItem()
В этомы выпуске я опишу функцию AddItem(), которую мы будем использовать для добавления нового элемента в массив.
Вот ее исходный код:
//+--------------------------------------------------------------------------------------------------------+
//| Функция AddItem() |
//| |
//| Добавляет элемент в массив. Возвращает новое количество элементов|
//| в случае успешного добавления и -1 - в случае ошибки |
//| |
//| Параметры: |
//| array - имя массива |
//| item - новый элемент массива |
//| is_sorted - флаг отсортированности массива (может быть изменен |
//| в процессе выполнения функции, если возникнут |
//| ошибки при сортировке) |
//+--------------------------------------------------------------------------------------------------------+
int AddItem(string array, double item, bool &is_sorted)
{
// Залокировать критическую секцию
string critical_section = array+"Lock";
if (Lock(critical_section)!=0) return(-1);
// Количество элементов массива хранится в переменной с именем,
// равным имя массива + "Count"
string gv_count;
gv_count = array+"Count";
int count, err;
// Если глобальная переменная не существует, то элементов нет
if (!GlobalVariableCheck(gv_count))
{
err = GetLastError();
if (err!=0)
{
// Разлокировать критическую секцию
Unlock(critical_section);
// Вывести сообщение об ошибке и выйти
Print("AddItem()->GlobalVariableCheck(): ошибка ", err);
return(-1);
}
else
count = 0;
}
else
// переменная существует, получим количество элементов
count = GlobalVariableGet(gv_count);
// Увеличим число элементов массива
count = count + 1;
// добавим в массив элемент
if (GlobalVariableSet(array+DoubleToStr(count, 0),item)==0)
{
// произошла ошибка при добавлении элемента
// Разлокировать критическую секцию
Unlock(critical_section);
// Вывести сообщение об ошибке и выйти
Print("AddItem()->GlobalVariableSet(item): ошибка ", GetLastError());
return(-1);
}
// запишем новое число элементов в массиве
if (GlobalVariableSet(gv_count,count)==0)
{
// произошла ошибка при изменении числа элементов
// Разлокировать критическую секцию
Unlock(critical_section);
// Вывести сообщение об ошибке и выйти
Print("AddItem()->GlobalVariableSet(count): ошибка ", GetLastError());
return(-1);
}
// массив сортированный?
if (is_sorted)
{
// да, поэтому отсортируем массив
int i;
for (i=count; i>1; i--)
{
double cur_item = GlobalVariableGet(array+DoubleToStr(i, 0));
double prev_item = GlobalVariableGet(array+DoubleToStr(i-1, 0));
if (cur_item>prev_item) break;
// поменять элементы местами
if (GlobalVariableSet(array+DoubleToStr(i, 0),prev_item)==0)
{
is_sorted = false;
break;
}
if (GlobalVariableSet(array+DoubleToStr(i-1, 0),cur_item)==0)
{
is_sorted = false;
// пытаемся вернуть перезаписанный текущий элемент в первоначальное
// состояние
if (GlobalVariableSet(array+DoubleToStr(i, 0),cur_item)==0)
{
Print("AddItem()->GlobalVariableSet: данные безвозвратно испорчены");
// Разлокировать критическую секцию
Unlock(critical_section);
return(-1);
}
break;
}
}
}
// Разлокировать критическую секцию
Unlock(critical_section);
return(count);
}
Думаю, что у Вас не возникло проблем с пониманием этой функции. Возможно, что единственным непонятным местом может быть сортировка массива после вставки нового элемента. В этом случае мы просто "опускаем" новый элемент до тех пор, пока он не встанет на свое место:
// массив сортированный?
if (is_sorted)
{
// да, поэтому отсортируем массив
int i;
for (i=count; i>1; i--)
{
double cur_item = GlobalVariableGet(array+DoubleToStr(i, 0));
double prev_item = GlobalVariableGet(array+DoubleToStr(i-1, 0));
if (cur_item>prev_item) break;
// поменять элементы местами
if (GlobalVariableSet(array+DoubleToStr(i, 0),prev_item)==0)
{
is_sorted = false;
break;
}
if (GlobalVariableSet(array+DoubleToStr(i-1, 0),cur_item)==0)
{
is_sorted = false;
// пытаемся вернуть перезаписанный текущий элемент в первоначальное
// состояние
if (GlobalVariableSet(array+DoubleToStr(i, 0),cur_item)==0)
{
Print("AddItem()->GlobalVariableSet: данные безвозвратно испорчены");
// Разлокировать критическую секцию
Unlock(critical_section);
return(-1);
}
break;
}
}
}
Сортировку массива после добавления нового элемента мы производим только в том случае, если массив уже до этого был отсортирован, т.е. флаг is_sorted был равен true.
Если во время сортировки мы столкнулись с критической ошибкой (данные массива были испорчены), то мы выйдем с кодом -1. А если ошибка была некритическая (т.е. данные испорчены не были, но сортировка была прервана), то мы изменим значение флага is_sorted на false.
В следующем выпуске я расскажу о функции поиска в массиве.
Глобальные массивы: функция SearchItem()
Поскольку мы используем глобальные массивы для хранения данных, то вполне вероятно, что нам понадобятся функции поиска элемента в массиве.
Технология поиска будет зависеть от того, упорядочен ли наш массив:
Если массив неупорядоченный, то самым очевидным методом найти какой-нибудь элемент будет простой перебор всех элементов массива.
Если массив упорядоченный, то наилучшим решением (наиболее оптимальным по скорости поиска) будет двоичный поиск.
Итак, напишем функцию SearchItem(), которая осуществляет поиск в массиве и возвращает:
индекс найденного элемента в массиве, если элемент найден; или
0 - если элемента в массиве нет; или
-1 - если произошла какая-то ошибка.
//+--------------------------------------------------------------------------------------------------------+
//| Функция SearchItem() |
//| |
//| Ищет элемент в массиве. Возвращает индекс элемента или 0, если |
//| элемент был не найден, и -1 - в случае ошибки |
//| |
//| Параметры: |
//| array - имя массива |
//| item - новый элемент массива |
//| is_sorted - флаг отсортированности массива (может быть изменен |
//| в процессе выполнения функции, если возникнут |
//| ошибки при сортировке) |
//+--------------------------------------------------------------------------------------------------------+
int SearchItem(string array, double item, bool &is_sorted)
{
// Залокировать критическую секцию
string critical_section = array+"Lock";
if (Lock(critical_section)!=0) return(-1);
// Количество элементов массива хранится в переменной с именем,
// равным имя массива + "Count"
string gv_count;
gv_count = array+"Count";
int count, err;
// Если глобальная переменная не существует, то элементов нет
if (!GlobalVariableCheck(gv_count))
{
err = GetLastError();
if (err!=0)
{
// Разлокировать критическую секцию
Unlock(critical_section);
// Вывести сообщение об ошибке и выйти
Print("SearchItem()->GlobalVariableCheck(): ошибка ", err);
return(-1);
}
else
count = 0;
}
else
// переменная существует, получим количество элементов
count = GlobalVariableGet(gv_count);
// если массив пустой - вернуть 0
if (count==0)
{
// Разлокировать критическую секцию
Unlock(critical_section);
return(0);
}
// если массив неотсортированный, то найдем элемент перебором
if (!is_sorted)
{
int i;
for (i=1; i<=count; i++)
if (item==GlobalVariableGet(array+DoubleToStr(i, 0)))
{
// Разлокировать критическую секцию
Unlock(critical_section);
return(i);
}
}
else
{
// массив отсортированный, поэтому произведен двоичный поиск
int pos1, pos2, mid;
pos1 = 1;
pos2 = count;
while (pos1+1<pos2)
{
mid = (pos1+pos2)/2;
if (item>GlobalVariableGet(array+DoubleToStr(mid, 0)))
pos1 = mid;
else
pos2 = mid;
}
if (GlobalVariableGet(array+DoubleToStr(pos1, 0))==item)
{
// Разлокировать критическую секцию
Unlock(critical_section);
return(pos1);
}
if (GlobalVariableGet(array+DoubleToStr(pos2, 0))==item)
{
// Разлокировать критическую секцию
Unlock(critical_section);
return(pos2);
}
}
// Разлокировать критическую секцию
Unlock(critical_section);
return(0);
}
В следующем выпуске я подробно разберу исходный код этой функции, а также немного раскажу о принципах двоичного поиска.
Глобальные массивы: технология поиска
В прошлом выпуске я привел код функции SearchItem(), с помощью которой можно найти элемент в массиве.
Эта функция возвращает:
индекс найденного элемента в массиве, если элемент найден; или
0 - если элемента в массиве нет; или
-1 - если произошла какая-то ошибка.
Для того, чтобы понять принцип работы этой функции, будет полезно рассказать о различных технологиях поиска.
Прежде всего код функции SearchItem() начинается с того, что мы локируем критическую секцию, чтобы обеспечить монопольный доступ к данным массива:
// Залокировать критическую секцию
string critical_section = array+"Lock";
if (Lock(critical_section)!=0) return(-1);
Для осуществления поиска мы должны знать количество элементов в массиве:
// Количество элементов массива хранится в переменной с именем,
// равным имя массива + "Count"
string gv_count;
gv_count = array+"Count";
int count, err;
// Если глобальная переменная не существует, то элементов нет
if (!GlobalVariableCheck(gv_count))
{
err = GetLastError();
if (err!=0)
{
// Разлокировать критическую секцию
Unlock(critical_section);
// Вывести сообщение об ошибке и выйти
Print("SearchItem()->GlobalVariableCheck(): ошибка ", err);
return(-1);
}
else
count = 0;
}
else
// переменная существует, получим количество элементов
count = GlobalVariableGet(gv_count);
После этого мы смотрим, отсортирован ли массив:
Если массив неотсортированный, то просто перебираем все элементы.
Если массив отсортированный, то используем технологию двоичного поиска.
Технология поиска простым перебором:
int i;
for (i=1; i<=count; i++)
if (item==GlobalVariableGet(array+DoubleToStr(i, 0)))
{
// Разлокировать критическую секцию
Unlock(critical_section);
return(i); }
Технология двоичного поиска:
Левая граница поиска - первый элемент, правая граница поиска - последний эелемент.
Берем элемент, который находится посередине между левой и правой границами.
Если этот элемент меньше того, который мы ищем, то сдвигаем левую границу поиска к середине нашего диапазона (т.к. однозначно, что искомый элемент находится выше серединного). В противном случае - сдвигаем правую границу поиска к середине диапазона. Каждый раз диапазон поиска уменьшается в два раза.
Повторяем шаги 2 и 3, пока диапазон больше 2 элементов
Такая технология поиска гарантирует быстрое нахождение элемента в отсортированном массиве. Например, в массиве из 1,000 элементов мы найдем элемент за 10 итераций, а в массиве из 1,000,000,000 элементов - за 30 итераций. В то время как поиск путем простого перебора даст тот же результат в среднем за 500 и 500,000,000 итераций соответственно. Выигрыш в скорости очень ощутим.
Напомню кусок кода, который осуществляет двоичный поиск в отсортированном массиве:
int pos1, pos2, mid;
pos1 = 1;
pos2 = count;
while (pos1+1<pos2)
{
mid = (pos1+pos2)/2;
if (item>GlobalVariableGet(array+DoubleToStr(mid, 0)))
pos1 = mid;
else
pos2 = mid;
}
if (GlobalVariableGet(array+DoubleToStr(pos1, 0))==item)
{
// Разлокировать критическую секцию
Unlock(critical_section);
return(pos1);
}
if (GlobalVariableGet(array+DoubleToStr(pos2, 0))==item)
{
// Разлокировать критическую секцию
Unlock(critical_section);
return(pos2);
}
}
В следующем выпуске я расскажу о том, как с помощьюе функции GetItem() получить значение элемента в определенной позиции массиве.
Глобальные массивы: функция GetItem()
Очень полезной функцией для работы с глобальным массивом может стать функция GetItem():
bool GetItem(string array, int index, double &result)
Эта функция пытается получить значение элемента с индексом index и вернуть это значение через переменную result в переменную, указанную в качестве третьего параметра при вызове этой функции.
Параметры:
