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

Автор: lexy Среда, Сентябрь 10th, 2014 Нет комментариев

Рубрика: Разное

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

Прикрепленные файлы:
 TradeContext.mqh (11.5 Kb)
Источник: mql4.com

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты