Рецепты MQL5 - Наблюдение за несколькими таймфреймами в одном окне
Введение
При выборе направления для открытия позиции весьма полезно видеть ценовой график одновременно на нескольких таймфреймах. Клиентский терминал MetaTrader 5 предлагает на выбор 21 период для анализа. На график можно также поместить специальный графический объект-график и уже в нем задать символ, таймфрейм и еще некоторые свойства. Таких объектов-графиков можно добавлять сколько угодно, но вручную это делать довольно неудобно и долго. К тому же в ручном режиме для настройки доступны не все свойства графика.
В этой статье рассмотрим такие графические объекты более подробно. В качестве примера создадим индикатор с элементами управления (кнопками), с помощью которых можно будет устанавливать в подокно сразу несколько объектов-графиков. При этом объекты-графики будут точно вписываться и автоматически подстраиваться под размер подокна при изменении размеров главного окна графика или терминала.
Помимо кнопок для добавления объектов-графиков добавим еще кнопки, с помощью которых можно будет включать/отключать некоторые свойства графиков, в том числе и те, которые можно изменять только программным способом.
Процесс разработки
Вручную добавить объект-график можно из меню Вставка->Объекты->Графические объекты->График. Например, на часовом графике вот так выглядят объекты с таймфреймами H4 и D1:
Рис. 1. Графические объекты-графики
Изменяя параметры этих объектов, можно управлять только ограниченным набором свойств:
Рис. 2. Свойства графического объекта "График"
Но такие свойства, как уровни цен ask и bid, отступ от правого края графика, торговые уровни и т.д., можно отобразить только программным способом.
Приступим к разработке индикатора. Назовем его, например, ChartObjects (рабочее название для статьи). В редакторе MetaEditor с помощью Мастера MQL5 создайте шаблон для индикатора. На шаге Обработчики событий для индикатора выберите опции, которые показаны на скриншоте:
Рис. 3. Обработчики событий для индикатора
Код шаблона, после открытия в редакторе, в итоге будет выглядеть так:
//+------------------------------------------------------------------+ //| ChartObjects.mq5 | //| Copyright 2013, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2013, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" #property indicator_chart_window //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { //--- } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- } //+------------------------------------------------------------------+
Функция OnCalculate() в принципе нам не понадобится в этой реализации, но без нее невозможно скомпилировать индикатор. Из основных функций еще понадобится OnDeinit(). В ней будет отслеживаться удаление программы с графика. После первичной обработки шаблона код выглядит так:
//+------------------------------------------------------------------+ //| ChartObjects.mq5 | //| Copyright 2013, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2013, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" //--- #property indicator_chart_window // Индикатор в главном окне #property indicator_plots 0 // Ноль серий для отрисовки //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Установим короткое имя индикатора IndicatorSetString(INDICATOR_SHORTNAME,"TimeFramesPanel"); //--- Инициализация прошла успешно return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Деинициализация индикатора | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Если индикатор удален с графика if(reason==REASON_REMOVE) { } } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- } //+------------------------------------------------------------------+
Теперь нужно создать индикатор, который будет использоваться в качестве вместилища (подокно) для объектов-графиков. Фактически индикатор-пустышка. Назовем его SubWindow. Его код представлен ниже:
//+------------------------------------------------------------------+ //| SubWindow.mq5 | //| Copyright 2013, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2013, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" //--- #property indicator_chart_window // Индикатор в подокне #property indicator_plots 0 // Ноль серий для отрисовки //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Установим короткое имя индикатора IndicatorSetString(INDICATOR_SHORTNAME,"SubWindow"); //--- Инициализация прошла успешно return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
Индикатор SubWindow.ex5 будет храниться в качестве ресурса внутри программы ChartObjects.ex5 после компиляции. В итоге, разработчик программы может передать конечному пользователю только один файл вместо двух.
Как уже было показано в предыдущей статье Рецепты MQL5 - Озвучиваем торговые события в MetaTrader 5, подключить файлы-ресурсы в программу можно с помощью директивы #resource. В начале нашей программы ChartObjects нужно добавить вот такую строчку кода:
//--- Подключаемый ресурс-индикатор #resource "\\Indicators\\SubWindow.ex5"
Директивами #define обозначим размеры массивов, которые будут относиться к элементам управления:
//--- Количество кнопок-таймфреймов #define TIMEFRAME_BUTTONS 21 //--- Количество кнопок для свойств объекта-графика #define PROPERTY_BUTTONS 5
И как обычно, в самом начале программы, объявим глобальные переменные:
//--- Путь к индикатору SubWindow из ресурса string subwindow_path ="::Indicators\\SubWindow.ex5"; int subwindow_number =-1; // Номер подокна int subwindow_handle =INVALID_HANDLE; // Хэндл индикатора SubWindow string subwindow_shortname ="SubWindow"; // Короткое имя индикатора //--- int chart_width =0; // Ширина графика int chart_height =0; // Высота графика int chart_scale =0; // Масштаб графика //--- color cOffButtonFont =clrWhite; // Цвет текста в отжатой кнопке color cOffButtonBackground =clrDarkSlateGray; // Цвет фона в отжатой кнопке color cOffButtonBorder =clrLightGray; // Цвет рамки в отжатой кнопке //--- color cOnButtonFont =clrGold; // Цвет текста в нажатой кнопке color cOnButtonBackground =C'28,47,47'; // Цвет фона в нажатой кнопке color cOnButtonBorder =clrLightGray; // Цвет рамки в нажатой кнопке
Затем массивы для кнопок-таймфреймов:
//--- Массив с именами объектов для кнопок-таймфреймов string timeframe_button_names[TIMEFRAME_BUTTONS]= { "button_M1","button_M2","button_M3","button_M4","button_M5","button_M6","button_M10", "button_M12","button_M15","button_M20","button_M30","button_H1","button_H2","button_H3", "button_H4","button_H6","button_H8","button_H12","button_D1","button_W1","button_MN" }; //--- Массив с отображаемым текстом в кнопках-таймфреймах string timeframe_button_texts[TIMEFRAME_BUTTONS]= { "M1","M2","M3","M4","M5","M6","M10", "M12","M15","M20","M30","H1","H2","H3", "H4","H6","H8","H12","D1","W1","MN" }; //--- Массив состояний кнопок-таймфреймов bool timeframe_button_states[TIMEFRAME_BUTTONS]={false};
Массивы для кнопок, управляющих свойствами объекта-графика:
//--- Массив с именами объектов для кнопок настроек графиков string property_button_names[PROPERTY_BUTTONS]= { "property_button_date","property_button_price", "property_button_ohlc","property_button_askbid", "property_button_trade_levels" }; //--- Массив с отображаемым текстом в кнопках настроек графиков string property_button_texts[PROPERTY_BUTTONS]= { "Date","Price","OHLC","Ask / Bid","Trade Levels" }; //--- Массив состояний кнопок настроек графиков bool property_button_states[PROPERTY_BUTTONS]={false}; //--- Массив размеров кнопок настроек графиков int property_button_widths[PROPERTY_BUTTONS]= { 66,68,66,100,101 };
И, наконец, массив с именами объектов-графиков:
//--- Массив с именами объектов-графиков string chart_object_names[TIMEFRAME_BUTTONS]= { "chart_object_m1","chart_object_m2","chart_object_m3","chart_object_m4","chart_object_m5","chart_object_m6","chart_object_m10", "chart_object_m12","chart_object_m15","chart_object_m20","chart_object_m30","chart_object_h1","chart_object_h2","chart_object_h3", "chart_object_h4","chart_object_h6","chart_object_h8","chart_object_h12","chart_object_d1","chart_object_w1","chart_object_mn" };
Прежде, чем приступить к написанию функций, которые относятся к взаимодействию с графическими объектами, напишем сначала функции, которые создают эти объекты на графике. Для нашей программы понадобятся два типа графических объектов: OBJ_BUTTON и OBJ_CHART.
Создание кнопки поручим функции CreateButton():
//+------------------------------------------------------------------+ //| Создает объект "кнопку" | //+------------------------------------------------------------------+ void CreateButton(long chart_id, // id графика int window_number, // номер окна string name, // имя объекта string text, // отображаемое имя ENUM_ANCHOR_POINT anchor, // точка привязки ENUM_BASE_CORNER corner, // угол графика string font_name, // шрифт int font_size, // размер шрифта color font_color, // цвет шрифта color background_color, // цвет фона color border_color, // цвет рамки int x_size, // ширина int y_size, // высота int x_distance, // координата по шкале X int y_distance, // координата по шкале Y long z_order) // приоритет { //--- Если объект успешно создался if(ObjectCreate(chart_id,name,OBJ_BUTTON,window_number,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_BGCOLOR,background_color); // установка цвета фона ObjectSetInteger(chart_id,name,OBJPROP_BORDER_COLOR,border_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_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); // объект недоступен для выделения ObjectSetInteger(chart_id,name,OBJPROP_STATE,false); // состояние кнопки (нажата/отжата) ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order); // приоритет на получение события нажатия мышью ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n"); // нет всплывающей подсказки } }
Соответственно, создание графика в подокне - функции CreateChartInSubwindow():
//+------------------------------------------------------------------+ //| Создает объект "график" в подокне | //+------------------------------------------------------------------+ void CreateChartInSubwindow(int window_number, // номер подокна int x_distance, // координата X int y_distance, // координата Y int x_size, // ширина int y_size, // высота string name, // имя объекта string symbol, // символ ENUM_TIMEFRAMES timeframe, // таймфрейм int subchart_scale, // масштаб баров bool show_dates, // показывать шкалу дат bool show_prices, // показывать шкалу цен bool show_ohlc, // показывать цены OHLC bool show_ask_bid, // показывать уровни ask/bid bool show_levels, // показывать торговые уровни string tooltip) // всплывающая подсказка { //--- Если объект успешно создался if(ObjectCreate(0,name,OBJ_CHART,window_number,0,0)) { //--- Установим свойства объекта-графика ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER); // угол привязки графика ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x_distance); // координата X ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y_distance); // координата Y ObjectSetInteger(0,name,OBJPROP_XSIZE,x_size); // ширина ObjectSetInteger(0,name,OBJPROP_YSIZE,y_size); // высота ObjectSetInteger(0,name,OBJPROP_CHART_SCALE,subchart_scale); // масштаб баров ObjectSetInteger(0,name,OBJPROP_DATE_SCALE,show_dates); // шкала дат ObjectSetInteger(0,name,OBJPROP_PRICE_SCALE,show_prices); // шкала цен ObjectSetString(0,name,OBJPROP_SYMBOL,symbol); // символ ObjectSetInteger(0,name,OBJPROP_PERIOD,timeframe); // таймфрейм ObjectSetString(0,name,OBJPROP_TOOLTIP,tooltip); // всплывающая подсказка ObjectSetInteger(0,name,OBJPROP_BACK,false); // объект на переднем плане ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); // объект недоступен для выделения ObjectSetInteger(0,name,OBJPROP_COLOR,clrWhite); // белый цвет //--- Получим идентификатор объекта-графика long subchart_id=ObjectGetInteger(0,name,OBJPROP_CHART_ID); //--- Установим специальные свойства объекта-графика ChartSetInteger(subchart_id,CHART_SHOW_OHLC,show_ohlc); // OHLC ChartSetInteger(subchart_id,CHART_SHOW_TRADE_LEVELS,show_levels); // торговые уровни ChartSetInteger(subchart_id,CHART_SHOW_BID_LINE,show_ask_bid); // уровень bid ChartSetInteger(subchart_id,CHART_SHOW_ASK_LINE,show_ask_bid); // уровень ask ChartSetInteger(subchart_id,CHART_COLOR_LAST,clrLimeGreen); // цвет уровня последней совершенной сделки ChartSetInteger(subchart_id,CHART_COLOR_STOP_LEVEL,clrRed); // цвет уровней стоп-ордеров //--- Обновим объект-график ChartRedraw(subchart_id); } }
В коде выше, для объекта-графика сначала устанавливаются обычные свойства графика. Затем, после получения идентификатора объекта-графика, устанавливаются специальные свойства. Также важно обновить объект-график с помощью функции ChartRedraw() с переданным в нее идентификатором объекта-графика.
Установку элементов управления разделим на две функции AddTimeframeButtons() и AddPropertyButtons():
//+------------------------------------------------------------------+ //| Добавляет кнопки-таймфреймы | //+------------------------------------------------------------------+ void AddTimeframeButtons() { int x_dist =1; // Отступ от левого края графика int y_dist =125; // Отступ от нижней части графика int x_size =28; // Ширина кнопок int y_size =20; // Высота кнопок //--- for(int i=0; i<TIMEFRAME_BUTTONS; i++) { //--- Если в ряду уже добавлено 7 кнопок, зададим координаты для следующего ряда if(i%7==0) { x_dist=1; y_dist-=21; } //--- Добавим кнопку-таймфрейм CreateButton(0,0,timeframe_button_names[i],timeframe_button_texts[i], ANCHOR_LEFT_LOWER,CORNER_LEFT_LOWER,"Arial",8, cOffButtonFont,cOffButtonBackground,cOffButtonBorder, x_size,y_size,x_dist,y_dist,3); //--- Установим координату X для следующей кнопки x_dist+=x_size+1; } } //+------------------------------------------------------------------+ //| Добавляет кнопки настроек графиков | //+------------------------------------------------------------------+ void AddPropertyButtons() { int x_dist =1; // Отступ от левого края графика int y_dist =41; // Отступ от нижней части графика int x_size =66; // Ширина кнопок int y_size =20; // Высота кнопок //--- for(int i=0; i<PROPERTY_BUTTONS; i++) { //--- Если первые три кнопки уже добавлены, зададим координаты для следующего ряда if(i==3) { x_dist=1; y_dist-=21; } //--- Добавим кнопку CreateButton(0,0,property_button_names[i],property_button_texts[i], ANCHOR_LEFT_LOWER,CORNER_LEFT_LOWER,"Arial",8, cOffButtonFont,cOffButtonBackground,cOffButtonBorder, property_button_widths[i],y_size,x_dist,y_dist,3); //--- Установим координату X для следующей кнопки x_dist+=property_button_widths[i]+1; } }
При удалении индикатора с графика нужно удалить объекты, которые были созданы программой. Для этого нам понадобятся следующие служебные функции:
//+------------------------------------------------------------------+ //| Удаляет панель с кнопками таймфреймов | //+------------------------------------------------------------------+ void DeleteTimeframeButtons() { for(int i=0; i<TIMEFRAME_BUTTONS; i++) DeleteObjectByName(timeframe_button_names[i]); } //+------------------------------------------------------------------+ //| Удаляет панель c кнопками настроек графиков | //+------------------------------------------------------------------+ void DeletePropertyButtons() { for(int i=0; i<PROPERTY_BUTTONS; i++) DeleteObjectByName(property_button_names[i]); } //+------------------------------------------------------------------+ //| Удаляет объекты по имени | //+------------------------------------------------------------------+ void DeleteObjectByName(string object_name) { //--- Если есть такой объект if(ObjectFind(ChartID(),object_name)>=0) { //--- Удалим его или сообщим об ошибке if(!ObjectDelete(ChartID(),object_name)) Print("Ошибка ("+IntegerToString(GetLastError())+") при удалении объекта!"); } }
Теперь, чтобы при загрузке индикатора панель устанавливалась на график, а при удалении индикатора с графика все объекты панели удалялись, в соответствующие функции-обработчики OnInit() и OnDeinit() нужно добавить вот такие строчки кода:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Добавим на график панель с кнопками-таймфреймами AddTimeframeButtons(); //--- Добавим на график панель с кнопками настроек графиков AddPropertyButtons(); //--- Перерисуем график ChartRedraw(); //--- Инициализация прошла успешно return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Деинициализация индикатора | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Если индикатор удален с графика if(reason==REASON_REMOVE) { //--- Удалим кнопки DeleteTimeframeButtons(); DeletePropertyButtons(); //--- Перерисуем график ChartRedraw(); } }
Если сейчас скомпилировать индикатор и загрузить его на график, то будет добавлена панель, как показано на скриншоте ниже:
Рис. 4. Панель с кнопками
Теперь все готово для того, чтобы приступить к созданию функций для взаимодействия пользователя с панелью. В основном все они будут вызываться из главной функции OnChartEvent(). В этой статье рассмотрим два события, которые будем обрабатывать в этой функции:
- CHARTEVENT_OBJECT_CLICK - нажатие мышки на графическом объекте.
- CHARTEVENT_CHART_CHANGE - изменение размеров графика или изменение свойств графика через диалог свойств.
Начнем с события CHARTEVENT_OBJECT_CLICK. Напишем функцию ChartEventObjectClick(), в которую будут передаваться все аргументы из функции OnChartEvent() (для остальных событий будут созданы подобные функции):
//+------------------------------------------------------------------+ //| Событие нажатия мышки на графическом объекте | //+------------------------------------------------------------------+ bool ChartEventObjectClick(int id, long lparam, double dparam, string sparam) { //--- Кликнули мышью на графическом объекте if(id==CHARTEVENT_OBJECT_CLICK) { //--- Если кликнули на кнопке-таймфрейме, установим/удалим окно 'SubWindow' и объект-график if(ToggleSubwindowAndChartObject(sparam)) return(true); //--- Если кликнули на кнопке настроек графиков, установим/удалим свойство в объектах-графиках if(ToggleChartObjectProperty(sparam)) return(true); } //--- return(false); }
Код функции ChartEventObjectClick() прост. По идентификатору определяется событие нажатия на кнопку панели. Затем логика разветвляется на два направления: обработка нажатия кнопок-таймфреймов и обработка нажатия кнопок со свойствами графиков. В соответствующие функции ToggleSubwindowAndChartObject() и ToggleChartObjectProperty() передается строковый параметр sparam, содержащий имя объекта, на котором было произведено нажатие левой кнопки мыши.
Рассмотрим код этих функций. Начнем с ToggleSubwindowAndChartObject():
//+------------------------------------------------------------------+ //| Устанавливает/удаляет подокно SubWindow и объект-график | //+------------------------------------------------------------------+ bool ToggleSubwindowAndChartObject(string clicked_object_name) { //--- Убедимся, что нажатие было на объекте кнопка-таймфрейм if(CheckClickOnTimeframeButton(clicked_object_name)) { //--- Проверим, есть ли подокно SubWindow subwindow_number=ChartWindowFind(0,subwindow_shortname); //--- Если нет подокна SubWindow, установим его if(subwindow_number<0) { //--- Если подокно SubWindow установлено if(AddSubwindow()) { //--- Установим в нем объекты-графики AddChartObjectsToSubwindow(clicked_object_name); return(true); } } //--- Если есть подокно SubWindow if(subwindow_number>0) { //--- Установим в нем объекты-графики AddChartObjectsToSubwindow(clicked_object_name); return(true); } } //--- return(false); }
По комментариям в коде выше не сложно разобраться с ходом событий. Выделенные строки показывают еще ряд пользовательских функций, с кодом которых можно ознакомиться ниже.
Функция CheckClickOnTimeframeButton() возвращает истину (true), если кнопка, которую нажали, относится к панели таймфреймов:
//+------------------------------------------------------------------+ //| Проверяет, было ли нажатие на кнопке-таймфрейме | //+------------------------------------------------------------------+ bool CheckClickOnTimeframeButton(string clicked_object_name) { //--- Пройдемся по всем кнопкам-таймфреймам и сверим имена for(int i=0; i<TIMEFRAME_BUTTONS; i++) { //--- Сообщим о совпадении if(clicked_object_name==timeframe_button_names[i]) return(true); } //--- return(false); }
Если подтверждается, что клик был по кнопке-таймфрейму, то далее производится проверка, добавлено ли в настоящий момент на основной график подокно SubWindow. Если еще нет, то оно устанавливается с помощью функции AddSubwindow():
//+------------------------------------------------------------------+ //| Добавляет подокно для размещения объектов-графиков | //+------------------------------------------------------------------+ bool AddSubwindow() { //--- Получим хэндл индикатора "SubWindow" subwindow_handle=iCustom(_Symbol,_Period,subwindow_path); //--- Если хэндл получен if(subwindow_handle!=INVALID_HANDLE) { //--- Определим количество окон на графике для номера подокна subwindow_number=(int)ChartGetInteger(0,CHART_WINDOWS_TOTAL); //--- Установим подокно SubWindow на график if(!ChartIndicatorAdd(0,subwindow_number,subwindow_handle)) Print("Не удалось установить индикатор SUBWINDOW ! "); //--- Есть подокно else return(true); } //--- Нет подокна return(false); }
Затем в созданном подокне мы будем добавлять объекты-графики с помощью функции AddChartObjectsToSubwindow():
//+------------------------------------------------------------------+ //| Добавляет объекты-графики в подокно | //+------------------------------------------------------------------+ void AddChartObjectsToSubwindow(string clicked_object_name) { ENUM_TIMEFRAMES tf =WRONG_VALUE; // Таймфрейм string object_name =""; // Имя объекта string object_text =""; // Текст в объекте int x_distance =0; // Координата по оси X int total_charts =0; // Всего объектов-графиков int chart_object_width =0; // Ширина объекта-графика //--- Узнаем масштаб баров, высоту/ширину подокна SubWindow chart_scale=(int)ChartGetInteger(0,CHART_SCALE); chart_width=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,subwindow_number); chart_height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,subwindow_number); //--- Получим количество объектов-графиков в подокне SUBWINDOW total_charts=ObjectsTotal(0,subwindow_number,OBJ_CHART); //--- Если нет объектов-графиков if(total_charts==0) { //--- Проверим, было ли нажатие на кнопке-таймфрейме if(CheckClickOnTimeframeButton(clicked_object_name)) { //--- Инициализируем массив кнопок-таймфреймов InitializeTimeframeButtonStates(); //--- Получим текст в кнопке-таймфрейме для всплывающей подсказки для объекта-графика object_text=ObjectGetString(0,clicked_object_name,OBJPROP_TEXT); //--- Получим таймфрейм для объекта-графика tf=StringToTimeframe(object_text); //--- Установим объект-график CreateChartInSubwindow(subwindow_number,0,0,chart_width,chart_height, "chart_object_"+object_text,_Symbol,tf,chart_scale, property_button_states[0],property_button_states[1], property_button_states[2],property_button_states[3], property_button_states[4],object_text); //--- Обновим график и выйдем ChartRedraw(); return; } } //--- Если объекты-графики уже есть в подокне SubWindow if(total_charts>0) { //--- Получим количество нажатых кнопок-таймфреймов и проинициализируем массив состояний int pressed_buttons_count=InitializeTimeframeButtonStates(); //--- Если нет нажатых кнопок, удалим подокно SubWindow if(pressed_buttons_count==0) DeleteSubwindow(); //--- Если нажатые кнопки есть else { //--- Удалим все объекты-графики из подокна ObjectsDeleteAll(0,subwindow_number,OBJ_CHART); //--- Получим ширину для объектов-графиков chart_object_width=chart_width/pressed_buttons_count; //--- Пройдемся в цикле по всем кнопкам for(int i=0; i<TIMEFRAME_BUTTONS; i++) { //--- Если кнопка нажата if(timeframe_button_states[i]) { //--- Получим текст в кнопке-таймфрейме для всплывающей подсказки для объекта-графика object_text=ObjectGetString(0,timeframe_button_names[i],OBJPROP_TEXT); //--- Получим таймфрейм для объекта-графика tf=StringToTimeframe(object_text); //--- Установим объект-график CreateChartInSubwindow(subwindow_number,x_distance,0,chart_object_width,chart_height, chart_object_names[i],_Symbol,tf,chart_scale, property_button_states[0],property_button_states[1], property_button_states[2],property_button_states[3], property_button_states[4],object_text); //--- Определим координату X для следующего объекта-графика x_distance+=chart_object_width; } } } } //--- Обновим график ChartRedraw(); }
Подробные комментарии в коде выше помогут разобраться с работой функции. Выделенные строки показывают пользовательские функции, которые еще не встречались до этого.
Функция InitializeTimeframeButtonStates() возвращает количество нажатых кнопок-таймфреймов и инициализирует массив состояний этих кнопок. Также в ней устанавливаются цвета в зависимости от состояния кнопки:
//+------------------------------------------------------------------+ //| Инициализирует массив состояний кнопок-таймфреймов и | //| возвращает количество нажатых кнопок | //+------------------------------------------------------------------+ int InitializeTimeframeButtonStates() { //--- Счетчик нажатых кнопок-таймфреймов int pressed_buttons_count=0; //--- Пройдемся по всем кнопкам-таймфреймам и посчитаем нажатые for(int i=0; i<TIMEFRAME_BUTTONS; i++) { //--- Если кнопка нажата if(ObjectGetInteger(0,timeframe_button_names[i],OBJPROP_STATE)) { //--- Обозначим это в текущем индексе массива timeframe_button_states[i]=true; //--- Установим цвета нажатой кнопки ObjectSetInteger(0,timeframe_button_names[i],OBJPROP_COLOR,cOnButtonFont); ObjectSetInteger(0,timeframe_button_names[i],OBJPROP_BGCOLOR,cOnButtonBackground); //--- Увеличим счетчик на один pressed_buttons_count++; } else { //--- Установим цвета отжатой кнопки ObjectSetInteger(0,timeframe_button_names[i],OBJPROP_COLOR,cOffButtonFont); ObjectSetInteger(0,timeframe_button_names[i],OBJPROP_BGCOLOR,cOffButtonBackground); //--- Обозначим, что эта кнопка отжата timeframe_button_states[i]=false; } } //--- Вернем количество нажатых кнопок return(pressed_buttons_count); }
Функция DeleteSubwindow() очень простая: она проверяет наличие подокна для графиков и удаляет его:
//+------------------------------------------------------------------+ //| Удаляет подокно для объектов-графиков | //+------------------------------------------------------------------+ void DeleteSubwindow() { //--- Если есть подокно SubWindow if((subwindow_number=ChartWindowFind(0,subwindow_shortname))>0) { //--- Удалим его if(!ChartIndicatorDelete(0,subwindow_number,subwindow_shortname)) Print("Не удалось удалить индикатор "+subwindow_shortname+"!"); } }
Теперь нам нужно разобраться со свойствами объектов-графиков. То есть, вернемся в функцию ChartEventObjectClick() и рассмотрим функцию ToggleChartObjectProperty(). В нее так же передается имя объекта, на который нажали мышью.
//+------------------------------------------------------------------+ //| Устанавливает/удаляет свойство объекта-графика | //| в зависимости от состояния нажатой кнопки | //+------------------------------------------------------------------+ bool ToggleChartObjectProperty(string clicked_object_name) { //--- Если нажали на кнопку "Date" if(clicked_object_name=="property_button_date") { //--- Если кнопка нажата if(SetButtonColor(clicked_object_name)) ShowDate(true); //--- Если кнопка отжата else ShowDate(false); //--- Обновим график и выйдем ChartRedraw(); return(true); } //--- Если нажали на кнопку Price if(clicked_object_name=="property_button_price") { //--- Если кнопка нажата if(SetButtonColor(clicked_object_name)) ShowPrice(true); //--- Если кнопка отжата else ShowPrice(false); //--- Обновим график и выйдем ChartRedraw(); return(true); } //--- Если нажали на кнопку OHLC if(clicked_object_name=="property_button_ohlc") { //--- Если кнопка нажата if(SetButtonColor(clicked_object_name)) ShowOHLC(true); //--- Если кнопка отжата else ShowOHLC(false); //--- Обновим график и выйдем ChartRedraw(); return(true); } //--- Если нажали на кнопку Ask/Bid if(clicked_object_name=="property_button_askbid") { //--- Если кнопка нажата if(SetButtonColor(clicked_object_name)) ShowAskBid(true); //--- Если кнопка отжата else ShowAskBid(false); //--- Обновим график и выйдем ChartRedraw(); return(true); } //--- Если нажали на кнопку Trade Levels if(clicked_object_name=="property_button_trade_levels") { //--- Если кнопка нажата if(SetButtonColor(clicked_object_name)) ShowTradeLevels(true); //--- Если кнопка отжата else ShowTradeLevels(false); //--- Обновим график и выйдем ChartRedraw(); return(true); } //--- Нет совпадений return(false); }
В коде выше последовательно сравниваются имя нажатого объекта и имя объекта, который относится к настройкам графика. Если есть совпадение, то далее в функции SetButtonColor() проверяется, нажата кнопка или отжата, и при этом к этой кнопке применяются соответствующие цвета.
//+------------------------------------------------------------------+ //| Устанавливает цвет элементов кнопки в зависимости от состояния | //+------------------------------------------------------------------+ bool SetButtonColor(string clicked_object_name) { //--- Если кнопка нажата if(ObjectGetInteger(0,clicked_object_name,OBJPROP_STATE)) { //--- Установим цвета нажатой кнопки ObjectSetInteger(0,clicked_object_name,OBJPROP_COLOR,cOnButtonFont); ObjectSetInteger(0,clicked_object_name,OBJPROP_BGCOLOR,cOnButtonBackground); return(true); } //--- Если кнопка отжата if(!ObjectGetInteger(0,clicked_object_name,OBJPROP_STATE)) { //--- Установим цвета отжатой кнопки ObjectSetInteger(0,clicked_object_name,OBJPROP_COLOR,cOffButtonFont); ObjectSetInteger(0,clicked_object_name,OBJPROP_BGCOLOR,cOffButtonBackground); return(false); } //--- return(false); }
Функция SetButtonColor() возвращает состояние кнопки. В зависимости от этого признака программа передает в соответствующую функцию, что свойство во всех объектах-графиках в подокне SubWindow нужно включить или отключить. Для каждого свойства написана своя отдельная функция. С кодом каждой из них можно ознакомиться ниже:
//+------------------------------------------------------------------+ //| Включает/отключает даты у всех объектов графиков | //+------------------------------------------------------------------+ void ShowDate(bool state) { int total_charts =0; // Количество объектов string chart_name =""; // Имя объекта-графика //--- Проверим, есть ли подокно SubWindow // Если есть, то... if((subwindow_number=ChartWindowFind(0,subwindow_shortname))>0) { //--- Получим кол-во объектов-графиков total_charts=ObjectsTotal(0,subwindow_number,OBJ_CHART); //--- Пройдёмся в цикле по всем объектам-графикам for(int i=0; i<total_charts; i++) { //--- Получим имя объекта-графика chart_name=ObjectName(0,i,subwindow_number,OBJ_CHART); //--- Установим свойство ObjectSetInteger(0,chart_name,OBJPROP_DATE_SCALE,state); } //--- Установим в соответствующий индекс состояние кнопки if(state) property_button_states[0]=true; else property_button_states[0]=false; //--- Обновим график ChartRedraw(); } } //+------------------------------------------------------------------+ //| Включает/отключает цены у всех объектов графиков | //+------------------------------------------------------------------+ void ShowPrice(bool state) { int total_charts =0; // Количество объектов string chart_name =""; // Имя объекта-графика //--- Проверим, есть ли подокно SubWindow // Если есть, то... if((subwindow_number=ChartWindowFind(0,subwindow_shortname))>0) { //--- Получим кол-во объектов графиков total_charts=ObjectsTotal(0,subwindow_number,OBJ_CHART); //--- Пройдёмся в цикле по всем объектам-графикам for(int i=0; i<total_charts; i++) { //--- Получим имя объекта-графика chart_name=ObjectName(0,i,subwindow_number,OBJ_CHART); //--- Установим свойство ObjectSetInteger(0,chart_name,OBJPROP_PRICE_SCALE,state); } //--- Установим в соответствующий индекс состояние кнопки if(state) property_button_states[1]=true; else property_button_states[1]=false; //--- Обновим график ChartRedraw(); } } //+------------------------------------------------------------------+ //| Включает/отключает OHLC у всех объектов графиков | //+------------------------------------------------------------------+ void ShowOHLC(bool state) { int total_charts =0; // Количество объектов long subchart_id =0; // Идентификатор объекта-графика string chart_name =""; // Имя объекта-графика //--- Проверим, есть ли подокно SubWindow // Если есть, то... if((subwindow_number=ChartWindowFind(0,subwindow_shortname))>0) { //--- Получим кол-во объектов графиков total_charts=ObjectsTotal(0,subwindow_number,OBJ_CHART); //--- Пройдёмся в цикле по всем объектам-графикам for(int i=0; i<total_charts; i++) { //--- Получим имя объекта-графика chart_name=ObjectName(0,i,subwindow_number,OBJ_CHART); //--- Получим идентификатор объекта-графика subchart_id=ObjectGetInteger(0,chart_name,OBJPROP_CHART_ID); //--- Установим свойство ChartSetInteger(subchart_id,CHART_SHOW_OHLC,state); //--- Обновим объект-график ChartRedraw(subchart_id); } //--- Установим в соответствующий индекс состояние кнопки if(state) property_button_states[2]=true; else property_button_states[2]=false; //--- Обновим график ChartRedraw(); } } //+------------------------------------------------------------------+ //| Включает/отключает уровни Ask/Bid у всех объектов графиков | //+------------------------------------------------------------------+ void ShowAskBid(bool state) { int total_charts =0; // Количество объектов long subchart_id =0; // Идентификатор объекта-графика string chart_name =""; // Имя объекта-графика //--- Проверим, есть ли подокно SubWindow // Если есть, то... if((subwindow_number=ChartWindowFind(0,subwindow_shortname))>0) { //--- Получим кол-во объектов графиков total_charts=ObjectsTotal(0,subwindow_number,OBJ_CHART); //--- Пройдёмся в цикле по всем объектам-графикам for(int i=0; i<total_charts; i++) { //--- Получим имя объекта-графика chart_name=ObjectName(0,i,subwindow_number,OBJ_CHART); //--- Получим идентификатор объекта-графика subchart_id=ObjectGetInteger(0,chart_name,OBJPROP_CHART_ID); //--- Установим свойства ChartSetInteger(subchart_id,CHART_SHOW_ASK_LINE,state); ChartSetInteger(subchart_id,CHART_SHOW_BID_LINE,state); //--- Обновим объект-график ChartRedraw(subchart_id); } //--- Установим в соответствующий индекс состояние кнопки if(state) property_button_states[3]=true; else property_button_states[3]=false; //--- Обновим график ChartRedraw(); } } //+------------------------------------------------------------------+ //| Включает/отключает торговые уровни у всех объектов графиков | //+------------------------------------------------------------------+ void ShowTradeLevels(bool state) { int total_charts =0; // Количество объектов long subchart_id =0; // Идентификатор объекта-графика string chart_name =""; // Имя объекта-графика //--- Проверим, есть ли подокно SubWindow // Если есть, то... if((subwindow_number=ChartWindowFind(0,subwindow_shortname))>0) { //--- Получим кол-во объектов графиков total_charts=ObjectsTotal(0,subwindow_number,OBJ_CHART); //--- Пройдёмся в цикле по всем объектам-графикам for(int i=0; i<total_charts; i++) { //--- Получим имя объекта-графика chart_name=ObjectName(0,i,subwindow_number,OBJ_CHART); //--- Получим идентификатор объекта-графика subchart_id=ObjectGetInteger(0,chart_name,OBJPROP_CHART_ID); //--- Установим свойство ChartSetInteger(subchart_id,CHART_SHOW_TRADE_LEVELS,state); //--- Обновим объект-график ChartRedraw(subchart_id); } //--- Установим в соответствующий индекс состояние кнопки if(state) property_button_states[4]=true; else property_button_states[4]=false; //--- Обновим график ChartRedraw(); } }
Для взаимодействия с панелью все функции готовы. Осталось только добавить одну строчку кода в главную функцию OnChartEvent():
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Событие CHARTEVENT_OBJECT_CLICK if(ChartEventObjectClick(id,lparam,dparam,sparam)) return; }
Если сейчас скомпилировать индикатор и загрузить его на график, нажимая на кнопки-таймфреймы объекты-графики будут устанавливаться в подокно, а если нажимать кнопки со свойствами, то в объектах-графиках можно будет наблюдать соответствующие изменения:
Рис. 5. Добавление объектов-графиков с указанными свойствами
Но если изменить размер окна графика или подокна, размеры объектов-графиков не будут корректироваться под новые значения. Поэтому сейчас пришло время разобраться с событием CHARTEVENT_CHART_CHANGE.
Подобно тому, как была создана функция ChartEventObjectClick() для отслеживания события "нажатие на графическом объекте", создадим функцию ChartEventChartChange():
//+------------------------------------------------------------------+ //| Событие изменения свойств графика | //+------------------------------------------------------------------+ bool ChartEventChartChange(int id, long lparam, double dparam, string sparam) { //--- Изменили размеры или свойства графика if(id==CHARTEVENT_CHART_CHANGE) { //--- Если окно SubWindow удалено (или отсутствует), а кнопки таймфреймов нажаты, // то отожмем все кнопки (переустановим) if(OnSubwindowDelete()) return(true); //--- Сохраним значения высоты и ширины главного графика и подокна SubWindow, если оно есть GetSubwindowWidthAndHeight(); //--- Отрегулируем размеры объектов графиков AdjustChartObjectsSizes(); //--- Обновим график и выйдем ChartRedraw(); return(true); } //--- return(false); }
Если программа определила, что размеры или свойства главного графика были изменены, то далее сначала производится проверка с помощью функции OnSubwindowDelete(), было ли удалено подокно SubWindow. Если подокно не обнаружено, панель переустанавливается.
//+------------------------------------------------------------------+ //| Реакция на удаление подокна Subwindow | //+------------------------------------------------------------------+ bool OnSubwindowDelete() { //--- Если подокна SubWindow нет if(ChartWindowFind(0,subwindow_shortname)<1) { //--- Переустановим панель с кнопками-таймфреймами AddTimeframeButtons(); ChartRedraw(); return(true); } //--- Подокно SubWindow есть return(false); }
Если подокно на месте, то в функции GetSubwindowWidthAndHeight() глобальным переменным присваиваются значения ширины и высоты подокна:
//+------------------------------------------------------------------+ //| Сохраняет значение высоты и ширины подокна SubWindow | //+------------------------------------------------------------------+ void GetSubwindowWidthAndHeight() { //--- Узнаем, есть ли подокно с именем SubWindow if((subwindow_number=ChartWindowFind(0,subwindow_shortname))>0) { // Получим высоту и ширину подокна chart_height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,subwindow_number); chart_width=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,subwindow_number); } }
И наконец, в функции AdjustChartObjectsSizes() производится корректировка размеров объектов-графиков:
//+------------------------------------------------------------------+ //| Регулирует ширину объектов-графиков при изменении ширины окна | //+------------------------------------------------------------------+ void AdjustChartObjectsSizes() { int x_distance =0; // Координата по оси X int total_objects =0; // Количество объектов-графиков int chart_object_width =0; // Ширина объекта-графика string object_name =""; // Имя объекта ENUM_TIMEFRAMES TF =WRONG_VALUE; // Таймфрейм //--- Получим номер подокна SubWindow if((subwindow_number=ChartWindowFind(0,subwindow_shortname))>0) { //--- Получим общее количество объектов-графиков total_objects=ObjectsTotal(0,subwindow_number,OBJ_CHART); //--- Если объектов нет, то удалим подокно и выйдем if(total_objects==0) { DeleteSubwindow(); return; } //--- Получим ширину для объектов-графиков chart_object_width=chart_width/total_objects; //--- Пройдемся в цикле по всем объектам-графикам for(int i=total_objects-1; i>=0; i--) { //--- Получим имя object_name=ObjectName(0,i,subwindow_number,OBJ_CHART); //--- Установим ширину и высоту объекта-графика ObjectSetInteger(0,object_name,OBJPROP_YSIZE,chart_height); ObjectSetInteger(0,object_name,OBJPROP_XSIZE,chart_object_width); //--- Установим положение объекта-графика ObjectSetInteger(0,object_name,OBJPROP_YDISTANCE,0); ObjectSetInteger(0,object_name,OBJPROP_XDISTANCE,x_distance); //--- Установим новую координату по оси X для следующего объекта-графика x_distance+=chart_object_width; } } }
Чтобы отслеживать событие изменения размеров и свойств основного графика, нужно добавить вот такую строчку в функцию OnChartEvent():
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Событие CHARTEVENT_OBJECT_CLICK if(ChartEventObjectClick(id,lparam,dparam,sparam)) return; //--- Событие CHARTEVENT_CHART_CHANGE if(ChartEventChartChange(id,lparam,dparam,sparam)) return; }
После компиляции индикатора и размещения его на графике, вы можете убедиться, что при изменении размеров основного окна теперь объекты-графики всегда подстраиваются под размеры подокна.
Заключение
На этом закончим эту статью. В качестве домашнего задания попробуйте реализовать такие возможности, как корректировка символов в объектах-графиках при смене символа в основном графике. Еще, возможно, вам захочется, чтобы таймфреймы в объектах-графиках устанавливались последовательно от меньшего к большему (слева направо). В описанном выше индикаторе версии эта возможность не реализована.
В описании уже готового приложения TF PANEL можно посмотреть видео, где реализованы вышеперечисленные возможности. Файлы с исходниками можно скачать в приложении к статье.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Опубликована статья Рецепты MQL5 - Наблюдение за несколькими таймфреймами в одном окне:
Автор: Anatoli Kazharski
Подскажите пожалуста, а возможно ли добавить в него отображение секундных таймфреймов например 10 сек, 30 сек?
Очень удобный индикатор.
Подскажите пожалуста, а возможно ли добавить в него отображение секундных таймфреймов например 10 сек, 30 сек?