Скачать советник OpenOrder

 

    Всем известен принцип работы отложенных ордеров. Такой механизм MT4 освобождает трейдера от утомительного ожидания нужной цены открытия сделки. Но далеко не всех может устраивать работа при помощи отложенных ордеров. Их очевидный плюс – ордера хранятся на сервере брокера и срабатывают даже при отключенном терминале со стороны клиента – некоторые считают минусом. Дескать, брокеру в этом случае заранее известны наши намерения, что может быть использовано им для ложных движений цены. То есть ради передачи брокеру как можно меньшего количества информации о своих дальнейших действиях трейдеры готовы пожертвовать возможными реквотами при открытии позиции с рынка.
    Еще одним, но уже очевидным, недостатком отложенных ордеров, является невозможность их установки в определенных случаях. Речь идет о диллинговых центрах, в которых запрещено локирование – открытие двух встречных позиций на одном инструменте. Побочным эффектом этого запрета стал отказ брокера при запросе на установку ордера BuyStop при имеющейся позиции Sell и наоборот – невозможность установки SellStop при открытой позиции Buy.
    Обе вышеуказанные проблемы решаются использованием советника, который открывает сделку в нужном направлении, когда цена достигает заданного уровня. Какие данные для этого нужно передать программе? Конечно же, необходимо указать тип сделки (Buy или Sell). У этой сделки должен быть какой-то объем. Также необходимо указать уровень цены, пересечение которого должно привести к открытию сделки. Это всего лишь минимум необходимых параметров. Для удобства пользователя список параметров можно расширить до такого набора:

 
//---- input parameters
extern double    Lots            = 0.1;       // Объем открываемой сделки
extern string    Direction       = "Buy";     // Направление сделки - Buy или Sell
extern double    StartPrice      = 1.23450;   // Цена открытия позиции
extern double    StopLoss        = 1.22;      // Уровень стоп-приказа или отключения 
extern double    TakeProfit      = 1.25;      // Уровень фиксации прибыли
extern int       TrailingStop1   = 150;       // Единоразовый перенос уровня в безубыток
extern int       TrailingStop2   = 200;       // Классический трейлинг
extern string    OpenOrderSound  = "ok.wav";  // Звук для открытия позиции 
extern string    StopLossSound   = "alert2.wav";// Звук для уровня стопа
extern string    TakeProfitSound = "news.wav";// Звук для фиксации прибыли
extern int       MagicNumber     = 594;       // Магик для пометки своей позиции

    Назначение первых пяти переменных должно быть понятно. Единственное "но" - StopLoss задает не только уровень стоп-приказа для открытой позиции, но и уровень, при котором  работа советника будет закончена даже без открытия позиции. Параметром TrailingStop1 можно задать количество пунктов прибыли, при котором стоп позиции будет перенесен в беузбыток. После того, как сработает TrailingStop1, слежение за позицией возьмет на себя TrailingStop2. Это уже классический трейлинг, который реализован в самом МТ4. Переменные OpenOrderSound, StopLossSound и TakeProfitSound указывают имена звуковых файлов, запускаемых на воспроизведение при наступлении соответствующих событий - открытие позиции, достижение стоп-приказа и достижение уровня фиксации прибыли. MagicNumber служит для отделения позиции, открытой советником, от других позиций, открываемых на торговом счете.

    Чтобы советник мог использовать данные настроечных параметров в дальнейшей работе, необходимо проверить правильность их значений при запуске эксперта. Делается это в предопределенной функции init. Она выполняется всего один раз за все время работы советника - при подключении эксперта к графику валютной пары. Вместе с функцией init приведем описание глобальных переменных эксперта и еще одной небольшой функции - ND, которая нужна только для более компактного написания вызова функции нормализации (округления) дробных значений NormalizeDouble.     

 
bool Activate, IsWorkEnd, FreeMarginAlert, IsComment, FatalError;
double Spread, StopLevel, NBid, NAsk;
int TDir;
//+--------------------------------------------------------------------------------------+
//| Функция инициализации  эксперта                                                      |
//+--------------------------------------------------------------------------------------+
int init()
  {
// Блок 1. Сброс всех используемых флагов ------------------------------------------------
   Activate = False;
   FatalError = False;
   IsWorkEnd = False; 
   IsComment = False; 
// Блок 1. -------------------------------------------------------------------------------
  
// Блок 2. Проверка разрешения автоторговли ----------------------------------------------
   if(!IsTradeAllowed() && !IsTradeContextBusy())
     {
      Comment("Автоматическая торговля запрещена!",
      " Измените соответсвующие настройки МТ4 и проверьте",
      " возможность автоторговли у брокера!");
      return(0);
     }
// Блок 2. -------------------------------------------------------------------------------
     
// Блок 3. Сбор информации об условиях торговли ------------------------------------------
   Spread = ND(MarketInfo(Symbol(), MODE_SPREAD)*Point);
   StopLevel = ND(MarketInfo(Symbol(), MODE_STOPLEVEL)*Point);  
   double MinLot = MarketInfo(Symbol(), MODE_MINLOT);
   double MaxLot = MarketInfo(Symbol(), MODE_MAXLOT);
   double LotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
// Блок 3. -------------------------------------------------------------------------------
   
// Блок 4. Проверка корректности объема сделки -------------------------------------------
   if(Lots < MinLot || Lots > MaxLot)
     {
      Comment("Параметром Lots был задан неправильный объем сделки! Советник отключен!");
      return(0);
     }
   Lots = MathRound(Lots/LotStep)*LotStep;
// Блок 4. -------------------------------------------------------------------------------
   
// Блок 5. Проверка корректности типа сделки (Direction) ---------------------------------
   if(Direction == "buy" || Direction == "buY" || Direction == "bUy" || 
      Direction == "bUY" || Direction == "Buy" || Direction == "BuY" || 
      Direction == "BUy" || Direction == "BUY")
       TDir = 1;
      else
       if(Direction == "sell" || Direction == "selL" || Direction == "seLl" || 
          Direction == "seLL" || Direction == "sEll" || Direction == "sElL" || 
          Direction == "sELl" || Direction == "sELL" || Direction == "Sell" || 
          Direction == "SelL" || Direction == "SeLl" || Direction == "SeLL" || 
          Direction == "SEll" || Direction == "SElL" || Direction == "SELl" || 
          Direction == "SELL")
           TDir = 2;
          else
           {
            Comment("В параметре Direction указан неизвестный тип сделки.",
                    " Советник отключен!");
            return(0);
           }   
// Блок 5. -------------------------------------------------------------------------------

// Блок 6. Проверка корректности параметров TrailingStop1 и TrailingStop2 ----------------
   if(ND(TrailingStop1*Point - StopLevel) < 0)
     {
      Comment("Слишком малое значение параметра TrailingStop1. Советник отключен!");
      return(0);
     }
   if(ND(TrailingStop2*Point - StopLevel) < 0)
     {
      Comment("Слишком малое значение параметра TrailingStop2. Советник отключен!");
      return(0);
     }
// Блок 6. -------------------------------------------------------------------------------
   
// Блок 7. Проверка корректности переменных StratPrice, StopLoss и TakeProfit ------------
   // Работал ли советник на этом счете?   
   if(IsOwnOrder())   // да, работал, т. к. есть открытая позиция
      {
       IsWorkEnd = True;
       Comment("Советник запущен заново и отслеживает работу текущей позиции #", 
               OrderTicket());
      }
     else   // не работал или уже отработал, т. к. открытых позиций нет
      { 
       // Проверка правильности стартовой цены и параметров StopLoss и TakeProfit
       if(TDir == 1)
         {
          // StartPrice
          if(ND(StartPrice-Ask) < 0)
            {
             Comment("Указана неправильная цена StartPrice. Советник отключен!");
             return(0);
            }
          //StopLoss  
          if(ND(StartPrice-StopLoss-StopLevel-Spread) < 0)
            {
             Comment("Уровень StopLoss выставлен очень близко от StartPrice.",
                     " Советник отключен!");
             return(0);
            }  
          // TakeProfit
          if(ND(TakeProfit-StartPrice-StopLevel+Spread) < 0)
            {
             Comment("Уровень TakeProfit выставлен очень близко от StartPrice.",
                     " Советник отключен!");
             return(0);
            }  
         }
        else
         {
          // StartPrice
          if(ND(Bid-StartPrice) < 0)
            {
             Comment("Указана неправильная цена StartPrice. Советник отключен!");
             return(0);
            }
          //StopLoss  
          if(ND(StopLoss-StartPrice-StopLevel-Spread) < 0)
            {
             Comment("Уровень StopLoss выставлен очень близко от StartPrice.",
                     " Советник отключен!");
             return(0);
            }  
          // TakeProfit
          if(ND(StartPrice-TakeProfit-StopLevel+Spread) < 0)
            {
             Comment("Уровень TakeProfit выставлен очень близко от StartPrice.",
                     " Советник отключен!");
             return(0);
            }   
         } 
             
       Comment("Советник включен. Ожидается достижение цены ", 
               DoubleToStr(StartPrice, Digits), " для открытия позиции ", Direction);
      }
// Блок 7. -------------------------------------------------------------------------------
        
   Activate = True; // Разрешаем работу советника, все параметры корректны.
   
//----
   return(0);
  }
  
//+--------------------------------------------------------------------------------------+
//| Приведение значений к точности одного тика                                           |
//+--------------------------------------------------------------------------------------+
double ND(double A)
{
 return(NormalizeDouble(A, Digits));
}  

    В самом начале функции init (блок 1) сбрасываются все флаги - Activate (флаг успешной активации советника), FatalError (флаг фатальной ошибки, который мы уже использовали в функции OpenOrder), IsWorkEnd (флаг, свидетельствующий о том, что  в текущем сеансе работы эксперта уже была открыта позиция). Далее следует блок 2, в котором проверяется разрешение на автоторговлю. Если автоторговля запрещена, то это приведет к досрочному окончанию функции init без поднятия флага Activate, в результате чего советник не сможет работать дальше.

    Если же с автоторговлей порядок, то дальше (блок 3) производится сбор информации об основных параметрах торговых условий  - спред, минимальный уровень стопов, минимальный и максимальный разрешенный объем сделки, а также шаг, с которым разрешается изменять объем. С использованием этой информации в блоке 4 происходит проверка корректности первого внешнего параметра эксперта - Lots. Для продолжения работы функции init, как и всего эксперта, параметр Lots должен находиться в пределах от MinLot (минимальный допустимый объем сделки) до MaxLot (максимальный допустимый объем сделки). Если это условие выполняется, то в дальнейшем производится приведение значения Lots к точности LotStep.

    В блоке 5 производится проверка второго внешнего параметра - Direction, в котором пользователь должен указать тип сделки. Для правильного восприятия экспертом значения введенной пользователем строки, перебираются все возможные написания строк "BUY" и "SELL". Тип сделки Buy приводит значение переменной TDir, которая и задает направление сделок советнику в дальнейшем, к единице. Тип Sell соответствует значению TDir = 2. Если тип сделки определить не удалось, то эксперт заканчивает свою работу сообщением об ошибке.

    Если же по окончанию пятого блока ошибок не обнаружено, то производится проверка переменных TrailingStop1 и TrailingStop2 (блок 6). Эти значения должны быть не меньше минимального уровня стопов. В противном случае советник завершает работу сообщением об ошибке. Особое внимание в этом блоке следует обратить на метод сравнения двух вещественных чисел - StopLevel и TrailingStop1 (или 2). Он заключается не в прямом сравнении значений, а в сравнении их нормализованной разницы с нулем. Дело в том, что все числа в машинной памяти хранятся не в десятичном, а в двоичном виде и, например, такое дробное число как 0.7, не может быть переведено в двоичный код и обратно без потери точности (оно может принять значение 0.6999999 или 0.70000001). А вот число "ноль" в любой системе исчисления будет нулем, что с ним не делай. Поэтому для получения точного сравнения двух вещественных чисел нужно брать их разницу, которую, в свою очередь, приводить к нужной точности и только потом сравнивать ее с нулем.

    Кроме проверки корректности входных параметров, функция инициализации должна задавать режим работы эксперта. Именно здесь необходимо учесть два варианта событий, которые привели к запуску советника: "советник уже был запущен, но не закончил работу", и "советник еще не работал на счете". Первый вариант может возникнуть и при неумышленных действиях пользователя. Дело в том, что даже такие его действия, как переключения периодов графика (таймфреймов), приводят к отключению и включению советника заново, что вызывает запуск функции init. Также не стоит забывать о таких случаях, когда пользователю потребовалось элементарное изменение одного из параметров советника. А ведь при этом тоже происходит переинициализация эксперта. Как же мы можем определить, что советник предыдущий раз не довел дело до конца? В принципе, нас интересует лишь один из всех возможных случаев - была открыта позиция, но не была доведена до стоп-приказа или уровня фиксации прибыли. Случай, когда позиция еще не была открыта, ничем не отличается от работы эксперта с самого сначала.

    Получается, что при входе в функцию init нам нужно определить, есть на счете открытая советником позиция  или нет. Как раз это и делает функция IsOwnOrder (см. код ниже), которая возвращает True, если позиция есть, и False, если ее нет. В случае присутствия позиции, дальнейшая работа функции init пойдет по более короткому пути - возведение флага IsWorkEnd (позиция открыта) и выдача сообщения о "подхвате" имеющейся позиции. В другом случае программе предстоит проверка значений внешних переменных StartPrice, StopLoss и TakeProfit.

    При направлении торговли "BUY", значение StartPrice должно быть меньше цены Ask хотя бы на один пункт, значение StopLoss - ниже StartPrice на (StopLevel+Spread) пунктов, а значение TakeProfit - выше StartPrice на (StopLevel-Spread) пунктов. Соответственно, при направлении торговли "SELL", значение StartPrice должно быть больше цены Bid хотя бы на один пункт, значение StopLoss - больше StartPrice на (StopLevel+Spread) пунктов, а значение TakeProfit - меньше StartPrice на (StopLevel-Spread) пунктов. Все эти сравнения выполняются точно таким же методом, при помощи которого сравнивалось значение TrailingStop и StopLevel. Если в блоке проверки значений StartPrice, StopLoss и TakeProfit будет найдено несоответствие хотя бы одного параметра, то будет выдано сообщение об ошибке и завершена работа советника. При отсутствии ошибок будет возведен флаг Activate, который позволит дальнейшую работу эксперта по приходу первого тика.

    Теперь рассмотрим функцию IsOwnOrder, которая должна определять наличие открытой советником позиции. Ее код довольно прост:

 
//+--------------------------------------------------------------------------------------+
//| Проверка существования своей позиции                                                 |
//+--------------------------------------------------------------------------------------+
bool IsOwnOrder()
{
 for(int i = 0; i < OrdersTotal(); i++)  // Проверка всех позиций и ордеров счета
  if(OrderSelect(i, SELECT_BY_POS))      // Если позиция с номером i существует и выбрана
   if(OrderSymbol() == Symbol() &&       // Позиция должна быть на текущем инструменте
       OrderMagicNumber() == MagicNumber)// и с тем же магиком
     return(True);
 return(False);    
} 

    В цикле проверяются все открытые на счете позиции. Позиция должна быть открыта по текущему инструменту (та валютная пара, к графику которой подсоединен эскперт) и иметь такое же значение Magic Number, которое указано в параметре MagicNumber. Если это условие выполняется, то сразу же завершается цикл, а с ним и течение функции IsOwnOrder  с возвратом значения True - есть позиция. Если же цикл доходит до конца, то выход из функции происходит с возвратом другого значения - False - нет позиции.

    Следующая функция занимается непосредственно открытием позиции с рынка. Такая функция по определению не может быть простой и короткой. Вот ее код:

 
//+--------------------------------------------------------------------------------------+
//| Открытие позиции                                                                     |
//| Возвращает:                                                                          |
//|   True - Позиция открыта успешно                                                     |
//|   False - Ошибка открытия                                                            |
//+--------------------------------------------------------------------------------------+
bool OpenOrder(int Type, double Price, double SL, double TP)
{
// Блок 1. Проверки достаточности свободных средств --------------------------------------
 if(AccountFreeMarginCheck(Symbol(), OP_BUY, Lots) <= 0 || GetLastError() == 134) 
  {
   if(!FreeMarginAlert)  // если недостаток свободных средств зафиксирован впервые
    {
     Print("Недостаточно средств для открытия позиции. Free Margin = ", 
           AccountFreeMargin());
     FreeMarginAlert = True;  // возводим флаг, чтобы не было повторных сообщений
    } 
   return(False);  
  }
 FreeMarginAlert = False;     // Сброс флага нехватки средств, средства есть
// Блок 1. -------------------------------------------------------------------------------

// Блок 2. Перевод типа поиции в строковый вид -------------------------------------------
 if(Type == OP_BUY)  
   string S = "BUY";
  else
   S = "SELL"; 
// Блок 2. -------------------------------------------------------------------------------

// Блок 3. Открытие позиции --------------------------------------------------------------
 if(WaitForTradeContext())  // ожидание освобождения торгового потока
   {  
    Comment("Отправлен запрос на открытие позиции...");
    int ticket=OrderSend(Symbol(), Type, Lots, Price, 0, SL, TP, 
                         NULL, MagicNumber, 0, CLR_NONE);  // открытие позиции
    // Попытка открытия позиции завершилась неудачей
    if(ticket<0)
      {
       int Error = GetLastError(); // запоминаем номер ошибки
       if(Error == 2 || Error == 5 || Error == 6 || Error == 64 
          || Error == 132 || Error == 133) // список фатальных ошибок
         {
          Comment("Фатальная ошибка при открытии позиции т. к. "+ErrorToString(Error)+
                  " Советник отключен!");
          FatalError = True;  // советник не будет работать после фатальной ошибки
         }
        else 
         Comment("Ошибка открытия позиции ", S, ": ", Error); // нефатальная ошибка
       return(False);
      }
    // ---------------------------------------------
    
    // Удачное открытие позиции   
    Comment("Позиция ", S, " открыта успешно!"); 
    PlaySound(OpenOrderSound); // выдача звукового сигнала об успешном открытии позиции
    return(True);              // успешное открытие позиции
    // ------------------------
   }
  else
   {
    Comment("Время ожидания освобождения торгового потока истекло!");
    return(False);  // открыть позицию не удалось
   } 
// Блок 3. -------------------------------------------------------------------------------
}

        В блоке 1 производится проверка достаточности свободных средств, чтобы не пытаться открыть сделку, на которую заведомо не хватает денег. Если средств на открытие позиции не хватает, то в журнал выдается соответствующее сообщение. Чтобы не повторять это сообщение с каждой новой попыткой открытия позиции, используется переменная FreeMarginAlert, которая запоминает последний результат проверки свободных средств.

   Открытие позиции производится в блоке 3. Вход в блок произойдет при освобождении торгового потока, за чем следит пользовательская функция WaitForTradeContext. Сделка будет открыта с параметрами, которые передаются функции OpenOrder  в качестве входных параметров (Type - тип позиции Buy или Sell, Price - цена открытия позиции, SL - уровень стоп-приказа, TP - уровень TakeProfit). Результат открытия (сохраняется в переменной ticket) будет положительным в случае успешного выполнения функции OrderSend. В этом случае выполнение перейдет к блоку успешного открытия, в котором будет выдано сообщение об открытии позиции, прозвучит звуковой сигнал и произойдет выход из функции OpenOrder с возвратом результата True (успешное завершение функции).

    Если же результат открытия позиции отрицательный, то выполнится блок анализа ошибки. В ряде случаев ошибка будет признана фатальной (например, "общая ошибка" или "старая версия клиентского терминала"). Ожидать быстрого исправления подобных ситуаций не приходится, поэтому советник в таких случаях отключается с выводом сообщения о причине остановки. Это делается присвоением переменной FatalError значения True, что, как мы дальше увидим, приводит к блокировке работы советника. Но чаще всего причиной неудачного открытия будут вполне обыденные ошибки. Самая распространенная из них - 135 ("цена изменилась", более известная как "реквот"). В этом случае на экране появится сообщение о полученной ошибке и будет произведен выход из функции OpenOrder с возвратом значения False (неудачное завершение функции OpenOrder).

     В блоке анализа ошибки внимательный читатель должен был заметить использование пользовательской функции ErrorToString. Как несложно догадаться, функция просто преобразует номер ошибки в ее словесное описание, которое дополняет сообщение о фатальной ошибке.

    И только после рассмотрения большинства вспомогательных функций мы переходим к связующей функции эксперта - start. Она выполняется каждый раз, когда терминал МТ4 получает новую котировку по текущему инструменту (другими словами, по приходу нового тика).

 
//+--------------------------------------------------------------------------------------+
//| Функция start эксперта                                                               |
//+--------------------------------------------------------------------------------------+
int start()
  {
// Блок 1. Разрешено ли советнику работать? ----------------------------------------------
   if(!Activate || IsComment || FatalError)  // Отключается работа советника, если функция
    return(0);                               //  init завершилась с ошибкой или выдано со-
                                             //  общение об окончании работы эксперта, или
                                             //  имела место фатальная ошибка
// Блок 1. -------------------------------------------------------------------------------
    
// Блок 2. сбор информации о свойствах инструмента ---------------------------------------
   Spread = ND(MarketInfo(Symbol(), MODE_SPREAD)*Point);   // текущий спрэд по инструменту
   StopLevel = ND(MarketInfo(Symbol(), MODE_STOPLEVEL)*Point); //текущий уровень стопов
   NBid = ND(Bid);                                     // нормализованная текущая цена BID
   NAsk = ND(Ask);                                     // нормализованная текущая цена ASK
// Блок 2. -------------------------------------------------------------------------------

// Блок 3 - Действия при наличии открытой позиции ----------------------------------------
   if(IsOwnOrder())                               // если есть открытая советником позиция
     {
      TrailingStopDoubleLevel();                  // пытаемся подтянуть стоп
      return(0);                                  // выход до следующего тика
     }
// Блок 3. -------------------------------------------------------------------------------

// Блок 4 - Действия при отсутствии открытой позиции -------------------------------------
    else
// Блок 4.1 - Позиция еще не была открыта ------------------------------------------------
     if(!IsWorkEnd)                            // если не поднят флаг окончания работы, то
       {
// Блок 4.1.1 - Направление "вверх" (длинная позиция) ------------------------------------
        if(TDir == 1)                         // если направление "вверх"
          {
           if(ND(NBid-StopLoss) <= 0)  // BID меньше уровня StopLoss, заканчиваем работу
             {
              IsComment = True;        // Поднимается флаг выдачи сообщения о конце работы
              Comment("Работа советника завершена достижением уровня ",
                      "стопа до открытия позиции!");          // Выдача сообщения на экран
              PlaySound(StopLossSound);   // Звуковое оповещение о достижении уровня стопа
             }
            else                                             // BID больше уровня StopLoss
             // Проверка допустимости уровней стоп-приказа и профита
             if(ND(NAsk-StartPrice) >= 0 &&             // ASK больше или равен StartPrice
                ND(NBid-StopLoss-StopLevel) >= 0 &&  // а BID больше StopLoss на StopLevel
                ND(TakeProfit-NBid-StopLevel) >= 0)    // TakeProfit дальше, чем StopLevel
               if(OpenOrder(OP_BUY, NAsk, StopLoss, TakeProfit))//открытие длинной позиции
                 IsWorkEnd = True;                      // Поднимаем флаг открытия позиции
          }
// Блок 4.1.1 ----------------------------------------------------------------------------

// Блок 4.1.2 - Направление "вниз" (короткая позиция) ------------------------------------
         else
          {
           if(ND(StopLoss-NAsk) <= 0)           // ASK больше StopLoss, заканчиваем работу
             {
              IsComment = True;   // Поднимается флаг выдачи сообщения о завершении работы
              Comment("Работа советника завершена достижением уровня ",
                      "стопа до открытия позиции!");          // Выдача сообщения на экран
              PlaySound(StopLossSound);   // Звуковое оповещение о достижении уровня стопа
             }
            else                                             // ASK меньше уровня StopLoss
             // Проверка допустимости уровней стоп-приказа и профита
             if(ND(NBid-StartPrice) <= 0 &&             // BID меньше или равен StartPrice
                ND(NAsk-TakeProfit-StopLevel) >= 0 &&// ASK больше TP на StopLevel пунктов
                ND(StopLoss-NAsk-StopLevel) >= 0)  // SL больше ASK более чем на StopLevel
               if(OpenOrder(OP_SELL, NBid, StopLoss, TakeProfit))      // открытие позиции
                 IsWorkEnd = True;                      // Поднимаем флаг открытия позиции
          }
// Блок 4.1.2 ----------------------------------------------------------------------------
       }
// Блок 4.1 ------------------------------------------------------------------------------

// Блок 4.2 - Позиция была закрыта на прошлом тике ---------------------------------------
      else                             // Позиции нет, но в текущем сеансе она открывалась
         {
          IsComment = True;       // Поднимается флаг выдачи сообщения о завершении работы
          switch(CloseReason())   // Определение причины закрытия позиции
            {
             case 0:                                    // позиция была закрыта по профиту
               Comment("Работа советника завершена закрытием позиции по TakeProfit!");
               PlaySound(TakeProfitSound);      // Звуковое оповещение закрытия по прибыли
             break;  
             case 1:                                      // позиция была закрыта по стопу
               Comment("Работа советника завершена закрытием позиции по StopLoss...");
               PlaySound(StopLossSound);          // Звуковое оповещение закрытия по стопу
             break;  
             case 2:                                       // позиция была закрыта вручную
               Comment("Работа советника завершена ручным закрытием позиции!");
             break;  
             case 3:                                                 // неизвестная ошибка
               Comment("Работа советника завершена из-зи неизвестной ошибки.");
             break;  
            } 
         }
// Блок 4.2 ------------------------------------------------------------------------------

// Блок 4. -------------------------------------------------------------------------------

   return(0);
  }
//+------------------------------------------------------------------+

 

    В начале функции (блок 1) следует именно та конструкция, которая не дает работать эксперту, если работа функции Init была завершена с ошибкой (Activate не равно True) или было выдано сообщение об окончании работы эксперта (IsComment равно True). Также советник не сможет работать, если зафиксирована фатальная ошибка (FatalError равно True).

    В блоке 2 происходит примерно такой же сбор информации о счете, как и в функции init, но в немного усеченном варианте. Обновляются значения Spread и StopLevel, так как они могут быть изменены брокером в любой момент. Также в этом блоке нормализуются значения цены Bid и Ask.

    Блок 3 запускается на исполнение, если существует открытая советником позиция. В этом случае достаточно лишь проверить, нужно передвигать уровень стоп-приказа за ценой или нет. За это отвечает функция TrailingStopDoubleLevel, которую рассмотрим самой последней.

    Блок 4 самый большой в функции start. Он выполняется, если нет открытых советником позиций и состоит из подблоков 4.1 и 4.2.

    Блок 4.1 выполняется, если в текущем сеансе позиция еще не открывалась. Он тоже состоит из двух блоков - 4.1.1 и 4.1.2, которые разделяются по типу потенциальной сделки. Их алгоритм примерно одинаков. Сначала проверяется, был ли достигнут уровень StopLoss (для длинных позиций Bid должен быть ниже StopLoss, для коротких Ask - выше StopLoss). Если условие выполняется, то возводится флаг IsCommet (этим запрещается дальнейшая работа эксперта), выдается сообщение об окончании работы советника и воспроизводится сигнал достижения уровня стопа. В случае, если цена не достигла уровня StopLoss, производится проверка возможности открытия позиции (для длинной позиции Ask больше или равен StartPrice, а для короткой Bid меньше или равен StartPrice). Если выполняются эти условия, то попутно проверяется корректность StopLoss и TakeProfit по отношению к текущему уровню цены и уровню стопов StopLevel. Прохождение всех проверок приводит выполнение программы к вызову функции OpenOrder. Успешное выполнение этой функции служит сигналом к возведению флага IsWorkEnd, который переводит работу эксперта в режим слежения за открытой позицией.

    Выполнение блока 4.2 происходит всего один раз, когда торговый счет не содержит позицию, открытую экспертом, но в течение текущего сеанса она была открыта. Другими словами, в блок 4.2 мы попадаем на следующем тике после закрытия ранее открытой позиции. В этом случае необходимо знать, по какой причине была закрыта позиция. Возможных причин всего три: достижение уровня TakeProfit, достижение уровня стоп-приказа и закрытие позиции пользователем вручную. Одна из этих причин отображается в сообщении, которое выдается по окончании работы эксперта. При этом возводится флаг IsComment и работа советника прекращается. Для выяснения причины закрытия позиции вызывается функция CloseReason:

 
//+--------------------------------------------------------------------------------------+
//| Выяснение причины закрытия последней позиции:                                        |
//|   0 - по TakeProfit                                                                  |
//|   1 - по StopLoss                                                                    |
//|   2 - ручное закрытие (между TP и SL)                                                |
//|   3 - неизвестная ошибка или закрытая позиция не найдена)                            |
//+--------------------------------------------------------------------------------------+
int CloseReason()
{
// Блок 1. Поиск своей позиции -----------------------------------------------------------
  for(int i = OrdersHistoryTotal()-1; i >= 0; i--)         // Поиск среди закрытых позиций
    if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY))        // Позиция выбрана
      if(OrderSymbol() == Symbol() &&                      // По текущему инструменту
         OrderMagicNumber() == MagicNumber)                // и с нужным магиком
        break;                                             // Нашли - прерывается цикл
// Блок 1. -------------------------------------------------------------------------------

  if(i >= 0)                    // Цикл не дошел до конца, значит, поиск увенчался успехом
    {
// Блок 2. Выяснение причины для длинной позиции -----------------------------------------
     if(OrderType() == OP_BUY)
       {
        if(ND(OrderClosePrice()-OrderTakeProfit()) >= 0   // Если цена закрытия больше или
          && OrderTakeProfit() != 0)                      // равна TakeProfit и он был
          return(0);                               // установлен, то закрыто по TakeProfit
        if(ND(OrderStopLoss()-OrderClosePrice()) >= 0 && // Если цена закрытия меньше или
           OrderStopLoss() != 0)                         // равна StopLoss и он был
          return(1);                                // установлен, то закрыто по StopLoss
        return(2);                                             // иначе - закрыто вручную
       }
// Блок 2. -------------------------------------------------------------------------------

// Блок 3. Выяснение причины для короткой позиции ----------------------------------------
     if(OrderType() == OP_SELL)
       {
        if(ND(OrderTakeProfit()-OrderClosePrice()) >= 0   // Если цена закрытия меньше или
          && OrderTakeProfit() != 0)                      // равна TakeProfit и он был
          return(0);                               // установлен, то закрыто по TakeProfit
        if(ND(OrderClosePrice()-OrderStopLoss()) >= 0     // Если цена закрытия меньше или
          && OrderStopLoss() != 0)                        // равна StopLoss и он был
          return(1);                                 // установлен, то закрыто по StopLoss
        return(2);                                              // иначе - закрыто вручную
       }
    }
// Блок 3. -------------------------------------------------------------------------------
  
  return(3);                                 // неизвестная ошибка
}
  

Несмотря на кажущуюся внушительность, функция довольно проста. Сначала в цикле (блок 1) производится поиск последней закрытой позиции, которая была открыта советником. Когда таковая находится, то цена закрытия позиции сравнивается с уровнями TakeProfit и StopLoss (блоки 2 и 3 для длинной и короткой позиции соответственно). При закрытии позиции по TakeProfit функция CloseReason вернет 0, по StopLoss - 1, по закрытию пользователем (не достигнут ни один из указанных уровней) - 2. На возможный сбой отведено значение 3.

    И последняя функция эксперта, которую стоит рассмотреть, это TrailingStopDoubleLevel, в которой происходит проверка необходимости изменения уровня стоп-приказа в соответствии с параметрами TrailingStop1 и TrailingStop2.

 
//+--------------------------------------------------------------------------------------+
//| Двухуровневый трейлинг-стоп                                                          |
//| TrailingStop1 - выставляется один раз после достижения нужного кол-ва пунктов прибыли|
//| TrailingStop2 - движущийся. Включается после отработки трейлинг-стопа первого уровня |
//+--------------------------------------------------------------------------------------+
void TrailingStopDoubleLevel()
{
// Блок 1. Нормализация основных характеристик позиции -----------------------------------
   {
    double OpPrice = ND(OrderOpenPrice()); // Приведение цены открытия к нужной точности
    double SL = ND(OrderStopLoss());       // Приведение стоп-приказа к нужной точности
// Блок 1. -------------------------------------------------------------------------------

// Блок 2. Классический трейлинг длинной позиции -----------------------------------------
    if(OrderType() == OP_BUY)
      if(ND(SL-OpPrice) >= 0)            // Если стоп выше или равен цене открытия позиции
        {
         if(ND(NBid-Point*TrailingStop2-SL) > 0) // Если BID больше стопа на TrailingStop2
           if(WaitForTradeContext())            // Ждем освобождение торгового потока
             if(!OrderModify(OrderTicket(), 0, 
                ND(NBid-TrailingStop2*Point), OrderTakeProfit(), 0)) // Модификация
               Print("Не удалось изменить ордер BUY. Второй трал. Ошибка №",
                     GetLastError(), ", Старый: ", SL, ", Новый: ", 
                     NBid-TrailingStop2*Point);  // Развернутое сообщение об ошибке
        }
// Блок 2. -------------------------------------------------------------------------------

// Блок 3. Перенос стопа в безубыток длинной позиции -------------------------------------
       else  
        {    
         if(ND(NBid-OpPrice-TrailingStop1*Point) > 0 &&   // Если BID больше цены открытия
            ND(NBid-OpPrice-StopLevel) > 0)      // на TrailingStop1 пунктов и дальше, чем
           if(WaitForTradeContext())             // уровень стопов
             if(!OrderModify(OrderTicket(), 0, OpPrice,
                OrderTakeProfit(), 0))           // модификация
               Print("Не удалось изменить ордер BUY. Первый трал. Ошибка №", // сообщение
                     GetLastError(), ", Старый: ", SL, ", Новый: ", OpPrice);// в журнал
        } 
// Блок 3. -------------------------------------------------------------------------------
     
// Блок 4. Классический трейлинг короткой позиции ----------------------------------------
    if(OrderType()==OP_SELL)                    // Для короткой позиции
     if(ND(OpPrice-SL) >= 0 && SL != 0)         // Стоп ниже цены открытия и не равен нулю
       {
        if(ND(OpPrice-NAsk-StopLevel) > 0 &&    // ASK ниже цены открытия на StopLevel
           ND(SL-Point*TrailingStop2-NAsk) > 0) // и ниже стопа на TrailingStop2 пунктов
          if(WaitForTradeContext())             // Ожидаем освобождения торгового потока
            if(!OrderModify(OrderTicket(), 0, ND(NAsk+TrailingStop2*Point),
               OrderTakeProfit(), 0))           // модификация
              Print("Не удалось изменить ордер SELL. Второй трал. Ошибка №", 
                 GetLastError(), ", Старый: ", SL, ", Новый: ", NAsk+TrailingStop2*Point);
       }
// Блок 4. -------------------------------------------------------------------------------

// Блок 5. Перенос стопа в безубыток короткой позиции ------------------------------------
      else
       { 
        if(ND(OpPrice-NAsk-Point*TrailingStop1) > 0 &&//ASK ниже открытия на TrailingStop1
           ND(OpPrice-NAsk-StopLevel) > 0)                       // и на StopLevel пунктов
          if(WaitForTradeContext())                   // Ожидаем торговый поток
            if(!OrderModify(OrderTicket(), 0, OpPrice, OrderTakeProfit(), 0))//модификация
              Print("Не удалось изменить ордер SELL. Первый трал. Ошибка №", 
                   GetLastError(), ", Старый: ", SL, ", Новый: ", OpPrice);
       }
// Блок 5. -------------------------------------------------------------------------------
   }    
  return(0);
 }
 

    В блоке 1 значения цены открытия и уровня стоп-приказа позиции приводятся к одинаковой точности. Позицию выбирать в данном случае не требуется, так как перед вызовом функции TrailingStopDoubleLevel отработала функция IsOwnOrder, которая уже выставила указатель на "свою" сделку.

    Далее выполнение разделяется на две ветки, в зависимости от типа найденной позиции - BUY (блоки 2 и 3) или SELL (блоки 4 и 5). В обоих случаях сначала определяется, какой из трейлингов сейчас должен отслеживаться - TrailingStop1 (однократное срабатывание, выводящее позицию в безубыток) или TrailingStop2 (классический трейлинг). Критерий довольно простой. Если стоп-приказ находится в убыточной зоне позиции, то отслеживается TrailingStop1, если же в безубытке или лучше, то включается TrailingStop2. То есть для длинных позиций работа TrailingStop2 будет происходить, если уровень стоп-приказа выше или равен цене открытия, а для коротких - ниже или равен (заметьте, что учитывается еще и вариант, когда стоп-приказ не выставлен, то есть StopLoss == 0). Оба трейлинга включаются только в случаях, когда цена находится на расстоянии от установленного уровня стоп-приказа более, чем на TrailingStop1 или TrailingStop2 пунктов. В случае TrailingStop1 дополнительно проверяется условие отдаленности цены от цены открытия позиции на TrailingStop1 пунктов. После самой модификации, в случае неудачи, в журнал МТ4 выводится сообщение о причине неудачи с указанием номера ошибки.

    Рассмотренный эксперт наглядно показывает, с каким количеством возможных сложностей связано обычное открытие позиции в автоматическом режиме. Тем не менее, плюс от применения такого способа, помимо указанных в самом начале статьи, существует еще один. Он заключается в возможности отложенного открытия позиции ближе установленного брокером минимального уровня стопов, что невозможно сделать при использовании ордеров BuyStop и SellStop. Ведь при определенных условиях минимальный уровень стопов может быть достаточно большим - 20 пунктов и более. А вот использование открытия позиции при помощи советника позволяет указывать цену открытия, отличающуюся от текущей всего на один пункт.