Свойства позиции на пользовательской информационной панели

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

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

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

Окно настроек есть у каждого объекта и его можно открыть из контекстного меню, которое вызывается правой кнопкой мыши по выделенному объекту. Также его можно открыть из окна Список объектов (Ctrl+B) выделив нужный объект и нажав кнопку Свойства. Ниже на рисунке показан подготовленный макет для панели. Он может служить ещё и для того, чтобы подсматривать размеры и координаты при написании кода. После того, как код панели будет подготовлен, объекты относящиеся к макету нужно будет удалить вручную, так как эксперт не будет их "видеть" и не удалит с графика.

Подготовка макета информационной панели

Теперь нужно создать шаблон для эксперта. Это можно сделать также быстро, как и для скрипта. В Мастере MQL5 уже по умолчанию выделен вариант Эксперт (шаблон). На следующих шагах все опции остаются без изменений, в этот раз они не понадобятся. После нажатия кнопки Готово открывается вот такой шаблон:

//+------------------------------------------------------------------+
//|                                           expGetPropPosition.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Сразу видно, что шаблон для эксперта отличается от шаблона для скрипта. Кроме свойств программы (#property) присутствуют три основные функции: OnInit(), OnDeinit() и OnTick(). 

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

Функция OnDeinit() вызывается при удалении с графика, смене счёта, символа и периода. Все возможные причины перечислены в Справке, а в этом эксперте мы, как раз будем использовать пользовательскую функцию GetTextReason(), которая переводит идентификатор причины деинициализации, параметр функции OnDeinit(), в текст.

И наконец, функция OnTick(). Она вызывается каждый раз, когда приходит новый тик на символе, на котором находится в текущий момент эксперт.

Теперь подготовим все константы, переменные и массивы, которые будем использовать в эксперте. Разместим их в самом начале программы. Сначала определим те переменные, значения которых не меняются на всём протяжении работы программы:

//---
#define SZIP 14 // Размер массива для объектов инфо-панели
#define NAME_EXPERT MQL5InfoString(MQL5_PROGRAM_NAME) // Имя эксперта
//---

Далее идут глобальные переменные для свойств позиции:

//---
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
bool isPos=false; // Признак наличия/отсутствия открытой позиции
//---
string   pos_symbol="";       // Символ
long     pos_magic=0;         // Магический номер
string   pos_comment="";      // Комментарий
double   pos_swap=0.0;        // Своп
double   pos_commission=0.0;  // Комиссия
double   pos_price=0.0;       // Текущая цена позиции
double   pos_cprice=0.0;      // Текущая цена позиции
double   pos_profit=0.0;      // Прибыль/убыток позиции
double   pos_volume=0.0;      // Объём позиции
double   pos_sl=0.0;          // Stop Loss позиции
double   pos_tp=0.0;          // Take Profit позиции
datetime pos_time=NULL;       // Время открытия позиции
long     pos_id=0;            // Идентификатор позиции
int      pos_type=-1;         // Tип позиции
//---

После переменных объявим массивы для названий графических объектов, которые будут отображать на графике имена свойств позиции и их значений. Для этого сделаем два строковых массива и сразу инициализируем их элементы значениями. В квадратных скобках используем значение константы SZIP, которую объявили до этого в самом начале программы. То есть, в каждом массиве будет по 14 элементов.

//---
// МАССИВЫ
// Массив имён объектов названий свойств
string nm_prop[SZIP]=
  {
   "nm_pos_symbol",
   "nm_pos_magic",
   "nm_pos_comment",
   "nm_pos_swap",
   "nm_pos_commission",
   "nm_pos_price",
   "nm_pos_cprice",
   "nm_pos_profit",
   "nm_pos_volume",
   "nm_pos_sl",
   "nm_pos_tp",
   "nm_pos_time",
   "nm_pos_id",
   "nm_pos_type"
  };
//---
// Массив имён объектов значений свойств
string vl_prop[SZIP]=
  {
   "val_pos_symbol",
   "val_pos_magic",
   "val_pos_comment",
   "val_pos_swap",
   "val_pos_commission",
   "val_pos_price",
   "val_pos_cprice",
   "val_pos_profit",
   "val_pos_volume",
   "val_pos_sl",
   "val_pos_tp",
   "val_pos_time",
   "val_pos_id",
   "val_pos_type"
  };
//---

По этим именам программно можно найти нужный объект на графике и установить или изменить его свойства. Например, отображаемый текст, цвет, размер и т.д. Также именно эти названия объектов можно увидеть в окне Список объектов (Ctrl+B) после их создания на графике. Но просто открыв это окно Вы их там не увидите, так как те объекты, которые были созданы программой на MQL5 скрыты, и чтобы их увидеть, нужно нажать на кнопку Все в окне Список объектов. Так сделано для того, чтобы отделить объекты, которые были созданы вручную и с помощью программ. Это удобно.

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

Для создания фона и заголовка информационной панели мы будем использовать графический объект Поле ввода и для этого напишем функцию CreateEdit():

//+------------------------------------------------------------------+
//| СОЗДАНИЕ ОБЪЕКТА EDIT                                            |
//+------------------------------------------------------------------+
void CreateEdit(long   chrt_id,   // id графика
                int    nmb_win,   // номер окна (подокна)
                string lable_nm,  // имя объекта
                string text,      // отображаемый текст
                long   corner,    // угол привязки
                string font_bsc,  // шрифт
                int    font_size, // размер шрифта
                color  font_clr,  // цвет шрифта
                int    xsize,     // ширина
                int    ysize,     // высота
                int    x_dist,    // координата по шкале X
                int    y_dist,    // координата по шкале Y
                long   zorder,    // приоритет
                color  clr,       // цвет фона
                bool   flgORead)  // флаг Только для чтения
  {
   // Если объект создался успешно, то...
   if(ObjectCreate(chrt_id,lable_nm,OBJ_EDIT,nmb_win,0,0))
     {
      // ...установим его свойства
      ObjectSetString(chrt_id,lable_nm,OBJPROP_TEXT,text);            // отображаемый текст
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_CORNER,corner);       // установка угла привязки
      ObjectSetString(chrt_id,lable_nm,OBJPROP_FONT,font_bsc);        // установка шрифта
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_FONTSIZE,font_size);  // установка размера шрифта
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_COLOR,font_clr);      // цвет шрифта
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_BGCOLOR,clr);         // цвет фона
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XSIZE,xsize);         // ширина
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YSIZE,ysize);         // высота
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XDISTANCE,x_dist);    // установка координаты X
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YDISTANCE,y_dist);    // установка координаты Y
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_SELECTABLE,false);    // нельзя выделить объект, если FALSE
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ZORDER,zorder);       // Приоритет выше/ниже
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_READONLY,flgORead);   // Только для чтения
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ALIGN,ALIGN_LEFT);    // Выравнить по левому краю
      ObjectSetString(chrt_id,lable_nm,OBJPROP_TOOLTIP,"\n");         // нет всплывающей подсказки, если "\n"
     }
  }
//---

Теперь, чтобы создать графический объект Поле ввода (OBJ_EDIT) достаточно ввести одну строку. Пример этого рассмотрим при создании функции, которая будет устанавливать информационную панель на график.

Для графических объектов Текстовая метка, которые будут служить для отображения списка свойств позиции и их значений, создадим функцию CreateLabel():

//+------------------------------------------------------------------+
//| СОЗДАНИЕ ОБЪЕКТА LABEL                                           |
//+------------------------------------------------------------------+
void CreateLabel(long   chrt_id,   // id графика
                 int    nmb_wnd,   // id окна
                 string lable_nm,  // имя объекта
                 string text,      // отображаемый текст
                 long   anchor,    // точка привязки
                 long   corner,    // угол привязки
                 string font_bsc,  // шрифт
                 int    font_size, // размер шрифта
                 color  font_clr,  // цвет шрифта
                 int    x_dist,    // координата по шкале X
                 int    y_dist,    // координата по шкале Y
                 long   zorder)    // приоритет
  {
   // Если объект создался успешно, то...
   if(ObjectCreate(chrt_id,lable_nm,OBJ_LABEL,nmb_wnd,0,0))
     {
      // ...установим его свойства
      ObjectSetString(chrt_id,lable_nm,OBJPROP_TEXT,text);            // отображаемый текст
      ObjectSetString(chrt_id,lable_nm,OBJPROP_FONT,font_bsc);        // установка шрифта
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_COLOR,font_clr);      // установка цвета шрифта
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ANCHOR,anchor);       // установка точки привязки
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_CORNER,corner);       // установка угола привязки
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_FONTSIZE,font_size);  // установка размера шрифта
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XDISTANCE,x_dist);    // установка координаты X
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YDISTANCE,y_dist);    // установка координаты Y
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_SELECTABLE,false);    // нельзя выделить объект, если FALSE
      ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ZORDER,zorder);       // Приоритет выше/ниже
      ObjectSetString(chrt_id,lable_nm,OBJPROP_TOOLTIP,"\n");         // нет всплывающей подсказки, если "\n"
     }
  }
//---

Также рекомендуется ознакомиться с описанием каждой функции в Справке.

При удалении эксперта с графика нужно, чтобы он удалил за собой все объекты, которые установил до этого. Для этого напишем функцию DelObjbyName(), в которую можно просто передать название объекта, а далее будет произведена попытка найти объект по этому названию и, если он есть, то будет удалён. Для поиска объекта используется функция ObjectFind(), а для удаления ObjectDelete().

//+------------------------------------------------------------------+
//| УДАЛЯЕТ ОБЪЕКТЫ ПО ИМЕНИ                                         |
//+------------------------------------------------------------------+
void DelObjbyName(string Name)
  {
   int nm_obj=0;   // Возвращаемый номер подокна, в котором находится объект
   bool res=false; // Результат после попытки удалить объект
//---
// Найдём объект по имени
   nm_obj=ObjectFind(ChartID(),Name);
//---
   if(nm_obj>=0) // Если найден,..
     {
      res=ObjectDelete(ChartID(),Name); // ...удалим его
      //---
      // Если была ошибка при удалении,..
      if(!res)
        {// ...сообщим об этом
         Print("Ошибка при удалении объекта: ("+IS(GetLastError())+"): "+ErrorDesc(GetLastError())+"");
        }
     }
  }
//---

Также в функции DelObjbyName() установим проверку на ошибку при удалении объекта и сообщение, в котором, если была ошибка, показать её номер и текстовое описание. В коде выше видно, что используются ещё две дополнительные пользовательские функции IS() и ErrorDesc().

IS() это просто сокращённый вариант функции IntegerToString():

//+------------------------------------------------------------------+
//| КОНВЕРТАЦИЯ ИЗ INT В STRING                                      |
//+------------------------------------------------------------------+
string IS(int value)
  {
   return(IntegerToString(value));
  }
//---

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

//+------------------------------------------------------------------+
//| КОНВЕРТАЦИЯ ИЗ DOUBLE В STRING                                   |
//+------------------------------------------------------------------+
string DS(double value,int digits)
  {
   return(DoubleToString(value,digits));
  }
//+------------------------------------------------------------------+
//| КОНВЕРТАЦИЯ ИЗ DATETIME В STRING                                 |
//| В ФОРМАТЕ TIME_DATE|TIME_MINUTES                                 |
//+------------------------------------------------------------------+
string TSdm(datetime value)
  {
   return(TimeToString(value,TIME_DATE|TIME_MINUTES));
  }
//+------------------------------------------------------------------+
//| КОНВЕРТАЦИЯ ИЗ DATETIME В STRING_                                |
//| В ФОРМАТЕ TIME_DATE|TIME_MINUTES|TIME_SECONDS                    |
//+------------------------------------------------------------------+
string TSdms(datetime value)
  {
   return(TimeToString(value,TIME_DATE|TIME_MINUTES|TIME_SECONDS));
  }
//---

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

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ОПИСАНИЕ ОШИБКИ                                       |
//+------------------------------------------------------------------+
string ErrorDesc(int error_code)
  {
   string error_string="";
//---
   switch(error_code)
     {
      //--- Коды возврата торгового сервера

      case 10004: error_string="Реквота";                                                         break;
      case 10006: error_string="Запрос отвергнут";                                                break;
      case 10007: error_string="Запрос отменён трейдером";                                        break;
      case 10008: error_string="Ордер размещён";                                                  break;
      case 10009: error_string="Заявка выполнена";                                                break;
      case 10010: error_string="Заявка выполнена частично";                                       break;
      case 10011: error_string="Ошибка обработки запроса";                                        break;
      case 10012: error_string="Запрос отменён по истечению времени";                             break;
      case 10013: error_string="Неправильный запрос";                                             break;
      case 10014: error_string="Неправильный объём в запросе";                                    break;
      case 10015: error_string="Неправильная цена в запросе";                                     break;
      case 10016: error_string="Неправильные стопы в запросе";                                    break;
      case 10017: error_string="Торговля запрещена";                                              break;
      case 10018: error_string="Рынок закрыт";                                                    break;
      case 10019: error_string="Нет достаточных денежных средств";                                break;
      case 10020: error_string="Цены изменились";                                                 break;
      case 10021: error_string="Отсутствуют котировки для обработки запроса";                     break;
      case 10022: error_string="Неверная дата истечения ордера в запросе";                        break;
      case 10023: error_string="Состояние ордера изменилось";                                     break;
      case 10024: error_string="Слишком частые запросы";                                          break;
      case 10025: error_string="В запросе нет изменений";                                         break;
      case 10026: error_string="Автотрейдинг запрещён трейдером";                                 break;
      case 10027: error_string="Автотрейдинг запрещён клиентским терминалом";                     break;
      case 10028: error_string="Запрос заблокирован для обработки";                               break;
      case 10029: error_string="Ордер или позиция заморожены";                                    break;
      case 10030: error_string="Указан неподдерживаемый тип исполнения ордера по остатку";        break;
      case 10031: error_string="Нет соединения с торговым сервером";                              break;
      case 10032: error_string="Операция разрешена только для реальных счетов";                   break;
      case 10033: error_string="Достигнут лимит на количество отложенных ордеров";                break;
      case 10034: error_string="Достигнут лимит на объём ордеров и позиций для данного символа";  break;

      ...
     
     }
//---
   return(error_string);
  }
//---

В предыдущей статье мы рассматривали функцию GetPropPosition(). В ней переменным свойств позиции присваиваются значения. На этот раз устройство этой функции будет немного сложнее. Будем проверять, есть ли открытая позиция в текущий момент. В глобальной переменной isPos будет сохраняться флаг наличия/отсутствия позиции. Это понадобиться для того, чтобы использовать эту информацию в других функциях без необходимости каждый раз вызывать функцию PositionSelect().

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

//+------------------------------------------------------------------+
//| ОБНУЛЯЕТ ПЕРЕМЕННЫЕ СВОЙСТВ ПОЗИЦИИ                              |
//+------------------------------------------------------------------+
void ZeroPropPosition()
  {
   pos_symbol="";
   pos_comment="";
   pos_magic=0;
   pos_price=0.0;
   pos_cprice=0.0;
   pos_sl=0.0;
   pos_tp=0.0;
   pos_type=NULL;
   pos_volume=0.0;
   pos_commission=0.0;
   pos_swap=0.0;
   pos_profit=0.0;
   pos_time=0;
   pos_id=0;
  }
//---

После этого в конце функции GetPropPosition() будет вызываться пользовательская функция SetInfoPanel(), которая рисует/обновляет на графике информационную панель.

Ниже представлен код функции GetPropPosition():

//+------------------------------------------------------------------+
//| ПОЛУЧАЕТ СВОЙСТВА ПОЗИЦИИ                                        |
//+------------------------------------------------------------------+
void GetPropPosition()
  {
   // Узнаем, есть ли позиция
   isPos=PositionSelect(_Symbol);
//---
   if(isPos) // Если позиция есть, то...
     {
      // ...получим её свойства
      pos_symbol=PositionGetString(POSITION_SYMBOL);
      pos_comment=PositionGetString(POSITION_COMMENT);
      pos_magic=PositionGetInteger(POSITION_MAGIC);
      pos_price=PositionGetDouble(POSITION_PRICE_OPEN);
      pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT);
      pos_sl=PositionGetDouble(POSITION_SL);
      pos_tp=PositionGetDouble(POSITION_TP);
      pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      pos_volume=PositionGetDouble(POSITION_VOLUME);
      pos_commission=PositionGetDouble(POSITION_COMMISSION);
      pos_swap=PositionGetDouble(POSITION_SWAP);
      pos_profit=PositionGetDouble(POSITION_PROFIT);
      pos_time=(datetime)PositionGetInteger(POSITION_TIME);
      pos_id=PositionGetInteger(POSITION_IDENTIFIER);
     }
   else // Если позиции нет, то...
     { ZeroPropPosition(); } // ...обнулим переменные свойств позиции
//---
   SetInfoPanel(); // Установим/обновим информационную панель
  }
//---

Теперь напишем функцию SetInfoPanel(). Ниже представлен её код с подробными комментариями:

//+------------------------------------------------------------------+
//| УСТАНОВКА ИНФОРМАЦИОННОЙ ПАНЕЛИ                                  |
//|------------------------------------------------------------------+
void SetInfoPanel()
  {
   int
   yF=18,  // Координата по оси Y для фона и заголовка
   fln=32, // Координата по оси Y для списка свойств и их значений
   hln=12; // Высота строки
//---
   int font_sz=8;            // Размер шрифта
   string bsc_fnt="Calibri"; // Шрифт
   color clr_fnt=clrWhite;   // Цвет шрифта
//---
   int
   anchor=ANCHOR_RIGHT_UPPER, // Точка привязки в правом верхнем углу
   corner=CORNER_RIGHT_UPPER; // Центр координат в правом верхнем углу графика
//---
// Координаты по оси X
   int
   xF=120, // Первый столбец (названия свойств)
   xS=10;  // Второй столбец (значения свойств)
//---
// Массив с координатами по оси Y для
// cписка названий свойств позиции и их значений
   int arrY[SZIP]={0};
//---
// Заполним массив координатами
// для каждой строки на информационной панели
   arrY[0]=fln;
   arrY[1]=fln+hln;
   arrY[2]=fln+hln*2;
   arrY[3]=fln+hln*3;
   arrY[4]=fln+hln*4;
   arrY[5]=fln+hln*5;
   arrY[6]=fln+hln*6;
   arrY[7]=fln+hln*7;
   arrY[8]=fln+hln*8;
   arrY[9]=fln+hln*9;
   arrY[10]=fln+hln*10;
   arrY[11]=fln+hln*11;
   arrY[12]=fln+hln*12;
   arrY[13]=fln+hln*13;
//---
// Названия гр.объектов для фона и заголовка
   string
   nmFIP="nmFonInfoPanel",
   nmHIP="nmHeaderInfoPanel",
   txtHIP="  PROPERTIES  POSITION"; // Отображаемый текст в заголовке
//---
// Фон инфо-панели
   CreateEdit(0,0,nmFIP,"",corner,bsc_fnt,8,clrWhite,230,190,231,yF,0,C'15,15,15',1);
//---
// Заголовок инфо-панели
   CreateEdit(0,0,nmHIP,txtHIP,corner,bsc_fnt,8,clrWhite,230,14,231,yF,1,clrFireBrick,1);
//---
// Список названий свойств позиции и их значений
// Название свойства
   CreateLabel(0,0,nm_prop[0],"Symbol :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[0],2);
// Значение свойства
   CreateLabel(0,0,vl_prop[0],GetValInfoPanel(0),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[0],2);
//---
   CreateLabel(0,0,nm_prop[1],"Magic Number :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[1],2);
   CreateLabel(0,0,vl_prop[1],GetValInfoPanel(1),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[1],2);
//---
   CreateLabel(0,0,nm_prop[2],"Comment :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[2],2);
   CreateLabel(0,0,vl_prop[2],GetValInfoPanel(2),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[2],2);
//---
   CreateLabel(0,0,nm_prop[3],"Swap :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[3],2);
   CreateLabel(0,0,vl_prop[3],GetValInfoPanel(3),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[3],2);
//---
   CreateLabel(0,0,nm_prop[4],"Commission :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[4],2);
   CreateLabel(0,0,vl_prop[4],GetValInfoPanel(4),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[4],2);
//---
   CreateLabel(0,0,nm_prop[5],"Price Open :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[5],2);
   CreateLabel(0,0,vl_prop[5],GetValInfoPanel(5),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[5],2);
//---
   CreateLabel(0,0,nm_prop[6],"Current Price :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[6],2);
   CreateLabel(0,0,vl_prop[6],GetValInfoPanel(6),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[6],2);
//---
   CreateLabel(0,0,nm_prop[7],"Profit :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[7],2);
   CreateLabel(0,0,vl_prop[7],GetValInfoPanel(7),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[7],2);
//---
   CreateLabel(0,0,nm_prop[8],"Volume :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[8],2);
   CreateLabel(0,0,vl_prop[8],GetValInfoPanel(8),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[8],2);
//---
   CreateLabel(0,0,nm_prop[9],"Stop Loss :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[9],2);
   CreateLabel(0,0,vl_prop[9],GetValInfoPanel(9),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[9],2);
//---
   CreateLabel(0,0,nm_prop[10],"Take Profit :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[10],2);
   CreateLabel(0,0,vl_prop[10],GetValInfoPanel(10),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[10],2);
//---
   CreateLabel(0,0,nm_prop[11],"Time :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[11],2);
   CreateLabel(0,0,vl_prop[11],GetValInfoPanel(11),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[11],2);
//---
   CreateLabel(0,0,nm_prop[12],"Identifier :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[12],2);
   CreateLabel(0,0,vl_prop[12],GetValInfoPanel(12),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[12],2);
//---
   CreateLabel(0,0,nm_prop[13],"Type :",anchor,corner,bsc_fnt,font_sz,clr_fnt,xF,arrY[13],2);
   CreateLabel(0,0,vl_prop[13],GetValInfoPanel(13),anchor,corner,bsc_fnt,font_sz,clr_fnt,xS,arrY[13],2);
//---
   ChartRedraw(); // Перерисовать график
  }
//---

Рассмотрим подробнее функцию SetInfoPanel(). В начале функции объявлены переменные, которые относятся к свойствам графических объектов (координаты, цвет, шрифт, отображаемый текст и т.д.). Обратите внимание, как заполняется массив координат по оси Y для списка свойств позиции на инфо-панели. Для новичков такое заполнение более понятно. Но такую запись можно существенно сократить уместив её в пару строк кода с помощью цикла. Это можно записать вот так:

//---
// Заполним массив координатами
// для каждой строки с информационной панели
   for(int i=0; i<SZIP; i++)
     {
      if(i==0) { arrY[i]=fln; } else { arrY[i]=fln+hln*i; }
     }
//---

Затем с помощью ранее созданных функций CreateLabel() и CreateEdit(), последовательно для каждого объекта, который нужно отобразить на панели, указываются все его свойства в параметрах этих функций. Весь этот список тоже можно уместить в пару строк используя цикл. Для этого нужно создать ещё один массив для объектов, которые отображают на графике текст названий свойств позиции. Пусть это останется в качестве домашнего задания.

Обратите внимание на выделенные строки (228 и 231) в коде выше. В качестве примера я выделил только две строки, но для всех объектов, которые будут отображать значения свойств позиции, в функции CreateLabel() в качестве четвёртого параметра (отображаемый текст) передаётся функция GetValInfoPanel(), в которую передаётся номер объекта. Эта функция возвращает скорректированное строковое значение, которое и будет отображаться на панели.

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

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ СТРОКУ ДЛЯ ЗНАЧЕНИЙ СВОЙСТВ ПОЗИЦИИ                   |
//+------------------------------------------------------------------+
string GetValInfoPanel(int number)
  {
   // Знак отсутствия позиции или
   // отсутствие того или иного свойства
   // Например, отсутствие комментария, Stop Loss или Take Profit
   string empty="-";
//---
// Если позиция есть, то вернуть значение запрошенного свойства
   if(isPos)
     {
      switch(number)
        {
         case 0  : return(pos_symbol);
         case 1  : return(IS((int)pos_magic));
         case 2  :
            // Если комментарий есть, то вернуть его значение
            if(pos_comment!="") // Иначе вернуть знак отсутствия
              { return(pos_comment); } else { return(empty); }
         case 3  : return(DS(pos_swap,2));
         case 4  : return(DS(pos_commission,2));
         case 5  : return(DS(pos_price,_Digits));
         case 6  : return(DS(pos_cprice,_Digits));
         case 7  : return(DS(pos_profit,2));
         case 8  : return(DS(pos_volume,2));
         case 9  :
            // Если Stop Loss есть, то вернуть его значение
            if(pos_sl!=0) // Иначе вернуть знак отсутствия
               { return(DS(pos_sl,_Digits)); } else { return(empty); }
         case 10 :
            // Если Take Profit есть, то вернуть его значение
            if(pos_tp!=0) // Иначе вернуть знак отсутствия
               { return(DS(pos_tp,_Digits)); } else { return(empty); }
         case 11 : return(TSdm(pos_time));
         case 12 : return(IS((int)pos_id));
         case 13 : return(PosTypeToString(pos_type));
        }
     }
//---
// Если же позиции нет, то вернуть знак отсутствия позиции "-"
   return(empty);
  }
//---

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

В конце функции SetInfoPanel() вызывается функция ChartRedraw(). Она предназначена для обновления графика (перерисовка). Если её не вызывать, то не будет видно произведённых изменений.

Теперь нужно написать функцию, которая будет удалять все графические объекты, которые были созданы экспертом. Назовём её DeleteInfoPanel(). Её код представлен ниже:

//+------------------------------------------------------------------+
//| УДАЛЯЕТ ИНФОРМАЦИОННУЮ ПАНЕЛЬ                                    |
//+------------------------------------------------------------------+
void DeleteInfoPanel()
  {
   DelObjbyName("nmFonInfoPanel");    // Удалить фон панели
   DelObjbyName("nmHeaderInfoPanel"); // Удалить заголовок панели
//---
// Удалить список свойств позиции и их значений
   for(int i=0; i<SZIP; i++)
     {
      DelObjbyName(nm_prop[i]); // Удалить свойство
      DelObjbyName(vl_prop[i]); // Удалить значение
     }
//---
   ChartRedraw(); // Перерисовать график
  }
//---

Теперь осталось только распределить их по основным функциям эксперта, которые были изначально в шаблоне эксперта созданного Мастером MQL5. Это самое простое (см. код ниже):

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

Вопрос может вызвать только функция GetTextReason(), которая возвращает текстовое описание причины деинициализации. Вот её код:

//+------------------------------------------------------------------+
//| ВОЗВРАЩАЕТ ТЕКСТОВОЕ ОПИСАНИЕ ПРИЧИНЫ ДЕИНИЦИАЛИЗАЦИИ            |
//+------------------------------------------------------------------+
string GetTextReason(int reasonCode)
  {
   string text="";
//---
   switch(reasonCode)
     {
      case REASON_PROGRAM :     // 0
         text="Эксперт прекратил свою работу, вызвав функцию ExpertRemove()!";
         break;
         //---
      case REASON_REMOVE :      // 1
         text="Программа <- "+NAME_EXPERT+" -> была удалена с графика!";
         break;
         //---
      case REASON_RECOMPILE :   // 2
         text="Программа <- "+NAME_EXPERT+" -> была перекомпилирована!";
         break;
         //---
      case REASON_CHARTCHANGE : // 3
         text="Символ или период графика был изменен!";
         break;
         //---
      case REASON_CHARTCLOSE :  // 4
         text="График закрыт!";
         break;
         //---
      case REASON_PARAMETERS :  // 5
         text="Входные параметры были изменены пользователем!";
         break;
         //---
      case REASON_ACCOUNT :     // 6
         text="Активирован другой счет!";
         break;
         //---
      case REASON_TEMPLATE :    // 7
         text="Применен другой шаблон графика!";
         break;
         //---
      case REASON_INITFAILED :  // 8
         text="Признак того, что обработчик OnInit() вернул ненулевое значение!";
         break;
         //---
      case REASON_CLOSE :       // 9
         text="Терминал был закрыт!";
         break;
         //---
      default : text="Причина не определена!";
     }
//---
   return text;
  }
//---

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

Информационная панель при отсутствии позиции

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

Информационная панель со свойствами открытой позиции

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

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

Исходный код эксперта можно скачать ниже. Если у Вас возникли вопросы, то спрашивайте в комментариях ниже, постараюсь помочь.




Скачать эксперт expGetPropPosition.mq5



Комментариев нет :

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