Скачать MetaTrader 5

Рецепты MQL5 - Наблюдение за несколькими таймфреймами в одном окне

10 октября 2013, 17:24
Anatoli Kazharski
2
4 759

Введение

При выборе направления для открытия позиции весьма полезно видеть ценовой график одновременно на нескольких таймфреймах. Клиентский терминал MetaTrader 5 предлагает на выбор 21 период для анализа. На график можно также поместить специальный графический объект-график и уже в нем задать символ, таймфрейм и еще некоторые свойства. Таких объектов-графиков можно добавлять сколько угодно, но вручную это делать довольно неудобно и долго. К тому же в ручном режиме для настройки доступны не все свойства графика.

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

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


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

Вручную добавить объект-график можно из меню Вставка->Объекты->Графические объекты->График. Например, на часовом графике вот так выглядят объекты с таймфреймами H4 и D1:

Рис. 1. Графические объекты-графики

Рис. 1. Графические объекты-графики

Изменяя параметры этих объектов, можно управлять только ограниченным набором свойств:

Рис. 2. Свойства графического объекта "График"

Рис. 2. Свойства графического объекта "График"

Но такие свойства, как уровни цен ask и bid, отступ от правого края графика, торговые уровни и т.д., можно отобразить только программным способом.

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

Рис. 3. Обработчики событий для индикатора

Рис. 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. Панель с кнопками

Рис. 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. Добавление объектов-графиков с указанными свойствами

Рис. 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 можно посмотреть видео, где реализованы вышеперечисленные возможности. Файлы с исходниками можно скачать в приложении к статье.

Прикрепленные файлы |
subwindow.mq5 (1.53 KB)
chartobjects.mq5 (39.92 KB)
Anttty
Anttty | 17 май 2016 в 11:18
Очень удобный индикатор.

Подскажите пожалуста, а возможно ли добавить в него отображение секундных таймфреймов например 10 сек, 30 сек?
Anatoli Kazharski
Anatoli Kazharski | 17 май 2016 в 12:51
Anttty:
Очень удобный индикатор.

Подскажите пожалуста, а возможно ли добавить в него отображение секундных таймфреймов например 10 сек, 30 сек?
Да, такое сделать можно. Возможно потом напишу статью на эту тему. 
Переход на новые рельсы: пользовательские индикаторы в MQL5 Переход на новые рельсы: пользовательские индикаторы в MQL5

Я не буду перечислять все новые возможности и особенности нового терминала и языка. Их действительно много, и некоторые новинки вполне достойны освещения в отдельной статье. Вы не увидите здесь кода, написанного по принципам объектно-ориентированного программирования — это слишком серьезная тема для того, чтобы просто быть упомянутой в контексте как дополнительная вкусность для кодописателей. В этой статье остановимся подробней на индикаторах, их строении, отображении, видах, а также особенностях их написания по сравнению с MQL4.

Вот мы и получили долгожданные MetaTrader 5 и MQL5 Вот мы и получили долгожданные MetaTrader 5 и MQL5

Это очень краткий обзор MetaTrader 5. Я не могу описать все новшества системы за столь короткий период времени - тестирование стартовало 09-09-2009. Это символическая дата, и я уверен, что это будет счастливым числом. Всего несколько дней у меня на руках бета-версия терминала MetaTrader 5 и MQL5. Я не успел опробовать все, что в нем есть нового, но то, что есть, уже впечатляет.

Как построить и протестировать стратегию бинарных опционов в Тестере Стратегий MetaTrader 4 Как построить и протестировать стратегию бинарных опционов в Тестере Стратегий MetaTrader 4

Руководство по построению стратегии бинарных опционов и ее тестированию в Тестере Стратегий MetaTrader 4 с использованием утилиты Binary-Options-Strategy-Tester из Маркета на MQL5.com.

Портфельная торговля в MetaTrader 4 Портфельная торговля в MetaTrader 4

В статье обсуждаются принципы портфельной торговли и особенности применения к валютному рынку. Рассматриваются несколько простых математических моделей для формирования портфеля. Приводятся примеры практической реализации портфельной торговли в MetaTrader 4: портфельный индикатор и советник для полуавтоматической торговли. Описываются элементы торговых стратегий, их достоинства и "подводные камни".