Ошибка 146 ("Торговый поток занят") и как с ней бороться

Andrey Khatimlianskii | 12 мая, 2006


1. Понятие "Торгового потока" в терминале MetaTrader 4

Из справки MetaEditor:

Для проведения торговых операций из экспертов и скриптов предусмотрен всего один поток, который запускается в программном торговом контексте  (контекст  автоматической торговли из экспертов и скриптов).  Поэтому, если этот контекст  занят  торговой  операцией  какого-либо эксперта, то  другой эксперт или скрипт не может в этот момент вызывать торговые функции из-за ошибки 146 (ERR_TRADE_CONTEXT_BUSY)

Проще говоря, проводить торговые операции одновременно может только один эксперт (скрипт). Все остальные эксперты, пытающиеся торговать, будут "остановлены" ошибкой № 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