Из справки MetaEditor:
Для проведения торговых операций из экспертов и скриптов предусмотрен всего один поток, который запускается в программном торговом контексте (контекст автоматической торговли из экспертов и скриптов). Поэтому, если этот контекст занят торговой операцией какого-либо эксперта, то другой эксперт или скрипт не может в этот момент вызывать торговые функции из-за ошибки 146 (ERR_TRADE_CONTEXT_BUSY)
Проще говоря, проводить торговые операции одновременно может только один эксперт (скрипт). Все остальные эксперты, пытающиеся торговать, будут "остановлены" ошибкой № 146. Данная статья посвящена решению этой проблемы.
2. Функция IsTradeAllowed()
Самый простой способ определить, свободен ли торговый поток - использовать функцию IsTradeAllowed().
Из справки MetaEditor:
"bool IsTradeAllowed()
Возвращает TRUE, если эксперту разрешено торговать, и поток для выполнения торговых операций свободен, иначе возвращает FALSE
Т.е. пытаться торговать можно только в том случае, если функция IsTradeAllowed() возвращает TRUE.
Проверку надо делать непосредственно перед торговой операцией.
Пример неправильного использования функции:
int start()
{
if(!IsTradeAllowed())
{
Print("Торговый поток занят! Эксперт не может открыть позицию!");
return(-1);
}
else
{
Print("Торговый поток свободен! Продолжаем работу...");
}
...
...
if(OrderSend(...) < 0)
Alert("Ошибка открытия позиции № ", GetLastError());
return(0);
} В этом примере проверка состояния торгового потока происходит в самом начале функции start(). Это ошибочное решение - за время, потраченное экспертом на расчёты (необходимость входа в рынок, уровни Стоп Лосс, Тейк Профит, размер лота и т.п.), торговый поток может быть занят другим экспертом. В этом случае попытка открыть позицию не увенчается успехом.
Пример правильного использования функции:
int start()
{
...
...
if(!IsTradeAllowed())
{
Print("Торговый поток занят! Эксперт не может открыть позицию!");
return(-1);
}
else
Print("Торговый поток свободен! Пытаемся открыть позицию...");
if(OrderSend(...) < 0)
Alert("Ошибка открытия позиции № ", GetLastError());
return(0);
}
Здесь проверка происходит непосредственно перед открытием позиции, и вероятность того, что другой эксперт "вклинится" между этими двумя действиями намного меньше (но всё равно есть. Об этом будет сказано чуть позже).
В этом методе есть два существенных недостатка:
- остаётся вероятность того, что эксперты одновременно сделают проверку, и, получив "зелёный свет", будут одновременно пытаться торговать
- если проверка завершится неудачно, следующий раз эксперт будет пытаться торговать только на следующем тике. А такая задержка ничем не оправдана.
Вторую проблему решить достаточно просто - просто надо "ждать", пока торговый поток освободится. Тогда эксперт начнёт торговать сразу после того, как закончит другой эксперт.
Выглядеть это будет примерно так:
int start()
{
...
...
if(!IsTradeAllowed())
{
Print("Торговый поток занят! Ждём, пока он освободиться...");
while(true)
{
if(IsStopped())
{
Print("Эксперт был остановлен пользователем!");
return(-1);
}
if(IsTradeAllowed())
{
Print("Торговый поток освободился!");
break;
}
Sleep(100);
}
}
else
Print("Торговый поток свободен! Пытаемся открыть позицию...");
if(OrderSend(...) < 0)
Alert("Ошибка открытия позиции № ", GetLastError());
return(0);
} В текущей реализации у нас опять есть проблемные места:
- во-первых, поскольку функция IsTradeAllowed() отвечает не только за состояние торгового потока, но и за "галочку", разрешающую эксперту торговать, эксперт может "зависнуть" в бесконечном цикле, и остановится, только если его вручную удалят с графика
- во-вторых, если эксперт будет ждать освобождения торгового потока хотя бы секунду, цены могут измениться и торговать по ним уже нельзя - необходимо обновить данные и пересчитать уровни открытия, Стоп Лосс и ТейкПрофит будущей позиции.
Код, с учётом исправлений, будет выглядеть так:
int MaxWaiting_sec = 30;
int start()
{
...
...
if(!IsTradeAllowed())
{
int StartWaitingTime = GetTickCount();
Print("Торговый поток занят! Ждём, пока он освободиться...");
while(true)
{
if(IsStopped())
{
Print("Эксперт был остановлен пользователем!");
return(-1);
}
if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
{
Print("Превышен лимит ожидания (" + MaxWaiting_sec + " сек.)!");
return(-2);
}
if(IsTradeAllowed())
{
Print("Торговый поток освободился!");
RefreshRates();
...
break;
}
Sleep(100);
}
}
else
Print("Торговый поток свободен! Пытаемся открыть позицию...");
if(OrderSend(...) < 0)
Alert("Ошибка открытия позиции № ", GetLastError());
return(0);
} В этом примере добавлено:
- обновление рыночной информации (RefreshRates()) и последующий пересчёт уровней СЛ и ТП
- максимальное время ожидания MaxWaiting_sec, при превышении которого эксперт прекратит работу
В таком виде этот код уже можно использовать в своих экспертах.
Последний штрих - "вынесем" всё, что касается проверки, в отдельную функцию. Это облегчит её интеграцию в эксперты и упростит использование.
//
//
int _IsTradeAllowed(int MaxWaiting_sec = 30)
{
if(!IsTradeAllowed())
{
int StartWaitingTime = GetTickCount();
Print("Торговый поток занят! Ждём, пока он освободиться...");
while(true)
{
if(IsStopped())
{
Print("Эксперт был остановлен пользователем!");
return(-1);
}
if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
{
Print("Превышен лимит ожидания (" + MaxWaiting_sec + " сек.)!");
return(-2);
}
if(IsTradeAllowed())
{
Print("Торговый поток освободился!");
return(0);
}
Sleep(100);
}
}
else
{
Print("Торговый поток свободен!");
return(1);
}
}
Шаблон эксперта, использующего функцию:
int start()
{
...
...
int TradeAllow = _IsTradeAllowed();
if(TradeAllow < 0)
{
return(-1);
}
if(TradeAllow == 0)
{
RefreshRates();
...
}
if(OrderSend(...) < 0)
Alert("Ошибка открытия позиции № ", GetLastError());
return(0);
} Сделаем некоторые выводы:
Функция IsTradeAllowed() проста в использовании, и идеально подходит для разграничения доступа к торговому потоку при одновременной работе двух-трёх экспертов. Из-за недостатков, которые в ней присутствуют, её использование при работе большего количества экспертов не гарантирует отсутствие ошибки 146 и может вызвать "зависание" эксперта при отключённой галочке "Разрешить эксперту торговать".
Именно поэтому мы рассмотрим альтернативный способ решения этой проблемы - использование глобальной переменной в качестве "семафора".
3. Глобальные переменные клиентского терминала
Сначала немножко о понятиях...
Глобальные переменные клиентского терминала - переменные, доступ к которым есть у всех экспертов, скриптов и индикаторов. Т.е. глобальная переменная, созданная одним экспертом, может быть использована в других экспертах (в нашем случае, для разграничения доступа).
Для работы с глобальными переменными в языке MQL 4 предусмотрено несколько функций:
- GlobalVariableCheck() - для проверки, существует ли глобальная переменная
- GlobalVariableDel() - для удаления глобальной переменной
- GlobalVariableGet() - для получения значения глобальной переменной
- GlobalVariableSet() - для создания и изменения значения глобальной переменной
- GlobalVariableSetOnCondition() - для изменения глобальной переменной с одного значения (указываемого пользователем) на другое. Т.е. отличие от GlobalVariableSet() заключается в том, что новое значение будет присвоено только при определённом старом значении. Именно эта функция и является ключевой для создания семафора.
- GlobalVariablesDeleteAll() - для удаления всех глобальных переменных (не знаю, кому может такое понадобиться:)
Почему необходимо использовать функцию GlobalVariableSetOnCondition(), а не комбинацию функций GlobalVariableGet() и GlobalVariableSet()? Да всё по тем же причинам - между использованием 2-х функций может пройти какое-то время. И другой эксперт имеет шанс "вклиниться" в процесс переключения семафора. Нам же этого допустить нельзя.
4. Основная идея семафора
Эксперт, который хочет торговать, должен проверить состояние семафора. Если на семафоре "красный свет" (глобальная переменная = 1), значит уже торгует другой эксперт, и надо подождать. Если же на семафоре "зелёный свет" (глобальная переменная = 0), можно сразу приступать к торговле (не забыв установить "красный свет" для других экспертов).
Итого, нам надо создать 2 функции - одну для установки "красного света", и одну для установки "зелёного света". Задача на первый взгляд простая. Но не будем торопиться с выводами, а лучше попробуем сформулировать последовательность выполняемых действий для каждой функции (назовём их TradeIsBusy() и TradeIsNotBusy()) и, собственно, реализуем их.
5. Функция TradeIsBusy()
Как уже говорилось, основной задачей функции будет ожидание появления "зелёного света" и включение "красного света". Кроме того, нам необходимо проверять, существует ли глобальная переменная, и создавать её, в случае, если её нет. Эту проверку логичнее (и экономнее) было бы делать из функции init() эксперта. Но тогда существовала бы вероятность, что пользователь её удалит, и все работающие в тот момент эксперты не смогут торговать. Поэтому мы разместим её в теле создаваемой функции.
Все эти действия должны быть сопровождены выводом информации и обработкой ошибок, возникших при работе с глобальной переменной. Также не ст
оит забывать о "зависании" - время работы функции должно быть ограничено.
Вот, что у нас должно получиться:
//
int TradeIsBusy( int MaxWaiting_sec = 30 )
{
if(IsTesting())
return(1);
int _GetLastError = 0, StartWaitingTime = GetTickCount();
while(true)
{
if(IsStopped())
{
Print("Эксперт был остановлен пользователем!");
return(-1);
}
if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
{
Print("Превышен лимит ожидания (" + MaxWaiting_sec + " сек.)!");
return(-2);
}
if(GlobalVariableCheck( "TradeIsBusy" ))
break;
else
{
_GetLastError = GetLastError();
if(_GetLastError != 0)
{
Print("TradeIsBusy()-GlobalVariableCheck(\"TradeIsBusy\")-Error #",
_GetLastError );
Sleep(100);
continue;
}
}
if(GlobalVariableSet( "TradeIsBusy", 1.0 ) > 0 )
return(1);
else
{
_GetLastError = GetLastError();
if(_GetLastError != 0)
{
Print("TradeIsBusy()-GlobalVariableSet(\"TradeIsBusy\",0.0 )-Error #",
_GetLastError );
Sleep(100);
continue;
}
}
}
while(true)
{
if(IsStopped())
{
Print("Эксперт был остановлен пользователем!");
return(-1);
}
if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000)
{
Print("Превышен лимит ожидания (" + MaxWaiting_sec + " сек.)!");
return(-2);
}
if(GlobalVariableSetOnCondition( "TradeIsBusy", 1.0, 0.0 ))
return(1);
else
{
_GetLastError = GetLastError();
if(_GetLastError != 0)
{
Print("TradeIsBusy()-GlobalVariableSetOnCondition(\"TradeIsBusy\",1.0,0.0 )-Error #",
_GetLastError );
continue;
}
}
Comment("Ждём, пока другой эксперт закончит торговать...");
Sleep(1000);
Comment("");
}
}
Тут вроде бы всё понятно:
- проверка существования глобальной переменной и, в случае неудачи, её создание
- попытка изменить значение глобальной переменной с 0 на 1. Сработает только если её значение будет = 0.
Функция может работать максимум MaxWaiting_sec секунд и не препятствует удалению эксперта с графика.
Информация о всех возникающих ошибках выводится в журнал.
6. Функция TradeIsNotBusy()
Функция TradeIsNotBusy выполняет обратную задачу - включает "зелёный свет".
Она не имеет ограничения по времени работы и не может быть остановлена пользователем. Мотивация достаточно простая - если не включить "зелёный свет", ни один эксперт не сможет торговать.
Естественно, и кодов возврата у неё нет - результатом может быть только успешное завершение.
Вот как она выглядит:
//
void TradeIsNotBusy()
{
int _GetLastError;
if(IsTesting())
{
return(0);
}
while(true)
{
if(IsStopped())
{
Print("Эксперт был остановлен пользователем!");
return(-1);
}
if(GlobalVariableSet( "TradeIsBusy", 0.0 ) > 0)
return(1);
else
{
_GetLastError = GetLastError();
if(_GetLastError != 0 )
Print("TradeIsNotBusy()-GlobalVariableSet(\"TradeIsBusy\",0.0)-Error #",
_GetLastError );
}
Sleep(100);
}
}
7. Интеграция в экспертов и использование
Теперь у нас есть 3 функции для разграничения доступа к торговому потоку. Для облегчения их интеграции в экспертов можно создать файл TradeContext.mq4 и включать его директивой #include (файл прикреплён).
Шаблон эксперта, использующего функции TradeIsBusy() и TradeIsNotBusy():
#include <TradeContext.mq4>
int start()
{
...
...
if(TradeIsBusy() < 0)
return(-1);
RefreshRates();
...
if(OrderSend(...) < 0)
{
Alert("Ошибка открытия позиции № ", GetLastError());
}
TradeIsNotBusy();
return(0);
}
В использовании функций TradeIsBusy() и TradeIsNotBusy() может возникнуть только одна проблема - если после того, как торговый поток будет занят, эксперта удалить с графика, переменная TradeIsBusy останется равной 1. Другие эксперты после этого торговать не смогут.
Решается проблема просто - не надо удалять эксперта с графика, если он торгует ;)
Также возможна ситуация, что переменная TradeIsBusy не обнуляется при критическом завершении работы терминала. В этом случае помогает использование функции TradeIsNotBusy() из функции init() эксперта.
Ну, и в любой момент значение переменной можно поменять вручную - кнопка F3 в терминале ( это недокументированная возможность запретить всем экспертам торговать ;)
komposter (komposterius@mail.ru), 2006.04.11