Скачать MetaTrader 5

Рецепты MQL5 - Изучение свойств позиции в тестере MetaTrader 5

19 марта 2013, 10:03
Anatoli Kazharski
0
4 974

Введение

В этой статье мы будем модифицировать эксперта, созданного в предыдущей статье Рецепты MQL5 - Свойства позиции на пользовательской информационной панели. Будут рассмотрены следующие вопросы:

  • Отслеживание события "новый бар" на текущем символе;
  • Получение данных баров;
  • Подключение торгового класса стандартной библиотеки;
  • Создание функции поиска торговых сигналов;
  • Создание функции для выполнения торговых операций;
  • Определение торгового события в функции OnTrade().

На самом деле, каждому из этих вопросов можно посвятить целую статью, но такой подход, на мой взгляд, только усложнит изучение языка.

На этот раз я покажу, как реализовать ту или иную возможность на самых простых примерах. То есть, реализация каждой выше перечисленной возможности уместится буквально в одну простую и понятную функцию. А в следующих статьях, при развитии той или иной идеи, будем постепенно и по необходимости усложнять эти функции ровно настолько, насколько того требует та или иная задача.

Для начала сделайте копию эксперта из предыдущей статьи, так как нам понадобятся все его функции.


Процесс разработки эксперта

Сначала подключим в наш файл торговый класс CTrade из Стандартной библиотеки, в котором есть все необходимые функции для совершения торговых операций. Для начала можно легко и просто воспользоваться ими даже не заглядывая внутрь. Так мы и поступим.

Чтобы подключить класс нужно написать такую строчку кода:

//--- Подключаем класс стандартной библиотеки
#include <Trade/Trade.mqh>

Расположить ее можно в самом начале файла, чтобы было удобно найти, например, после директивы #define. Команда #include указывает, что нужно взять файл Trade.mqh из директории <каталог терминала MetaTrader 5>\MQL5\Include\Trade\. Таким же образом можно подключить и любой другой файл с функциями. Это очень актуально, когда объем кода проекта сильно увеличивается и в нем сложно ориентироваться.

Теперь нужно создать экземпляр класса, чтобы получить доступ ко всем его функциям. Для этого после названия класса нужно написать имя экземпляра:

//--- Загрузка класса
CTrade trade;

В этом эксперте мы воспользуемся только одной торговой функцией из класса CTrade. Это функция PositionOpen() для открытия позиции. Ее же можно использовать также для переворота уже открытой позиции. Как вызвать функцию из класса будет показано далее в статье при создании функции, в которой будут совершаться торговые операции.

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

//--- Массивы ценовых данных
double               close_price[]; // Close (цены закрытия бара)
double               open_price[];  // Open (цены открытия бара)

Далее создадим функцию CheckNewBar(), с помощью которой программа будет проверять, произошло ли событие нового бара, поскольку торговые операции будут совершаться только по сформировавшимся барам.

Ниже представлен код функции CheckNewBar() с подробными комментариями:

//+------------------------------------------------------------------+
//| ПРОВЕРКА НОВОГО БАРА                                             |
//+------------------------------------------------------------------+
bool CheckNewBar()
  {
//--- Переменная для времени открытия текущего бара
   static datetime new_bar=NULL;
//--- Массив для получения времени открытия текущего бара
   static datetime time_last_bar[1]={0};
//--- Получим время открытия текущего бара
//    Если возникла ошибка при получении, сообщим об этом
   if(CopyTime(_Symbol,Period(),0,1,time_last_bar)==-1)
     { Print(__FUNCTION__,": Ошибка копирования времени открытия бара: "+IntegerToString(GetLastError())+""); }
//--- Если это первый вызов функции
   if(new_bar==NULL)
     {
      // Установим время
      new_bar=time_last_bar[0];
      Print(__FUNCTION__,": Инициализация ["+_Symbol+"][TF: "+TimeframeToString(Period())+"]["
            +TimeToString(time_last_bar[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]");
      return(false); // Вернём false и выйдем 
     }
//--- Если время отличается
   if(new_bar!=time_last_bar[0])
     {
      new_bar=time_last_bar[0]; // Установим время и выйдем 
      return(true); // Запомним время и вернем true
     }
//--- Дошли до этого места - значит бар не новый, вернем false
   return(false);
  }

В коде выше видно, что функция CheckNewBar() возвращает true если бар новый, и false если нового бара еще нет. Таким способом можно контролировать ситуацию при торговле/тестировании, совершая торговые операции только по сформировавшимся барам.

В самом начале функции объявлена статическая (static) переменная и статический массив типа datetime. Статические локальные переменные сохраняют свои значения даже после выхода из функции. При каждом последующем вызове функции такие локальные переменные содержат те значения, которые они получили на предыдущем вызове вызове функции.

Также обратите внимание на функцию CopyTime(). С ее помощью мы получаем в массив time_last_bar время последнего бара. Обязательно ознакомьтесь с синтаксисом этой функции в Справочнике MQL5.

Можно ещё заметить пользовательскую функцию TimeframeToString(), которая до этого ни разу не упоминалась в этой серии статей. Она конвертирует значения таймфреймов в понятную для пользователя строку:

string TimeframeToString(ENUM_TIMEFRAMES timeframe)
  {
   string str="";
   //--- Если переданное значение некорректно, берем таймфрейм текущего графика
   if(timeframe==WRONG_VALUE || timeframe == NULL)
      timeframe = Period();
   switch(timeframe)
     {
      case PERIOD_M1  : str="M1";  break;
      case PERIOD_M2  : str="M2";  break;
      case PERIOD_M3  : str="M3";  break;
      case PERIOD_M4  : str="M4";  break;
      case PERIOD_M5  : str="M5";  break;
      case PERIOD_M6  : str="M6";  break;
      case PERIOD_M10 : str="M10"; break;
      case PERIOD_M12 : str="M12"; break;
      case PERIOD_M15 : str="M15"; break;
      case PERIOD_M20 : str="M20"; break;
      case PERIOD_M30 : str="M30"; break;
      case PERIOD_H1  : str="H1";  break;
      case PERIOD_H2  : str="H2";  break;
      case PERIOD_H3  : str="H3";  break;
      case PERIOD_H4  : str="H4";  break;
      case PERIOD_H6  : str="H6";  break;
      case PERIOD_H8  : str="H8";  break;
      case PERIOD_H12 : str="H12"; break;
      case PERIOD_D1  : str="D1";  break;
      case PERIOD_W1  : str="W1";  break;
      case PERIOD_MN1 : str="MN1"; break;
     }
//---
   return(str);
  }

Как использовать функцию CheckNewBar() будет показано далее в статье, когда мы подготовим для работы все остальные необходимые функции. А теперь рассмотрим функцию GetBarsData(), которая принимает значения запрошенного количества баров.

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ ЗНАЧЕНИЯ БАРОВ                                          |
//+------------------------------------------------------------------+
void GetBarsData()
  {
//--- Количество баров для получения их данных в массив
   int amount=2;
//--- Перевернем таймсерию ... 3 2 1 0
   ArraySetAsSeries(close_price,true);
   ArraySetAsSeries(open_price,true);
//--- Получим цену закрытия бара
//    Если полученных значений меньше, чем запрошено, вывести сообщение об этом
   if(CopyClose(_Symbol,Period(),0,amount,close_price)<amount)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+", "+TimeframeToString(Period())+") в массив цен Close! "
            "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Получим цену открытия бара
//    Если полученных значений меньше, чем запрошено, вывести сообщение об этом
   if(CopyOpen(_Symbol,Period(),0,amount,open_price)<amount)
     {
      Print("Не удалось скопировать значения ("
            +_Symbol+", "+TimeframeToString(Period())+") в массив цен Open! "
            "Ошибка "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
  }

Рассмотрим подробнее код выше. В начале, в переменной amount указываем, данные скольких баров нужно получить. Далее с помощью функции ArraySetAsSeries() устанавливаем индексацию массивов так, чтобы значения последнего (текущего) бара было в нулевом индексе массива. Т.е. если мы хотим в расчетах использовать значение последнего бара, то на примере цены открытия это будет выглядеть так: open_price[0]. А предпоследнего так: open_price[1].

Получение цен закрытия и открытия производится так же, как это было в функции CheckNewBar(), когда получали время последнего бара. Только в этом случае используются функции CopyClose() и CopyOpen(). Аналогичные функции есть для максимальной CopyHigh() и минимальной CopyLow() цен баров.

Идем дальше. Теперь рассмотрим простейший пример сигналов на открытие/переворот позиции. Мы получаем в ценовые массивы данные за два бара (текущий и предыдущий сформированный). Использовать будем данные сформированного бара.

  • Сигнал на покупку - цена закрытия выше цены открытия (бычий бар);
  • Сигнал на продажу - цена закрытия ниже цены открытия (медвежий бар).

Ниже представлен код этих простых условий:

//+------------------------------------------------------------------+
//| ОПРЕДЕЛЯЕТ ТОРГОВЫЕ СИГНАЛЫ                                      |
//+------------------------------------------------------------------+
int GetTradingSignal()
  {
//--- Сигнал на покупку (0) :
   if(close_price[1]>open_price[1])
      return(0);
//--- Сигнал на продажу (1) :
   if(close_price[1]<open_price[1])
      return(1);
//--- Отсутствие сигнала (3):
   return(3);
  }

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

Осталось создать функцию TradingBlock(), которая будет совершать торговые действия. Ниже представлен код этой функции с подробными комментариями:

//+------------------------------------------------------------------+
//| ТОРГОВЫЙ БЛОК                                                    |
//+------------------------------------------------------------------+
void TradingBlock()
  {
   int               signal=-1;           // Переменная для приема сигнала
   string            comment="hello :)";  // Комментарий для позиции
   double            start_lot=0.1;       // Начальный объем позиции
   double            lot=0.0;             // Объем для расчета позиции в случае переворота позиции
   double            ask=0.0;             // Цена ask
   double            bid=0.0;             // Цены bid
//--- Получим сигнал
   signal=GetTradingSignal();
//--- Узнаем, есть ли позиция
   pos_open=PositionSelect(_Symbol);
//--- Если сигнал на покупку
   if(signal==0)
     {
      //--- Получим цену Ask
      ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
      //--- Если позиции нет
      if(!pos_open)
        {
         //--- Откроем позицию. Если позиция не открылась, вывести сообщение об этом
         if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,start_lot,ask,0,0,comment))
           { Print("Ошибка при открытии позиции BUY: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
        }
      //--- Если позиция есть
      else
        {
         //--- Получим тип позиции
         pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         //--- Если позиция SELL
         if(pos_type==POSITION_TYPE_SELL)
           {
            //--- Получим объём позиции
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            //--- Скорректируем объём
            lot=NormalizeDouble(pos_volume+start_lot,2);
            //--- Откроем позицию. Если позиция не открылась, вывести сообщение об этом
            if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lot,ask,0,0,comment))
              { Print("Ошибка при открытии позиции SELL: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
           }
        }
      //---
      return;
     }
//--- Если сигнал на продажу
   if(signal==1)
     {
      //-- Получим цену Bid
      bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
      //--- Если позиции нет
      if(!pos_open)
        {
         //--- Откроем позицию. Если позиция не открылась, вывести сообщение об этом
         if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,start_lot,bid,0,0,comment))
           { Print("Ошибка при открытии позиции SELL: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
        }
      //--- Если позиция есть
      else
        {
         //--- Получим тип позиции
         pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
         //--- Если позиция BUY
         if(pos_type==POSITION_TYPE_BUY)
           {
            //--- Получим объём позиции
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            //--- Скорректируем объём
            lot=NormalizeDouble(pos_volume+start_lot,2);
            //--- Откроем позицию. Если позиция не открылась, вывести сообщение об этом
            if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,bid,0,0,comment))
              { Print("Ошибка при открытии позиции SELL: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
           }
        }
      //---
      return;
     }
  }

Думаю, что до момента открытия позиции не должно возникнуть вопросов. В коде выше видно, что после указателя (trade) установлена точка, после которой идет метод PositionOpen(). Именно так из класса вызывается тот или иной метод. После того как вы напечатаете точку, появляется список со всеми методами класса. Все что вам нужно - выбрать из списка нужный метод:

Рис. 1. Вызов метода класса.

Рис. 1. Вызов метода класса.

В функции TradingBlock() два основных блока, для покупки и для продажи. Сразу после определения направления сигнала получаем цену ask для покупки и bid для продажи.

Все цены/уровни, которые используются в торговых приказах, нужно нормализовать с помощью функции NormalizeDouble(), иначе при открытии или модификации позиции будет выходить ошибка. При расчете лота тоже лучше использовать эту функцию. Также обратите внимание, что параметры, в которых должны быть Stop Loss и Take Profit, имеют нулевые значения. Мы  рассмотрим установку торговых уровней более подробно в следующей статье.

Все пользовательские функции готовы и теперь их можно расставить по своим местам:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Инициализируем новый бар
   CheckNewBar();
//--- Получить свойства позиции и обновить значения на панели
   GetPositionProperties();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Вывести в журнал причину деинициализации
   Print(GetDeinitReasonText(reason));
//--- При удалении с графика
   if(reason==REASON_REMOVE)
      //--- Удалить все объекты с графика, которые относятся к информационной панели
      DeleteInfoPanel();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Если бар не новый, выходим
   if(!CheckNewBar())
      return;
//--- Если есть новый бар
   else
     {
      GetBarsData();  // Получим данные баров
      TradingBlock(); // Проверим условия и торгуем
     }
//--- Получить свойства и обновить значения на панели
   GetPositionProperties();
  }

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

//+------------------------------------------------------------------+
//| ТОРГОВОЕ СОБЫТИЕ                                                 |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- Получить свойства позиции и обновить значения на панели
   GetPositionProperties();
  }

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

Настройте тестер, включите визуализацию и нажмите кнопку Старт. Эксперт начнет проводить сделки в тестере, и вы будете видеть примерно такую картинку:

Рис. 2. Режим визуализации в тестере MetaTrader 5.

Рис. 2. Режим визуализации в тестере MetaTrader 5.

Тест в режиме визуализации можно в любой момент приостановить и, нажимая клавишу F12, продолжить пошаговый ход. Шаг будет равен одному бару, если в настройках тестера был выбран режим Только цены открытия, или одному тику, если был выбран режим Все тики. Можно также регулировать скорость теста.

Нужно протестировать эксперта и в режиме реального времени, чтобы убедиться, что значения на информационной панели обновляются сразу же после открытия/закрытия позиции вручную или добавления/модификации уровней Stop Loss/Take Profit. Чтобы долго не ждать, просто установите эксперта на минутный таймфрейм и торговые операции будут проводиться каждую минуту.

Кроме того, я добавил ещё один массив для названий свойств позиции на информационной панели:

// Массив названий свойств позиции
string pos_prop_texts[INFOPANEL_SIZE]=
  {
   "Symbol :",
   "Magic Number :",
   "Comment :",
   "Swap :",
   "Commission :",
   "Open Price :",
   "Current Price :",
   "Profit :",
   "Volume :",
   "Stop Loss :",
   "Take Profit :",
   "Time :",
   "Identifier :",
   "Type :"
  };

В предыдущей статье я упоминал о том, что такой массив понадобится для того, чтобы сократить код функции SetInfoPanel(). Если до сих пор этого не сделали или не разобрались сами, то можете посмотреть теперь, как это можно сделать. Список создания объектов касающихся свойств позиции теперь в коде выглядит так:

//--- Список названий свойств позиции и их значений
   for(int i=0; i<INFOPANEL_SIZE; i++)
     {
      //--- Название свойства
      CreateLabel(0,0,pos_prop_names[i],pos_prop_texts[i],anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[i],2);
      //--- Значение свойства
      CreateLabel(0,0,pos_prop_values[i],GetPropertyValue(i),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[i],2);
     }

Ещё в начале функции SetInfoPanel() можно увидеть вот такую строку:

//--- Тестирование в режиме визуализации
   if(MQL5InfoInteger(MQL5_VISUAL_MODE))
     {
      y_bg=2;
      y_property=16;
     }

Она сообщает программе, что если сейчас проводится тест в режиме визуализации, то координаты по оси Y для объектов информационной панели нужно скорректировать. В тестере в режиме визуализации не показывается название эксперта в правом верхнем углу графика, как это сделано в реальном времени, поэтому отступ смотрится неестественно, и его можно таким способом убрать.


Заключение

На этом закончим. В следующей статье рассмотрим более подробно установку и модификацию торговых уровней. Ниже можно скачать исходный код эксперта PositionPropertiesTester.mq5.

Прикрепленные файлы |
Рецепты MQL5 - Свойства позиции на пользовательской информационной панели Рецепты MQL5 - Свойства позиции на пользовательской информационной панели

На этот раз создадим простого эксперта, который во время ручной торговли будет показывать свойства позиции по текущему символу на пользовательской информационной панели, которая будет собрана из графических объектов. Данные будут обновляться на каждом тике, что уже намного удобнее, чем постоянно запускать вручную скрипт, который описывался в предыдущей статье "Рецепты MQL5 - Как получить свойства позиции?".

Отладка программ на MQL5 Отладка программ на MQL5

Эта статья ориентирована в первую очередь на программистов, которые уже изучили язык, но еще недостаточно освоились в разработке программ. Статья раскрывает практические приемы отладки программ и является объединенным опытом, не только моим, но и многих программистов, на опыте которых я учился.

Jeremy Scott - успешный продавец MQL5 Маркета Jeremy Scott - успешный продавец MQL5 Маркета

Джереми Скотт (Jeremy Scott), более известный в MQL5.community под ником Johnnypasado, снискал славу на ниве нашего сервиса MQL5 Маркет. Он уже заработал несколько тысяч долларов в Маркете и это далеко не предел. Мы решили внимательнее присмотреться к будущему миллионеру и получить от него инструкцию успеха для продавцов в MQL5 Маркете.

Индикатор для построения графика "Крестики - Нолики" Индикатор для построения графика "Крестики - Нолики"

Существует множество типов графиков, которые представляют информацию о текущей ситуации на рынке. Многие из них пришли к нам из далёкого прошлого, и как раз одним из таких является график "Крестики-нолики". В статье описан пример построения графика "Крестики - Нолики" в виде индикатора в реальном времени.