Наблюдение за несколькими таймфреймами в одном окне

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

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

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

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

Графические объекты Графики

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

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

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

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

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

Код шаблона, после открытия в редакторе, в итоге будет выглядеть так, как показано ниже:

//+------------------------------------------------------------------+
//|                                                ObjectsCharts.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| 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)
  {
//---
   
  }
//+------------------------------------------------------------------+


Функция OnCalculate() в принципе нам не понадобится в этой реализации, но на текущий момент без неё невозможно скомпилировать индикатор. Из основных функций ещё понадобится OnDeinit(). В ней будет отслеживаться удаление программы с графика. После первичной подготовки шаблона, код выглядит так, как показано ниже:

//+------------------------------------------------------------------+
//|                                                ObjectsCharts.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.0"
//---
#property indicator_chart_window // Индикатор в главном окне
#property indicator_plots 0      // Ноль серий для отрисовки
//---
//+------------------------------------------------------------------+
//| ИНИЦИАЛИЗАЦИЯ                                                    |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установим короткое имя индикатора
   IndicatorSetString(INDICATOR_SHORTNAME,"TF_PANEL");
//--- Инициализация прошла успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| ДЕИНИЦИАЛИЗАЦИЯ                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Если индикатор удалён с графика
   if(reason==REASON_REMOVE)
     {
     }
  }
//+------------------------------------------------------------------+
//| СОБЫТИЕ ТИК                                                      |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| ОБРАБОТЧИК ГРУППЫ СОБЫТИЙ                                        |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
  }
//+------------------------------------------------------------------+

Теперь нужно создать индикатор, который будет использоваться в качестве вместилища (подокно) для объектов-графиков. Фактически индикатор-пустышка. Назовём его SUBWINDOW. Его код представлен ниже:

//+------------------------------------------------------------------+
//|                                                    SUBWINDOW.mq5 |
//|            Copyright 2013, https://login.mql5.com/ru/users/tol64 |
//|                                  Site, http://tol64.blogspot.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2013, http://tol64.blogspot.com"
#property link        "http://tol64.blogspot.com"
#property description "email: hello.tol64@gmail.com"
#property version     "1.0"
//---
#property indicator_separate_window // Индикатор в подокне
#property indicator_minimum 0       // Минимум графика
#property indicator_maximum 1       // Максимум графика
#property indicator_plots   0       // Ноль серий для отрисовки
//---
//+------------------------------------------------------------------+
//| ИНИЦИАЛИЗАЦИЯ                                                    |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установим короткое имя индикатора
   IndicatorSetString(INDICATOR_SHORTNAME,"SUBWINDOW");
//--- Инициализация прошла успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| СОБЫТИЕ ТИК                                                      |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   return(rates_total);
  }
//+------------------------------------------------------------------+


Индикатор SUBWINDOW.ex5 будет храниться в качестве ресурса внутри программы ObjectsCharts.ex5 после компиляции. В итоге, разработчик программы может передать конечному пользователю только один файл вместо двух.

Как уже было показано в предыдущей статье Озвучиваем торговые события в MetaTrader 5, подключить файлы-ресурсы в программу можно с помощью директивы компилятора #resource. В начале программы нужно добавить вот такую строчку кода:

//---
#resource "\\Indicators\\SUBWINDOW.ex5" // Подключаемый ресурс-индикатор
//---

Директивами #define обозначим размеры массивов, которые будут относиться к элементам управления:

//---
#define AMOUNT_TF_BUTTONS 21     // Количество кнопок-таймфреймов
#define AMOUNT_SETTING_BUTTONS 5 // Количество кнопок для свойств объекта-графика
//---

И как обычно, в самом начале программы, объявим глобальные переменные и массивы для работы:

//+------------------------------------------------------------------+
//| ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ                                            |
//+------------------------------------------------------------------+
//--- Путь к индикатору SUBWINDOW из ресурса
string path_subwindow          ="::Indicators\\SUBWINDOW.ex5";
int    number_subwindow        =-1;                  // Номер подокна
int    handle_subwindow        =INVALID_HANDLE;      // Хэндл индикатора SUBWINDOW
string shortname_subwindow     ="SUBWINDOW";         // Короткое имя индикатора
//---
int    chart_width             =0;                   // Ширина графика
int    chart_height            =0;                   // Высота графика
int    chart_scale             =0;                   // Масштаб графика
//---
color  clrFontButtonOff        =clrWhite;            // Цвет текста в кнопке (Off)
color  clrBackgroundButtonOff  =clrDarkSlateGray;    // Цвет фона в кнопке (Off)
color  clrBorderButtonOff      =clrLightGray;        // Цвет рамки в кнопке (Off)
//---
color  clrFontButtonOn         =clrGold;             // Цвет текста в кнопке (On)
color  clrBackgroundButtonOn   =C'28,47,47';         // Цвет фона в кнопке (On)
color  clrBorderButtonOn       =clrLightGray;        // Цвет рамки в кнопке (On)
//---
//+------------------------------------------------------------------+
//| МАССИВЫ                                                          |
//+------------------------------------------------------------------+
//--- Массив с именами объектов для кнопок-таймфреймов
string button_tf_name[AMOUNT_TF_BUTTONS]=
  {
   "button_object_m1","button_object_m2","button_object_m3",
   "button_object_m4","button_object_m5","button_object_m6",
   "button_object_m10","button_object_m12","button_object_m15",
   "button_object_m20","button_object_m30","button_object_H1",
   "button_object_H2","button_object_H3","button_object_H4",
   "button_object_H6","button_object_H8","button_object_H12",
   "button_object_D1","button_object_W1","button_object_MN1"
  };
//--- Массив с отображаемым текстом в кнопках-таймфреймах
string button_tf_text[AMOUNT_TF_BUTTONS]=
  {
   "m1","m2","m3","m4","m5","m6","m10",
   "m12","m15","m20","m30","H1","H2","H3",
   "H4","H6","H8","H12","D1","W1","MN1"
  };
//--- Массив состояний кнопок-таймфреймов
bool button_tf_state[AMOUNT_TF_BUTTONS]={false};

//--- Массив с именами объектов для кнопок настроек графиков
string button_settings_name[AMOUNT_SETTING_BUTTONS]=
  {
   "button_settings_date","button_settings_price",
   "button_settings_ohlc","button_settings_askbid","button_settings_trade_levels"
  };
//--- Массив с отображаемым текстом в кнопках настроек графиков
string button_settings_text[AMOUNT_SETTING_BUTTONS]=
  {
   "Date","Price","OHLC","Ask / Bid","Trade Levels"
  };
//--- Массив состояний кнопок настроек графиков
bool button_settings_state[AMOUNT_SETTING_BUTTONS]={false};

//--- Массив размеров кнопок настроек графиков
int button_settings_width[AMOUNT_SETTING_BUTTONS]=
  {
   66,68,66,100,101
  };
//--- Массив с именами объектов-графиков
string chart_object_name[AMOUNT_TF_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_MN1"
  };
//---

Прежде, чем приступить к написанию функций, которые относятся к взаимодействию с графическими объектами, напишем сначала функции, которые создают эти объекты на графике.  Для этой программы нам понадобятся два типа графических объектов: OBJ_BUTTON и OBJ_CHART. Ниже представлен код пользовательских функций CreateButton() и CreateChartInSubwindow().

Код функции CreateButton():

//+------------------------------------------------------------------+
//| СОЗДАНИЕ ОБЪЕКТА BUTTON                                          |
//+------------------------------------------------------------------+
void CreateButton(long              chart_id,         // id графика
                  int               number_window,    // номер окна
                  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,number_window,0,0))
     {
      // ...установим свойства
      ObjectSetString(chart_id,name,OBJPROP_TEXT,text);                  // установка имени
      ObjectSetString(chart_id,name,OBJPROP_FONT,font_name);             // установка шрифта
      ObjectSetInteger(chart_id,name,OBJPROP_COLOR,font_color);          // установка цвета шрифта
      ObjectSetInteger(chart_id,name,OBJPROP_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);              // установка ширины X
      ObjectSetInteger(chart_id,name,OBJPROP_YSIZE,y_size);              // установка высоты Y
      ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x_distance);      // установка координаты X
      ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y_distance);      // установка координаты Y
      ObjectSetInteger(chart_id,name,OBJPROP_SELECTABLE,false);          // нельзя выделить объект, если FALSE
      ObjectSetInteger(chart_id,name,OBJPROP_STATE,false);               // состояние кнопки (нажата/отжата)
      ObjectSetInteger(chart_id,name,OBJPROP_ZORDER,z_order);            // Приоритет выше/ниже
      ObjectSetString(chart_id,name,OBJPROP_TOOLTIP,"\n");               // нет всплывающей подсказки, если "\n"
     }
  }
//---

Код функции CreateChartInSubwindow():

//+------------------------------------------------------------------+
//| СОЗДАЁТ ОБЪЕКТ ГРАФИК В ПОДОКНЕ                                  |
//+------------------------------------------------------------------+
void CreateChartInSubwindow(int             number_window,  // номер подокна
                            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            date_scale,     // шкала дат
                            bool            price_scale,    // шкала цен
                            bool            ohlc,           // OHLC
                            bool            ask_bid,        // уровни ask/bid
                            bool            levels,         // торговые уровни
                            string          tooltip)        // всплывающая подсказка
  {
//--- Если объект создался, то...
   if(ObjectCreate(0,name,OBJ_CHART,number_window,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,date_scale);      // шкала дат
      ObjectSetInteger(0,name,OBJPROP_PRICE_SCALE,price_scale);    // шкала цен
      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,ohlc);           // OHLC
      ChartSetInteger(subchart_id,CHART_SHOW_TRADE_LEVELS,levels); // торговые уровни
      ChartSetInteger(subchart_id,CHART_SHOW_BID_LINE,ask_bid);    // уровень bid
      ChartSetInteger(subchart_id,CHART_SHOW_ASK_LINE,ask_bid);    // уровень ask
      ChartSetInteger(subchart_id,CHART_COLOR_LAST,clrLimeGreen);  // цвет уровня последней совершенной сделки 
      ChartSetInteger(subchart_id,CHART_COLOR_STOP_LEVEL,clrRed);  // цвет уровней стоп-ордеров 
      //--- Обновить объект-график
      ChartRedraw(subchart_id);
     }
  }
//---

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

Установку элементов управления разделим на две функции: SetTimeframeButtons() и SetSettingsButtons(). Ниже представлен код этих функций:

//+------------------------------------------------------------------+
//| УСТАНАВЛИВАЕТ КНОПКИ-ТАЙМФРЕЙМЫ                                  |
//+------------------------------------------------------------------+
void SetTimeframeButtons()
  {
   int x_dist =1;   // Отступ от левого края графика
   int y_dist =125; // Отступ от нижней части графика
   int x_size =28;  // Ширина кнопок
   int y_size =20;  // Высота кнопок
//---
   for(int i=0; i<AMOUNT_TF_BUTTONS; i++)
     {
      //--- Если уже установлено семь кнопок в ряду, то
      //    установим координаты для следующего ряда
      if(i%7==0)
        {
         x_dist=1;
         y_dist-=21;
        }
      //--- Установим кнопку-таймфрейм
      CreateButton(0,0,button_tf_name[i],button_tf_text[i],
                   ANCHOR_LEFT_LOWER,CORNER_LEFT_LOWER,"Arial",8,
                   clrFontButtonOff,clrBackgroundButtonOff,clrBorderButtonOff,
                   x_size,y_size,x_dist,y_dist,3);
      //--- Установим координату X для следующей кнопки
      x_dist+=x_size+1;
     }
  }
//+------------------------------------------------------------------+
//| УСТАНАВЛИВАЕТ КНОПКИ ДЛЯ НАСТРОЙКИ                               |
//+------------------------------------------------------------------+
void SetSettingsButtons()
  {
   int x_dist =1;  // Отступ от левого края графика
   int y_dist =41; // Отступ от нижней части графика
   int x_size =66; // Ширина кнопок
   int y_size =20; // Высота кнопок
//---
   for(int i=0; i<AMOUNT_SETTING_BUTTONS; i++)
     {
      //--- Если первые три кнопки уже установлены
      //    установим координаты для следующего ряда
      if(i==3)
        {
         x_dist=1;
         y_dist-=21;
        }
      //--- Установим кнопку
      CreateButton(0,0,button_settings_name[i],button_settings_text[i],
                   ANCHOR_LEFT_LOWER,CORNER_LEFT_LOWER,"Arial",8,
                   clrFontButtonOff,clrBackgroundButtonOff,clrBorderButtonOff,
                   button_settings_width[i],y_size,x_dist,y_dist,3);
      //--- Установим координату X для следующей кнопки
      x_dist+=button_settings_width[i]+1;
     }
  }
//---

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

//+------------------------------------------------------------------+
//| УДАЛЯЕТ ПАНЕЛЬ ТАЙМФРЕЙМОВ                                       |
//+------------------------------------------------------------------+
void DeleteTimeframeButtons()
  {
   for(int i=0; i<AMOUNT_TF_BUTTONS; i++)
      DeleteObjectByName(button_tf_name[i]);
  }
//+------------------------------------------------------------------+
//| УДАЛЯЕТ ПАНЕЛЬ НАСТРОЕК                                          |
//+------------------------------------------------------------------+
void DeleteSettingsButtons()
  {
   for(int i=0; i<AMOUNT_SETTING_BUTTONS; i++)
      DeleteObjectByName(button_settings_name[i]);
  }
//+------------------------------------------------------------------+
//| УДАЛЯЕТ ОБЪЕКТЫ ПО ИМЕНИ                                         |
//+------------------------------------------------------------------+
void DeleteObjectByName(string Name)
  {
//--- Если есть такой объект
   if(ObjectFind(ChartID(),Name)>=0)
     {
      //--- Если была ошибка при удалении, сообщим об этом
      if(!ObjectDelete(ChartID(),Name))
         Print("Ошибка ("+IntegerToString(GetLastError())+") при удалении объекта!");
     }
  }
//---

Теперь, чтобы при загрузке  индикатора панель устанавливалась на график, а при удалении индикатора с графика все объекты панели удалялись, в функции OnInit() и OnDeinit() нужно добавить вот такие строчки кода:

//+------------------------------------------------------------------+
//| ИНИЦИАЛИЗАЦИЯ                                                    |
//+------------------------------------------------------------------+
int OnInit()
  {
   SetTimeframeButtons(); // Установим панель с кнопками-таймфреймами
   SetSettingsButtons();  // Установим панель с кнопками для настройки
//--- Обновим график
   ChartRedraw();
//--- Инициализация прошла успешно
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| ДЕИНИЦИАЛИЗАЦИЯ                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Если индикатор удалён с графика
   if(reason==REASON_REMOVE)
     {
      //--- Удалим кнопки панели
      DeleteTimeframeButtons();
      DeleteSettingsButtons();
      //--- Обновим график
      ChartRedraw();
     }
  }
//---

Если сейчас скомпилировать индикатор и загрузить его на график, то будет установлена панель, как показано на скриншоте ниже:

Панель индикатора

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

  • - CHARTEVENT_OBJECT_CLICK - нажатие мышки на графическом объекте.
  • - CHARTEVENT_CHART_CHANGE - изменение размеров графика или изменение свойств графика через диалог свойств.
  • - CHARTEVENT_MOUSE_MOVE - перемещение мыши и нажатие кнопок мыши.

Начнём с события CHARTEVENT_OBJECT_CLICK. Напишем функцию ChartEventObjectClick(), в которую будут передаваться все аргументы из функции OnChartEvent(). Для остальных событий после будут созданы также подобные функции.

Ниже можно ознакомиться с функцией ChartEventObjectClick() с подробными комментариями:

//+------------------------------------------------------------------+
//| СОБЫТИЕ CHARTEVENT OBJECT CLICK                                  |
//+------------------------------------------------------------------+
bool ChartEventObjectClick(int id,long lparam,double dparam,string sparam)
  {
//--- Если было событие клика по графическому объекту
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Установить/удалить окно SUBWINDOW и объекты-графики,
      //    если клик был по кнопке-таймфрейму
      if(SetSubwindowAndObjectsCharts(sparam))
         return(true);
      //--- Установить свойство в объектах-графиках,
      //    если клик был по кнопке свойств графиков
      if(SetPropertyObjectChart(sparam))
         return(true);
     }
//---
   return(false);
  }
//---

Код функции ChartEventObjectClick() прост. По идентификатору определяется было ли это событие "нажатие по графическому объекту". Далее можно увидеть выделенные строки с новыми функциями: SetSubwindowAndObjectsCharts() и SetPropertyObjectChart(). В них передаётся аргумент sparam, в котором содержится имя объекта, на котором было произведёно нажатие левой кнопкой мыши. Рассмотрим код этих функций и начнём с SetSubwindowAndObjectsCharts():

//+------------------------------------------------------------------+
//| УСТАНАВЛИВАЕТ ПОДОКНО SUBWINDOW И ОБЪЕКТЫ-ГРАФИКИ                |
//+------------------------------------------------------------------+
bool SetSubwindowAndObjectsCharts(string clicked_object)
  {
//--- Если нажатие было на объекте кнопка-таймфрейм
   if(CheckClickObjectTimeframesPanel(clicked_object))
     {
      //--- Проверим, есть ли подокно SUBWINDOW
      number_subwindow=ChartWindowFind(0,shortname_subwindow);
      //--- Если нет подокна SUBWINDOW, установим его
      if(number_subwindow<0)
        {
         //--- Если подокно SUBWINDOW установлено, то...
         if(SetSubwindow())
           {
            //--- Установим в нём объекты-графики
            SetObjectsChartsInSubwindow(clicked_object);
            return(true);
           }
        }
      //--- Если есть подокно SUBWINDOW
      if(number_subwindow>0)
        {
         //--- Установим в нём объекты-графики
         SetObjectsChartsInSubwindow(clicked_object);
         return(true);
        }
     }
//---
   return(false);
  }
//---

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

Функция CheckClickObjectTimeframesPanel() возвращает истину (true), если объект, на котором нажали левой кнопкой мыши, относится к панели таймфреймов:

//+------------------------------------------------------------------+
//| ПРОВЕРЯЕТ БЫЛО ЛИ НАЖАТИЕ НА КНОПКЕ-ТАЙМФРЕЙМЕ                   |
//+------------------------------------------------------------------+
bool CheckClickObjectTimeframesPanel(string clicked_object)
  {
//--- Пройдём по всем кнопкам-таймфреймам и сверим имена 
   for(int i=0; i<AMOUNT_TF_BUTTONS; i++)
     {
      //--- Если имя нажатого объекта 
      //    одно из кнопок массива таймфреймов, то сообщим об этом
      if(clicked_object==button_tf_name[i])
         return(true);
     }
//---
   return(false);
  }
//---

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

//+------------------------------------------------------------------+
//| УСТАНОВКА ПОДОКНА ДЛЯ ОБЪЕКТОВ CHART                             |
//+------------------------------------------------------------------+
bool SetSubwindow()
  {
//--- Получим хэндл индикатора "SUBWINDOW"
   handle_subwindow=iCustom(_Symbol,_Period,path_subwindow);
//--- Если хэндл получен
   if(handle_subwindow!=INVALID_HANDLE)
     {
      //--- Определим кол-во окон на графике для номера подокна
      number_subwindow=(int)ChartGetInteger(0,CHART_WINDOWS_TOTAL);
      //--- Установим подокно SUBWINDOW на график
      if(!ChartIndicatorAdd(0,number_subwindow,handle_subwindow))
        {
         Print("Не удалось установить индикатор SUBWINDOW ! ");
        }
      else { return(true); } // Есть подокно
     }
//---
   return(false); // Нет подокна
  }
//---

Если подокна SUBWINDOW нет или, если его не было, установилось успешно, то далее программа переходит в функцию SetObjectsChartsInSubwindow():

//+------------------------------------------------------------------+
//| УСТАНОВКА ОБЪЕКТОВ-ГРАФИКОВ В ПОДОКНО                            |
//+------------------------------------------------------------------+
void SetObjectsChartsInSubwindow(string clicked_object)
  {
   ENUM_TIMEFRAMES TF                 =WRONG_VALUE; // Таймфрейм
   string          object_name        ="";          // Имя объекта
   string          object_text        ="";          // Текст в объекте
   int             x_distance         =0;           // Координата по оси X
   int             total_charts       =0;           // Количество объектов-графиков
   int             object_chart_width =0;           // Ширина объекта-графика
//--- Узнаем масштаб баров, высоту/ширину подокна SUBWINDOW
   chart_scale=(int)ChartGetInteger(0,CHART_SCALE);
   chart_width=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,number_subwindow);
   chart_height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,number_subwindow);
//--- Получим кол-во объектов-график в подокне SUBWINDOW
   total_charts=ObjectsTotal(0,number_subwindow,OBJ_CHART);
//--- Если нет объектов
   if(total_charts==0)
     {
      //--- Проверим, было ли нажатие на кнопке-таймфрейме
      if(CheckClickObjectTimeframesPanel(clicked_object))
        {
         //--- Инициализируем массив кнопок-таймфреймов
         InitArrayStateButtonsTF();
         //--- Получим текст в кнопке-таймфрейме для всплывающей подсказки
         object_text=ObjectGetString(0,clicked_object,OBJPROP_TEXT);
         //--- Получим таймфрейм для объекта-графика
         TF=(ENUM_TIMEFRAMES)StringTimeframeToEnumTF(object_text);
         //--- Установим объект-график
         CreateChartInSubwindow(number_subwindow,0,0,chart_width,chart_height,
                                "chart_object_"+object_text,_Symbol,TF,chart_scale,
                                button_settings_state[0],button_settings_state[1],
                                button_settings_state[2],button_settings_state[3],
                                button_settings_state[4],object_text);
         //--- Обновим график и выйдем
         ChartRedraw(); return;
        }
     }
//--- Если объекты-графики уже есть в подокне SUBWINDOW
   if(total_charts>0)
     {
      //--- Получим кол-во нажатых кнопок-таймфреймов и
      //    проинициализируем массив состояний
      int amount_pressed_buttons=InitArrayStateButtonsTF();
      //--- Если нет нажатых кнопок, то...
      if(amount_pressed_buttons==0)
        {
         //--- Удалим подокно SUBWINDOW
         DeleteSubwindow();
        }
      else
        {
         //--- Если нажатые кнопки есть, то удалим все объекты-графики из подокна
         ObjectsDeleteAll(0,number_subwindow,OBJ_CHART);
         //--- Получим ширину для объектов-графиков
         object_chart_width=int(chart_width/amount_pressed_buttons);
         //--- пройдёмся в цикле по всем кнопкам
         for(int i=0; i<AMOUNT_TF_BUTTONS; i++)
           {
            //--- Если кнопка нажата
            if(button_tf_state[i])
              {
               //--- Получим текст в кнопке-таймфрейме для всплывающей подсказки
               object_text=ObjectGetString(0,button_tf_name[i],OBJPROP_TEXT);
               //--- Получим таймфрейм для объекта-графика
               TF=(ENUM_TIMEFRAMES)StringTimeframeToEnumTF(object_text);
               //--- Установим объект-график
               CreateChartInSubwindow(number_subwindow,x_distance,0,object_chart_width,chart_height,
                                      chart_object_name[i],_Symbol,TF,chart_scale,
                                      button_settings_state[0],button_settings_state[1],
                                      button_settings_state[2],button_settings_state[3],
                                      button_settings_state[4],object_text);
               //--- Определим координату X для следующего объекта-графика
               x_distance+=object_chart_width;
              }
           }
        }
     }
//--- Обновим график
   ChartRedraw();
  }
//---

Подробные комментарии в коде выше помогут разобраться с функцией SetObjectsChartsInSubwindow(). Выделенные строки показывают пользовательские функции, которые ещё не встречались до этого. Это функции InitArrayStateButtonsTF() и DeleteSubwindow().

Функция InitArrayStateButtonsTF() возвращает количество нажатых кнопок-таймфреймов и инициализирует массив состояний этих кнопок. Также в ней устанавливаются цвета в зависимости от состояния кнопки. Код функции InitArrayStateButtonsTF() представлен ниже:

//+------------------------------------------------------------------+
//| ИНИЦИАЛИЗИРУЕТ МАССИВ СОСТОЯНИЙ КНОПОК-ТАЙМФРЕЙМОВ И             |
//| ВОЗВРАЩАЕТ КОЛИЧЕСТВО НАЖАТЫХ КНОПОК                             |
//+------------------------------------------------------------------+
int InitArrayStateButtonsTF()
  {
   int count_pressed_buttons=0; // Счётчик нажатых кнопок-таймфреймов
//--- Пройдёмся по всем кнопкам-таймфреймам и посчитаем нажатые
   for(int i=0; i<AMOUNT_TF_BUTTONS; i++)
     {
      // Если кнопка нажата
      if(ObjectGetInteger(0,button_tf_name[i],OBJPROP_STATE))
        {
         //--- Обозначим это в текущем индексе массива
         button_tf_state[i]=true;
         //--- Установим цвета нажатой кнопки
         ObjectSetInteger(0,button_tf_name[i],OBJPROP_COLOR,clrFontButtonOn);
         ObjectSetInteger(0,button_tf_name[i],OBJPROP_BGCOLOR,clrBackgroundButtonOn);
         //--- Увеличим счётчик на один
         count_pressed_buttons++;
        }
      else
        {
         //--- Установим цвета отжатой кнопки
         ObjectSetInteger(0,button_tf_name[i],OBJPROP_COLOR,clrFontButtonOff);
         ObjectSetInteger(0,button_tf_name[i],OBJPROP_BGCOLOR,clrBackgroundButtonOff);
         //--- Обозначим, что эта кнопка отжата
         button_tf_state[i]=false;
        }
     }
//---
   return(count_pressed_buttons); // Вернём количество нажатых кнопок
  }
//---

Код функции DeleteSubwindow() очень прост. В ней производится проверка, есть ли подокно SUBWINDOW и, если есть, то программа удаляет его (см. код ниже):

//+------------------------------------------------------------------+
//| УДАЛЕНИЕ ПОДОКНА ДЛЯ ОБЪЕКТОВ CHART                              |
//+------------------------------------------------------------------+
void DeleteSubwindow()
  {
//--- Если есть подокно SUBWINDOW
   if((number_subwindow=ChartWindowFind(0,shortname_subwindow))>0)
     {
      //--- Удалим его
      if(!ChartIndicatorDelete(0,number_subwindow,shortname_subwindow))
         Print("Не удалось удалить индикатор "+shortname_subwindow+" !");
     }
  }
//---

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

Код функции SetPropertyObjectChart():

//+------------------------------------------------------------------+
//| УСТАНАВЛИВАЕТ СВОЙСТВО ОБЪЕКТА-ГРАФИКА                           |
//| В ЗАВИСИМОСТИ ОТ СОСТОЯНИЯ НАЖАТОЙ КНОПКИ                        |
//+------------------------------------------------------------------+
bool SetPropertyObjectChart(string name_button)
  {
//--- Если нажали на кнопку Date
   if(name_button=="button_settings_date")
     {
      //--- Если кнопка включена
      if(SetButtonColor(name_button))
         SetDateInObjectsCharts(true);
      //--- Если кнопка отжата
      else
         SetDateInObjectsCharts(false);
      //--- Обновим график и выйдем
      ChartRedraw(); return(true);
     }
//--- Если нажали на кнопку Price
   if(name_button=="button_settings_price")
     {
      //--- Если кнопка включена
      if(SetButtonColor(name_button))
         SetPriceInObjectsCharts(true);
      //--- Если кнопка отжата
      else
         SetPriceInObjectsCharts(false);
      //--- Обновим график и выйдем
      ChartRedraw(); return(true);
     }
//--- Если нажали на кнопку OHLC
   if(name_button=="button_settings_ohlc")
     {
      //--- Если кнопка включена
      if(SetButtonColor(name_button))
         SetOhlcInObjectsCharts(true);
      //--- Если кнопка отжата
      else
         SetOhlcInObjectsCharts(false);
      //--- Обновим график и выйдем
      ChartRedraw(); return(true);
     }
//--- Если нажали на кнопку Ask/Bid
   if(name_button=="button_settings_askbid")
     {
      //--- Если кнопка включена
      if(SetButtonColor(name_button))
         SetAskBidInObjectsCharts(true);
      //--- Если кнопка отжата
      else
         SetAskBidInObjectsCharts(false);
      //--- Обновим график и выйдем
      ChartRedraw(); return(true);
     }
//--- Если нажали на кнопку Trade Levels
   if(name_button=="button_settings_trade_levels")
     {
      //--- Если кнопка включена
      if(SetButtonColor(name_button))
         SetTradeLevelsInObjectsCharts(true);
      //--- Если кнопка отжата
      else
         SetTradeLevelsInObjectsCharts(false);
      //--- Обновим график и выйдем
      ChartRedraw(); return(true);
     }
//--- Нет совпадений
   return(false);
  }
//---

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

//+------------------------------------------------------------------+
//| УСТАНАВЛИВАЕТ ЦВЕТ В ЗАВИСИМОСТИ ОТ СОСТОЯНИЯ                    |
//+------------------------------------------------------------------+
bool SetButtonColor(string name_button)
  {
//--- Если кнопка включена
   if(ObjectGetInteger(0,name_button,OBJPROP_STATE))
     {
      //--- Установим цвета включенной кнопки
      ObjectSetInteger(0,name_button,OBJPROP_COLOR,clrFontButtonOn);
      ObjectSetInteger(0,name_button,OBJPROP_BGCOLOR,clrBackgroundButtonOn);
      return(true);
     }
//--- Если кнопка отключена
   if(!ObjectGetInteger(0,name_button,OBJPROP_STATE))
     {
      //--- Установим цвета отключенной кнопки
      ObjectSetInteger(0,name_button,OBJPROP_COLOR,clrFontButtonOff);
      ObjectSetInteger(0,name_button,OBJPROP_BGCOLOR,clrBackgroundButtonOff);
      return(false);
     }
//---
   return(false);
  }
//---

Функция SetButtonColor() возвращает состояние кнопки и, если кнопка включена, то программа передаёт в соответствующую функцию, что свойство во всех объектах-графиках в подокне SUBWINDOW нужно включить. Для включения того или иного свойства написана отдельная функция. С кодом каждой из них можно ознакомиться ниже:

//+------------------------------------------------------------------+
//| ВКЛЮЧАЕТ ДАТЫ У ВСЕХ ОБЪЕКТОВ-ГРАФИКОВ                           |
//+------------------------------------------------------------------+
void SetDateInObjectsCharts(bool state)
  {
   int    total_chart =0;  // Количество объектов
   string name_chart  =""; // Имя объекта-графика
//--- Проверим, есть ли подокно SUBWINDOW
//    Если есть, то...
   if((number_subwindow=ChartWindowFind(0,shortname_subwindow))>0)
     {
      //--- Получим кол-во объектов-графиков
      total_chart=ObjectsTotal(0,number_subwindow,OBJ_CHART);
      //--- Пройдёмся в цикле по всем объектам-графикам
      for(int i=0; i<total_chart; i++)
        {
         //--- Получим имя объекта-графика
         name_chart=ObjectName(0,i,number_subwindow,OBJ_CHART);
         //--- Установим свойство
         ObjectSetInteger(0,name_chart,OBJPROP_DATE_SCALE,state);
        }
      //--- Установим в соответствующий индекс состояние кнопки
      if(state)
         button_settings_state[0]=true;
      else
         button_settings_state[0]=false;
      //--- Обновим график
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+
//| ВКЛЮЧАЕТ ЦЕНЫ У ВСЕХ ОБЪЕКТОВ-ГРАФИКОВ                           |
//+------------------------------------------------------------------+
void SetPriceInObjectsCharts(bool state)
  {
   int    total_chart =0;  // Количество объектов
   string name_chart  =""; // Имя объекта-графика
//--- Проверим, есть ли подокно SUBWINDOW
//    Если есть, то...
   if((number_subwindow=ChartWindowFind(0,shortname_subwindow))>0)
     {
      //--- Получим кол-во объектов графиков
      total_chart=ObjectsTotal(0,number_subwindow,OBJ_CHART);
      //--- Пройдёмся в цикле по всем объектам-графикам
      for(int i=0; i<total_chart; i++)
        {
         //--- Получим имя объекта-графика
         name_chart=ObjectName(0,i,number_subwindow,OBJ_CHART);
         //--- Установим свойство
         ObjectSetInteger(0,name_chart,OBJPROP_PRICE_SCALE,state);
        }
      //--- Установим в соответствующий индекс состояние кнопки
      if(state)
         button_settings_state[1]=true;
      else
         button_settings_state[1]=false;
      //--- Обновим график
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+
//| ВКЛЮЧАЕТ OHLC У ВСЕХ ОБЪЕКТОВ-ГРАФИКОВ                           |
//+------------------------------------------------------------------+
void SetOhlcInObjectsCharts(bool state)
  {
   int    total_chart =0;  // Количество объектов
   long   subchart_id =0;  // Идентификатор объекта-графика
   string name_chart  =""; // Имя объекта-графика
//--- Проверим, есть ли подокно SUBWINDOW
//    Если есть, то...
   if((number_subwindow=ChartWindowFind(0,shortname_subwindow))>0)
     {
      //--- Получим кол-во объектов графиков
      total_chart=ObjectsTotal(0,number_subwindow,OBJ_CHART);
      //--- Пройдёмся в цикле по всем объектам-графикам
      for(int i=0; i<total_chart; i++)
        {
         //--- Получим имя объекта-графика
         name_chart=ObjectName(0,i,number_subwindow,OBJ_CHART);
         //--- Получим идентификатор объекта-графика
         subchart_id=ObjectGetInteger(0,name_chart,OBJPROP_CHART_ID);
         //--- Установим свойство
         ChartSetInteger(subchart_id,CHART_SHOW_OHLC,state);
         //--- Обновим объект-график
         ChartRedraw(subchart_id);
        }
      //--- Установим в соответствующий индекс состояние кнопки
      if(state)
         button_settings_state[2]=true;
      else
         button_settings_state[2]=false;
      //--- Обновим график
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+
//| ВКЛЮЧАЕТ УРОВНИ ASK/BID У ВСЕХ ОБЪЕКТОВ-ГРАФИКОВ                 |
//+------------------------------------------------------------------+
void SetAskBidInObjectsCharts(bool state)
  {
   int    total_chart =0;  // Количество объектов
   long   subchart_id =0;  // Идентификатор объекта-графика
   string name_chart  =""; // Имя объекта-графика
//--- Проверим, есть ли подокно SUBWINDOW
//    Если есть, то...
   if((number_subwindow=ChartWindowFind(0,shortname_subwindow))>0)
     {
      //--- Получим кол-во объектов графиков
      total_chart=ObjectsTotal(0,number_subwindow,OBJ_CHART);
      //--- Пройдёмся в цикле по всем объектам-графикам
      for(int i=0; i<total_chart; i++)
        {
         //--- Получим имя объекта-графика
         name_chart=ObjectName(0,i,number_subwindow,OBJ_CHART);
         //--- Получим идентификатор объекта-графика
         subchart_id=ObjectGetInteger(0,name_chart,OBJPROP_CHART_ID);
         //--- Установим свойства
         ChartSetInteger(subchart_id,CHART_SHOW_ASK_LINE,state);
         ChartSetInteger(subchart_id,CHART_SHOW_BID_LINE,state);
         //--- Обновим объект-график
         ChartRedraw(subchart_id);
        }
      //--- Установим в соответствующий индекс состояние кнопки
      if(state)
         button_settings_state[3]=true;
      else
         button_settings_state[3]=false;
      //--- Обновим график
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+
//| ВКЛЮЧАЕТ ТОРГОВЫЕ УРОВНИ У ВСЕХ ОБЪЕКТОВ-ГРАФИКОВ                |
//+------------------------------------------------------------------+
void SetTradeLevelsInObjectsCharts(bool state)
  {
   int    total_chart =0;  // Количество объектов
   long   subchart_id =0;  // Идентификатор объекта-графика
   string name_chart  =""; // Имя объекта-графика
//--- Проверим, есть ли подокно SUBWINDOW
//    Если есть, то...
   if((number_subwindow=ChartWindowFind(0,shortname_subwindow))>0)
     {
      //--- Получим кол-во объектов графиков
      total_chart=ObjectsTotal(0,number_subwindow,OBJ_CHART);
      //--- Пройдёмся в цикле по всем объектам-графикам
      for(int i=0; i<total_chart; i++)
        {
         //--- Получим имя объекта-графика
         name_chart=ObjectName(0,i,number_subwindow,OBJ_CHART);
         //--- Получим идентификатор объекта-графика
         subchart_id=ObjectGetInteger(0,name_chart,OBJPROP_CHART_ID);
         //--- Установим свойство
         ChartSetInteger(subchart_id,CHART_SHOW_TRADE_LEVELS,state);
         //--- Обновим объект-график
         ChartRedraw(subchart_id);
        }
      //--- Установим в соответствующий индекс состояние кнопки
      if(state)
         button_settings_state[4]=true;
      else
         button_settings_state[4]=false;
      //--- Обновим график
      ChartRedraw();
     }
  }
//---

Для взаимодействия с панелью все функции готовы. Осталось только добавить одну строчку кода в главную функцию OnChartEvent():

//+------------------------------------------------------------------+
//| ОБРАБОТЧИК ГРУППЫ СОБЫТИЙ                                        |
//+------------------------------------------------------------------+
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.

Подобно тому, как была создана функция ChartEventObjectClick() для отслеживания события "нажатие на графическом объекте", создадим функцию ChartEventChartChange(). Ниже представлен её код:

//+------------------------------------------------------------------+
//| СОБЫТИЕ CHARTEVENT CHART CHANGE                                  |
//+------------------------------------------------------------------+
bool ChartEventChartChange(int id,long lparam,double dparam,string sparam)
  {
//--- Если было изменение размеров или свойств графика
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      //--- Если окно SUBWINDOW удалено (или отсутствует),
      //    а кнопки таймфреймов нажаты, то отожмём все кнопки (переустановим)
      if(EventDeleteSubwindow())
        return(true);
      //--- Сохраняет значения высоты и ширины главного графика и
      //    подокна SUBWINDOW, если оно есть
      GetWidthAndHeightSubwindow();
      //--- Регулирует размеры объектов графиков
      CorrectSizeObjectsCharts();
      //---
      ChartRedraw(); return(true);
     }
//---
   return(false);
  }
//---

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

Код функции EventDeleteSubwindow():

//+------------------------------------------------------------------+
//| РЕАКЦИЯ НА УДАЛЕНИЕ ПОДОКНА SUBWINDOW                            |
//+------------------------------------------------------------------+
bool EventDeleteSubwindow()
  {
//--- Если подокна SUBWINDOW нет
   if(ChartWindowFind(0,shortname_subwindow)<1)
     {
      //--- Переустановим панель с кнопками-таймфреймами
      SetTimeframeButtons();
      ChartRedraw(); return(true);
     }
//--- Подокно SUBWINDOW есть
   return(false);
  }
//---

Если подокно на месте, то в функции GetWidthAndHeightSubwindow() глобальным переменным присваиваются значения ширины и высоты подокна:

//+------------------------------------------------------------------+
//| СОХРАНЯЕТ ЗНАЧЕНИЯ ВЫСОТЫ И ШИРИНЫ ПОДОКНА SUBWINDOW             |
//+------------------------------------------------------------------+
void GetWidthAndHeightSubwindow()
  {
//--- Узнаем, есть ли подокно с именем SUBWINDOW
//    Если есть, то...
   if((number_subwindow=ChartWindowFind(0,shortname_subwindow))>0)
     {
      // Получим высоту и ширину подокна
      chart_height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,number_subwindow);
      chart_width=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,number_subwindow);
     }
  }
//---

И наконец, в функции CorrectSizeObjectsCharts() производится корректировка размеров объектов-графиков. Её код представлен ниже:

//+------------------------------------------------------------------+
//| РЕГУЛИРУЕТ ШИРИНУ ОБЪЕКТОВ ГРАФИКОВ ПРИ ИЗМЕНЕНИИ ШИРИНЫ ОКНА    |
//+------------------------------------------------------------------+
void CorrectSizeObjectsCharts()
  {
   int             x_distance         =0;           // Координата по оси X
   int             total_objects      =0;           // Количество объектов-графиков
   int             object_chart_width =0;           // Ширина объекта-графика
   string          object_name        ="";          // Имя объекта
   ENUM_TIMEFRAMES TF                 =WRONG_VALUE; // Таймфрейм
//--- Получим номер подокна SUBWINDOW
//    Если подокно SUBWINDOW есть,то...
   if((number_subwindow=ChartWindowFind(0,shortname_subwindow))>0)
     {
      //--- Получим общее кол-во объектов-графиков
      total_objects=ObjectsTotal(0,number_subwindow,OBJ_CHART);
      //--- Если объектов нет, то удалим подокно и выйдем
      if(total_objects==0)
        {
         DeleteSubwindow();
         return;
        }
      //--- Получим ширину для объектов-графиков
      object_chart_width=int(chart_width/total_objects);
      //--- Пройдёмся в цикле по всем объектам-графикам
      for(int i=total_objects-1; i>=0; i--)
        {
         //--- Получим имя
         object_name=ObjectName(0,i,number_subwindow,OBJ_CHART);
         //--- Установим ширину и высоту объекта-графика
         ObjectSetInteger(0,object_name,OBJPROP_YSIZE,chart_height);
         ObjectSetInteger(0,object_name,OBJPROP_XSIZE,object_chart_width);
         //--- Установим положение объекта-графика
         ObjectSetInteger(0,object_name,OBJPROP_YDISTANCE,0);
         ObjectSetInteger(0,object_name,OBJPROP_XDISTANCE,x_distance);
         //--- Установим новую координату по оси X для следующего объекта-графика
         x_distance+=object_chart_width;
        }
     }
  }
//---

Теперь нужно добавить вот такую строчку (выделена) в функцию OnChartEvent():

//+------------------------------------------------------------------+
//| ОБРАБОТЧИК ГРУППЫ СОБЫТИЙ                                        |
//+------------------------------------------------------------------+
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
Скачать индикатор ObjectsChart.mq5


4 комментария :

  1. VarangaOfficial - варанга цена киев - все, что бы хотели знать об этом препарате. Воспользовавшись данным ресурсом, вы получите возможность узнать всеисчерпывающую информацию касательно данного лекарственного средства. Увидеть данные о проведенных клинических тестированиях, прочесть отзывы реальных пациентов и медицинского персонала. Изучить инструкцию по применению, прочитать особенности и методы работы комплекса, уяснить, почему крем Варанга настолько эффективен, где можно приобрести оригинальный сертифицированный препарат и, как избежать покупки подделки. Мы очень тщательно проверяем публикуемые данные. Предоставляем пользователям нашего ресурса сведения, которые были почерпнуты исключительно из надежных источников. Если вы нашли у себя признаки развития грибка или уже довольно продолжительное время, без ощутимых результатов стараетесь избавиться от этого коварного недуга, у нас на сайте вы найдете быстрый и простой способ решения проблемы. Приобщайтесь и живите полноценной, здоровой жизнью. Все, что вы хотели знать, теперь можно найти на одном ресурсе.

    ОтветитьУдалить
  2. Sexvirtchat - это беспрецедентная услуга веб-секс-чата 18-плюс, которая позволит сделать вирт секс в интернете по-настоящему незабываемым, объединив самые интересные и тщательно отобранные порно-чаты из сегмента вирт онлайн. Вы хотите погрузиться в незабываемый виртуальный мир с помощью веб-камеры и надолго записать этот секс-чат в интернете? Кстати, даже будьте уверены, что впечатления после сексуального контакта с красотками на форуме наверняка станут самым ярким эротическим приключением раз и навсегда!, Вы давно пристрастились? Тогда не стесняйтесь, мгновенно выбирайте секс-видеочат на свой день и открывайтесь, чтобы реализовать свои греховные желания со всеми выбранными сексуальными, а не холодными незнакомцами. Она с радостью согласится на жесткий трах по вебке и покажет высший пилотаж в арбитраже!, Вирт чат всегда сможет поразить вас богатым ассортиментом и, несомненно, безупречным качеством работы и богатым выбором опытных девушек-моделей, которые знают, что называется качественной визуальной близостью не понаслышке и которые посещали эротический чат не так давно, но твердо. И как это останется иначе, когда этот интимный чат уже успел стать для нас домом на все время? Побывав однажды в таком потрясающем порно видео чате и увидев своими глазами в виде настоящего вирта по веб-камере от профессионала, вы, наверное, не сможете забыть это восхитительное место. Тем более, предыдущий контроль над любыми девушками онлайн здесь на 100% бесплатный. Чат порно контента не потребует скучной регистрации от клиента, хотя и имеет ряд преимуществ (например, компания без ограничений по времени для всех, кто прошел самую простую и бесплатную регистрацию). Также наш порно чат очень позабавит зрителя такими заманчивыми стимулами и функциями, как просмотр прямых видеотрансляций их частных выступлений в аккаунте и, несомненно, в воплощении сексуальных моделей. И такой эровидео чат страдает захватывающей функцией "подглядывания" инкогнито за всем, что модель показывает непосредственно в этот момент другому пользователю, пригласившему ресурс в приват. Но давайте не будем беспокоиться, если случится так, что вы лично не можете представить свою жизнь без виртсекса без лишних глаз - если случится так, что вы захотите повеселиться с каждой интересующей вас девушкой исключительно вместе, то за определенную плату определенно действительно можно будет перейти в полностью приватный режим - в этом случае за вашими виртуальными играми вообще никто не будет шпионить. У вашего интерьера будет уникальный шанс стать режиссером и сценаристом эротических зрелищ в реальном времени. Любой безумный каприз будет безоговорочно и хладнокровно исполнен, если вы захотите. Закажете ли вы своей личной секс-рабыне использовать эротические игрушки, интимные наряды, дополнительно пригласите ее подружек в эротический чат для семейного разврата в ее родном привате? Элементарно! Все сделано на самом высоком уровне. Так как вы обожали этот короткий материал и читали данные о порно чат бесплатно, Щедро посещая платформу.

    ОтветитьУдалить
  3. Вы сможете получить денежные средства наличными в express finance в день регистрации заявки всего за четверть часа, если только появится паспорт. Наличие дополнительного документа: 2ндфл, копии трудовой книжки, справки о деятельности по адресу запроса, посадочный талон, пропуск, служебный сертификат позволяет повысить доступную сумму. Взять минизайм до зарплаты, До выхода на пенсию - для любых нужд, у вас есть возможность в каждом из 25 филиалов организации в 21 регионе страны или отправив запрос на нашей платформе. Многие миллионы игроков уже доверили управление своими срочными финансовыми трудностями экспресс финансам. Мы предоставляем кредиты до ста тысяч рублей один раз. Перспектива немедленного принятия положительного решения по вызову увеличивается при дальнейших условиях: - Вы зарегистрированы как совершеннолетний житель российской федерации, вы зарегистрировались как продавец из нашей страны, - при проверке вы выбрали свои достоверные персональные данные, - ваш опыт работы в современном месте более 1 месяца, - вы погасили все обязательства по ранее выданным кредитам компании. Прямо сегодня воспользуйтесь возможностью оформить заказ онлайн через интернет и оформить кредит онлайн срочно без залога, без справок и поиска поручителей. По завершении пересмотра заказа вы получите необходимую сумму кредита наличными, если у вас есть паспорт.

    ОтветитьУдалить
  4. Если вам срочно нужны быстрые наличные на не большой срок, почему бы и нет? Я обращалась в фирму Кредит-финанс и довольна займ под залог авто в красноярске! Все рассказали без секретов, если не отдавать средства, конечно долг вырастет! Но это я думаю касается банков, но и любой организации предлагающей средства! Меня проконсультировали и обслужили быстро и воспитанно, есть процент разовый за помощь но это если помогу с получением кредита либо займа, если до получки денег нет, то это хороший выход! Я буду обращаться при необходимости!

    ОтветитьУдалить