Пример создания эксперта

MetaQuotes | 21 декабря, 2005

Принципы построения пользовательских программ на языке MQL4 рассматриваются на примере создания простейшей экспертной системы на основе стандартного индикатора MACD. В данной экспертной системе также рассмотрены примеры реализации таких функций как выставление уровней тейкпрофита с поддержкой трейлинг-стопа и большинство предохраняющих условий для безопасной работы. В предлагаемом примере торговля идет с открытием и контролем одной позиции.

Принципы торговли:

  • вход в Long (BUY) – индикатор MACD ниже нуля, идет снизу вверх, а его сверху вниз пересекает сигнальная линия.

  • вход в Short (SELL) – индикатор MACD выше нуля, идет cверху вниз, а его снизу вверх пересекает сигнальная линия.

  • выход из Long – по тейкпрофиту, по трейлинг-стопу или при пересечении MACD со своей сигнальной линией (MACD выше нуля, идет cверху вниз, а его снизу вверх пересекает сигнальная линия).

  • выход из Short – по тейкпрофиту, по трейлинг-стопу или при пересечении MACD со своей сигнальной линией (MACD ниже нуля, идет снизу вверх, а его сверху вниз пересекает сигнальная линия).

Важное замечание: Для исключения из анализа незначительных(мелкие 'бугорки' на графике) изменений индикатора MACD введем дополнительный контроль за размером рисуемых 'бугров' в виде следующего условия – величина индикатора должна составлять не менее 5 единиц минимальной цены (5*Point, что для USD/CHF равно 0.0005, для USD/JPY = 0.05).


1-й этап – создание описания эксперта

В окне "Navigator" устанавливаем курсор мыши на разделе "Expert Advisors", нажимаем на правую кнопку мыши и в появившемся меню выбираем команду "Create". Мастер первоначальных настроек эксперта предложит ввести некоторые данные: "Name" – название программы, "Author" – автор программы, "Link" – ссылка на веб-сайт.

2-й этап – создание первичной структуры программы

Код тестового эксперта будет занимать всего несколько страниц, но и такой объем зачастую бывает труден для восприятия. Особенно, если мы с вами не профессиональные программисты... Так ведь? Иначе этого описания не пришлось бы писать :)

Для ознакомления со структурой стандартного эксперта взглянем на предлагаемое описание:

  1. Инициализация переменных

  2. Первичные проверки данных

    • проверка графика, количество баров на графике

    • проверка значений внешних переменных Lots, S/L, T/P, T/S

  3. Установка внутренних переменных для быстрого доступа к данным

  4. Проверка торгового терминала – пустой ли? Eсли да, то:

    • проверки: если ли деньги на счету и тд...

    • можно встать в длинную позицию(BUY)?

      • открыть длинную позицию и выйти

  5. можно встать в короткую позицию(SELL)?

    • открыть короткую позицию и выйти

выход из эксперта...
                                                        • Контроль открытых ранее позиций в цикле

                                                          • если это длинная позиция

                                                            • нужно ли закрыть?

                                                            • нужно ли передвинуть трейлинг-стоп?

                                                          • если это короткая позиция

                                                            • нужно ли закрыть?

                                                            • нужно ли передвинуть трейлинг-стоп?

                                                        • Получилось достаточно просто, всего 4 основных блока.

                                                          Теперь попробуем по шагам сформировать куски кода под каждый раздел структурной схемы:

                                                          1. Инициализация переменных
                                                            Все переменные, которые будут использоваться в программе-эксперте, должны быть предварительно описаны в соответствии с синтаксисом языка MetaQuotes Language 4. Поэтому в начале программы вставляем блок инициализации переменных

                                                            extern double TakeProfit = 50;
                                                            extern double Lots = 0.1;
                                                            extern double TrailingStop = 30;
                                                            extern double MACDOpenLevel=3;
                                                            extern double MACDCloseLevel=2;
                                                            extern double MATrendPeriod=26;

                                                            В MetaQuotes Language 4 введено такое понятие, как дополнительные пользовательские переменные, которые могут быть установлены извне, без вмешательства в исходный текст программы-эксперта. Это придает дополнительную гибкость. Переменная MATrendPeriod как раз и является такой пользовательской переменной. В начале программы вставляем описание этой переменной.

                                                            extern double MATrendPeriod=26;
                                                          2. Первичные проверки данных
                                                            Этот кусок кода обычно кочует из одного эксперта в другой с мелкими изменениями – практически стандартный блок проверок:

                                                            // первичные проверки данных
                                                            // важно удостовериться что эксперт работает на нормальном графике и
                                                            // пользователь правильно выставил внешние переменные (Lots, StopLoss,
                                                            // TakeProfit, TrailingStop)
                                                            // в нашем случае проверяем только TakeProfit
                                                               if(Bars<100)
                                                                 {
                                                                  Print("bars less than 100");
                                                                  return(0);  
                                                                 }
                                                               if(TakeProfit<10)
                                                                 {
                                                                  Print("TakeProfit less than 10");
                                                                  return(0);  // проверяем TakeProfit
                                                                 }
                                                          3. Установка внутренних переменных для быстрого доступа к данным
                                                            В коде программы приходится достаточно часто обращаться к значениям индикаторов или оперировать вычисляемыми значениями.

                                                            int start()
                                                              {
                                                               double MacdCurrent, MacdPrevious, SignalCurrent;
                                                               double SignalPrevious, MaCurrent, MaPrevious;
                                                               int cnt, ticket, total;

                                                            Для облегчения кодирования и ускорения доступа применяется предварительное помещение данных во внутренние переменные.

                                                               MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
                                                               MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
                                                               SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
                                                               SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
                                                               MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
                                                               MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);

                                                            Теперь вместо громадной записи iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0) в тексте программы можно использовать MacdCurrent.

                                                          4. Проверка торгового терминала – пустой ли? Если да, то:
                                                            В нашем эксперте мы используем только позиции, открытые по маркету и не трогаем отложенные ордеры. Но для безопасности лучше внесем проверку торгового терминала на наличие выставленных ордеров:

                                                            // теперь надо определиться - в каком состоянии торговый терминал?
                                                            // проверим, есть ли ранее открытые позиции или ордеры?
                                                               total=OrdersTotal();
                                                               if(total<1) 
                                                                 {
                                                            • проверки: доступны ли деньги на счете и тд...
                                                              Перед анализом рыночной ситуации желательно проверить состояние своего счета – есть ли свободные деньги для открытия позиции?

                                                                    // нет ни одного открытого ордера
                                                                    // на всякий случай проверим, если у нас свободные деньги на счету?
                                                                    // значение 1000 взято для примера, обычно можно открыть 1 лот
                                                                    if(AccountFreeMargin()<(1000*Lots))
                                                                      {
                                                                       Print("We have no money. Free Margin = ", AccountFreeMargin());
                                                                       return(0);  
                                                                      }
                                                            • проверка возможности встать в длинную позицию (BUY)
                                                              Условие входа в длинную позицию: MACD ниже нуля, идет снизу вверх, а его сверху вниз пересекает сигнальная линия. Как это записывается на MQL4 (обратите внимание что работа идет с сохраненными ранее в переменных значениями индикаторов):

                                                                    // проверяем на возможность встать в длинную позицию (BUY)
                                                                    if(MacdCurrent<0 && MacdCurrent>SignalCurrent && 
                                                                       MacdPrevious<SingnalPrevious &&
                                                                       MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && 
                                                                       MaCurrent>MaPrevious)
                                                                      {
                                                                       ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,
                                                                                        Ask+TakeProfit*Point,"macd sample",
                                                                                        16384,0,Green);
                                                                       if(ticket>0)
                                                                         {
                                                                          if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) 
                                                                             Print("BUY order opened : ",OrderOpenPrice());
                                                                         }
                                                                       else Print("Error opening BUY order : ",GetLastError()); 
                                                                       return(0); 
                                                                      }

                                                              Выше говорилось о дополнительном контроле за размером рисуемых 'бугров'. Переменная MACDOpenLevel является 'пользовательской' переменной, которую можно менять, не трогая текста программы, для гибкости контроля. В начале программы вставляем описание этой переменной (заодно и описание переменной, используемой ниже).

                                                            • проверка возможности встать в короткую позицию (SELL)
                                                              Условие входа в короткую позицию: MACD выше нуля, идет сверху вниз, а его снизу вверх пересекает сигнальная линия. Как это записывается:

                                                                    // проверяем на возможность встать в короткую позицию (SELL)
                                                                    if(MacdCurrent>0 && MacdCurrent<SignalCurrent &&
                                                                       MacdPrevious>SignalPrevious && 
                                                                       MacdCurrent>(MACDOpenLevel*Point) && 
                                                                       MaCurrent<MaPrevious)
                                                                      {
                                                                       ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,
                                                                                        Bid-TakeProfit*Point,"macd sample",
                                                                                        16384,0,Red);
                                                                       if(ticket>0)
                                                                         {
                                                                          if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) 
                                                                             Print("SELL order opened : ",OrderOpenPrice());
                                                                         }
                                                                       else Print("Error opening SELL order : ",GetLastError()); 
                                                                       return(0); 
                                                                      }
                                                                  // здесь мы завершили проверку на возможность открытия новых позиций.
                                                                  // новые позиции открыты не были и просто выходим 
                                                                  return(0);
                                                                 }
                                                          5. Контроль открытых ранее позиций в цикле

                                                               // переходим к важной части эксперта - контролю открытых позиций
                                                               // 'важно правильно войти в рынок, но выйти - еще важнее...'
                                                               for(cnt=0;cnt<total;cnt++)
                                                                 {
                                                                  OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
                                                                  if(OrderType()<=OP_SELL &&   // это открытая позиция? OP_BUY или OP_SELL 
                                                                     OrderSymbol()==Symbol())  // инструмент совпадает?
                                                            
                                                                    {

                                                            "Cnt" – это переменная цикла, которая должна быть описана в начале программы следующим образом :

                                                             int cnt = 0; 

                                                            • Eсли это длинная позиция

                                                              if(OrderType()==OP_BUY)   // открыта длинная позиция
                                                                {
                                                              • нужно ли закрыть?
                                                                Условие выхода из длиной позиции: при пересечении MACD со своей сигнальной линией, когда MACD выше нуля, идет вверху вниз, а его снизу вверх пересекает сигнальная линия.

                                                                // проверим, может уже пора закрываться?
                                                                if(MacdCurrent>0 && MacdCurrent<SignalCurrent &&
                                                                   MacdPrevious>SignalPrevious &&
                                                                   MacdCurrent>(MACDCloseLevel*Point))
                                                                  {
                                                                   OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // закрываем позицию
                                                                   return(0); // выходим
                                                                  }
                                                              • нужно ли передвинуть трейлинг-стоп?
                                                                Трейлинг-стоп выставляем только тогда, когда у позиции уже есть прибыль, превышающая величину трейлинг-стопа в пунктах и если новый уровень стопа лучше предыдущего.

                                                                // проверим - может можно/нужно уже трейлинг стоп ставить?
                                                                if(TrailingStop>0)  
                                                                  {                 
                                                                   if(Bid-OrderOpenPrice()>Point*TrailingStop)
                                                                     {
                                                                      if(OrderStopLoss()
                                                                        {
                                                                         OrderModify(OrderTicket(),OrderOpenPrice(),
                                                                                     Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
                                                                         return(0);
                                                                        }
                                                                     }
                                                                  }

                                                              Закрываем операторную скобку

                                                              }
                                                          6. Eсли это короткая позиция

                                                            else // иначе это короткая позиция
                                                              {
                                                            • нужно ли закрыть?
                                                              Условие выхода из короткой позиции: при пересечении MACD со своей сигнальной линией, когда MACD ниже нуля, идет снизу вверх, а его сверху вниз пересекает сигнальная линия.

                                                              // проверим, может уже пора закрываться?
                                                              if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&
                                                                 MacdPrevious<SignalPrevious &&
                                                                 MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
                                                                {
                                                                 OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // закрываем позицию
                                                                 return(0); // выходим
                                                                }
                                                            • нужно ли передвинуть трейлинг-стоп?
                                                              Трейлинг-стоп выставляем только тогда, когда у позиции уже есть прибыль, превышающая величину трейлинг-стопа в пунктах и если новый уровень стопа лучше предыдущего.

                                                              // проверим - может можно/нужно уже трейлинг стоп ставить?
                                                              if(TrailingStop>0)  
                                                                {                 
                                                                 if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
                                                                   {
                                                                    if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
                                                                      {
                                                                       OrderModify(OrderTicket(),OrderOpenPrice(),
                                                                                   Ask+Point*TrailingStop,OrderTakeProfit(),0,Red);
                                                                       return(0);
                                                                      }
                                                                   }
                                                                }

                                                            Закрываем все оставшиеся открытые операторные скобки

                                                                       }
                                                                    }
                                                                 }
                                                               return(0);
                                                              }

                                                      Вот так, шаг за шагом и написан эксперт...

                                                      3-й этап – сборка результирующего кода программы

                                                      Откроем настройки эксперта (кнопка F7 или строка меню "Properties..."). Перед нами появится окно, в котором необходимо выставить внешние настройки параметров работы:


                                                      Соберем весь код из предыдущего раздела воедино...

                                                      //+------------------------------------------------------------------+
                                                      //|                                                  MACD Sample.mq4 |
                                                      //|                      Copyright © 2005, MetaQuotes Software Corp. |
                                                      //|                                       https://www.metaquotes.net/ |
                                                      //+------------------------------------------------------------------+
                                                       
                                                      extern double TakeProfit = 50;
                                                      extern double Lots = 0.1;
                                                      extern double TrailingStop = 30;
                                                      extern double MACDOpenLevel=3;
                                                      extern double MACDCloseLevel=2;
                                                      extern double MATrendPeriod=26;
                                                      
                                                      //+------------------------------------------------------------------+
                                                      //|                                                                  |
                                                      //+------------------------------------------------------------------+
                                                      int start()
                                                        {
                                                         double MacdCurrent, MacdPrevious, SignalCurrent;
                                                         double SignalPrevious, MaCurrent, MaPrevious;
                                                         int cnt=0, ticket, total;
                                                      // первичные проверки данных
                                                      // важно удостовериться что эксперт работает на нормальном графике и
                                                      // пользователь правильно выставил внешние переменные (Lots, StopLoss,
                                                      // TakeProfit, TrailingStop)
                                                      // в нашем случае проверяем только TakeProfit
                                                         if(Bars<100)
                                                           {
                                                            Print("bars less than 100");
                                                            return(0);  
                                                           }
                                                         if(TakeProfit<10)
                                                           {
                                                            Print("TakeProfit less than 10");
                                                            return(0);  // проверяем TakeProfit
                                                           }
                                                      // ради упрощения и ускорения кода, сохраним необходимые
                                                      // данные индикаторов во временных переменных
                                                         MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
                                                         MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
                                                         SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
                                                         SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
                                                         MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
                                                         MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
                                                       
                                                         total=OrdersTotal();
                                                         if(total<1) 
                                                           {
                                                            // нет ни одного открытого ордера
                                                            if(AccountFreeMargin()<(1000*Lots))
                                                              {
                                                               Print("We have no money. Free Margin = ", AccountFreeMargin());
                                                               return(0);  
                                                              }
                                                            // проверяем на возможность встать в длинную позицию (BUY)
                                                            if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
                                                               MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious)
                                                              {
                                                               ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point,
                                                                                "macd sample",16384,0,Green);
                                                               if(ticket>0)
                                                                 {
                                                                  if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
                                                                     Print("BUY order opened : ",OrderOpenPrice());
                                                                 }
                                                               else Print("Error opening BUY order : ",GetLastError()); 
                                                               return(0); 
                                                              }
                                                            // проверяем на возможность встать в короткую позицию (SELL)
                                                            if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
                                                               MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious)
                                                              {
                                                               ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,Bid-TakeProfit*Point,
                                                                                "macd sample",16384,0,Red);
                                                               if(ticket>0)
                                                                 {
                                                                  if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
                                                                     Print("SELL order opened : ",OrderOpenPrice());
                                                                 }
                                                               else Print("Error opening SELL order : ",GetLastError()); 
                                                               return(0); 
                                                              }
                                                            return(0);
                                                           }
                                                         // переходим к важной части эксперта - контролю открытых позиций
                                                         // 'важно правильно войти в рынок, но выйти - еще важнее...'
                                                         for(cnt=0;cnt<total;cnt++)
                                                           {
                                                            OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
                                                            if(OrderType()<=OP_SELL &&   // это открытая позиция? OP_BUY или OP_SELL 
                                                               OrderSymbol()==Symbol())  // инструмент совпадает?
                                                              {
                                                               if(OrderType()==OP_BUY)   // открыта длинная позиция
                                                                 {
                                                                  // проверим, может уже пора закрываться?
                                                                  if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
                                                                     MacdCurrent>(MACDCloseLevel*Point))
                                                                      {
                                                                       OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // закрываем позицию
                                                                       return(0); // выходим
                                                                      }
                                                                  // проверим - может можно/нужно уже трейлинг стоп ставить?
                                                                  if(TrailingStop>0)  
                                                                    {                 
                                                                     if(Bid-OrderOpenPrice()>Point*TrailingStop)
                                                                       {
                                                                        if(OrderStopLoss()<Bid-Point*TrailingStop)
                                                                          {
                                                                           OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,
                                                                                       OrderTakeProfit(),0,Green);
                                                                           return(0);
                                                                          }
                                                                       }
                                                                    }
                                                                 }
                                                               else // иначе это короткая позиция
                                                                 {
                                                                  // проверим, может уже пора закрываться?
                                                                  if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&
                                                                     MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
                                                                    {
                                                                     OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // закрываем позицию
                                                                     return(0); // выходим
                                                                    }
                                                                  // проверим - может можно/нужно уже трейлинг стоп ставить?
                                                                  if(TrailingStop>0)  
                                                                    {                 
                                                                     if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
                                                                       {
                                                                        if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
                                                                          {
                                                                           OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,
                                                                                       OrderTakeProfit(),0,Red);
                                                                           return(0);
                                                                          }
                                                                       }
                                                                    }
                                                                 }
                                                              }
                                                           }
                                                         return(0);
                                                        }
                                                      // конец.

                                                      Для финальной настройки эксперта необходимо лишь указать значения внешних переменных "Lots = 1", "Stop Loss (S/L) = 0" (не используется), "Take Profit (T/P) = 120" (для часовок подходит), "Trailing Stop (T/S) = 30". Конечно же, значения вы можете поставить свои.
                                                      Нажмите на кнопку "Compile" и, если не обнаружатся ошибки (кстати, в редактор MetaEditor можно скопировать текст из вышеприведенной распечатки программы), нажмите на кнопку "Save", чтобы эксперт сохранился.