![Рецепты MQL5 - Свойства позиции на пользовательской информационной панели](https://c.mql5.com/i/articles/overlay.png)
Рецепты MQL5 - Свойства позиции на пользовательской информационной панели
Введение
На этот раз создадим простого эксперта, который во время ручной торговли будет показывать свойства позиции по текущему символу на пользовательской информационной панели, которая будет собрана из графических объектов. Данные будут обновляться на каждом тике, что уже намного удобнее, чем постоянно запускать вручную скрипт, который описывался в предыдущей статье Рецепты MQL5 - Как получить свойства позиции?.
Процесс разработки эксперта
Начнем с графических объектов. Для создания информационной панели нам понадобятся объекты для фона, заголовка, названий свойств позиции и их значений. Для фона и заголовка нужен прямоугольник, который не перемещается вместе с ценой. В качестве такого прямоугольника можно использовать графический объект Прямоугольная метка или Поле ввода. А для названий свойств объектов и их значений будем использовать Текстовые метки.
Прежде чем начать писать код, сначала можно подготовить макет для информационной панели. Удобство заключается в том, что все свойства можно быстро изменять в окне настроек и настроить внешний вид для информационной панели.
У каждого объекта есть окно настроек, которое можно открыть из контекстного меню выделенного объекта. Также окно свойств можно открыть из окна Список объектов (Ctrl+B), выделив нужный объект и нажав кнопку Свойства. Ниже на рисунке показан макет панели. Он может служить ещё и для того, чтобы подсматривать размеры и координаты при написании кода. После того как код панели будет подготовлен, объекты относящиеся к макету нужно будет удалить вручную, так как эксперт не будет их "видеть" и не удалит с графика.
Рис. 1. Подготовка макета информационной панели.
Теперь нужно создать шаблон для эксперта. Это можно сделать так же быстро, как и для скрипта. В Мастере MQL5 уже по умолчанию выделен вариант Эксперт (шаблон). На последующих шагах все опции остаются без изменений, в этот раз они не понадобятся. После нажатия кнопки Готово открывается вот такой шаблон:
//+------------------------------------------------------------------+ //| PositionPropertiesPanel.mq5 | //| Copyright 2012, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2012, 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() вызывается при удалении программы с графика, при смене счета, символа и периода. Все возможные причины перечислены в Справке, а в этом эксперте мы, как раз будем использовать пользовательскую функцию GetDeinitReasonText(), которая переводит идентификатор причины деинициализации (параметр функции OnDeinit()) в текст.
И наконец, функция OnTick(). Она вызывается каждый раз, когда приходит новый тик по символу, на графике которого находится в текущий момент эксперт.
Теперь подготовим все константы, переменные и массивы, которые будем использовать в эксперте. Разместим их в самом начале программы. Сначала определим те переменные, значения которых не меняются на всем протяжении работы программы:
//--- #define INFOPANEL_SIZE 14 // Размер массива для объектов инфо-панели #define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME) // Имя эксперта //---
Далее идут глобальные переменные для свойств позиции:
//--- ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ bool pos_open=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; // Идентификатор позиции ENUM_POSITION_TYPE pos_type=WRONG_VALUE; // Тип позиции
После переменных объявим массивы имен графических объектов. Эти объекты будут отображать на графике свойства позиции и их значения. Для этого сделаем два строковых массива и сразу инициализируем их элементы значениями. В квадратных скобках используем значение константы INFOPANEL_SIZE, которую объявили до этого в самом начале программы. То есть, в каждом массиве будет по 14 элементов.
// Массив имен объектов, отображающих имена свойств позиции string positionPropertyNames[INFOPANEL_SIZE]= { "name_pos_symbol", "name_pos_magic", "name_pos_comment", "name_pos_swap", "name_pos_commission", "name_pos_price", "name_pos_cprice", "name_pos_profit", "name_pos_volume", "name_pos_sl", "name_pos_tp", "name_pos_time", "name_pos_id", "name_pos_type" }; //--- // Массив имен объектов, отображающих значения свойств позиции string positionPropertyValues[INFOPANEL_SIZE]= { "value_pos_symbol", "value_pos_magic", "value_pos_comment", "value_pos_swap", "value_pos_commission", "value_pos_price", "value_pos_cprice", "value_pos_profit", "value_pos_volume", "value_pos_sl", "value_pos_tp", "value_pos_time", "value_pos_id", "value_pos_type" }; //---
По этим именам можно программно найти нужный объект на графике и установить или изменить его свойства, такие как отображаемый текст, цвет, размер и т.д. Кроме того, именно эти названия объектов можно увидеть в окне Список объектов (Ctrl+B) после их создания на графике. Но, просто открыв это окно, вы их там не увидите, так как те объекты, которые были созданы MQL5 программой, по умолчанию скрыты. Чтобы их увидеть, нужно нажать на кнопку Все в окне Список объектов. Так было сделано для того, чтобы разделить объекты, созданные вручную и программным путем, что, согласитесь, очень удобно.
Нам понадобятся пользовательские функции, с помощью которых эксперт будет создавать графические объекты. В MQL5 уже есть функция ObjectCreate() для создания графических объектов. Но, поскольку нам понадобится установить ещё и свойства объектов, а сами эти объекты будут создаваться не один раз, лучше всего сделать более удобный и компактный вариант, который можно было бы использовать одной строкой кода.
Для создания фона и заголовка информационной панели мы будем использовать графический объект Поле ввода и для этого напишем функцию CreateEdit():
//+------------------------------------------------------------------+ //| СОЗДАЕТ ОБЪЕКТ EDIT | //+------------------------------------------------------------------+ void CreateEdit(long chart_id, // id графика int sub_window, // номер окна (подокна) string name, // имя объекта string text, // отображаемый текст ENUM_BASE_CORNER corner, // угол графика string font_name, // шрифт int font_size, // размер шрифта color font_color, // цвет шрифта int x_size, // ширина int y_size, // высота int x_distance, // координата по оси X int y_distance, // координата по оси Y long z_order, // приоритет color background_color, // цвет фона bool read_only) // флаг "только для чтения" { // Если объект создался успешно, то... if(ObjectCreate(chart_id,name,OBJ_EDIT,sub_window,0,0)) { // ...установим его свойства ObjectSetString(chart_id,name,OBJPROP_TEXT,text); // отображаемый текст ObjectSetInteger(chart_id,name,OBJPROP_CORNER,corner); // установка угла графика ObjectSetString(chart_id,name,OBJPROP_FONT,font_name); // установка шрифта ObjectSetInteger(chart_id,name,OBJPROP_FONTSIZE,font_size); // установка размера шрифта ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color); // цвет шрифта ObjectSetInteger(chart_id,name,OBJPROP_BGCOLOR,background_color); // цвет фона ObjectSetInteger(chart_id,name,OBJPROP_XSIZE,x_size); // ширина ObjectSetInteger(chart_id,name,OBJPROP_YSIZE,y_size); // высота ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_distance); // установка координаты X ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_distance); // установка координаты Y ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false); // нельзя выделить объект, если FALSE ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order); // приоритет объекта ObjectSetInteger(chart_id,name,OBJPROP_READONLY,read_only); // только для чтения ObjectSetInteger(chart_id,name,OBJPROP_ALIGN,ALIGN_LEFT); // выравнивание по левому краю ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n"); // нет всплывающей подсказки, если "\n" } }
Теперь создать графический объект Поле ввода (OBJ_EDIT) можно всего одной строкой кода. Пример этого рассмотрим при создании функции, которая будет устанавливать информационную панель на график.
Для графических объектов Текстовая метка, которые будут служить для отображения списка свойств позиции и их значений, создадим аналогичным образом функцию CreateLabel():
//+------------------------------------------------------------------+ //| СОЗДАЕТ ОБЪЕКТ LABEL | //+------------------------------------------------------------------+ void CreateLabel(long chart_id, // id графика int sub_window, // номер окна (подокна) string name, // имя объекта string text, // отображаемый текст ENUM_ANCHOR_POINT anchor, // точка привязки ENUM_BASE_CORNER corner, // угол графика string font_name, // шрифт int font_size, // размер шрифта color font_color, // цвет шрифта int x_distance, // координата по оси X int y_distance, // координата по оси Y long z_order) // приоритет { // Если объект создался успешно, то... if(ObjectCreate(chart_id,name,OBJ_LABEL,sub_window,0,0)) { // ...установим его свойства ObjectSetString(chart_id,name,OBJPROP_TEXT,text); // отображаемый текст ObjectSetString(chart_id,name,OBJPROP_FONT,font_name); // установка шрифта ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color); // установка цвета шрифта ObjectSetInteger(chart_id,name,OBJPROP_ANCHOR,anchor); // установка точки привязки ObjectSetInteger(chart_id,name,OBJPROP_CORNER,corner); // установка угла графика ObjectSetInteger(chart_id,name,OBJPROP_FONTSIZE,font_size); // установка размера шрифта ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_distance); // установка координаты X ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_distance); // установка координаты Y ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false); // нельзя выделить объект, если FALSE ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order); // приоритет объекта ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n"); // нет всплывающей подсказки, если "\n" } }
Также рекомендуется ознакомиться с описанием каждой функции в Справке.
При удалении эксперта с графика нужно, чтобы он удалил за собой все объекты, которые установил до этого. Для этого напишем функцию DeleteObjectByName(), в которую можно просто передать название объекта. Далее будет произведена попытка найти объект по этому названию и, если он есть, удалить его. Для поиска объекта используется встроенная функция ObjectFind(), а для удаления - ObjectDelete().
//+------------------------------------------------------------------+ //| УДАЛЯЕТ ОБЪЕКТ ПО ИМЕНИ | //+------------------------------------------------------------------+ void DeleteObjectByname(string name) { int sub_window=0; // Возвращаемый номер подокна, в котором находится объект bool res =false; // Результат после попытки удалить объект //--- Найдём объект по имени sub_window=ObjectFind(ChartID(),name); //--- if(sub_window>=0) // Если найден,.. { res=ObjectDelete(ChartID(),name); // ...удалим его //--- // Если была ошибка при удалении,.. if(!res) // ...сообщим об этом { Print("Ошибка при удалении объекта: ("+IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); } } }
В функции DeleteObjectByName() мы также введем проверку ошибки при удалении объекта. В случае возникновения ошибки выводится сообщение с ее номером и текстовым описанием. В коде выше видно, что используется дополнительная пользовательская функция ErrorDescription(), которая переводит номер ошибки в текстовое описание. Кодов ошибок очень много, поэтому я приведу для примера только часть этой функции (см. код ниже). Полный вариант можете посмотреть в исходнике в конце статьи.
//+------------------------------------------------------------------+ //| ВОЗВРАЩАЕТ ОПИСАНИЕ ОШИБКИ | //+------------------------------------------------------------------+ string ErrorDescription(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); }
В предыдущей статье мы рассматривали функцию GetPositionProperties() для получения свойств позиции. На этот раз устройство этой функции будет немного сложнее. Будем проверять, есть ли открытая позиция в текущий момент. В глобальной переменной pos_open будет сохраняться флаг наличия/отсутствия позиции. Это информация может потребоваться в других функциях без необходимости каждый раз вызывать функцию PositionSelect().
Далее, если оказывается, что позиция есть, то получаем её свойства, если же позиции нет - обнулим все переменные. Для этого напишем простую функцию ZeroPositionProperties():
//+------------------------------------------------------------------+ //| ОБНУЛЯЕТ ПЕРЕМЕННЫЕ СВОЙСТВ ПОЗИЦИИ | //+------------------------------------------------------------------+ void ZeroPositionProperties() { 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 =WRONG_VALUE; pos_volume =0.0; pos_commission =0.0; pos_swap =0.0; pos_profit =0.0; pos_time =NULL; pos_id =0; }
После этого в конце функции GetPositionProperties() будет вызываться пользовательская функция SetInfoPanel(), которая рисует/обновляет на графике информационную панель.
//+------------------------------------------------------------------+ //| ПОЛУЧАЕТ СВОЙСТВА ПОЗИЦИИ | //+------------------------------------------------------------------+ void GetPositionProperties() { // Узнаем, есть ли позиция pos_open=PositionSelect(_Symbol); //--- if(pos_open) // Если позиция есть, получим её свойства { 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 // Если позиции нет, обнулим переменные свойств позиции ZeroPositionProperties(); //--- SetInfoPanel(); // Установим/обновим информационную панель }
Теперь напишем функцию SetInfoPanel(). Ниже представлен её код с подробными комментариями:
//+------------------------------------------------------------------+ //| УСТАНАВЛИВАЕТ ИНФОРМАЦИОННУЮ ПАНЕЛЬ | //|------------------------------------------------------------------+ void SetInfoPanel() { int y_bg=18; // Координата по оси Y для фона и заголовка int y_property=32; // Координата по оси Y для списка свойств и их значений int line_height=12; // Высота строки //--- int font_size=8; // Размер шрифта string font_name="Calibri"; // Шрифт color font_color=clrWhite; // Цвет шрифта //--- ENUM_ANCHOR_POINT anchor=ANCHOR_RIGHT_UPPER; // Точка привязки в правом верхнем углу ENUM_BASE_CORNER corner=CORNER_RIGHT_UPPER; // Начало координат в правом верхнем углу графика //--- Координаты по оси X int x_first_column=120; // Первый столбец (названия свойств) int x_second_column=10; // Второй столбец (значения свойств) //--- Массив с координатами по оси Y для названий свойств позиции и их значений int y_prop_array[INFOPANEL_SIZE]={0}; //--- Заполним массив координатами для каждой строки на информационной панели y_prop_array[0]=y_property; y_prop_array[1]=y_property+line_height; y_prop_array[2]=y_property+line_height*2; y_prop_array[3]=y_property+line_height*3; y_prop_array[4]=y_property+line_height*4; y_prop_array[5]=y_property+line_height*5; y_prop_array[6]=y_property+line_height*6; y_prop_array[7]=y_property+line_height*7; y_prop_array[8]=y_property+line_height*8; y_prop_array[9]=y_property+line_height*9; y_prop_array[10]=y_property+line_height*10; y_prop_array[11]=y_property+line_height*11; y_prop_array[12]=y_property+line_height*12; y_prop_array[13]=y_property+line_height*13; //--- Фон инфо-панели CreateEdit(0,0,"InfoPanelBackground","",corner,font_name,8,clrWhite,230,190,231,y_bg,0,C'15,15,15',true); //--- Заголовок инфо-панели CreateEdit(0,0,"InfoPanelHeader","POSITION PROPERTIES",corner,font_name,8,clrWhite,230,14,231,y_bg,1,clrFireBrick,true); //--- Список названий свойств позиции и их значений // Название свойства CreateLabel(0,0,pos_prop_names[0],"Symbol :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[0],2); // Значение свойства CreateLabel(0,0,pos_prop_values[0],GetValInfoPanel(0),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[0],2); //--- CreateLabel(0,0,pos_prop_names[1],"Magic Number :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[1],2); CreateLabel(0,0,pos_prop_values[1],GetValInfoPanel(1),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[1],2); //--- CreateLabel(0,0,pos_prop_names[2],"Comment :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[2],2); CreateLabel(0,0,pos_prop_values[2],GetValInfoPanel(2),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[2],2); //--- CreateLabel(0,0,pos_prop_names[3],"Swap :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[3],2); CreateLabel(0,0,pos_prop_values[3],GetValInfoPanel(3),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[3],2); //--- CreateLabel(0,0,pos_prop_names[4],"Commission :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[4],2); CreateLabel(0,0,pos_prop_values[4],GetValInfoPanel(4),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[4],2); //--- CreateLabel(0,0,pos_prop_names[5],"Open Price :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[5],2); CreateLabel(0,0,pos_prop_values[5],GetValInfoPanel(5),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[5],2); //--- CreateLabel(0,0,pos_prop_names[6],"Current Price :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[6],2); CreateLabel(0,0,pos_prop_values[6],GetValInfoPanel(6),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[6],2); //--- CreateLabel(0,0,pos_prop_names[7],"Profit :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[7],2); CreateLabel(0,0,pos_prop_values[7],GetValInfoPanel(7),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[7],2); //--- CreateLabel(0,0,pos_prop_names[8],"Volume :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[8],2); CreateLabel(0,0,pos_prop_values[8],GetValInfoPanel(8),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[8],2); //--- CreateLabel(0,0,pos_prop_names[9],"Stop Loss :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[9],2); CreateLabel(0,0,pos_prop_values[9],GetValInfoPanel(9),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[9],2); //--- CreateLabel(0,0,pos_prop_names[10],"Take Profit :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[10],2); CreateLabel(0,0,pos_prop_values[10],GetValInfoPanel(10),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[10],2); //--- CreateLabel(0,0,pos_prop_names[11],"Time :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[11],2); CreateLabel(0,0,pos_prop_values[11],GetValInfoPanel(11),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[11],2); //--- CreateLabel(0,0,pos_prop_names[12],"Identifier :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[12],2); CreateLabel(0,0,pos_prop_values[12],GetValInfoPanel(12),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[12],2); //--- CreateLabel(0,0,pos_prop_names[13],"Type :",anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[13],2); CreateLabel(0,0,pos_prop_values[13],GetValInfoPanel(13),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[13],2); //--- ChartRedraw(); // Перерисовать график }
Рассмотрим подробнее функцию SetInfoPanel(). В начале функции объявлены переменные, которые относятся к свойствам графических объектов (координаты, цвет, шрифт, отображаемый текст и т.д.). Обратите внимание, как заполняется массив координат по оси Y для списка свойств позиции на инфо-панели. Для новичков такое заполнение более понятно. Но такую запись можно существенно сократить уместив её в пару строк кода с помощью цикла. Это можно записать вот так:
//--- Заполним массив координатами для каждой строки на информационной панели for(int i=0; i<INFOPANEL_SIZE; i++) { if(i==0) y_prop_array[i]=y_property; else y_prop_array[i]=y_property+line_height*i; }
Затем с помощью ранее созданных функций CreateLabel() и CreateEdit(), последовательно для каждого объекта, который нужно отобразить на панели, указываются все его свойства в параметрах этих функций. Весь этот список также можно уместить в пару строк с помощью цикла. Для этого нужно создать еще один массив для объектов, которые отображают на графике текст названий свойств позиции. Пусть это останется в качестве домашнего задания.
Для всех объектов, которые будут отображать значения свойств позиции, в функции CreateLabel() в качестве четвертого параметра (отображаемый текст) передается значение, возвращаемое функцией GetPropertyValue(), в которую передается номер объекта. Эта функция возвращает скорректированное строковое значение, которое и будет отображаться на панели. Ниже представлен код функции с подробными комментариями:
//+------------------------------------------------------------------+ //| ВОЗВРАЩАЕТ СТРОКУ СО ЗНАЧЕНИЕМ СВОЙСТВА ПОЗИЦИИ | //+------------------------------------------------------------------+ string GetPropertyValue(int number) { //--- Знак отсутствия позиции или отсутствие того или иного свойства // Например, отсутствие комментария, Stop Loss или Take Profit string empty="-"; //--- Если позиция есть, возвращаем значение запрошенного свойства if(pos_open) { switch(number) { case 0 : return(pos_symbol); break; case 1 : return(IntegerToString((int)pos_magic)); break; //--- возвращаем значение комментария, если есть, иначе - знак отсутствия case 2 : return(pos_comment!="" ? pos_comment : empty); break; case 3 : return(DoubleToString(pos_swap,2)); break; case 4 : return(DoubleToString(pos_commission,2)); break; case 5 : return(DoubleToString(pos_price,_Digits)); break; case 6 : return(DoubleToString(pos_cprice,_Digits)); break; case 7 : return(DoubleToString(pos_profit,2)); break; case 8 : return(DoubleToString(pos_volume,2)); break; case 9 : return(pos_sl!=0.0 ? DoubleToString(pos_sl,_Digits) : empty); break; case 10 : return(pos_tp!=0.0 ? DoubleToString(pos_tp,_Digits) : empty); break; case 11 : return(TimeToString(pos_time,TIME_DATE|TIME_MINUTES)); break; case 12 : return(IntegerToString((int)pos_id)); break; case 13 : return(PositionTypeToString(pos_type)); break; default : return(empty); } } //--- // Если же позиции нет, возвращаем знак отсутствия позиции "-" return(empty); }
В коде выше видно, что для каждого переданного в функцию номера подготовлено свое значение, если есть открытая позиция. Если же в текущий момент нет открытой позиции, то функция вернёт прочерк (-), который будет отображаться у всех объектов, относящихся к значениям свойств позиции.
В конце функции SetInfoPanel() вызывается функция ChartRedraw(). Она предназначена для принудительной перерисовки графика. Если ее не вызывать, то не будет видно произведенных изменений.
Теперь нужно написать функцию, которая будет удалять все графические объекты, созданные экспертом. Назовём ее DeleteInfoPanel():
//+------------------------------------------------------------------+ //| УДАЛЯЕТ ИНФОРМАЦИОННУЮ ПАНЕЛЬ | //+------------------------------------------------------------------+ void DeleteInfoPanel() { DeleteObjectByName("InfoPanelBackground"); // Удалить фон панели DeleteObjectByName("InfoPanelHeader"); // Удалить заголовок панели //--- Удалить свойства позиции и их значения for(int i=0; i<INFOPANEL_SIZE; i++) { DeleteObjectByName(pos_prop_names[i]); // Удалить свойство DeleteObjectByName(pos_prop_values[i]); // Удалить значение } //--- ChartRedraw(); // Перерисовать график }
Теперь осталось только распределить созданные нами методы по основным функциям эксперта, которые изначально присутствовали в шаблоне эксперта после его создания в Мастере MQL5. Это самое простое:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Получить свойства и установить панель GetPositionProperties(); //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Вывести в журнал причину деинициализации Print(GetDeinitReasonText(reason)); //--- При удалении с графика if(reason==REASON_REMOVE) //--- Удалить все объекты с графика, которые относятся к информационной панели DeleteInfoPanel(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Получить свойства и обновить значения на панели GetPositionProperties(); } //+------------------------------------------------------------------+
Вопрос может вызвать только функция GetDeinitReasonText(), которая возвращает текстовое описание причины деинициализации:
//+------------------------------------------------------------------+ //| ВОЗВРАЩАЕТ ТЕКСТОВОЕ ОПИСАНИЕ ПРИЧИНЫ ДЕИНИЦИАЛИЗАЦИИ | //+------------------------------------------------------------------+ string GetDeinitReasonText(int reason_code) { string text=""; //--- switch(reason_code) { case REASON_PROGRAM : // 0 text="Эксперт прекратил свою работу, вызвав функцию ExpertRemove()."; break; case REASON_REMOVE : // 1 text="Программа '"+EXPERT_NAME+"' была удалена с графика."; break; case REASON_RECOMPILE : // 2 text="Программа '"+EXPERT_NAME+"' была перекомпилирована."; 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; }
Если загрузить эксперта на график символа, на котором сейчас нет позиции, то вместо значений свойств позиции на панели мы увидим прочерки. Такой же вид панели будет после закрытия позиции.
Рис. 2. Информационная панель при отсутствии позиции.
Если же по символу, на график которого загружается эксперт, есть позиция, или если открыть позицию в момент, когда эксперт уже на графике, то все прочерки меняются на соответствующие значения свойств позиции:
Рис. 3. Информационная панель со свойствами открытой позиции.
Есть еще один маленький нюанс. После того, как позиция будет закрыта, необходимо дождаться прихода нового тика для обновления значений на панели. Можно сделать так, чтобы значения обновлялись сразу, но что для этого нужно сделать, я расскажу в следующей статье.
Заключение
Некоторые функции из этой статьи будут использоваться в следующих статьях из серии Рецепты MQL5, а некоторые будут модифицироваться и дополняться в зависимости от поставленной задачи. Рекомендуется читать все статьи последовательно, так как каждая статья это логическое продолжение предыдущей. Конечно, всё зависит ещё и от уровня подготовки, поэтому, возможно, будет удобнее и интереснее начать с более поздних выпусков.
Исходный код эксперта прилагается к статье.
![Рецепты MQL5 - Изучение свойств позиции в тестере MetaTrader 5](https://c.mql5.com/2/0/Avatar_Recipes_tester.png)
![Отладка программ на MQL5](https://c.mql5.com/2/0/avatar21a.png)
![Jeremy Scott - успешный продавец MQL5 Маркета](https://c.mql5.com/2/0/Johnnypasado.png)
![Рецепты MQL5 - Как получить свойства позиции?](https://c.mql5.com/2/0/avatar3__2.png)
![MQL5 - Язык торговых стратегий для клиентского терминала MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Анатолий, хотел бы уточнить один момент, на каждом тике Вы создаёте все элементы (графические) заново с новыми значениями или всё же как-то редактируте свойства у "старых" элементов?
т.к. функция CreateEdit имеет только ObjectCreate ... если всё происходит именно так как я это предполагаю, то старые объекты (метки с устаревшими данными) удаляются? сами... как-то этот момент не совсем очевидень и ясен...