Ошибка 146 ("Торговый поток занят") и как с ней бороться
Andrey Khatimlianskii | 12 мая, 2006
1. Понятие "Торгового потока" в терминале MetaTrader 4
Из справки MetaEditor:
Проще говоря, проводить торговые операции одновременно может только один эксперт (скрипт). Все остальные эксперты, пытающиеся торговать, будут "остановлены" ошибкой № 146. Данная статья посвящена решению этой проблемы.
2. Функция IsTradeAllowed()
Самый простой способ определить, свободен ли торговый поток - использовать функцию IsTradeAllowed().
Из справки MetaEditor:
"bool IsTradeAllowed()
Возвращает TRUE, если эксперту разрешено торговать, и поток для выполнения торговых операций свободен, иначе возвращает FALSE
Т.е. пытаться торговать можно только в том случае, если функция IsTradeAllowed() возвращает TRUE.
Проверку надо делать непосредственно перед торговой операцией.
Пример неправильного использования функции:
int start() { // проверяем, свободен ли торговый поток if(!IsTradeAllowed()) { // если функция IsTradeAllowed() вернула FALSE, сообщаем об этом пользователю, Print("Торговый поток занят! Эксперт не может открыть позицию!"); // и прекращаем работу эксперта. Она будет возобновлена с приходом следующего // тика return(-1); } else { // если функция IsTradeAllowed() вернула TRUE, сообщаем об этом пользователю // и продолжаем работу 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; } // если ни одно из условий остановки цикла не сработало, "ждём" 0,1 секунды // и начинаем проверку сначала 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); } // если ожидание длится дольше времени, указанного в переменной // MaxWaiting_sec, тоже прекращаем работу if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000) { Print("Превышен лимит ожидания (" + MaxWaiting_sec + " сек.)!"); return(-2); } // если торговый поток освободился, if(IsTradeAllowed()) { Print("Торговый поток освободился!"); // обновляем рыночную информацию RefreshRates(); // пересчитываем уровни Стоп Лосс и Тейк Профит ... // выходим из цикла и переходим к торговле break; } // если ни одно из условий остановки цикла не сработало, "ждём" 0,1 // секунды и начинаем проверку сначала Sleep(100); } } else Print("Торговый поток свободен! Пытаемся открыть позицию..."); // пытаемся открыть позицию if(OrderSend(...) < 0) Alert("Ошибка открытия позиции № ", GetLastError()); return(0); }В этом примере добавлено:
- обновление рыночной информации (RefreshRates()) и последующий пересчёт уровней СЛ и ТП
- максимальное время ожидания MaxWaiting_sec, при превышении которого эксперт прекратит работу
В таком виде этот код уже можно использовать в своих экспертах.
Последний штрих - "вынесем" всё, что касается проверки, в отдельную функцию. Это облегчит её интеграцию в эксперты и упростит использование.
///////////////////////////////////////////////////////////////////////////////// // int _IsTradeAllowed( int MaxWaiting_sec = 30 ) // // функция определяет состояние торгового потока. Коды возвратов: // 1 - торговый поток свободен, можно торговать // 0 - торговый поток был занят, но освободился. Торговать можно только после // обновления рыночной информации. // -1 - торговый поток занят, ожидание прервано пользователем (эксперт удалён с // графика, закрыт терминал, изменился период и/или символ графика, ... ) // -2 - торговый поток занят, истекло максимальное время ожидания (MaxWaiting_sec). // Возможно, эксперту запрещена торговля (галочка "Разрешить эксперту торговать" // в настройках эксперта). // // MaxWaiting_sec - время (в секундах), в течении которого функция будет ждать // освобождения торгового потока (если он занят). По умолчанию = 30. ///////////////////////////////////////////////////////////////////////////////// int _IsTradeAllowed(int MaxWaiting_sec = 30) { // проверяем, свободен ли торговый поток if(!IsTradeAllowed()) { int StartWaitingTime = GetTickCount(); Print("Торговый поток занят! Ждём, пока он освободиться..."); // бесконечный цикл while(true) { // если эксперт был остановлен пользователем, прекращаем работу if(IsStopped()) { Print("Эксперт был остановлен пользователем!"); return(-1); } // если ожидание длится дольше времени, указанного в переменной // MaxWaiting_sec, тоже прекращаем работу if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000) { Print("Превышен лимит ожидания (" + MaxWaiting_sec + " сек.)!"); return(-2); } // если торговый поток освободился, if(IsTradeAllowed()) { Print("Торговый поток освободился!"); return(0); } // если ни одно из условий остановки цикла не сработало, "ждём" 0,1 // секунды и начинаем проверку сначала 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 ) // // Функция меняет значение глобальной переменной TradeIsBusy с 0 на 1. // Если в момент запуска TradeIsBusy = 1, функция ждёт, пока TradeIsBusy станет = 0, // и только потом меняет. // Если глобальной переменной TradeIsBusy не существует, функция создаёт её. // Коды возвратов: // 1 - успешное завершение. Глобальной переменной TradeIsBusy присвоено значение 1 // -1 - в момент запуска функции TradeIsBusy = 1, ожидание было прервано пользователем // (эксперт удалён с графика, закрыт терминал, изменился период и/или символ // графика, ... ) // -2 - в момент запуска функции TradeIsBusy = 1, истекло максимальное время ожидания // (MaxWaiting_sec) ///////////////////////////////////////////////////////////////////////////////// int TradeIsBusy( int MaxWaiting_sec = 30 ) { // при тестировании нет смысла в разделении торгового потока - просто завершаем // работу функции if(IsTesting()) return(1); int _GetLastError = 0, StartWaitingTime = GetTickCount(); //+------------------------------------------------------------------+ //| Проверяем, существует ли гл. переменная и, если нет, создаём её | //+------------------------------------------------------------------+ while(true) { // если эксперт был остановлен пользователем, прекращаем работу if(IsStopped()) { Print("Эксперт был остановлен пользователем!"); return(-1); } // если ожидание длится дольше времени, указанного в переменной // MaxWaiting_sec, тоже прекращаем работу if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000) { Print("Превышен лимит ожидания (" + MaxWaiting_sec + " сек.)!"); return(-2); } // проверяем, существует ли гл. переменная // если она есть, выходим из этого цикла и переходим к блоку изменения // значения TradeIsBusy if(GlobalVariableCheck( "TradeIsBusy" )) break; else // если GlobalVariableCheck вернула FALSE, значит либо переменной нет, либо // при проверке возникла ошибка { _GetLastError = GetLastError(); // если это всё таки ошибка, выводим информацию, ждём 0,1 секунды и // начинаем проверку сначала if(_GetLastError != 0) { Print("TradeIsBusy()-GlobalVariableCheck(\"TradeIsBusy\")-Error #", _GetLastError ); Sleep(100); continue; } } // если ошибки нет, значит глобальной переменной просто нет, пытаемся создать // её // если GlobalVariableSet > 0, значит глобальная переменная успешно создана. // Выходим из ф-ции if(GlobalVariableSet( "TradeIsBusy", 1.0 ) > 0 ) return(1); else // если GlobalVariableSet вернула значение <= 0, значит при создании // переменной возникла ошибка { _GetLastError = GetLastError(); // выводим информацию, ждём 0,1 секунды и начинаем попытку сначала if(_GetLastError != 0) { Print("TradeIsBusy()-GlobalVariableSet(\"TradeIsBusy\",0.0 )-Error #", _GetLastError ); Sleep(100); continue; } } } //+------------------------------------------------------------------------------+ //| Если выполнение функции дошло до этого места, значит глобальная переменная | //| существует. | //| Ждём, пока TradeIsBusy станет = 0 и меняем значение TradeIsBusy с 0 на 1 | //+------------------------------------------------------------------------------+ while(true) { // если эксперт был остановлен пользователем, прекращаем работу if(IsStopped()) { Print("Эксперт был остановлен пользователем!"); return(-1); } // если ожидание длится дольше времени, указанного в переменной // MaxWaiting_sec, тоже прекращаем работу if(GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000) { Print("Превышен лимит ожидания (" + MaxWaiting_sec + " сек.)!"); return(-2); } // пытаемся менять значение TradeIsBusy с 0 на 1 // если нам это удаётся, выходим из ф-ции, возвращая 1 - "успешное завершение" if(GlobalVariableSetOnCondition( "TradeIsBusy", 1.0, 0.0 )) return(1); else // если нет, возможны 2 причины: TradeIsBusy = 1 (тогда надо ждать), либо // возникла ошибка (это мы и проверим) { _GetLastError = GetLastError(); // если это всё таки ошибка, выводим информацию и пробуем ещё раз if(_GetLastError != 0) { Print("TradeIsBusy()-GlobalVariableSetOnCondition(\"TradeIsBusy\",1.0,0.0 )-Error #", _GetLastError ); continue; } } // если ошибки нет, значит TradeIsBusy = 1 (другой эксперт торгует) - выводим // информацию и ждём... Comment("Ждём, пока другой эксперт закончит торговать..."); Sleep(1000); Comment(""); } }
Тут вроде бы всё понятно:
- проверка существования глобальной переменной и, в случае неудачи, её создание
- попытка изменить значение глобальной переменной с 0 на 1. Сработает только если её значение будет = 0.
Функция может работать максимум MaxWaiting_sec секунд и не препятствует удалению эксперта с графика.
Информация о всех возникающих ошибках выводится в журнал.
6. Функция TradeIsNotBusy()
Функция TradeIsNotBusy выполняет обратную задачу - включает "зелёный свет".
Она не имеет ограничения по времени работы и не может быть остановлена пользователем. Мотивация достаточно простая - если не включить "зелёный свет", ни один эксперт не сможет торговать.
Естественно, и кодов возврата у неё нет - результатом может быть только успешное завершение.
Вот как она выглядит:
///////////////////////////////////////////////////////////////////////////////// // void TradeIsNotBusy() // // Функция устанавливает значение глобальной переменной TradeIsBusy = 0. // Если глобальной переменной TradeIsBusy не существует, функция создаёт её. ///////////////////////////////////////////////////////////////////////////////// void TradeIsNotBusy() { int _GetLastError; // при тестировании нет смысла в разделении торгового потока - просто завершаем // работу функции if(IsTesting()) { return(0); } while(true) { // если эксперт был остановлен пользователем, прекращаем работу if(IsStopped()) { Print("Эксперт был остановлен пользователем!"); return(-1); } // пытаемся установить значение гл. переменной = 0 (или создать гл. // переменную) // если GlobalVariableSet вернула значение > 0, значит всё закончилось // хорошо. Выходим из ф-ции if(GlobalVariableSet( "TradeIsBusy", 0.0 ) > 0) return(1); else // если GlobalVariableSet вернула значение <= 0, значит возникла ошибка. // Выводим информацию, ждём, и пробуем ещё раз { _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