English 中文 Deutsch 日本語 Português
preview
Готовые шаблоны для подключения индикаторов в экспертах (Часть 1): Осцилляторы

Готовые шаблоны для подключения индикаторов в экспертах (Часть 1): Осцилляторы

MetaTrader 5Примеры | 4 сентября 2023, 10:01
1 499 7
Artyom Trishkin
Artyom Trishkin

Содержание


Введение

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

Целью данной статьи является создание шаблонов для подключения индикаторов к советникам. Рассмотрим индикаторы из категории осцилляторов, их входные переменные, создание хэндла индикатора и получение от него требуемых данных. Для каждого индикатора напишем:

  1. пример входных параметров,
  2. инициализацию входных параметров и создание хэндла,
  3. деинициализацию,
  4. получение данных указанной индикаторной линии по указанному индексу таймсерии,
  5. контроль значений полученных данных на предмет состояния линии и её состояние относительно какого-либо уровня.

Под состоянием линии будем понимать её внешний вид, форму:

  1. Направление вверх (значение 2 меньше, либо равно значению 1, а значение 1 меньше значения 0),
  2. Направление вниз ( значение 2 больше, либо равно значению 1, а значение 1 больше значения 0 ),
  3. Разворот вверх (значение 2 больше значения 1, а значение 1 меньше значения 0),
  4. Разворот вниз (значение 2 меньше значения 1, а значение 1 больше значения 0),
  5. Остановка направления вверх (значение 2 меньше, либо равно значению 1, а значение 1 равно значению 0),
  6. Остановка направления вниз (значение 2 больше, либо равно значению 1, а значение 1 равно значению 0),
  7. Неопределённое состояние (не предусмотренные состояния)

Под состоянием линии относительно какого-либо уровня будем понимать:

  1. Над значением (значение линии больше значения уровня)
  2. Под значением (значение линии меньше значения уровня)
  3. Пересечение значения вверх (значение 1 меньше, либо равно значению уровня на баре 1, а значение 0 больше значения уровня на баре 0)
  4. Пересечение значения вниз (значение 1 больше, либо равно значению уровня на баре 1, а значение 0 меньше значения уровня на баре 0)
  5. Касание значения снизу (значение 1 меньше значения уровня на баре 1, а значение 0 равно значению уровня на баре 0)
  6. Касание значения сверху (значение 1 больше значения уровня на баре 1, а значение 0 равно значению уровня на баре 0)
  7. Равно значению (значения линии на баре 1 и 0 равны значению уровня на баре 1 и 0)

Таких условий вполне достаточно для определения состояний линии (её формы или фигуры на двух отрезках между 2-м, 1-м и нулевым баром), и для определения пересечений с другими линиями индикаторов, либо горизонтальных уровней.

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

Все примеры и коды, предложенные в статье, будут законченными блоками кода, и их можно будет использовать "как есть" в своих программах.


Average True Range

Технический индикатор Средний Истинный Диапазон (Average True Range, ATR) — это показатель волатильности рынка. Его ввел Уэллс Уайлдер в книге "Новые концепции технических торговых систем" и с тех пор индикатор применяется как составляющая многих других индикаторов и торговых систем.

Индикатор Average True Range часто достигает высоких значений в основаниях рынка после стремительного падения цен, вызванного паническими продажами. Низкие значения индикатора часто соответствуют продолжительным периодам горизонтального движения, которые наблюдаются на вершинах рынка и во время консолидации. Его можно интерпретировать по тем же правилам, что и другие индикаторы волатильности. Принцип прогнозирования с помощью Average True Range формулируется так: чем выше значение индикатора, тем выше вероятность смены тренда; чем ниже его значение, тем слабее направленность тренда.

Истинный диапазон (True Range) есть наибольшая из следующих трех величин:

  • разность между текущими максимумом и минимумом;
  • разность между предыдущей ценой закрытия и текущим максимумом;
  • разность между предыдущей ценой закрытия и текущим минимумом.

Индикатор Среднего Истинного Диапазона (Average True Range, ATR) представляет собой скользящее среднее значений истинного диапазона.


Параметры

Индикатор имеет всего один настраиваемый параметр: период сглаживания скользящей средней. По умолчанию имеет значение 14.

Условимся, что все входные параметры всех индикаторов будем прописывать настраиваемыми параметрами советника.

Создадим пустой шаблон советника:


Введём имя и входной параметр:


Из обработчиков выберем Таймер и Обработчик событий:



и нажмём кнопку "Готово". В итоге получим пустой шаблон эксперта:

//+------------------------------------------------------------------+
//|                                            TestOscillatorATR.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- input parameters
input uint     InpPeriod=14;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   
  }
//+------------------------------------------------------------------+

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

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

//--- input parameters
input uint  InpPeriod   =  14;   /* ATR Period  */ // Период расчёта ATR
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта ATR
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора


В предыдущей статье мы создали панель для использования её в индикаторах и советниках. Класс теперь немного доработан, и может создавать на панели любое количество табличек для расположения в них различных данных. Здесь описывать внесённые изменения не будем, но вернёмся к ним чуть позже — в последующих статьях вкратце опишем внесённые изменения и доработки.  Для тестирования экспертов из этой статьи, файл классов панели должен находиться в расположении \MQL5\Include\Dashboard\Dashboard.mqh. Файл с исходным кодом классов панели прикреплён к статье вместе с файлами тестовых советников.

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

#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- includes
#include <Dashboard\Dashboard.mqh>
//--- input parameters
input uint  InpPeriod   =  14;   /* ATR Period  */ // Период расчёта ATR
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта ATR
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора
//--- переменные для панели
int      mouse_bar_index;        // Индекс бара, с которого берутся данные
CDashboard *panel=NULL;          // Указатель на объект панели


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

#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- includes
#include <Dashboard\Dashboard.mqh>
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint  InpPeriod   =  14;   /* ATR Period  */ // Период расчёта ATR
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта ATR
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора
//--- переменные для панели
int      mouse_bar_index;        // Индекс бара, с которого берутся данные
CDashboard *panel=NULL;          // Указатель на объект панели

Далее нам необходимо проверить значения входных параметров и скорректировать их при необходимости.


Инициализация

Во всех индикаторах, если ввести в качестве значения периода ноль, то будет выставлено значение по умолчанию. Соответственно, точно такое же поведение необходимо сделать и в советнике — чтобы при установке нулевого значения в значении периода сглаживания, советник создавал индикатор, ничем не отличающийся от стандартного, которому тоже установили значение периода в ноль. У обоих индикаторов оно должно в этом случае стать значением по умолчанию. Кроме того, при вводе единички в значении периода, некоторые индикаторы не рассчитываются. Т.е. их минимальный период равен двум. В дополнение — у каждого индикатора количество цифр после запятой различно, и их нужно проверять и учитывать при работе с индикатором. Все такие нюансы необходимо учитывать. Во всех шаблонах, публикуемых в статье, учтены все нюансы, все проверки уже сделаны и корректно обрабатываются. Достаточно просто скопировать код из статьи в код своего советника и использовать.

Обработчик OnInit() советника с установкой значений переменных индикатора и созданием хэндла индикатора:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта
   period=int(InpPeriod<1 ? 14 : InpPeriod);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("ATR(%lu)",period);
   ind_digits=Digits();
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iATR(Symbol(),PERIOD_CURRENT,period);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта
   period=int(InpPeriod<1 ? 14 : InpPeriod);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("ATR(%lu)",period);
   ind_digits=Digits();
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iATR(Symbol(),PERIOD_CURRENT,period);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Панель
//--- Создаёи панель
   panel=new CDashboard(1,20,20,199,225);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Устанавливаем параметры шрифта
   panel.SetFontParams("Calibri",9);
//--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
//--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
   panel.CreateNewTable(0);
//--- Рисуем таблицу с идентификатором 0 на фоне панели
   panel.DrawGrid(0,2,20,6,2,18,97);

//--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
   panel.CreateNewTable(1);
//--- Получаем координату Y2 таблицы с идентификатором 0 и
//--- устанавливаем координату Y1 для таблицы с идентификатором 1
   int y1=panel.TableY2(0)+22;
//--- Рисуем таблицу с идентификатором 1 на фоне панели
   panel.DrawGrid(1,2,y1,3,2,18,97);
   
//--- Выводим в журнал табличные данные
   panel.GridPrint(0,2);
   panel.GridPrint(1,2);
//--- Инициализируем переменную с индексом бара указателя мышки
   mouse_bar_index=0;
//--- Выводим на панель данные текущего бара
   DrawData(mouse_bar_index,TimeCurrent());

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }

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


Деинициализация

В обработчике OnDeinit() советника нужно освободить хэндл созданного индикатора

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
  }

Если используется панель, то нужно дополнительно удалить созданный объект панели:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
   
//--- Если объект панели существует - удаляем его
   if(panel!=NULL)
      delete panel;
  }


Получение данных

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

Есть три способа получения данных:

Обращение по начальной позиции и количеству требуемых элементов:

int  CopyBuffer(
   int       indicator_handle,     // handle индикатора
   int       buffer_num,           // номер буфера индикатора
   int       start_pos,            // откуда начнем 
   int       count,                // сколько копируем
   double    buffer[]              // массив, куда будут скопированы данные
   );

Обращение по начальной дате и количеству требуемых элементов:

int  CopyBuffer(
   int       indicator_handle,     // handle индикатора
   int       buffer_num,           // номер буфера индикатора
   datetime  start_time,           // с какой даты
   int       count,                // сколько копируем
   double    buffer[]              // массив, куда будут скопированы данные
   );

Обращение по начальной и конечной датам требуемого интервала времени:

int  CopyBuffer(
   int       indicator_handle,     // handle индикатора
   int       buffer_num,           // номер буфера индикатора
   datetime  start_time,           // с какой даты
   datetime  stop_time,            // по какую дату
   double    buffer[]              // массив, куда будут скопированы данные
   );

Мы будем использовать первый вариант получения данных — по индексу бара.

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

//+------------------------------------------------------------------+
//| Возвращает данные индикатора на указанном баре                   |
//+------------------------------------------------------------------+
double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
  {
   double array[1]={0};
   ResetLastError();
   if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
     {
      PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }

Стоит отметить, что функция CopyBuffer() позволяет получить за раз не только одно значение с одного бара, а сразу указанный диапазон значений линии индикатора. Такая возможность здесь использоваться не будет, чтобы не усложнять контроль за состоянием линии индикатора, храня где-то в памяти полученный диапазон значений, а потом высчитывать из него необходимые значения по смещениям от указанного индекса. Здесь будет всё проще: нужно получить значение по указанному индексу — получаем его, нужно сравнить с другим значением — получаем другое значение, а затем полученные значения сравниваем. Для универсальности функции так проще и удобнее.

Функция, возвращающая состояние линии индикатора:

//+------------------------------------------------------------------+
//| Возвращает состояние линии индикатора                            |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
  {
//--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
   const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Разворот линии вверх (value2>value1 && value0>value1)
   if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Направление линии вверх (value2<=value1 && value0>value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_UP;
//--- Остановка направления линии вверх (value2<=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Разворот линии вниз (value2<value1 && value0<value1)
   if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Направление линии вниз (value2>=value1 && value0<value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_DOWN;
//--- Остановка направления линии вниз (value2>=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }

Здесь мы получаем значения индикаторной линии с трёх баров — с текущего и двух предыдущих. Для получения простой конфигурации фигуры линии этого достаточно. В функцию передаётся индекс самого правого бара из трёх, например с индексом 15. Для расчёта состояния линии в этом случае берутся бары 17, 16 и 15. Так как здесь мы работаем с вещественными числами, то для сравнения величин значений с каждого бара используем нормализованную разницу двух значений и сравниваем её с нулём.

Функция, возвращающая состояние линии индикатора относительно указанного уровня:

//+------------------------------------------------------------------+
//| Возвращает состояние линии относительно указанного уровня        |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Определяем второй сравниваемый уровень
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- Линия находится под уровнем (value1<level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_UNDER;
//--- Линия находится над уровнем (value1>level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_ABOVE;
//--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия равна значению уровня (value1==level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_EQUALS;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }

Функция проверяет соотношение двух значений линии индикатора на двух соседних барах с уровнем, переданным в параметрах. В параметрах указывается индекс правого бара из двух, с которых берутся значения индикаторной линии, и значение уровня, с которым нужно сравнить значения двух баров линии для выявления их соотношения.

Если нужно получить соотношение с горизонтальным уровнем, то его нужно передать в параметре level0. Второй параметр уровня level1 по умолчанию равен EMPTY_VALUE, что говорит о том, что оба значения данных индикаторной линии с двух соседних баров будут сравниваться только с одним значением, переданным в level0. (бар линии по index+1 сравнивается со значением level0, и бар по index сравнивается с level0).

Если же в level1 передано значение, отличное от EMPTY_VALUE, то каждый бар линии индикатора из двух сравнивается с двумя соответствующими значениями в level1 и level0 (бар линии по index+1 сравнивается со значением level1, а бар по index сравнивается с level0). Таким образом, линию индикатора можно сравнить с другой линией этого же, либо иного индикатора, для выявления их соотношений, в частности — пересечений.

Для вывода описаний выявленных состояний и соотношений индикаторной линии напишем функцию, возвращающую описание состояния линии индикатора:

//+------------------------------------------------------------------+
//| Возвращает описание состояния линии индикатора                   |
//+------------------------------------------------------------------+
string LineStateDescription(const ENUM_LINE_STATE state)
  {
   switch(state)
     {
      case LINE_STATE_UP         :  return "Up";
      case LINE_STATE_STOP_UP    :  return "Stop Up";
      case LINE_STATE_TURN_UP    :  return "Turn Up";
      case LINE_STATE_DOWN       :  return "Down";
      case LINE_STATE_STOP_DOWN  :  return "Stop Down";
      case LINE_STATE_TURN_DOWN  :  return "Turn Down";
      case LINE_STATE_ABOVE      :  return "Above level";
      case LINE_STATE_UNDER      :  return "Under level";
      case LINE_STATE_CROSS_UP   :  return "Crossing Up";
      case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
      case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
      case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
      case LINE_STATE_EQUALS     :  return "Equals";
      default                    :  return "Unknown";
     }
  }

Три вышерассмотренные функции могут использоваться "как есть" в советниках для получения данных от любых индикаторов и контроля их состояния и взаимного расположения.

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

//+------------------------------------------------------------------+
//| Выводит данные с указанного индекса таймсерии на панель          |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Объявляем переменные для получения в них данных
   MqlTick  tick={0};
   MqlRates rates[1];

//--- Если текущие цены получить не удалось - уходим
   if(!SymbolInfoTick(Symbol(),tick))
      return;
//--- Если данные бара по указанному индексу получить не удалось - уходим
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Устанавливаем параметры шрифта для данных бара и индикатора
   panel.SetFontParams(name,9);

//--- Выводим на панель данные указанного бара в таблицу 0
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Выводим на панель данные индикатора с указанного бара в таблицу 1
   panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value=IndicatorValue(handle,index,0);
   string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
   panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
   
//--- Выводим описание состояния линии индикатора
   panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   ENUM_LINE_STATE state=LineState(handle,index,0);
   panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,90);
   
//--- Перерисовываем график для немедленного отображения всех изменений на панели
   ChartRedraw(ChartID());
  }

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

Для работы панели, необходимо дописать код в обработчике событий OnChartEvent():

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Работа с панелью
//--- Вызываем обработчик событий панели
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- Если курсор перемещается или щелчок по графику
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Объявляем переменные для записи в них координат времени и цены
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- Если координаты курсора преобразованы в дату и время
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- записываем индекс бара, где расположен курсор в глобальную переменную
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Выводим данные бара под курсором на панель
         DrawData(mouse_bar_index,time);
        }
     }

//--- Если получили пользовательское событие - выводим об этом сообщение в журнал
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }

Логику обработчика мы уже рассматривали в прошлой статье. Здесь его используем как есть.

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



Файл тестового советника TestOscillatorATR.mq5 можно посмотреть в прикреплённых к статье файлах.


Bears Power

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

Задача оценки баланса сил "медведей" имеет большое значение, так как изменение этого баланса является одним из первых сигналов, позволяющим предугадать вероятную смену тенденции. Эта задача решается осциллятором Bears Power, который был разработан Александром Элдером и описан в его книге "Как играть и выигрывать на бирже". При его выводе Элдер использовал следующие посылки:

  • скользящее среднее является соглашением о цене между продавцами и покупателями в течение определенного промежутка времени,
  • минимальная цена отражает максимальную силу продавцов в течение дня.

Базируясь на этих посылках, Элдер разработал Bear Power как разницу между минимальной ценой и 13-периодной экспоненциальной скользящей средней (LOW - ЕМА).

Данный индикатор лучше всего использовать в совокупности с одним из трендовых индикаторов (чаще всего это скользящая средняя):

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



Параметры

Индикатор имеет один настраиваемый параметр — период расчёта. По умолчанию значение равно 13.

Внесём в код параметры и переменные для работы с индикатором:

//+------------------------------------------------------------------+
//|                                          TestOscillatorBears.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint  InpPeriod   =  13;   /* Bears Power Period   */ // Период расчёта Bears Power
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта Bears Power
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора

При использовании панели, подключим файл класса информационной панели и добавим переменные для работы с ней:

//+------------------------------------------------------------------+
//|                                          TestOscillatorBears.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- includes
#include <Dashboard\Dashboard.mqh>
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint  InpPeriod   =  13;   /* Bears Power Period   */ // Период расчёта Bears Power
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта Bears Power
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора
//--- переменные для панели
int      mouse_bar_index;        // Индекс бара, с которого берутся данные
CDashboard *panel=NULL;          // Указатель на объект панели


Инициализация

В обработчике OnInit() инициализируем и скорректируем входные параметры индикатора и создадим его хэндл:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта
   period=int(InpPeriod<1 ? 13 : InpPeriod);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("Bears(%lu)",period);
   ind_digits=Digits()+1;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iBearsPower(Symbol(),PERIOD_CURRENT,period);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }

При использовании в советнике информационной панели, создадим здесь же панель:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта
   period=int(InpPeriod<1 ? 13 : InpPeriod);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("Bears(%lu)",period);
   ind_digits=Digits()+1;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iBearsPower(Symbol(),PERIOD_CURRENT,period);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Панель
//--- Создаёи панель
   panel=new CDashboard(1,20,20,199,225);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Устанавливаем параметры шрифта
   panel.SetFontParams("Calibri",9);
//--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
//--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
   panel.CreateNewTable(0);
//--- Рисуем таблицу с идентификатором 0 на фоне панели
   panel.DrawGrid(0,2,20,6,2,18,97);

//--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
   panel.CreateNewTable(1);
//--- Получаем координату Y2 таблицы с идентификатором 0 и
//--- устанавливаем координату Y1 для таблицы с идентификатором 1
   int y1=panel.TableY2(0)+22;
//--- Рисуем таблицу с идентификатором 1 на фоне панели
   panel.DrawGrid(1,2,y1,3,2,18,97);
   
//--- Выводим в журнал табличные данные
   panel.GridPrint(0,2);
   panel.GridPrint(1,2);
//--- Инициализируем переменную с индексом бара указателя мышки
   mouse_bar_index=0;
//--- Выводим на панель данные текущего бара
   DrawData(mouse_bar_index,TimeCurrent());

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }


Деинициализация

В обработчике OnDeinit() советника освободим хэндл индикатора:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
  }

Если в советнике используется панель, то удалим созданный объект класса панели:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
   
//--- Если объект панели существует - удаляем его
   if(panel!=NULL)
      delete panel;
  }


Получение данных

Для получения данных от индикатора мы уже написали общие для всех индикаторов функции:

//+------------------------------------------------------------------+
//| Возвращает данные индикатора на указанном баре                   |
//+------------------------------------------------------------------+
double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
  {
   double array[1]={0};
   ResetLastError();
   if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
     {
      PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии индикатора                            |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
  {
//--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
   const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Разворот линии вверх (value2>value1 && value0>value1)
   if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Направление линии вверх (value2<=value1 && value0>value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_UP;
//--- Остановка направления линии вверх (value2<=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Разворот линии вниз (value2<value1 && value0<value1)
   if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Направление линии вниз (value2>=value1 && value0<value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_DOWN;
//--- Остановка направления линии вниз (value2>=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии относительно указанного уровня        |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Определяем второй сравниваемый уровень
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- Линия находится под уровнем (value1<level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_UNDER;
//--- Линия находится над уровнем (value1>level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_ABOVE;
//--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия равна значению уровня (value1==level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_EQUALS;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает описание состояния линии индикатора                   |
//+------------------------------------------------------------------+
string LineStateDescription(const ENUM_LINE_STATE state)
  {
   switch(state)
     {
      case LINE_STATE_UP         :  return "Up";
      case LINE_STATE_STOP_UP    :  return "Stop Up";
      case LINE_STATE_TURN_UP    :  return "Turn Up";
      case LINE_STATE_DOWN       :  return "Down";
      case LINE_STATE_STOP_DOWN  :  return "Stop Down";
      case LINE_STATE_TURN_DOWN  :  return "Turn Down";
      case LINE_STATE_ABOVE      :  return "Above level";
      case LINE_STATE_UNDER      :  return "Under level";
      case LINE_STATE_CROSS_UP   :  return "Crossing Up";
      case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
      case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
      case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
      case LINE_STATE_EQUALS     :  return "Equals";
      default                    :  return "Unknown";
     }
  }

В каждом примере использования индикаторов в этой статье будем их использовать без каких-либо изменений.

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

//+------------------------------------------------------------------+
//| Выводит данные с указанного индекса таймсерии на панель          |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Объявляем переменные для получения в них данных
   MqlTick  tick={0};
   MqlRates rates[1];

//--- Если текущие цены получить не удалось - уходим
   if(!SymbolInfoTick(Symbol(),tick))
      return;
//--- Если данные бара по указанному индексу получить не удалось - уходим
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Устанавливаем параметры шрифта для данных бара и индикатора
   panel.SetFontParams(name,9);

//--- Выводим на панель данные указанного бара в таблицу 0
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Выводим на панель данные индикатора с указанного бара в таблицу 1
   panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value=IndicatorValue(handle,index,0);
   string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
   panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
   
//--- Выводим описание состояния линии индикатора
   panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   ENUM_LINE_STATE state=LineState(handle,index,0);
//--- Цвет надписи меняется в зависимости от значения линии выше/ниже нуля
   color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE);
   panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90);
   
//--- Перерисовываем график для немедленного отображения всех изменений на панели
   ChartRedraw(ChartID());
  }

Так как в индикаторе сигналы зависят от нахождения линии относительно нуля, то будем выделять цветом положительные и отрицательные значения индикатора на панели.

В обработчике событий OnChartEvent() пропишем код для работы с событиями панели и для указания номера бара, над которым находится курсор:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Работа с панелью
//--- Вызываем обработчик событий панели
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- Если курсор перемещается или щелчок по графику
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Объявляем переменные для записи в них координат времени и цены
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- Если координаты курсора преобразованы в дату и время
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- записываем индекс бара, где расположен курсор в глобальную переменную
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Выводим данные бара под курсором на панель
         DrawData(mouse_bar_index,time);
        }
     }

//--- Если получили пользовательское событие - выводим об этом сообщение в журнал
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }


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



Файл советника "TestOscillatorBears.mq5" можно посмотреть в прикреплённых к статье файлах.


Bulls Power

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

Задача оценки баланса сил "быков" имеет большое значение, так как изменение этого баланса является одним из первых сигналов, позволяющим предугадать вероятную смену тенденции. Эта задача решается осциллятором Bulls Power, который был разработан Александром Элдером и описан в его книге "Как играть и выигрывать на бирже". При его выводе Элдер использовал следующие посылки:

  • скользящее среднее является соглашением о цене между продавцами и покупателями в течение определенного промежутка времени,
  • максимальная цена отражает максимальную силу покупателей в течение дня.

Базируясь на этих посылках, Элдер разработал Bulls Power как разницу между максимальной ценой и 13-периодной экспоненциальной скользящей средней (HIGH - ЕМА).

Данный индикатор лучше всего использовать в совокупности с одним из трендовых индикаторов (чаще всего это скользящая средняя):

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



Параметры

Индикатор имеет один настраиваемый параметр — период сглаживания. По умолчанию равен 13.

Список входных и глобальных переменных для использования в советнике:

//+------------------------------------------------------------------+
//|                                          TestOscillatorBulls.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint  InpPeriod   =  13;   /* Bulls Power Period   */ // Период расчёта Bulls Power
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта Bulls Power
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора

При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

//+------------------------------------------------------------------+
//|                                          TestOscillatorBulls.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- includes
#include <Dashboard\Dashboard.mqh>
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint  InpPeriod   =  13;   /* Bulls Power Period   */ // Период расчёта Bulls Power
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта Bulls Power
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора
//--- переменные для панели
int      mouse_bar_index;        // Индекс бара, с которого берутся данные
CDashboard *panel=NULL;          // Указатель на объект панели


Инициализация

Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта
   period=int(InpPeriod<1 ? 13 : InpPeriod);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("Bulls(%lu)",period);
   ind_digits=Digits()+1;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iBullsPower(Symbol(),PERIOD_CURRENT,period);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }

При использовании информационной панели, создаётся панель:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта
   period=int(InpPeriod<1 ? 13 : InpPeriod);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("Bulls(%lu)",period);
   ind_digits=Digits()+1;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iBullsPower(Symbol(),PERIOD_CURRENT,period);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Панель
//--- Создаёи панель
   panel=new CDashboard(1,20,20,199,225);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Устанавливаем параметры шрифта
   panel.SetFontParams("Calibri",9);
//--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
//--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
   panel.CreateNewTable(0);
//--- Рисуем таблицу с идентификатором 0 на фоне панели
   panel.DrawGrid(0,2,20,6,2,18,97);

//--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
   panel.CreateNewTable(1);
//--- Получаем координату Y2 таблицы с идентификатором 0 и
//--- устанавливаем координату Y1 для таблицы с идентификатором 1
   int y1=panel.TableY2(0)+22;
//--- Рисуем таблицу с идентификатором 1 на фоне панели
   panel.DrawGrid(1,2,y1,3,2,18,97);
   
//--- Выводим в журнал табличные данные
   panel.GridPrint(0,2);
   panel.GridPrint(1,2);
//--- Инициализируем переменную с индексом бара указателя мышки
   mouse_bar_index=0;
//--- Выводим на панель данные текущего бара
   DrawData(mouse_bar_index,TimeCurrent());

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }


Деинициализация

В обработчике OnDeinit() советника освобождается хэндл индикатора:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
  }

При использовании информационной панели — удаляется созданный объект панели:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
   
//--- Если объект панели существует - удаляем его
   if(panel!=NULL)
      delete panel;
  }


Получение данных

Общие функции получения данных по хэндлу индикатора:

//+------------------------------------------------------------------+
//| Возвращает данные индикатора на указанном баре                   |
//+------------------------------------------------------------------+
double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
  {
   double array[1]={0};
   ResetLastError();
   if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
     {
      PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии индикатора                            |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
  {
//--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
   const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Разворот линии вверх (value2>value1 && value0>value1)
   if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Направление линии вверх (value2<=value1 && value0>value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_UP;
//--- Остановка направления линии вверх (value2<=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Разворот линии вниз (value2<value1 && value0<value1)
   if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Направление линии вниз (value2>=value1 && value0<value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_DOWN;
//--- Остановка направления линии вниз (value2>=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии относительно указанного уровня        |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Определяем второй сравниваемый уровень
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- Линия находится под уровнем (value1<level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_UNDER;
//--- Линия находится над уровнем (value1>level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_ABOVE;
//--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия равна значению уровня (value1==level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_EQUALS;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает описание состояния линии индикатора                   |
//+------------------------------------------------------------------+
string LineStateDescription(const ENUM_LINE_STATE state)
  {
   switch(state)
     {
      case LINE_STATE_UP         :  return "Up";
      case LINE_STATE_STOP_UP    :  return "Stop Up";
      case LINE_STATE_TURN_UP    :  return "Turn Up";
      case LINE_STATE_DOWN       :  return "Down";
      case LINE_STATE_STOP_DOWN  :  return "Stop Down";
      case LINE_STATE_TURN_DOWN  :  return "Turn Down";
      case LINE_STATE_ABOVE      :  return "Above level";
      case LINE_STATE_UNDER      :  return "Under level";
      case LINE_STATE_CROSS_UP   :  return "Crossing Up";
      case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
      case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
      case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
      case LINE_STATE_EQUALS     :  return "Equals";
      default                    :  return "Unknown";
     }
  }

При использовании информационной панели, данные выводятся на панель при помощи функции:

//+------------------------------------------------------------------+
//| Выводит данные с указанного индекса таймсерии на панель          |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Объявляем переменные для получения в них данных
   MqlTick  tick={0};
   MqlRates rates[1];

//--- Если текущие цены получить не удалось - уходим
   if(!SymbolInfoTick(Symbol(),tick))
      return;
//--- Если данные бара по указанному индексу получить не удалось - уходим
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Устанавливаем параметры шрифта для данных бара и индикатора
   panel.SetFontParams(name,9);

//--- Выводим на панель данные указанного бара в таблицу 0
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Выводим на панель данные индикатора с указанного бара в таблицу 1
   panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value=IndicatorValue(handle,index,0);
   string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
   panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
   
//--- Выводим описание состояния линии индикатора
   panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   ENUM_LINE_STATE state=LineState(handle,index,0);
//--- Цвет надписи меняется в зависимости от значения линии выше/ниже нуля
   color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE);
   panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90);
   
//--- Перерисовываем график для немедленного отображения всех изменений на панели
   ChartRedraw(ChartID());
  }

Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Работа с панелью
//--- Вызываем обработчик событий панели
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- Если курсор перемещается или щелчок по графику
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Объявляем переменные для записи в них координат времени и цены
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- Если координаты курсора преобразованы в дату и время
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- записываем индекс бара, где расположен курсор в глобальную переменную
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Выводим данные бара под курсором на панель
         DrawData(mouse_bar_index,time);
        }
     }

//--- Если получили пользовательское событие - выводим об этом сообщение в журнал
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }


После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



Файл советника "TestOscillatorBulls.mq5" можно посмотреть в прикреплённых к статье файлах.


Chaikin Oscillator

Осциллятор Чайкина (Chaikin Oscillator, CHO) представляет собой разность скользящих средних индикатора Accumulation/Distribution.

"Концепция этого осциллятора опирается на три основных положения. Первое: если акция или индекс закрываются выше своего среднего значения за день (которое определяется как [максимум + минимум] / 2) — значит в этот день происходило накопление. Чем ближе уровень закрытия акции или индекса к максимуму, тем активнее накопление. И наоборот, если акция закрывается ниже средней цены дня — то в этот день происходило распределение. Чем ближе к минимуму закрывается акция, тем активнее распределение.

Второе положение: устойчивый рост цен сопровождается ростом объема торгов и сильным накоплением объема. Поскольку объем — это своего рода топливо, питающее рост рынка, то отставание объема при росте цен свидетельствует о недостатке топлива для продолжения подъема.

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

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

Поскольку не существует безошибочно действующих методов технического анализа, рекомендуется применять этот осциллятор совместно с другими техническими индикаторами. Надежность краткосрочных и среднесрочных торговых сигналов будет выше, если вместе с осциллятором Чайкина использовать, например, Envelopes на основе 21 дневного скользящего среднего и какой-либо осциллятор перекупленности/перепроданности.

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

  • Сигналы в направлении среднесрочной тенденции надежнее сигналов против тенденции.
  • Подтверждение осциллятором нового максимума или минимума не означает, что цены продолжат движение в том же направлении. Данное событие расценивается как незначащее.

Существует и другой способ использования осциллятора Чайкина, при котором изменение его направления считается сигналом к покупке или продаже, но только если оно совпадает с направлением ценовой тенденции. Так, если акция на подъеме и ее цена выше 90-дневного скользящего среднего, то поворот кривой осциллятора вверх в области отрицательных значений можно считать сигналом к покупке (но только если цена акции выше 90-дневного скользящего среднего — не ниже него).

Разворот осциллятора вниз в области положительных значений (выше нуля) можно считать сигналом к продаже, только если цена акции в этот момент ниже 90-дневного скользящего среднего цен закрытия."



Параметры

Индикатор имеет четыре настраиваемых параметра:

  • Используемые Объёмы, по умолчанию — тиковые,
  • Период расчёта быстрой МА, по умолчанию — 3,
  • Период расчёта медленной МА, по умолчанию — 10,
  • Метод расчёта — по умолчанию EMA.

Входные и глобальные переменные для использования индикатора в советнике:

//+------------------------------------------------------------------+
//|                                            TestOscillatorCHO.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint                 InpPeriodFast  =  3;             /* CHO Fast MA Period   */ // Период расчёта быстрого скользящего среднего
input uint                 InpPeriodSlow  =  10;            /* CHO Slow MA Period   */ // Период расчёта медленного скользящего среднего
input ENUM_MA_METHOD       InpMethod      =  MODE_EMA;      /* Method */               // Метод расчёта
input ENUM_APPLIED_VOLUME  InpAppliedVol  =  VOLUME_TICK;   /* Applied Volume       */ // Объемы
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period_fast=0;          // Период расчёта быстрого скользящего среднего
int      period_slow=0;          // Период расчёта медленного скользящего среднего
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора

При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

//+------------------------------------------------------------------+
//|                                            TestOscillatorCHO.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- includes
#include <Dashboard\Dashboard.mqh>
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint                 InpPeriodFast  =  3;             /* CHO Fast MA Period   */ // Период расчёта быстрого скользящего среднего
input uint                 InpPeriodSlow  =  10;            /* CHO Slow MA Period   */ // Период расчёта медленного скользящего среднего
input ENUM_MA_METHOD       InpMethod      =  MODE_EMA;      /* Method */               // Метод расчёта
input ENUM_APPLIED_VOLUME  InpAppliedVol  =  VOLUME_TICK;   /* Applied Volume       */ // Объемы
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period_fast=0;          // Период расчёта быстрого скользящего среднего
int      period_slow=0;          // Период расчёта медленного скользящего среднего
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора
//--- переменные для панели
int      mouse_bar_index;        // Индекс бара, с которого берутся данные
CDashboard *panel=NULL;          // Указатель на объект панели


Инициализация

Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта
   period_fast=int(InpPeriodFast<1 ? 3  : InpPeriodFast);
   period_slow=int(InpPeriodSlow<1 ? 10 : InpPeriodSlow);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("CHO(%lu,%lu)",period_slow,period_fast);
   ind_digits=0;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iChaikin(Symbol(),PERIOD_CURRENT,period_fast,period_slow,InpMethod,InpAppliedVol);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }

При использовании информационной панели, создаётся панель:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта
   period_fast=int(InpPeriodFast<1 ? 3  : InpPeriodFast);
   period_slow=int(InpPeriodSlow<1 ? 10 : InpPeriodSlow);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("CHO(%lu,%lu)",period_slow,period_fast);
   ind_digits=0;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iChaikin(Symbol(),PERIOD_CURRENT,period_fast,period_slow,InpMethod,InpAppliedVol);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Панель
//--- Создаёи панель
   panel=new CDashboard(1,20,20,199,225);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Устанавливаем параметры шрифта
   panel.SetFontParams("Calibri",9);
//--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
//--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
   panel.CreateNewTable(0);
//--- Рисуем таблицу с идентификатором 0 на фоне панели
   panel.DrawGrid(0,2,20,6,2,18,97);

//--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
   panel.CreateNewTable(1);
//--- Получаем координату Y2 таблицы с идентификатором 0 и
//--- устанавливаем координату Y1 для таблицы с идентификатором 1
   int y1=panel.TableY2(0)+22;
//--- Рисуем таблицу с идентификатором 1 на фоне панели
   panel.DrawGrid(1,2,y1,3,2,18,97);
   
//--- Выводим в журнал табличные данные
   panel.GridPrint(0,2);
   panel.GridPrint(1,2);
//--- Инициализируем переменную с индексом бара указателя мышки
   mouse_bar_index=0;
//--- Выводим на панель данные текущего бара
   DrawData(mouse_bar_index,TimeCurrent());

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }


Деинициализация

В обработчике OnDeinit() советника освобождается хэндл индикатора:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
  }

При использовании информационной панели — удаляется созданный объект панели:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
   
//--- Если объект панели существует - удаляем его
   if(panel!=NULL)
      delete panel;
  }

Получение данных

Общие функции получения данных по хэндлу индикатора:

//+------------------------------------------------------------------+
//| Возвращает данные индикатора на указанном баре                   |
//+------------------------------------------------------------------+
double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
  {
   double array[1]={0};
   ResetLastError();
   if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
     {
      PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии индикатора                            |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
  {
//--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
   const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Разворот линии вверх (value2>value1 && value0>value1)
   if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Направление линии вверх (value2<=value1 && value0>value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_UP;
//--- Остановка направления линии вверх (value2<=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Разворот линии вниз (value2<value1 && value0<value1)
   if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Направление линии вниз (value2>=value1 && value0<value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_DOWN;
//--- Остановка направления линии вниз (value2>=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии относительно указанного уровня        |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Определяем второй сравниваемый уровень
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- Линия находится под уровнем (value1<level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_UNDER;
//--- Линия находится над уровнем (value1>level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_ABOVE;
//--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия равна значению уровня (value1==level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_EQUALS;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает описание состояния линии индикатора                   |
//+------------------------------------------------------------------+
string LineStateDescription(const ENUM_LINE_STATE state)
  {
   switch(state)
     {
      case LINE_STATE_UP         :  return "Up";
      case LINE_STATE_STOP_UP    :  return "Stop Up";
      case LINE_STATE_TURN_UP    :  return "Turn Up";
      case LINE_STATE_DOWN       :  return "Down";
      case LINE_STATE_STOP_DOWN  :  return "Stop Down";
      case LINE_STATE_TURN_DOWN  :  return "Turn Down";
      case LINE_STATE_ABOVE      :  return "Above level";
      case LINE_STATE_UNDER      :  return "Under level";
      case LINE_STATE_CROSS_UP   :  return "Crossing Up";
      case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
      case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
      case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
      case LINE_STATE_EQUALS     :  return "Equals";
      default                    :  return "Unknown";
     }
  }

При использовании информационной панели, данные выводятся на панель при помощи функции:

//+------------------------------------------------------------------+
//| Выводит данные с указанного индекса таймсерии на панель          |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Объявляем переменные для получения в них данных
   MqlTick  tick={0};
   MqlRates rates[1];

//--- Если текущие цены получить не удалось - уходим
   if(!SymbolInfoTick(Symbol(),tick))
      return;
//--- Если данные бара по указанному индексу получить не удалось - уходим
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Устанавливаем параметры шрифта для данных бара и индикатора
   panel.SetFontParams(name,9);

//--- Выводим на панель данные указанного бара в таблицу 0
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Выводим на панель данные индикатора с указанного бара в таблицу 1
   panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value=IndicatorValue(handle,index,0);
   string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
   panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
   
//--- Выводим описание состояния линии индикатора
   panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   ENUM_LINE_STATE state=LineState(handle,index,0);
   color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE); // Цвет надписи меняется в зависимости от значения линии выше/ниже нуля
   panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90);
   
//--- Перерисовываем график для немедленного отображения всех изменений на панели
   ChartRedraw(ChartID());
  }

Значения линии индикатора выше/ниже нуля помечаются цветом на панели.

Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Работа с панелью
//--- Вызываем обработчик событий панели
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- Если курсор перемещается или щелчок по графику
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Объявляем переменные для записи в них координат времени и цены
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- Если координаты курсора преобразованы в дату и время
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- записываем индекс бара, где расположен курсор в глобальную переменную
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Выводим данные бара под курсором на панель
         DrawData(mouse_bar_index,time);
        }
     }

//--- Если получили пользовательское событие - выводим об этом сообщение в журнал
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }


После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:


Файл советника "TestOscillatorCHO.mq5" можно посмотреть в прикреплённых к статье файлах.


Commodity Channel Index

Технический индикатор Индекс Товарного Канала (Commodity Channel Index, CCI) измеряет отклонение цены инструмента от его среднестатистической цены. Высокие значения индекса указывают на то, что цена необычно высока по сравнению со средней, а низкие — что она слишком занижена. Несмотря на название, Commodity Channel Index применим к любому финансовому инструменту, а не только к товарам.

Существует два основных способа использования Commodity Channel Index:

  1. Для поиска расхождений
    Расхождение образуется, когда цена достигает нового максимума, а Commodity Channel Index не удается подняться выше предыдущих максимумов. За этим классическим расхождением обычно следует ценовая коррекция.
  2. В качестве индикатора перекупленности/перепроданности
    Индекс Товарного Канала обычно колеблется в диапазоне ±100. Значения выше +100 говорят о состоянии перекупленности (и вероятности корректирующего спада), а значения ниже -100 — о состоянии перепроданности (и вероятности корректирующего подъема).



Параметры

Индикатор имеет два настраиваемых параметра:

  • Период расчёта, по умолчанию — 14
  • Цена расчёта, по умолчанию  — Typical Price (HLC/3)

Помимо стандартных входных параметров индикатора, для поиска его сигналов могут использоваться уровни перекупленности и перепроданности. В советнике их тоже нужно указать во входных параметрах.

Входные и глобальные переменные для использования индикатора в советнике:

//+------------------------------------------------------------------+
//|                                            TestOscillatorCCI.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint                 InpPeriod   =  14;            /* CCI Period     */ // Период расчёта CCI
input ENUM_APPLIED_PRICE   InpPrice    =  PRICE_TYPICAL; /* Applied Price  */ // Цена расчёта
input double               InpOverbough=  100.0;         /* Overbough level*/ // Уровень перекупленности
input double               InpOversold = -100.0;         /* Oversold level */ // Уровень перепроданности
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта CCI
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
double   overbough=0;            // Уровень перекупленности
double   oversold=0;             // Уровень перепроданности
string   ind_title;              // Описание индикатора

При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

//+------------------------------------------------------------------+
//|                                            TestOscillatorCCI.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- includes
#include <Dashboard\Dashboard.mqh>
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint                 InpPeriod   =  14;            /* CCI Period     */ // Период расчёта CCI
input ENUM_APPLIED_PRICE   InpPrice    =  PRICE_TYPICAL; /* Applied Price  */ // Цена расчёта
input double               InpOverbough=  100.0;         /* Overbough level*/ // Уровень перекупленности
input double               InpOversold = -100.0;         /* Oversold level */ // Уровень перепроданности
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта CCI
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
double   overbough=0;            // Уровень перекупленности
double   oversold=0;             // Уровень перепроданности
string   ind_title;              // Описание индикатора
//--- переменные для панели
int      mouse_bar_index;        // Индекс бара, с которого берутся данные
CDashboard *panel=NULL;          // Указатель на объект панели


Инициализация

Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта и уровни
   period=int(InpPeriod<1 ? 14  : InpPeriod<2 ? 2 : InpPeriod);
   overbough=InpOverbough;
   oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("CCI(%lu)",period);
   ind_digits=2;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iCCI(Symbol(),PERIOD_CURRENT,period,InpPrice);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }

При использовании информационной панели, создаётся панель:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта и уровни
   period=int(InpPeriod<1 ? 14  : InpPeriod<2 ? 2 : InpPeriod);
   overbough=InpOverbough;
   oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("CCI(%lu)",period);
   ind_digits=2;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iCCI(Symbol(),PERIOD_CURRENT,period,InpPrice);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Панель
//--- Создаёи панель
   panel=new CDashboard(1,20,20,229,243);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Устанавливаем параметры шрифта
   panel.SetFontParams("Calibri",9);
//--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
//--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
   panel.CreateNewTable(0);
//--- Рисуем таблицу с идентификатором 0 на фоне панели
   panel.DrawGrid(0,2,20,6,2,18,112);

//--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
   panel.CreateNewTable(1);
//--- Получаем координату Y2 таблицы с идентификатором 0 и
//--- устанавливаем координату Y1 для таблицы с идентификатором 1
   int y1=panel.TableY2(0)+22;
//--- Рисуем таблицу с идентификатором 1 на фоне панели
   panel.DrawGrid(1,2,y1,4,2,18,112);
   
//--- Выводим в журнал табличные данные
   panel.GridPrint(0,2);
   panel.GridPrint(1,2);
//--- Инициализируем переменную с индексом бара указателя мышки
   mouse_bar_index=0;
//--- Выводим на панель данные текущего бара
   DrawData(mouse_bar_index,TimeCurrent());

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }


Деинициализация

В обработчике OnDeinit() советника освобождается хэндл индикатора:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
  }

При использовании информационной панели — удаляется созданный объект панели:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
   
//--- Если объект панели существует - удаляем его
   if(panel!=NULL)
      delete panel;
  }


Получение данных

Общие функции получения данных по хэндлу индикатора:

//+------------------------------------------------------------------+
//| Возвращает данные индикатора на указанном баре                   |
//+------------------------------------------------------------------+
double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
  {
   double array[1]={0};
   ResetLastError();
   if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
     {
      PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии индикатора                            |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
  {
//--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
   const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Разворот линии вверх (value2>value1 && value0>value1)
   if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Направление линии вверх (value2<=value1 && value0>value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_UP;
//--- Остановка направления линии вверх (value2<=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Разворот линии вниз (value2<value1 && value0<value1)
   if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Направление линии вниз (value2>=value1 && value0<value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_DOWN;
//--- Остановка направления линии вниз (value2>=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии относительно указанного уровня        |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Определяем второй сравниваемый уровень
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- Линия находится под уровнем (value1<level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_UNDER;
//--- Линия находится над уровнем (value1>level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_ABOVE;
//--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия равна значению уровня (value1==level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_EQUALS;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает описание состояния линии индикатора                   |
//+------------------------------------------------------------------+
string LineStateDescription(const ENUM_LINE_STATE state)
  {
   switch(state)
     {
      case LINE_STATE_UP         :  return "Up";
      case LINE_STATE_STOP_UP    :  return "Stop Up";
      case LINE_STATE_TURN_UP    :  return "Turn Up";
      case LINE_STATE_DOWN       :  return "Down";
      case LINE_STATE_STOP_DOWN  :  return "Stop Down";
      case LINE_STATE_TURN_DOWN  :  return "Turn Down";
      case LINE_STATE_ABOVE      :  return "Above level";
      case LINE_STATE_UNDER      :  return "Under level";
      case LINE_STATE_CROSS_UP   :  return "Crossing Up";
      case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
      case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
      case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
      case LINE_STATE_EQUALS     :  return "Equals";
      default                    :  return "Unknown";
     }
  }

При использовании информационной панели, данные выводятся на панель при помощи функции:

//+------------------------------------------------------------------+
//| Выводит данные с указанного индекса таймсерии на панель          |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Объявляем переменные для получения в них данных
   MqlTick  tick={0};
   MqlRates rates[1];

//--- Если текущие цены получить не удалось - уходим
   if(!SymbolInfoTick(Symbol(),tick))
      return;
//--- Если данные бара по указанному индексу получить не удалось - уходим
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Устанавливаем параметры шрифта для данных бара и индикатора
   panel.SetFontParams(name,9);

//--- Выводим на панель данные указанного бара в таблицу 0
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Выводим на панель данные индикатора с указанного бара в таблицу 1
   panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value=IndicatorValue(handle,index,0);
   string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
   panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
   
//--- Выводим описание состояния линии индикатора относительно уровня перекупленности
   string ovb=StringFormat("%+.2f",overbough);
   panel.DrawText("Overbough", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
   panel.DrawText(ovb, panel.CellX(1,2,0)+66, panel.CellY(1,2,0)+2);
   ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough);
//--- Цвет надписи меняется в зависимости от значения линии относительно уровня
   color clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
   string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb));
   panel.DrawText(ovb_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90);
   
//--- Выводим описание состояния линии индикатора относительно уровня перепроданности
   panel.DrawText("Oversold", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2);
   string ovs=StringFormat("%+.2f",oversold);
   panel.DrawText(ovs, panel.CellX(1,3,0)+68, panel.CellY(1,3,0)+2);
   ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold);
//--- Цвет надписи меняется в зависимости от значения линии относительно уровня
   clr=(state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE);
   string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs));
   panel.DrawText(ovs_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,90);
   
//--- Выводим описание состояния линии индикатора
   panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   ENUM_LINE_STATE state=LineState(handle,index,0);
//--- Цвет надписи меняется в зависимости от нахождения линии в областях перекупленности/перепроданности
   clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE);
   panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90);
   
//--- Перерисовываем график для немедленного отображения всех изменений на панели
   ChartRedraw(ChartID());
  }

Нахождение линии индикатора в областях перекупленности и перепроданности, а также сигналы в этих областях, помечаются цветом на панели.

Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Работа с панелью
//--- Вызываем обработчик событий панели
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- Если курсор перемещается или щелчок по графику
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Объявляем переменные для записи в них координат времени и цены
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- Если координаты курсора преобразованы в дату и время
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- записываем индекс бара, где расположен курсор в глобальную переменную
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Выводим данные бара под курсором на панель
         DrawData(mouse_bar_index,time);
        }
     }

//--- Если получили пользовательское событие - выводим об этом сообщение в журнал
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }


После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



Файл советника "TestOscillatorCCI.mq5" можно посмотреть в прикреплённых к статье файлах.


DeMarker

Технический индикатор Демарка (DeMarker, DeM) строится на основе сопоставлений максимума текущего бара, сравнивается с максимумом предыдущего. Если максимум текущего бара выше, то регистрируется соответствующая разность. Если текущий максимум меньше или равен максимуму предыдущего бара, то регистрируется нулевое значение. Затем полученные таким образом разности за n периодов суммируются. Полученное значение становится числителем индикатора DeMarker и делится на ту же самую величину плюс сумма разностей между ценовыми минимумами предшествующего и текущего баров. Если текущий ценовой минимум больше того, который был на предыдущем баре, то фиксируется нулевое значение.

Когда показания индикатора DeMarker опускаются ниже отметки 30, то ожидается разворот цен вверх. Когда показания индикатора поднимаются выше отметки 70, то ожидается разворот цен вниз.

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


Параметры

Индикатор имеет один настраиваемый параметр: период расчёта. По умолчанию равен 14.

Кроме того, так как для поиска сигналов индикатора используются области перекупленности/перепроданности, то их тоже необходимо вписать во входные параметры советника.

Входные и глобальные переменные для использования индикатора в советнике:

//+------------------------------------------------------------------+
//|                                            TestOscillatorDeM.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint                 InpPeriod   =  14;   /* Period         */ // Период расчёта DeMarker
input double               InpOverbough=  0.7;  /* Overbough level*/ // Уровень перекупленности
input double               InpOversold =  0.3;  /* Oversold level */ // Уровень перепроданности
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта DeMarker
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
double   overbough=0;            // Уровень перекупленности
double   oversold=0;             // Уровень перепроданности
string   ind_title;              // Описание индикатора

При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

//+------------------------------------------------------------------+
//|                                            TestOscillatorDeM.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- includes
#include <Dashboard\Dashboard.mqh>
//--- enums
enum ENUM_LINE_STATE
  {
   LINE_STATE_NONE,        // Неопределённое состояние
   LINE_STATE_UP,          // Направление вверх
   LINE_STATE_DOWN,        // Направление вниз
   LINE_STATE_TURN_UP,     // Разворот вверх
   LINE_STATE_TURN_DOWN,   // Разворот вниз
   LINE_STATE_STOP_UP,     // Остановка направления вверх
   LINE_STATE_STOP_DOWN,   // Остановка направления вниз
   LINE_STATE_ABOVE,       // Над значением
   LINE_STATE_UNDER,       // Под значением
   LINE_STATE_CROSS_UP,    // Пересечение значения вверх
   LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
   LINE_STATE_TOUCH_BELOW, // Касание значения снизу
   LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
   LINE_STATE_EQUALS,      // Равно значению
  };
//--- input parameters
input uint                 InpPeriod   =  14;   /* Period         */ // Период расчёта DeMarker
input double               InpOverbough=  0.7;  /* Overbough level*/ // Уровень перекупленности
input double               InpOversold =  0.3;  /* Oversold level */ // Уровень перепроданности
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      period=0;               // Период расчёта DeMarker
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
double   overbough=0;            // Уровень перекупленности
double   oversold=0;             // Уровень перепроданности
string   ind_title;              // Описание индикатора
//--- переменные для панели
int      mouse_bar_index;        // Индекс бара, с которого берутся данные
CDashboard *panel=NULL;          // Указатель на объект панели


Инициализация

Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта и уровни
   period=int(InpPeriod<1 ? 14  : InpPeriod);
   overbough=InpOverbough;
   oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("DeM(%lu)",period);
   ind_digits=3;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iDeMarker(Symbol(),PERIOD_CURRENT,period);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }

При использовании информационной панели, создаётся панель:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetTimer(60);

//--- Индикатор
//--- Устанавливаем и корректируем при необходимости период расчёта и уровни
   period=int(InpPeriod<1 ? 14  : InpPeriod);
   overbough=InpOverbough;
   oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title=StringFormat("DeM(%lu)",period);
   ind_digits=3;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iDeMarker(Symbol(),PERIOD_CURRENT,period);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
      return INIT_FAILED;
     }

//--- Панель
//--- Создаём панель
   panel=new CDashboard(1,20,20,229,243);
   if(panel==NULL)
     {
      Print("Error. Failed to create panel object");
      return INIT_FAILED;
     }
//--- Устанавливаем параметры шрифта
   panel.SetFontParams("Calibri",9);
//--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
   panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
//--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
   panel.CreateNewTable(0);
//--- Рисуем таблицу с идентификатором 0 на фоне панели
   panel.DrawGrid(0,2,20,6,2,18,112);

//--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
   panel.CreateNewTable(1);
//--- Получаем координату Y2 таблицы с идентификатором 0 и
//--- устанавливаем координату Y1 для таблицы с идентификатором 1
   int y1=panel.TableY2(0)+22;
//--- Рисуем таблицу с идентификатором 1 на фоне панели
   panel.DrawGrid(1,2,y1,4,2,18,112);
   
//--- Выводим в журнал табличные данные
   panel.GridPrint(0,2);
   panel.GridPrint(1,2);
//--- Инициализируем переменную с индексом бара указателя мышки
   mouse_bar_index=0;
//--- Выводим на панель данные текущего бара
   DrawData(mouse_bar_index,TimeCurrent());

//--- Успешная инициализация
   return(INIT_SUCCEEDED);
  }


Деинициализация

В обработчике OnDeinit() советника освобождается хэндл индикатора:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
  }

При использовании информационной панели — удаляется созданный объект панели:

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   EventKillTimer();
   
//--- Освобождаем хэндл индикатора
   ResetLastError();
   if(!IndicatorRelease(handle))
      PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
//--- Очищаем все комментарии на графике
   Comment("");
   
//--- Если объект панели существует - удаляем его
   if(panel!=NULL)
      delete panel;
  }


Получение данных

Общие функции получения данных по хэндлу индикатора:

//+------------------------------------------------------------------+
//| Возвращает данные индикатора на указанном баре                   |
//+------------------------------------------------------------------+
double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
  {
   double array[1]={0};
   ResetLastError();
   if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
     {
      PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
      return EMPTY_VALUE;
     }
   return array[0];
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии индикатора                            |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
  {
//--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
   const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Разворот линии вверх (value2>value1 && value0>value1)
   if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_TURN_UP;
//--- Направление линии вверх (value2<=value1 && value0>value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
      return LINE_STATE_UP;
//--- Остановка направления линии вверх (value2<=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_UP;
//--- Разворот линии вниз (value2<value1 && value0<value1)
   if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_TURN_DOWN;
//--- Направление линии вниз (value2>=value1 && value0<value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
      return LINE_STATE_DOWN;
//--- Остановка направления линии вниз (value2>=value1 && value0==value1)
   else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
      return LINE_STATE_STOP_DOWN;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает состояние линии относительно указанного уровня        |
//+------------------------------------------------------------------+
ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
  {
//--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
   const double value0=IndicatorValue(ind_handle,index,  buffer_num);
   const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
//--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
   if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
      return LINE_STATE_NONE;
//--- Определяем второй сравниваемый уровень
   double level=(level1==EMPTY_VALUE ? level0 : level1);
//--- Линия находится под уровнем (value1<level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_UNDER;
//--- Линия находится над уровнем (value1>level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_ABOVE;
//--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
   if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
      return LINE_STATE_CROSS_UP;
//--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
   if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
      return LINE_STATE_CROSS_DOWN;
//--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_TOUCH_BELOW;
//--- Линия равна значению уровня (value1==level0 && value0==level0)
   if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
      return LINE_STATE_EQUALS;
//--- Неопределённое состояние
   return LINE_STATE_NONE;
  }
//+------------------------------------------------------------------+
//| Возвращает описание состояния линии индикатора                   |
//+------------------------------------------------------------------+
string LineStateDescription(const ENUM_LINE_STATE state)
  {
   switch(state)
     {
      case LINE_STATE_UP         :  return "Up";
      case LINE_STATE_STOP_UP    :  return "Stop Up";
      case LINE_STATE_TURN_UP    :  return "Turn Up";
      case LINE_STATE_DOWN       :  return "Down";
      case LINE_STATE_STOP_DOWN  :  return "Stop Down";
      case LINE_STATE_TURN_DOWN  :  return "Turn Down";
      case LINE_STATE_ABOVE      :  return "Above level";
      case LINE_STATE_UNDER      :  return "Under level";
      case LINE_STATE_CROSS_UP   :  return "Crossing Up";
      case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
      case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
      case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
      case LINE_STATE_EQUALS     :  return "Equals";
      default                    :  return "Unknown";
     }
  }

При использовании информационной панели, данные выводятся на панель при помощи функции:

//+------------------------------------------------------------------+
//| Выводит данные с указанного индекса таймсерии на панель          |
//+------------------------------------------------------------------+
void DrawData(const int index,const datetime time)
  {
//--- Объявляем переменные для получения в них данных
   MqlTick  tick={0};
   MqlRates rates[1];

//--- Если текущие цены получить не удалось - уходим
   if(!SymbolInfoTick(Symbol(),tick))
      return;
//--- Если данные бара по указанному индексу получить не удалось - уходим
   if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
      return;

//--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
   int  size=0;
   uint flags=0;
   uint angle=0;
   string name=panel.FontParams(size,flags,angle);
   panel.SetFontParams(name,9,FW_BOLD);
   panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
   panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
//--- Устанавливаем параметры шрифта для данных бара и индикатора
   panel.SetFontParams(name,9);

//--- Выводим на панель данные указанного бара в таблицу 0
   panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
   panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
   panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
   panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
   panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
   panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);

//--- Выводим на панель данные индикатора с указанного бара в таблицу 1
   panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
   double value=IndicatorValue(handle,index,0);
   string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
   panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,100);
   
//--- Выводим описание состояния линии индикатора относительно уровня перекупленности
   string ovb=StringFormat("%+.2f",overbough);
   panel.DrawText("Overbough", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
   panel.DrawText(ovb, panel.CellX(1,2,0)+66, panel.CellY(1,2,0)+2);
   ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough);
//--- Цвет надписи меняется в зависимости от значения линии относительно уровня
   color clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
   string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb));
   panel.DrawText(ovb_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,100);
   
//--- Выводим описание состояния линии индикатора относительно уровня перепроданности
   panel.DrawText("Oversold", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2);
   string ovs=StringFormat("%+.2f",oversold);
   panel.DrawText(ovs, panel.CellX(1,3,0)+68, panel.CellY(1,3,0)+2);
   ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold);
//--- Цвет надписи меняется в зависимости от значения линии относительно уровня
   clr=(state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE);
   string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs));
   panel.DrawText(ovs_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,100);
   
//--- Выводим описание состояния линии индикатора
   panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
   ENUM_LINE_STATE state=LineState(handle,index,0);
//--- Цвет надписи меняется в зависимости от нахождения линии в областях перекупленности/перепроданности
   clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE);
   panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,100);
   
//--- Перерисовываем график для немедленного отображения всех изменений на панели
   ChartRedraw(ChartID());
  }

Нахождение линии индикатора в областях перекупленности/перепроданности помечаются цветом на панели.

Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Работа с панелью
//--- Вызываем обработчик событий панели
   panel.OnChartEvent(id,lparam,dparam,sparam);

//--- Если курсор перемещается или щелчок по графику
   if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
     {
      //--- Объявляем переменные для записи в них координат времени и цены
      datetime time=0;
      double price=0;
      int wnd=0;
      //--- Если координаты курсора преобразованы в дату и время
      if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
        {
         //--- записываем индекс бара, где расположен курсор в глобальную переменную
         mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
         //--- Выводим данные бара под курсором на панель
         DrawData(mouse_bar_index,time);
        }
     }

//--- Если получили пользовательское событие - выводим об этом сообщение в журнал
   if(id>CHARTEVENT_CUSTOM)
     {
      //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
      PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
     }
  }


После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



Файл советника "TestOscillatorDeM.mq5" можно посмотреть в прикреплённых к статье файлах.


Force Index

Технический индикатор Индекс Силы (Force Index, FRC) был разработан Александром Элдером и измеряет силу быков при каждом подъеме и силу медведей при каждом спаде. Он связывает основные элементы рыночной информации: направление цены, ее перепады и объем сделок. Данный индекс можно использовать в чистом виде, однако, лучше его сгладить с помощью скользящей средней. Сглаживание с помощью короткой скользящей средней (автор предлагает использовать 2 периода) помогает найти благоприятные моменты для открытия и закрытия позиций. Если же сглаживание производится с помощью длинной скользящей средней (например, 13-периодной), то индекс выявляет перемены тенденций.

  • Покупать желательно тогда, когда во время тенденции к повышению Force Index станет минусовым (упадет ниже нулевой линии);
  • Поднимаясь до новой высоты, индикатор сигнализирует о продолжении тенденции к повышению;
  • Сигнал к продаже поступает, когда во время тенденции к понижению Force Index становится положительным;
  • Падая на новую глубину, Индикатор Силы сигнализирует о силе медведей и продолжении тенденции к понижению;
  • Если изменения цен не подкреплены аналогичным изменением объема, то Force Index остается на одном уровне, что предупреждает о близком развороте тенденции.


    Параметры

    Индикатор имеет три настраиваемых параметра:

    • Период расчёта, по умолчанию — 2,
    • Метод расчёта, по умолчанию  — SMA,
    • Используемые Объёмы, по умолчанию — тиковые.

    Входные и глобальные переменные для использования индикатора в советнике:

    //+------------------------------------------------------------------+
    //|                                          TestOscillatorForce.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriod      =  13;            /* Period         */ // Период расчёта Force Index
    input ENUM_MA_METHOD       InpMethod      =  MODE_SMA;      /* Method         */ // Метод расчёта
    input ENUM_APPLIED_VOLUME  InpAppliedVol  =  VOLUME_TICK;   /* Applied Volume */ // Объемы
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта Force Index
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

    При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

    //+------------------------------------------------------------------+
    //|                                          TestOscillatorForce.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- includes
    #include <Dashboard\Dashboard.mqh>
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriod      =  13;            /* Period         */ // Период расчёта Force Index
    input ENUM_MA_METHOD       InpMethod      =  MODE_SMA;      /* Method         */ // Метод расчёта
    input ENUM_APPLIED_VOLUME  InpAppliedVol  =  VOLUME_TICK;   /* Applied Volume */ // Объемы
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта Force Index
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


    Инициализация

    Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period=int(InpPeriod<1 ? 13 : InpPeriod);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("Force(%lu)",period);
       ind_digits=Digits()+1;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iForce(Symbol(),PERIOD_CURRENT,period,InpMethod,InpAppliedVol);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    

    При использовании информационной панели, создаётся панель:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period=int(InpPeriod<1 ? 13 : InpPeriod);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("Force(%lu)",period);
       ind_digits=Digits()+1;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iForce(Symbol(),PERIOD_CURRENT,period,InpMethod,InpAppliedVol);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Панель
    //--- Создаёи панель
       panel=new CDashboard(1,20,20,199,225);
       if(panel==NULL)
         {
          Print("Error. Failed to create panel object");
          return INIT_FAILED;
         }
    //--- Устанавливаем параметры шрифта
       panel.SetFontParams("Calibri",9);
    //--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
       panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
    //--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
       panel.CreateNewTable(0);
    //--- Рисуем таблицу с идентификатором 0 на фоне панели
       panel.DrawGrid(0,2,20,6,2,18,97);
    
    //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
       panel.CreateNewTable(1);
    //--- Получаем координату Y2 таблицы с идентификатором 0 и
    //--- устанавливаем координату Y1 для таблицы с идентификатором 1
       int y1=panel.TableY2(0)+22;
    //--- Рисуем таблицу с идентификатором 1 на фоне панели
       panel.DrawGrid(1,2,y1,3,2,18,97);
       
    //--- Выводим в журнал табличные данные
       panel.GridPrint(0,2);
       panel.GridPrint(1,2);
    //--- Инициализируем переменную с индексом бара указателя мышки
       mouse_bar_index=0;
    //--- Выводим на панель данные текущего бара
       DrawData(mouse_bar_index,TimeCurrent());
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    


    Деинициализация

    В обработчике OnDeinit() советника освобождается хэндл индикатора:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
      }
    

    При использовании информационной панели — удаляется созданный объект панели:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
       
    //--- Если объект панели существует - удаляем его
       if(panel!=NULL)
          delete panel;
      }


    Получение данных

    Общие функции получения данных по хэндлу индикатора:

    //+------------------------------------------------------------------+
    //| Возвращает данные индикатора на указанном баре                   |
    //+------------------------------------------------------------------+
    double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
      {
       double array[1]={0};
       ResetLastError();
       if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
         {
          PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
          return EMPTY_VALUE;
         }
       return array[0];
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии индикатора                            |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
      {
    //--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
       const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Разворот линии вверх (value2>value1 && value0>value1)
       if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_TURN_UP;
    //--- Направление линии вверх (value2<=value1 && value0>value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_UP;
    //--- Остановка направления линии вверх (value2<=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_UP;
    //--- Разворот линии вниз (value2<value1 && value0<value1)
       if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_TURN_DOWN;
    //--- Направление линии вниз (value2>=value1 && value0<value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_DOWN;
    //--- Остановка направления линии вниз (value2>=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_DOWN;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии относительно указанного уровня        |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
      {
    //--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Определяем второй сравниваемый уровень
       double level=(level1==EMPTY_VALUE ? level0 : level1);
    //--- Линия находится под уровнем (value1<level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_UNDER;
    //--- Линия находится над уровнем (value1>level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_ABOVE;
    //--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_CROSS_UP;
    //--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_CROSS_DOWN;
    //--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия равна значению уровня (value1==level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_EQUALS;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает описание состояния линии индикатора                   |
    //+------------------------------------------------------------------+
    string LineStateDescription(const ENUM_LINE_STATE state)
      {
       switch(state)
         {
          case LINE_STATE_UP         :  return "Up";
          case LINE_STATE_STOP_UP    :  return "Stop Up";
          case LINE_STATE_TURN_UP    :  return "Turn Up";
          case LINE_STATE_DOWN       :  return "Down";
          case LINE_STATE_STOP_DOWN  :  return "Stop Down";
          case LINE_STATE_TURN_DOWN  :  return "Turn Down";
          case LINE_STATE_ABOVE      :  return "Above level";
          case LINE_STATE_UNDER      :  return "Under level";
          case LINE_STATE_CROSS_UP   :  return "Crossing Up";
          case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
          case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
          case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
          case LINE_STATE_EQUALS     :  return "Equals";
          default                    :  return "Unknown";
         }
      }
    

    При использовании информационной панели, данные выводятся на панель при помощи функции:

    //+------------------------------------------------------------------+
    //| Выводит данные с указанного индекса таймсерии на панель          |
    //+------------------------------------------------------------------+
    void DrawData(const int index,const datetime time)
      {
    //--- Объявляем переменные для получения в них данных
       MqlTick  tick={0};
       MqlRates rates[1];
    
    //--- Если текущие цены получить не удалось - уходим
       if(!SymbolInfoTick(Symbol(),tick))
          return;
    //--- Если данные бара по указанному индексу получить не удалось - уходим
       if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
          return;
    
    //--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
       int  size=0;
       uint flags=0;
       uint angle=0;
       string name=panel.FontParams(size,flags,angle);
       panel.SetFontParams(name,9,FW_BOLD);
       panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
       panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
    //--- Устанавливаем параметры шрифта для данных бара и индикатора
       panel.SetFontParams(name,9);
    
    //--- Выводим на панель данные указанного бара в таблицу 0
       panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
       panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
       panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
       panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
       panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
       panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);
    
    //--- Выводим на панель данные индикатора с указанного бара в таблицу 1
       panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       double value=IndicatorValue(handle,index,0);
       string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
       
    //--- Выводим описание состояния линии индикатора
       panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
       ENUM_LINE_STATE state=LineState(handle,index,0);
       color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE); // Цвет надписи меняется в зависимости от значения линии выше/ниже нуля
       panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90);
       
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

    Значения линии индикатора выше/ниже нуля помечаются цветом на панели.

    Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Работа с панелью
    //--- Вызываем обработчик событий панели
       panel.OnChartEvent(id,lparam,dparam,sparam);
    
    //--- Если курсор перемещается или щелчок по графику
       if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
         {
          //--- Объявляем переменные для записи в них координат времени и цены
          datetime time=0;
          double price=0;
          int wnd=0;
          //--- Если координаты курсора преобразованы в дату и время
          if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
            {
             //--- записываем индекс бара, где расположен курсор в глобальную переменную
             mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
             //--- Выводим данные бара под курсором на панель
             DrawData(mouse_bar_index,time);
            }
         }
    
    //--- Если получили пользовательское событие - выводим об этом сообщение в журнал
       if(id>CHARTEVENT_CUSTOM)
         {
          //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
          PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
         }
      }
    


    После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



    Файл советника "TestOscillatorForce.mq5" можно посмотреть в прикреплённых к статье файлах.


    MACD

    Технический индикатор Схождение/Расхождение Скользящих Средних (Moving Average Convergence/Divergence, MACD) — это следующий за тенденцией динамический индикатор. Он показывает соотношение между двумя скользящими средними цены.

    Технический индикатор MACD строится как разность между двумя экспоненциальными скользящими средними (EMA) с периодами в 12 и 26. Чтобы четко обозначить благоприятные моменты для покупки или продажи, на график MACD наносится так называемая сигнальная линия — 9-периодное скользящее среднее индикатора.

    MACD наиболее эффективен в условиях, когда рынок колеблется с большой амплитудой в торговом коридоре. Чаще всего используемые сигналы MACD — пересечения, состояния перекупленности/перепроданности и расхождения.

    Пересечения

    Основное правило торговли с помощью MACD построено на пересечениях индикатора со своей сигнальной линией: когда Moving Average Convergence/Divergence опускается ниже сигнальной линии — следует продавать, а когда поднимается выше сигнальной линии — покупать. В качестве сигналов к покупке/продаже также используются пересечения MACD нулевой линии вверх/вниз.

    Состояния перекупленности/перепроданности

    Moving Average Convergence/Divergence также весьма ценен как индикатор перекупленности/перепроданности. Когда короткое скользящее среднее поднимается существенно выше длинного (т.е. MACD растет), это означает, что цена рассматриваемого инструмента, скорее всего, слишком завышена и скоро вернется к более реалистичному уровню.

    Расхождения

    Когда между MACD и ценой образуется расхождение, это означает возможность скорого окончания текущей тенденции. Бычье расхождение возникает тогда, когда MACD достигает новых минимумов, а цене не удается их достичь. Медвежье расхождение образуется, когда индикатор достигает новых максимумов, а цена — нет. Оба вида расхождений наиболее значимы, если они формируются в областях перекупленности/перепроданности.



    Параметры

    Индикатор имеет четыре настраиваемых параметра:

    • Период расчёта быстрой EMA, по умолчанию — 12
    • Период расчёта медленной EMA, по умолчанию — 26,
    • Период расчёта сигнальной SMA, по умолчанию — 9,
    • Цена расчёта, по умолчанию — Close.

    Входные и глобальные переменные для использования индикатора в советнике:

    //+------------------------------------------------------------------+
    //|                                           TestOscillatorMACD.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriodFast  =  12;            /* Fast MA Period */ // Период расчёта быстрого EMA
    input uint                 InpPeriodSlow  =  26;            /* Slow MA Period */ // Период расчёта медленного EMA
    input uint                 InpPeriodSignal=  9;             /* MACD SMA       */ // Период расчёта сигнальной линии
    input ENUM_APPLIED_PRICE   InpAppliedPrice=  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period_fast=0;          // Период расчёта быстрого EMA
    int      period_slow=0;          // Период расчёта медленного EMA
    int      period_signal=0;        // Период расчёта сигнальной линии
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

    При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

    //+------------------------------------------------------------------+
    //|                                           TestOscillatorMACD.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- includes
    #include <Dashboard\Dashboard.mqh>
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriodFast  =  12;            /* Fast MA Period */ // Период расчёта быстрого EMA
    input uint                 InpPeriodSlow  =  26;            /* Slow MA Period */ // Период расчёта медленного EMA
    input uint                 InpPeriodSignal=  9;             /* MACD SMA       */ // Период расчёта сигнальной линии
    input ENUM_APPLIED_PRICE   InpAppliedPrice=  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period_fast=0;          // Период расчёта быстрого EMA
    int      period_slow=0;          // Период расчёта медленного EMA
    int      period_signal=0;        // Период расчёта сигнальной линии
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


    Инициализация

    Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period_fast=int(InpPeriodFast<1 ? 12 : InpPeriodFast);
       period_slow=int(InpPeriodSlow<1 ? 26 : InpPeriodSlow==period_fast ? period_fast+1 : InpPeriodSlow);
       period_signal=int(InpPeriodSignal<1 ? 9 : InpPeriodSignal);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("MACD(%lu,%lu,%lu)",period_fast,period_slow,period_signal);
       ind_digits=Digits()+1;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iMACD(Symbol(),PERIOD_CURRENT,period_fast,period_slow,period_signal,InpAppliedPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    

    При использовании информационной панели, создаётся панель:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period_fast=int(InpPeriodFast<1 ? 12 : InpPeriodFast);
       period_slow=int(InpPeriodSlow<1 ? 26 : InpPeriodSlow==period_fast ? period_fast+1 : InpPeriodSlow);
       period_signal=int(InpPeriodSignal<1 ? 9 : InpPeriodSignal);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("MACD(%lu,%lu,%lu)",period_fast,period_slow,period_signal);
       ind_digits=Digits()+1;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iMACD(Symbol(),PERIOD_CURRENT,period_fast,period_slow,period_signal,InpAppliedPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Панель
    //--- Создаёи панель
       panel=new CDashboard(1,20,20,199,261);
       if(panel==NULL)
         {
          Print("Error. Failed to create panel object");
          return INIT_FAILED;
         }
    //--- Устанавливаем параметры шрифта
       panel.SetFontParams("Calibri",9);
    //--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
       panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
    //--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
       panel.CreateNewTable(0);
    //--- Рисуем таблицу с идентификатором 0 на фоне панели
       panel.DrawGrid(0,2,20,6,2,18,97);
    
    //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
       panel.CreateNewTable(1);
    //--- Получаем координату Y2 таблицы с идентификатором 0 и
    //--- устанавливаем координату Y1 для таблицы с идентификатором 1
       int y1=panel.TableY2(0)+22;
    //--- Рисуем таблицу с идентификатором 1 на фоне панели
       panel.DrawGrid(1,2,y1,5,2,18,97);
       
    //--- Выводим в журнал табличные данные
       panel.GridPrint(0,2);
       panel.GridPrint(1,2);
    //--- Инициализируем переменную с индексом бара указателя мышки
       mouse_bar_index=0;
    //--- Выводим на панель данные текущего бара
       DrawData(mouse_bar_index,TimeCurrent());
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    


    Деинициализация

    В обработчике OnDeinit() советника освобождается хэндл индикатора:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
      }
    

    При использовании информационной панели — удаляется созданный объект панели:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
       
    //--- Если объект панели существует - удаляем его
       if(panel!=NULL)
          delete panel;
      }


    Получение данных

    Общие функции получения данных по хэндлу индикатора:

    //+------------------------------------------------------------------+
    //| Возвращает данные индикатора на указанном баре                   |
    //+------------------------------------------------------------------+
    double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
      {
       double array[1]={0};
       ResetLastError();
       if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
         {
          PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
          return EMPTY_VALUE;
         }
       return array[0];
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии индикатора                            |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
      {
    //--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
       const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Разворот линии вверх (value2>value1 && value0>value1)
       if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_TURN_UP;
    //--- Направление линии вверх (value2<=value1 && value0>value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_UP;
    //--- Остановка направления линии вверх (value2<=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_UP;
    //--- Разворот линии вниз (value2<value1 && value0<value1)
       if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_TURN_DOWN;
    //--- Направление линии вниз (value2>=value1 && value0<value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_DOWN;
    //--- Остановка направления линии вниз (value2>=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_DOWN;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии относительно указанного уровня        |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
      {
    //--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Определяем второй сравниваемый уровень
       double level=(level1==EMPTY_VALUE ? level0 : level1);
    //--- Линия находится под уровнем (value1<level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_UNDER;
    //--- Линия находится над уровнем (value1>level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_ABOVE;
    //--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_CROSS_UP;
    //--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_CROSS_DOWN;
    //--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия равна значению уровня (value1==level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_EQUALS;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает описание состояния линии индикатора                   |
    //+------------------------------------------------------------------+
    string LineStateDescription(const ENUM_LINE_STATE state)
      {
       switch(state)
         {
          case LINE_STATE_UP         :  return "Up";
          case LINE_STATE_STOP_UP    :  return "Stop Up";
          case LINE_STATE_TURN_UP    :  return "Turn Up";
          case LINE_STATE_DOWN       :  return "Down";
          case LINE_STATE_STOP_DOWN  :  return "Stop Down";
          case LINE_STATE_TURN_DOWN  :  return "Turn Down";
          case LINE_STATE_ABOVE      :  return "Above level";
          case LINE_STATE_UNDER      :  return "Under level";
          case LINE_STATE_CROSS_UP   :  return "Crossing Up";
          case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
          case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
          case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
          case LINE_STATE_EQUALS     :  return "Equals";
          default                    :  return "Unknown";
         }
      }
    

    При использовании информационной панели, данные выводятся на панель при помощи функции:

    //+------------------------------------------------------------------+
    //| Выводит данные с указанного индекса таймсерии на панель          |
    //+------------------------------------------------------------------+
    void DrawData(const int index,const datetime time)
      {
    //--- Объявляем переменные для получения в них данных
       MqlTick  tick={0};
       MqlRates rates[1];
    
    //--- Если текущие цены получить не удалось - уходим
       if(!SymbolInfoTick(Symbol(),tick))
          return;
    //--- Если данные бара по указанному индексу получить не удалось - уходим
       if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
          return;
    
    //--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
       int  size=0;
       uint flags=0;
       uint angle=0;
       string name=panel.FontParams(size,flags,angle);
       panel.SetFontParams(name,9,FW_BOLD);
       panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
       panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
    //--- Устанавливаем параметры шрифта для данных бара и индикатора
       panel.SetFontParams(name,9);
    
    //--- Выводим на панель данные указанного бара в таблицу 0
       panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
       panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
       panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
       panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
       panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
       panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);
    
    //--- Выводим на панель данные индикатора с указанного бара в таблицу 1
       panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       double value=IndicatorValue(handle,index,0);
       string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
    //--- Выводим на панель данные сигнальной линии индикатора с указанного бара в таблицу 1
       panel.DrawText("Signal", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
       double signal=IndicatorValue(handle,index,1);
       string signal_str=(signal!=EMPTY_VALUE ? DoubleToString(signal,ind_digits) : "");
       panel.DrawText(signal_str,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,90);
       
    //--- Выводим описание состояния линии индикатора относительно нуля
       panel.DrawText("MACD vs Zero", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
       ENUM_LINE_STATE state_zero=LineStateRelative(handle,index,0,0);
       string state_zero_str=
         (
          state_zero==LINE_STATE_ABOVE        ?  "MACD > 0"  : 
          state_zero==LINE_STATE_UNDER        ?  "MACD < 0"  : 
          state_zero==LINE_STATE_TOUCH_ABOVE  || 
          state_zero==LINE_STATE_TOUCH_BELOW  ?  "Touch"     :
          LineStateDescription(state_zero)
         );
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       color clr=(state_zero==LINE_STATE_CROSS_UP ? clrBlue : state_zero==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
       panel.DrawText(state_zero_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90);
       
    //--- Выводим описание состояния линии индикатора относительно сигнальной линии
       panel.DrawText("MACD vs Signal", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2);
       ENUM_LINE_STATE state_signal=LineStateRelative(handle,index,0,signal,IndicatorValue(handle,index+1,1));
       string state_signal_str=(state_signal==LINE_STATE_ABOVE ? "MACD > Signal" : state_signal==LINE_STATE_UNDER ? "MACD < Signal" : LineStateDescription(state_signal));
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       clr=(state_signal==LINE_STATE_CROSS_UP ? clrBlue : state_signal==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
       panel.DrawText(state_signal_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,90);
       
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

    Соотношение гистограммы относительно сигнальной линии и относительно нуля помечаются на панели цветом.

    Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Работа с панелью
    //--- Вызываем обработчик событий панели
       panel.OnChartEvent(id,lparam,dparam,sparam);
    
    //--- Если курсор перемещается или щелчок по графику
       if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
         {
          //--- Объявляем переменные для записи в них координат времени и цены
          datetime time=0;
          double price=0;
          int wnd=0;
          //--- Если координаты курсора преобразованы в дату и время
          if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
            {
             //--- записываем индекс бара, где расположен курсор в глобальную переменную
             mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
             //--- Выводим данные бара под курсором на панель
             DrawData(mouse_bar_index,time);
            }
         }
    
    //--- Если получили пользовательское событие - выводим об этом сообщение в журнал
       if(id>CHARTEVENT_CUSTOM)
         {
          //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
          PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
         }
      }
    


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



    Файл советника "TestOscillatorMACD.mq5" можно посмотреть в прикреплённых к статье файлах.


    Momentum

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

    • В качестве осциллятора, следующего за тенденцией, аналогично Техническому Индикатору Схождение/Расхождение Скользящих Средних (Moving Average Convergence/Divergence, MACD). В этом случае сигнал к покупке возникает, если индикатор Momentum образует впадину и начинает расти; а сигнал к продаже — когда он достигает пика и поворачивает вниз. Для более точного определения моментов разворота индикатора можно использовать его короткое скользящее среднее.
      Крайне высокие или низкие значения индикатора Momentum предполагают продолжение текущей тенденции. Так, если индикатор достигает крайне высоких значений и затем поворачивает вниз, следует ожидать дальнейшего роста цен. Но в любом случае с открытием (или закрытием) позиции не нужно спешить до тех пор, пока цены не подтвердят сигнал индикатора.
    • В качестве опережающего индикатора. Этот способ основан на предположении о том, что заключительная фаза восходящей тенденции обычно сопровождается стремительным ростом цен (так как все верят в его продолжение), а окончание медвежьего рынка — их резким падением (так как все стремятся выйти из рынка). Именно так нередко и происходит, но все же это слишком широкое обобщение.
      Приближение рынка к вершине сопровождается резким скачком индикатора Momentum. Затем он начинает падать, в то время как цены продолжают расти или движутся горизонтально. По аналогии, в основании рынка Momentum резко падает, а затем поворачивает вверх задолго до начала роста цен. В обоих случаях образуются расхождения между индикатором и ценами.


    Параметры

    Индикатор имеет два настраиваемых параметра:

    • Период расчёта, по умолчанию — 14,
    • Цена расчёта, по умолчанию — Close.

    Входные и глобальные переменные для использования индикатора в советнике:

    //+------------------------------------------------------------------+
    //|                                       TestOscillatorMomentum.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriod=  14;            /* Period         */ // Период расчёта Momentum
    input ENUM_APPLIED_PRICE   InpPrice =  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта Momentum
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

    При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

    //+------------------------------------------------------------------+
    //|                                       TestOscillatorMomentum.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- includes
    #include <Dashboard\Dashboard.mqh>
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriod=  14;            /* Period         */ // Период расчёта Momentum
    input ENUM_APPLIED_PRICE   InpPrice =  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта Momentum
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


    Инициализация

    Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period=int(InpPeriod<1 ? 14 : InpPeriod);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("Momentum(%lu)",period);
       ind_digits=2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iMomentum(Symbol(),PERIOD_CURRENT,period,InpPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    

    При использовании информационной панели, создаётся панель:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period=int(InpPeriod<1 ? 14 : InpPeriod);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("Momentum(%lu)",period);
       ind_digits=2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iMomentum(Symbol(),PERIOD_CURRENT,period,InpPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Панель
    //--- Создаёи панель
       panel=new CDashboard(1,20,20,199,225);
       if(panel==NULL)
         {
          Print("Error. Failed to create panel object");
          return INIT_FAILED;
         }
    //--- Устанавливаем параметры шрифта
       panel.SetFontParams("Calibri",9);
    //--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
       panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
    //--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
       panel.CreateNewTable(0);
    //--- Рисуем таблицу с идентификатором 0 на фоне панели
       panel.DrawGrid(0,2,20,6,2,18,97);
    
    //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
       panel.CreateNewTable(1);
    //--- Получаем координату Y2 таблицы с идентификатором 0 и
    //--- устанавливаем координату Y1 для таблицы с идентификатором 1
       int y1=panel.TableY2(0)+22;
    //--- Рисуем таблицу с идентификатором 1 на фоне панели
       panel.DrawGrid(1,2,y1,3,2,18,97);
       
    //--- Выводим в журнал табличные данные
       panel.GridPrint(0,2);
       panel.GridPrint(1,2);
    //--- Инициализируем переменную с индексом бара указателя мышки
       mouse_bar_index=0;
    //--- Выводим на панель данные текущего бара
       DrawData(mouse_bar_index,TimeCurrent());
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    


    Деинициализация

    В обработчике OnDeinit() советника освобождается хэндл индикатора:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
      }
    

    При использовании информационной панели — удаляется созданный объект панели:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
       
    //--- Если объект панели существует - удаляем его
       if(panel!=NULL)
          delete panel;
      }


    Получение данных

    Общие функции получения данных по хэндлу индикатора:

    //+------------------------------------------------------------------+
    //| Возвращает данные индикатора на указанном баре                   |
    //+------------------------------------------------------------------+
    double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
      {
       double array[1]={0};
       ResetLastError();
       if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
         {
          PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
          return EMPTY_VALUE;
         }
       return array[0];
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии индикатора                            |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
      {
    //--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
       const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Разворот линии вверх (value2>value1 && value0>value1)
       if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_TURN_UP;
    //--- Направление линии вверх (value2<=value1 && value0>value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_UP;
    //--- Остановка направления линии вверх (value2<=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_UP;
    //--- Разворот линии вниз (value2<value1 && value0<value1)
       if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_TURN_DOWN;
    //--- Направление линии вниз (value2>=value1 && value0<value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_DOWN;
    //--- Остановка направления линии вниз (value2>=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_DOWN;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии относительно указанного уровня        |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
      {
    //--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Определяем второй сравниваемый уровень
       double level=(level1==EMPTY_VALUE ? level0 : level1);
    //--- Линия находится под уровнем (value1<level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_UNDER;
    //--- Линия находится над уровнем (value1>level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_ABOVE;
    //--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_CROSS_UP;
    //--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_CROSS_DOWN;
    //--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия равна значению уровня (value1==level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_EQUALS;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает описание состояния линии индикатора                   |
    //+------------------------------------------------------------------+
    string LineStateDescription(const ENUM_LINE_STATE state)
      {
       switch(state)
         {
          case LINE_STATE_UP         :  return "Up";
          case LINE_STATE_STOP_UP    :  return "Stop Up";
          case LINE_STATE_TURN_UP    :  return "Turn Up";
          case LINE_STATE_DOWN       :  return "Down";
          case LINE_STATE_STOP_DOWN  :  return "Stop Down";
          case LINE_STATE_TURN_DOWN  :  return "Turn Down";
          case LINE_STATE_ABOVE      :  return "Above level";
          case LINE_STATE_UNDER      :  return "Under level";
          case LINE_STATE_CROSS_UP   :  return "Crossing Up";
          case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
          case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
          case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
          case LINE_STATE_EQUALS     :  return "Equals";
          default                    :  return "Unknown";
         }
      }
    

    При использовании информационной панели, данные выводятся на панель при помощи функции:

    //+------------------------------------------------------------------+
    //| Выводит данные с указанного индекса таймсерии на панель          |
    //+------------------------------------------------------------------+
    void DrawData(const int index,const datetime time)
      {
    //--- Объявляем переменные для получения в них данных
       MqlTick  tick={0};
       MqlRates rates[1];
    
    //--- Если текущие цены получить не удалось - уходим
       if(!SymbolInfoTick(Symbol(),tick))
          return;
    //--- Если данные бара по указанному индексу получить не удалось - уходим
       if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
          return;
    
    //--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
       int  size=0;
       uint flags=0;
       uint angle=0;
       string name=panel.FontParams(size,flags,angle);
       panel.SetFontParams(name,9,FW_BOLD);
       panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
       panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
    //--- Устанавливаем параметры шрифта для данных бара и индикатора
       panel.SetFontParams(name,9);
    
    //--- Выводим на панель данные указанного бара в таблицу 0
       panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
       panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
       panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
       panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
       panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
       panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);
    
    //--- Выводим на панель данные индикатора с указанного бара в таблицу 1
       panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       double value=IndicatorValue(handle,index,0);
       string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
       
    //--- Выводим описание состояния линии индикатора
       panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
       ENUM_LINE_STATE state=LineState(handle,index,0);
       panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,90);
       
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

    Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Работа с панелью
    //--- Вызываем обработчик событий панели
       panel.OnChartEvent(id,lparam,dparam,sparam);
    
    //--- Если курсор перемещается или щелчок по графику
       if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
         {
          //--- Объявляем переменные для записи в них координат времени и цены
          datetime time=0;
          double price=0;
          int wnd=0;
          //--- Если координаты курсора преобразованы в дату и время
          if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
            {
             //--- записываем индекс бара, где расположен курсор в глобальную переменную
             mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
             //--- Выводим данные бара под курсором на панель
             DrawData(mouse_bar_index,time);
            }
         }
    
    //--- Если получили пользовательское событие - выводим об этом сообщение в журнал
       if(id>CHARTEVENT_CUSTOM)
         {
          //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
          PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
         }
      }
    


    После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



    Файл советника "TestOscillatorMomentum.mq5" можно посмотреть в прикреплённых к статье файлах.


    Moving Average of Oscillator

    Технический индикатор Скользящая Средняя Осциллятора (Moving Average of Oscillator, OsMA) — это разность между осциллятором и сглаживанием осциллятора. В данном случае в качестве осциллятора используется основная линия MACD, а в качестве сглаживания — сигнальная.



    Параметры

    Индикатор имеет четыре настраиваемых параметра:

    • Период расчёта быстрой EMA MACD, по умолчанию — 12,
    • Период расчёта медленной EMA MACD, по умолчанию — 26,
    • Период расчёта сигнальной SMA MACD, по умолчанию — 9,
    • Цена расчёта MACD, по умолчанию — Close.

    Входные и глобальные переменные для использования индикатора в советнике:

    //+------------------------------------------------------------------+
    //|                                           TestOscillatorOsMA.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriodFast  =  12;            /* Fast MA Period */ // Период расчёта быстрого EMA
    input uint                 InpPeriodSlow  =  26;            /* Slow MA Period */ // Период расчёта медленного EMA
    input uint                 InpPeriodSignal=  9;             /* MACD SMA       */ // Период расчёта MACD SMA
    input ENUM_APPLIED_PRICE   InpAppliedPrice=  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period_fast=0;          // Период расчёта быстрого EMA
    int      period_slow=0;          // Период расчёта медленного EMA
    int      period_signal=0;        // Период расчёта сигнальной линии
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

    При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

    //+------------------------------------------------------------------+
    //|                                           TestOscillatorOsMA.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- includes
    #include <Dashboard\Dashboard.mqh>
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriodFast  =  12;            /* Fast MA Period */ // Период расчёта быстрого EMA
    input uint                 InpPeriodSlow  =  26;            /* Slow MA Period */ // Период расчёта медленного EMA
    input uint                 InpPeriodSignal=  9;             /* MACD SMA       */ // Период расчёта MACD SMA
    input ENUM_APPLIED_PRICE   InpAppliedPrice=  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period_fast=0;          // Период расчёта быстрого EMA
    int      period_slow=0;          // Период расчёта медленного EMA
    int      period_signal=0;        // Период расчёта сигнальной линии
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


    Инициализация

    Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period_fast=int(InpPeriodFast<1 ? 12 : InpPeriodFast);
       period_slow=int(InpPeriodSlow<1 ? 26 : InpPeriodSlow==period_fast ? period_fast+1 : InpPeriodSlow);
       period_signal=int(InpPeriodSignal<1 ? 9 : InpPeriodSignal);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("OsMA(%lu,%lu,%lu)",period_fast,period_slow,period_signal);
       ind_digits=Digits()+2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iOsMA(Symbol(),PERIOD_CURRENT,period_fast,period_slow,period_signal,InpAppliedPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    

    При использовании информационной панели, создаётся панель:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period_fast=int(InpPeriodFast<1 ? 12 : InpPeriodFast);
       period_slow=int(InpPeriodSlow<1 ? 26 : InpPeriodSlow==period_fast ? period_fast+1 : InpPeriodSlow);
       period_signal=int(InpPeriodSignal<1 ? 9 : InpPeriodSignal);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("OsMA(%lu,%lu,%lu)",period_fast,period_slow,period_signal);
       ind_digits=Digits()+2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iOsMA(Symbol(),PERIOD_CURRENT,period_fast,period_slow,period_signal,InpAppliedPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Панель
    //--- Создаёи панель
       panel=new CDashboard(1,20,20,199,225);
       if(panel==NULL)
         {
          Print("Error. Failed to create panel object");
          return INIT_FAILED;
         }
    //--- Устанавливаем параметры шрифта
       panel.SetFontParams("Calibri",9);
    //--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
       panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
    //--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
       panel.CreateNewTable(0);
    //--- Рисуем таблицу с идентификатором 0 на фоне панели
       panel.DrawGrid(0,2,20,6,2,18,97);
    
    //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
       panel.CreateNewTable(1);
    //--- Получаем координату Y2 таблицы с идентификатором 0 и
    //--- устанавливаем координату Y1 для таблицы с идентификатором 1
       int y1=panel.TableY2(0)+22;
    //--- Рисуем таблицу с идентификатором 1 на фоне панели
       panel.DrawGrid(1,2,y1,3,2,18,97);
       
    //--- Выводим в журнал табличные данные
       panel.GridPrint(0,2);
       panel.GridPrint(1,2);
    //--- Инициализируем переменную с индексом бара указателя мышки
       mouse_bar_index=0;
    //--- Выводим на панель данные текущего бара
       DrawData(mouse_bar_index,TimeCurrent());
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    


    Деинициализация

    В обработчике OnDeinit() советника освобождается хэндл индикатора:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
      }
    

    При использовании информационной панели — удаляется созданный объект панели:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
       
    //--- Если объект панели существует - удаляем его
       if(panel!=NULL)
          delete panel;
      }


    Получение данных

    Общие функции получения данных по хэндлу индикатора:

    //+------------------------------------------------------------------+
    //| Возвращает данные индикатора на указанном баре                   |
    //+------------------------------------------------------------------+
    double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
      {
       double array[1]={0};
       ResetLastError();
       if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
         {
          PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
          return EMPTY_VALUE;
         }
       return array[0];
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии индикатора                            |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
      {
    //--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
       const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Разворот линии вверх (value2>value1 && value0>value1)
       if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_TURN_UP;
    //--- Направление линии вверх (value2<=value1 && value0>value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_UP;
    //--- Остановка направления линии вверх (value2<=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_UP;
    //--- Разворот линии вниз (value2<value1 && value0<value1)
       if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_TURN_DOWN;
    //--- Направление линии вниз (value2>=value1 && value0<value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_DOWN;
    //--- Остановка направления линии вниз (value2>=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_DOWN;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии относительно указанного уровня        |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
      {
    //--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Определяем второй сравниваемый уровень
       double level=(level1==EMPTY_VALUE ? level0 : level1);
    //--- Линия находится под уровнем (value1<level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_UNDER;
    //--- Линия находится над уровнем (value1>level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_ABOVE;
    //--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_CROSS_UP;
    //--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_CROSS_DOWN;
    //--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия равна значению уровня (value1==level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_EQUALS;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает описание состояния линии индикатора                   |
    //+------------------------------------------------------------------+
    string LineStateDescription(const ENUM_LINE_STATE state)
      {
       switch(state)
         {
          case LINE_STATE_UP         :  return "Up";
          case LINE_STATE_STOP_UP    :  return "Stop Up";
          case LINE_STATE_TURN_UP    :  return "Turn Up";
          case LINE_STATE_DOWN       :  return "Down";
          case LINE_STATE_STOP_DOWN  :  return "Stop Down";
          case LINE_STATE_TURN_DOWN  :  return "Turn Down";
          case LINE_STATE_ABOVE      :  return "Above level";
          case LINE_STATE_UNDER      :  return "Under level";
          case LINE_STATE_CROSS_UP   :  return "Crossing Up";
          case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
          case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
          case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
          case LINE_STATE_EQUALS     :  return "Equals";
          default                    :  return "Unknown";
         }
      }
    

    При использовании информационной панели, данные выводятся на панель при помощи функции:

    //+------------------------------------------------------------------+
    //| Выводит данные с указанного индекса таймсерии на панель          |
    //+------------------------------------------------------------------+
    void DrawData(const int index,const datetime time)
      {
    //--- Объявляем переменные для получения в них данных
       MqlTick  tick={0};
       MqlRates rates[1];
    
    //--- Если текущие цены получить не удалось - уходим
       if(!SymbolInfoTick(Symbol(),tick))
          return;
    //--- Если данные бара по указанному индексу получить не удалось - уходим
       if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
          return;
    
    //--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
       int  size=0;
       uint flags=0;
       uint angle=0;
       string name=panel.FontParams(size,flags,angle);
       panel.SetFontParams(name,9,FW_BOLD);
       panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
       panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
    //--- Устанавливаем параметры шрифта для данных бара и индикатора
       panel.SetFontParams(name,9);
    
    //--- Выводим на панель данные указанного бара в таблицу 0
       panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
       panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
       panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
       panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
       panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
       panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);
    
    //--- Выводим на панель данные индикатора с указанного бара в таблицу 1
       panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       double value=IndicatorValue(handle,index,0);
       string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
       
    //--- Выводим описание состояния линии индикатора
       panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
       ENUM_LINE_STATE state=LineState(handle,index,0);
    //--- Цвет надписи меняется в зависимости от значения линии выше/ниже нуля
       color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE);
       panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90);
       
    //--- Выводим описание состояния линии индикатора относительно нуля
       panel.DrawText("OsMA vs Zero", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
       ENUM_LINE_STATE state_zero=LineStateRelative(handle,index,0,0);
       string state_zero_str=
         (
          state_zero==LINE_STATE_ABOVE        ?  "OsMA > 0"  : 
          state_zero==LINE_STATE_UNDER        ?  "OsMA < 0"  : 
          state_zero==LINE_STATE_TOUCH_ABOVE  || 
          state_zero==LINE_STATE_TOUCH_BELOW  ?  "Touch"     :
          LineStateDescription(state_zero)
         );
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       clr=(state_zero==LINE_STATE_CROSS_UP ? clrBlue : state_zero==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
       panel.DrawText(state_zero_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90);
       
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

    Состояния линии индикатора и её положение относительно нуля отмечаются цветом на панели.

    Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Работа с панелью
    //--- Вызываем обработчик событий панели
       panel.OnChartEvent(id,lparam,dparam,sparam);
    
    //--- Если курсор перемещается или щелчок по графику
       if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
         {
          //--- Объявляем переменные для записи в них координат времени и цены
          datetime time=0;
          double price=0;
          int wnd=0;
          //--- Если координаты курсора преобразованы в дату и время
          if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
            {
             //--- записываем индекс бара, где расположен курсор в глобальную переменную
             mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
             //--- Выводим данные бара под курсором на панель
             DrawData(mouse_bar_index,time);
            }
         }
    
    //--- Если получили пользовательское событие - выводим об этом сообщение в журнал
       if(id>CHARTEVENT_CUSTOM)
         {
          //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
          PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
         }
      }
    


    После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



    Файл советника "TestOscillatorOsMA.mq5" можно посмотреть в прикреплённых к статье файлах.


    Relative Strength Index

    Технический индикатор Индекс Относительной Силы (Relative Strength Index, RSI) это следующий за ценой осциллятор, который колеблется в диапазоне от 0 до 100. Вводя Relative Strength Index, У. Уайлдер рекомендовал использовать его 14-периодный вариант. В дальнейшем распространение получили также 9 и 25-периодные индикаторы. Один из распространенных методов анализа индикатора Relative Strength Index состоит в поиске расхождений, при которых цена образует новый максимум, а RSI не удается преодолеть уровень своего предыдущего максимума. Подобное расхождение свидетельствует о вероятности разворота цен. Если затем индикатор поворачивает вниз и опускается ниже своей впадины, то он завершает так называемый "неудавшийся размах" (failure swing). Этот неудавшийся размах считается подтверждением скорого разворота цен.

    При анализе графиков различают следующие сигналы Relative Strength Index:

    • Вершины и основания
      Вершины индикатора Relative Strength Index обычно формируются выше 70, а основания — ниже 30, причем они обычно опережают образования вершин и оснований на ценовом графике.
    • Графические модели
      Relative Strength Index часто образует графические модели — такие как ’голова и плечи’ или треугольники, которые на ценовом графике могут и не обозначиться.
    • Неудавшийся размах (прорыв уровня поддержки и сопротивления)
      Имеет место, когда Relative Strength Index поднимается выше предыдущего максимума (пика) или опускается ниже предыдущего минимума (впадина).
    • Уровни поддержки и сопротивления
      На графике индикатора Relative Strength Index уровни поддержки и сопротивления проступают даже отчетливее, чем на ценовом графике.
    • Расхождения
      Как уже сказано выше, расхождения образуются, когда цена достигает нового максимума (минимума), но он не подтверждается новым максимумом (минимумом) на графике RSI. При этом обычно происходит коррекция цен в направлении движения индикатора Relative Strength Index.



    Параметры

    Индикатор имеет два настраиваемых параметра:

    • Период расчёта, по умолчанию — 14,
    • Цена расчёта, по умолчанию — Close.

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

    Входные и глобальные переменные для использования индикатора в советнике:

    //+------------------------------------------------------------------+
    //|                                            TestOscillatorRSI.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriod=  14;            /* Period         */ // Период расчёта RSI
    input ENUM_APPLIED_PRICE   InpPrice =  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    input double               InpOverbough=  70;         /* Overbough level*/ // Уровень перекупленности
    input double               InpOversold =  30;         /* Oversold level */ // Уровень перепроданности
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта RSI
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    double   overbough=0;            // Уровень перекупленности
    double   oversold=0;             // Уровень перепроданности
    string   ind_title;              // Описание индикатора
    

    При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

    //+------------------------------------------------------------------+
    //|                                            TestOscillatorRSI.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- includes
    #include <Dashboard\Dashboard.mqh>
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriod=  14;            /* Period         */ // Период расчёта RSI
    input ENUM_APPLIED_PRICE   InpPrice =  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    input double               InpOverbough=  70;         /* Overbough level*/ // Уровень перекупленности
    input double               InpOversold =  30;         /* Oversold level */ // Уровень перепроданности
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта RSI
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    double   overbough=0;            // Уровень перекупленности
    double   oversold=0;             // Уровень перепроданности
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


    Инициализация

    Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта и уровни
       period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod);
       overbough=InpOverbough;
       oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("RSI(%lu)",period);
       ind_digits=2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iRSI(Symbol(),PERIOD_CURRENT,period,InpPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    

    При использовании информационной панели, создаётся панель:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта и уровни
       period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod);
       overbough=InpOverbough;
       oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("RSI(%lu)",period);
       ind_digits=2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iRSI(Symbol(),PERIOD_CURRENT,period,InpPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Панель
    //--- Создаём панель
       panel=new CDashboard(1,20,20,229,243);
       if(panel==NULL)
         {
          Print("Error. Failed to create panel object");
          return INIT_FAILED;
         }
    //--- Устанавливаем параметры шрифта
       panel.SetFontParams("Calibri",9);
    //--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
       panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
    //--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
       panel.CreateNewTable(0);
    //--- Рисуем таблицу с идентификатором 0 на фоне панели
       panel.DrawGrid(0,2,20,6,2,18,112);
    
    //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
       panel.CreateNewTable(1);
    //--- Получаем координату Y2 таблицы с идентификатором 0 и
    //--- устанавливаем координату Y1 для таблицы с идентификатором 1
       int y1=panel.TableY2(0)+22;
    //--- Рисуем таблицу с идентификатором 1 на фоне панели
       panel.DrawGrid(1,2,y1,4,2,18,112);
       
    //--- Выводим в журнал табличные данные
       panel.GridPrint(0,2);
       panel.GridPrint(1,2);
    //--- Инициализируем переменную с индексом бара указателя мышки
       mouse_bar_index=0;
    //--- Выводим на панель данные текущего бара
       DrawData(mouse_bar_index,TimeCurrent());
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    


    Деинициализация

    В обработчике OnDeinit() советника освобождается хэндл индикатора:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
      }
    

    При использовании информационной панели — удаляется созданный объект панели:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
       
    //--- Если объект панели существует - удаляем его
       if(panel!=NULL)
          delete panel;
      }


    Получение данных

    Общие функции получения данных по хэндлу индикатора:

    //+------------------------------------------------------------------+
    //| Возвращает данные индикатора на указанном баре                   |
    //+------------------------------------------------------------------+
    double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
      {
       double array[1]={0};
       ResetLastError();
       if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
         {
          PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
          return EMPTY_VALUE;
         }
       return array[0];
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии индикатора                            |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
      {
    //--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
       const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Разворот линии вверх (value2>value1 && value0>value1)
       if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_TURN_UP;
    //--- Направление линии вверх (value2<=value1 && value0>value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_UP;
    //--- Остановка направления линии вверх (value2<=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_UP;
    //--- Разворот линии вниз (value2<value1 && value0<value1)
       if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_TURN_DOWN;
    //--- Направление линии вниз (value2>=value1 && value0<value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_DOWN;
    //--- Остановка направления линии вниз (value2>=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_DOWN;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии относительно указанного уровня        |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
      {
    //--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Определяем второй сравниваемый уровень
       double level=(level1==EMPTY_VALUE ? level0 : level1);
    //--- Линия находится под уровнем (value1<level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_UNDER;
    //--- Линия находится над уровнем (value1>level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_ABOVE;
    //--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_CROSS_UP;
    //--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_CROSS_DOWN;
    //--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия равна значению уровня (value1==level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_EQUALS;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает описание состояния линии индикатора                   |
    //+------------------------------------------------------------------+
    string LineStateDescription(const ENUM_LINE_STATE state)
      {
       switch(state)
         {
          case LINE_STATE_UP         :  return "Up";
          case LINE_STATE_STOP_UP    :  return "Stop Up";
          case LINE_STATE_TURN_UP    :  return "Turn Up";
          case LINE_STATE_DOWN       :  return "Down";
          case LINE_STATE_STOP_DOWN  :  return "Stop Down";
          case LINE_STATE_TURN_DOWN  :  return "Turn Down";
          case LINE_STATE_ABOVE      :  return "Above level";
          case LINE_STATE_UNDER      :  return "Under level";
          case LINE_STATE_CROSS_UP   :  return "Crossing Up";
          case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
          case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
          case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
          case LINE_STATE_EQUALS     :  return "Equals";
          default                    :  return "Unknown";
         }
      }
    

    При использовании информационной панели, данные выводятся на панель при помощи функции:

    //+------------------------------------------------------------------+
    //| Выводит данные с указанного индекса таймсерии на панель          |
    //+------------------------------------------------------------------+
    void DrawData(const int index,const datetime time)
      {
    //--- Объявляем переменные для получения в них данных
       MqlTick  tick={0};
       MqlRates rates[1];
    
    //--- Если текущие цены получить не удалось - уходим
       if(!SymbolInfoTick(Symbol(),tick))
          return;
    //--- Если данные бара по указанному индексу получить не удалось - уходим
       if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
          return;
    
    //--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
       int  size=0;
       uint flags=0;
       uint angle=0;
       string name=panel.FontParams(size,flags,angle);
       panel.SetFontParams(name,9,FW_BOLD);
       panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
       panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
    //--- Устанавливаем параметры шрифта для данных бара и индикатора
       panel.SetFontParams(name,9);
    
    //--- Выводим на панель данные указанного бара в таблицу 0
       panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
       panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
       panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
       panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
       panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
       panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);
    
    //--- Выводим на панель данные индикатора с указанного бара в таблицу 1
       panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       double value=IndicatorValue(handle,index,0);
       string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,100);
       
    //--- Выводим описание состояния линии индикатора относительно уровня перекупленности
       string ovb=StringFormat("%+.2f",overbough);
       panel.DrawText("Overbough", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
       panel.DrawText(ovb, panel.CellX(1,2,0)+66, panel.CellY(1,2,0)+2);
       ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough);
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       color clr=(state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
       string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb));
       panel.DrawText(ovb_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,100);
       
    //--- Выводим описание состояния линии индикатора относительно уровня перепроданности
       panel.DrawText("Oversold", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2);
       string ovs=StringFormat("%+.2f",oversold);
       panel.DrawText(ovs, panel.CellX(1,3,0)+68, panel.CellY(1,3,0)+2);
       ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold);
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       clr=(state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE);
       string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs));
       panel.DrawText(ovs_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,100);
       
    //--- Выводим описание состояния линии индикатора
       panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
       ENUM_LINE_STATE state=LineState(handle,index,0);
    //--- Цвет надписи меняется в зависимости от нахождения линии в областях перекупленности/перепроданности
       clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE);
       panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,100);
       
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

    Нахождение линии в зонах перекупленности/перепроданности, а также состояния линии относительно уровней помечаются цветом на панели.

    Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Работа с панелью
    //--- Вызываем обработчик событий панели
       panel.OnChartEvent(id,lparam,dparam,sparam);
    
    //--- Если курсор перемещается или щелчок по графику
       if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
         {
          //--- Объявляем переменные для записи в них координат времени и цены
          datetime time=0;
          double price=0;
          int wnd=0;
          //--- Если координаты курсора преобразованы в дату и время
          if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
            {
             //--- записываем индекс бара, где расположен курсор в глобальную переменную
             mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
             //--- Выводим данные бара под курсором на панель
             DrawData(mouse_bar_index,time);
            }
         }
    
    //--- Если получили пользовательское событие - выводим об этом сообщение в журнал
       if(id>CHARTEVENT_CUSTOM)
         {
          //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
          PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
         }
      }
    


    После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



    Файл советника "TestOscillatorRSI.mq5" можно посмотреть в прикреплённых к статье файлах.


    Relative Vigor Index

    Технический индикатор Индекс Относительной Бодрости (Relative Vigor Index, RVI) базируется на идее того, что на бычьем рынке цена закрытия, как правило, выше, чем цена открытия. И наоборот — на медвежьем рынке. Таким образом, бодрость движения устанавливается положением, в котором цена находится в конце периода. Чтобы сделать индекс нормализованным к ежедневному диапазону торговли, изменение цены делится на максимальный диапазон цен в течение дня. Для большей сглаженности расчетов используется простое скользящее среднее. Лучшим периодом считается 10. Для исключения возможных неоднозначностей строится сигнальная линия — 4-периодное симметрично взвешенное сглаженное среднее значений Relative Vigor Index. Пересечение линий говорит о наличии сигнала на покупку или продажу.



    Параметры

    Индикатор имеет один настраиваемый параметр: период расчёта. Значение по умолчанию — 10.

    Входные и глобальные переменные для использования индикатора в советнике:

    //+------------------------------------------------------------------+
    //|                                            TestOscillatorRVI.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint  InpPeriod   =  10;   /* Period */ // Период расчёта RVI
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта RVI
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

    При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

    //+------------------------------------------------------------------+
    //|                                            TestOscillatorRVI.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- includes
    #include <Dashboard\Dashboard.mqh>
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint  InpPeriod   =  10;   /* Period */ // Период расчёта RVI
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта RVI
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


    Инициализация

    Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period=int(InpPeriod<1 ? 10 : InpPeriod);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("RVI(%lu)",period);
       ind_digits=3;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iRVI(Symbol(),PERIOD_CURRENT,period);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    

    При использовании информационной панели, создаётся панель:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period=int(InpPeriod<1 ? 10 : InpPeriod);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("RVI(%lu)",period);
       ind_digits=3;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iRVI(Symbol(),PERIOD_CURRENT,period);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Панель
    //--- Создаёи панель
       panel=new CDashboard(1,20,20,199,225);
       if(panel==NULL)
         {
          Print("Error. Failed to create panel object");
          return INIT_FAILED;
         }
    //--- Устанавливаем параметры шрифта
       panel.SetFontParams("Calibri",9);
    //--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
       panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
    //--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
       panel.CreateNewTable(0);
    //--- Рисуем таблицу с идентификатором 0 на фоне панели
       panel.DrawGrid(0,2,20,6,2,18,97);
    
    //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
       panel.CreateNewTable(1);
    //--- Получаем координату Y2 таблицы с идентификатором 0 и
    //--- устанавливаем координату Y1 для таблицы с идентификатором 1
       int y1=panel.TableY2(0)+22;
    //--- Рисуем таблицу с идентификатором 1 на фоне панели
       panel.DrawGrid(1,2,y1,3,2,18,97);
       
    //--- Выводим в журнал табличные данные
       panel.GridPrint(0,2);
       panel.GridPrint(1,2);
    //--- Инициализируем переменную с индексом бара указателя мышки
       mouse_bar_index=0;
    //--- Выводим на панель данные текущего бара
       DrawData(mouse_bar_index,TimeCurrent());
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    


    Деинициализация

    В обработчике OnDeinit() советника освобождается хэндл индикатора:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
      }
    

    При использовании информационной панели — удаляется созданный объект панели:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
       
    //--- Если объект панели существует - удаляем его
       if(panel!=NULL)
          delete panel;
      }


    Получение данных

    Общие функции получения данных по хэндлу индикатора:

    //+------------------------------------------------------------------+
    //| Возвращает данные индикатора на указанном баре                   |
    //+------------------------------------------------------------------+
    double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
      {
       double array[1]={0};
       ResetLastError();
       if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
         {
          PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
          return EMPTY_VALUE;
         }
       return array[0];
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии индикатора                            |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
      {
    //--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
       const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Разворот линии вверх (value2>value1 && value0>value1)
       if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_TURN_UP;
    //--- Направление линии вверх (value2<=value1 && value0>value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_UP;
    //--- Остановка направления линии вверх (value2<=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_UP;
    //--- Разворот линии вниз (value2<value1 && value0<value1)
       if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_TURN_DOWN;
    //--- Направление линии вниз (value2>=value1 && value0<value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_DOWN;
    //--- Остановка направления линии вниз (value2>=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_DOWN;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии относительно указанного уровня        |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
      {
    //--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Определяем второй сравниваемый уровень
       double level=(level1==EMPTY_VALUE ? level0 : level1);
    //--- Линия находится под уровнем (value1<level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_UNDER;
    //--- Линия находится над уровнем (value1>level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_ABOVE;
    //--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_CROSS_UP;
    //--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_CROSS_DOWN;
    //--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия равна значению уровня (value1==level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_EQUALS;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает описание состояния линии индикатора                   |
    //+------------------------------------------------------------------+
    string LineStateDescription(const ENUM_LINE_STATE state)
      {
       switch(state)
         {
          case LINE_STATE_UP         :  return "Up";
          case LINE_STATE_STOP_UP    :  return "Stop Up";
          case LINE_STATE_TURN_UP    :  return "Turn Up";
          case LINE_STATE_DOWN       :  return "Down";
          case LINE_STATE_STOP_DOWN  :  return "Stop Down";
          case LINE_STATE_TURN_DOWN  :  return "Turn Down";
          case LINE_STATE_ABOVE      :  return "Above level";
          case LINE_STATE_UNDER      :  return "Under level";
          case LINE_STATE_CROSS_UP   :  return "Crossing Up";
          case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
          case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
          case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
          case LINE_STATE_EQUALS     :  return "Equals";
          default                    :  return "Unknown";
         }
      }
    

    При использовании информационной панели, данные выводятся на панель при помощи функции:

    //+------------------------------------------------------------------+
    //| Выводит данные с указанного индекса таймсерии на панель          |
    //+------------------------------------------------------------------+
    void DrawData(const int index,const datetime time)
      {
    //--- Объявляем переменные для получения в них данных
       MqlTick  tick={0};
       MqlRates rates[1];
    
    //--- Если текущие цены получить не удалось - уходим
       if(!SymbolInfoTick(Symbol(),tick))
          return;
    //--- Если данные бара по указанному индексу получить не удалось - уходим
       if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
          return;
    
    //--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
       int  size=0;
       uint flags=0;
       uint angle=0;
       string name=panel.FontParams(size,flags,angle);
       panel.SetFontParams(name,9,FW_BOLD);
       panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
       panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
    //--- Устанавливаем параметры шрифта для данных бара и индикатора
       panel.SetFontParams(name,9);
    
    //--- Выводим на панель данные указанного бара в таблицу 0
       panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
       panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
       panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
       panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
       panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
       panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);
    
    //--- Выводим на панель данные индикатора с указанного бара в таблицу 1
       panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       double value=IndicatorValue(handle,index,0);
       string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
    //--- Выводим на панель данные сигнальной линии индикатора с указанного бара в таблицу 1
       panel.DrawText("Signal", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
       double signal=IndicatorValue(handle,index,1);
       string signal_str=(signal!=EMPTY_VALUE ? DoubleToString(signal,ind_digits) : "");
       panel.DrawText(signal_str,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,90);
       
    //--- Выводим описание состояния линии индикатора относительно сигнальной линии
       panel.DrawText("RVI vs Signal", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
       ENUM_LINE_STATE state_signal=LineStateRelative(handle,index,0,signal,IndicatorValue(handle,index+1,1));
       string state_signal_str=(state_signal==LINE_STATE_ABOVE ? "RVI > Signal" : state_signal==LINE_STATE_UNDER ? "RVI < Signal" : LineStateDescription(state_signal));
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       color clr=(state_signal==LINE_STATE_CROSS_UP ? clrBlue : state_signal==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
       panel.DrawText(state_signal_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90);
       
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

    Сигналы индикатора — пересечения линии индикатора с сигнальной линией, помечаются цветом на панели.

    Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Работа с панелью
    //--- Вызываем обработчик событий панели
       panel.OnChartEvent(id,lparam,dparam,sparam);
    
    //--- Если курсор перемещается или щелчок по графику
       if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
         {
          //--- Объявляем переменные для записи в них координат времени и цены
          datetime time=0;
          double price=0;
          int wnd=0;
          //--- Если координаты курсора преобразованы в дату и время
          if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
            {
             //--- записываем индекс бара, где расположен курсор в глобальную переменную
             mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
             //--- Выводим данные бара под курсором на панель
             DrawData(mouse_bar_index,time);
            }
         }
    
    //--- Если получили пользовательское событие - выводим об этом сообщение в журнал
       if(id>CHARTEVENT_CUSTOM)
         {
          //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
          PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
         }
      }
    


    После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



    Файл советника "TestOscillatorRVI.mq5" можно посмотреть в прикреплённых к статье файлах.


    Stochastic Oscillator

    Технический индикатор Стохастический Осциллятор (Stochastic Oscillator) сопоставляет текущую цену закрытия с диапазоном цен за выбранный период времени. Индикатор представлен двумя линиями. Главная линия называется %K. Вторая линия %D — это скользящее среднее линии %K. Обычно %K изображается сплошной линией, а %D — пунктирной. Существует три наиболее распространенных способа интерпретации Стохастического Осциллятора:

    • Покупайте, когда осциллятор (%K или %D) сначала опустится ниже определенного уровня (обычно 20), а затем поднимется выше него. Продавайте, когда осциллятор сначала поднимется выше определенного уровня (обычно 80), а потом опустится ниже него.
    • Покупайте, если линия %K поднимается выше линии %D. Продавайте, если линия %K опускается ниже линии %D.
    • Следите за расхождениями. Например: цены образуют ряд новых максимумов, а Stochastic Oscillator не удается подняться выше своих предыдущих максимумов.



    Параметры

    Индикатор имеет пять настраиваемых параметров:

    • Период расчёта линии %K, значение по умолчанию — 5,
    • Период расчёта линии %D, значение по умолчанию — 3,
    • Период замедления, значение по умолчанию — 3,
    • Цена расчёта стохастика, значение по умолчанию — Low/High,
    • Метод расчёта, значение по умолчанию — SMA.

    Кроме этих значений, во входных параметрах советника нужно указать уровни перекупленности и перепроданности.

    Входные и глобальные переменные для использования индикатора в советнике:

    //+------------------------------------------------------------------+
    //|                                          TestOscillatorStoch.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint           InpPeriodK  =  5;             /* Period %K   */ // Период %K
    input uint           InpPeriodD  =  3;             /* Period %D   */ // Период %D
    input uint           InpSlowing  =  3;             /* Slowing     */ // Период Slowing
    input ENUM_STO_PRICE InpPrice    =  STO_LOWHIGH;   /* Price       */ // Цены расчёта
    input ENUM_MA_METHOD InpMethod   =  MODE_SMA;      /* Method      */ // Метод расчёта
    input double         InpOverbough=  80;            /* Overbough level*/ // Уровень перекупленности
    input double         InpOversold =  20;            /* Oversold level */ // Уровень перепроданности
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period_k=0;             // Период %K
    int      period_d=0;             // Период %D
    int      slowing=0;              // Период Slowing
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    double   overbough=0;            // Уровень перекупленности
    double   oversold=0;             // Уровень перепроданности
    string   ind_title;              // Описание индикатора
    

    При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

    //+------------------------------------------------------------------+
    //|                                          TestOscillatorStoch.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- includes
    #include <Dashboard\Dashboard.mqh>
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint           InpPeriodK  =  5;             /* Period %K   */ // Период %K
    input uint           InpPeriodD  =  3;             /* Period %D   */ // Период %D
    input uint           InpSlowing  =  3;             /* Slowing     */ // Период Slowing
    input ENUM_STO_PRICE InpPrice    =  STO_LOWHIGH;   /* Price       */ // Цены расчёта
    input ENUM_MA_METHOD InpMethod   =  MODE_SMA;      /* Method      */ // Метод расчёта
    input double         InpOverbough=  80;            /* Overbough level*/ // Уровень перекупленности
    input double         InpOversold =  20;            /* Oversold level */ // Уровень перепроданности
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period_k=0;             // Период %K
    int      period_d=0;             // Период %D
    int      slowing=0;              // Период Slowing
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    double   overbough=0;            // Уровень перекупленности
    double   oversold=0;             // Уровень перепроданности
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


    Инициализация

    Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period_k=int(InpPeriodK<1 ? 5 : InpPeriodK);
       period_d=int(InpPeriodD<1 ? 3 : InpPeriodD);
       slowing =int(InpSlowing<1 ? 3 : InpSlowing);
       overbough=InpOverbough;
       oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("Stoch(%lu,%lu,%lu)",period_k,period_d,slowing);
       ind_digits=2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iStochastic(Symbol(),PERIOD_CURRENT,period_k,period_d,slowing,InpMethod,InpPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    

    При использовании информационной панели, создаётся панель:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period_k=int(InpPeriodK<1 ? 5 : InpPeriodK);
       period_d=int(InpPeriodD<1 ? 3 : InpPeriodD);
       slowing =int(InpSlowing<1 ? 3 : InpSlowing);
       overbough=InpOverbough;
       oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("Stoch(%lu,%lu,%lu)",period_k,period_d,slowing);
       ind_digits=2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iStochastic(Symbol(),PERIOD_CURRENT,period_k,period_d,slowing,InpMethod,InpPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Панель
    //--- Создаёи панель
       panel=new CDashboard(1,20,20,229,261);
       if(panel==NULL)
         {
          Print("Error. Failed to create panel object");
          return INIT_FAILED;
         }
    //--- Устанавливаем параметры шрифта
       panel.SetFontParams("Calibri",9);
    //--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
       panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
    //--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
       panel.CreateNewTable(0);
    //--- Рисуем таблицу с идентификатором 0 на фоне панели
       panel.DrawGrid(0,2,20,6,2,18,112);
    
    //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
       panel.CreateNewTable(1);
    //--- Получаем координату Y2 таблицы с идентификатором 0 и
    //--- устанавливаем координату Y1 для таблицы с идентификатором 1
       int y1=panel.TableY2(0)+22;
    //--- Рисуем таблицу с идентификатором 1 на фоне панели
       panel.DrawGrid(1,2,y1,5,2,18,112);
       
    //--- Выводим в журнал табличные данные
       panel.GridPrint(0,2);
       panel.GridPrint(1,2);
    //--- Инициализируем переменную с индексом бара указателя мышки
       mouse_bar_index=0;
    //--- Выводим на панель данные текущего бара
       DrawData(mouse_bar_index,TimeCurrent());
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    


    Деинициализация

    В обработчике OnDeinit() советника освобождается хэндл индикатора:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
      }
    

    При использовании информационной панели — удаляется созданный объект панели:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
       
    //--- Если объект панели существует - удаляем его
       if(panel!=NULL)
          delete panel;
      }


    Получение данных

    Общие функции получения данных по хэндлу индикатора:

    //+------------------------------------------------------------------+
    //| Возвращает данные индикатора на указанном баре                   |
    //+------------------------------------------------------------------+
    double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
      {
       double array[1]={0};
       ResetLastError();
       if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
         {
          PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
          return EMPTY_VALUE;
         }
       return array[0];
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии индикатора                            |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
      {
    //--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
       const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Разворот линии вверх (value2>value1 && value0>value1)
       if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_TURN_UP;
    //--- Направление линии вверх (value2<=value1 && value0>value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_UP;
    //--- Остановка направления линии вверх (value2<=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_UP;
    //--- Разворот линии вниз (value2<value1 && value0<value1)
       if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_TURN_DOWN;
    //--- Направление линии вниз (value2>=value1 && value0<value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_DOWN;
    //--- Остановка направления линии вниз (value2>=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_DOWN;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии относительно указанного уровня        |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
      {
    //--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Определяем второй сравниваемый уровень
       double level=(level1==EMPTY_VALUE ? level0 : level1);
    //--- Линия находится под уровнем (value1<level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_UNDER;
    //--- Линия находится над уровнем (value1>level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_ABOVE;
    //--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_CROSS_UP;
    //--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_CROSS_DOWN;
    //--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия равна значению уровня (value1==level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_EQUALS;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает описание состояния линии индикатора                   |
    //+------------------------------------------------------------------+
    string LineStateDescription(const ENUM_LINE_STATE state)
      {
       switch(state)
         {
          case LINE_STATE_UP         :  return "Up";
          case LINE_STATE_STOP_UP    :  return "Stop Up";
          case LINE_STATE_TURN_UP    :  return "Turn Up";
          case LINE_STATE_DOWN       :  return "Down";
          case LINE_STATE_STOP_DOWN  :  return "Stop Down";
          case LINE_STATE_TURN_DOWN  :  return "Turn Down";
          case LINE_STATE_ABOVE      :  return "Above level";
          case LINE_STATE_UNDER      :  return "Under level";
          case LINE_STATE_CROSS_UP   :  return "Crossing Up";
          case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
          case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
          case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
          case LINE_STATE_EQUALS     :  return "Equals";
          default                    :  return "Unknown";
         }
      }
    

    При использовании информационной панели, данные выводятся на панель при помощи функции:

    //+------------------------------------------------------------------+
    //| Выводит данные с указанного индекса таймсерии на панель          |
    //+------------------------------------------------------------------+
    void DrawData(const int index,const datetime time)
      {
    //--- Объявляем переменные для получения в них данных
       MqlTick  tick={0};
       MqlRates rates[1];
    
    //--- Если текущие цены получить не удалось - уходим
       if(!SymbolInfoTick(Symbol(),tick))
          return;
    //--- Если данные бара по указанному индексу получить не удалось - уходим
       if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
          return;
    
    //--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
       int  size=0;
       uint flags=0;
       uint angle=0;
       string name=panel.FontParams(size,flags,angle);
       panel.SetFontParams(name,9,FW_BOLD);
       panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
       panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
    //--- Устанавливаем параметры шрифта для данных бара и индикатора
       panel.SetFontParams(name,9);
    
    //--- Выводим на панель данные указанного бара в таблицу 0
       panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
       panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
       panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
       panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
       panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
       panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);
    
    //--- Выводим на панель данные индикатора с указанного бара в таблицу 1
       panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       double value=IndicatorValue(handle,index,0);
       string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,100);
    //--- Выводим на панель данные сигнальной линии индикатора с указанного бара в таблицу 1
       panel.DrawText("Signal", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
       double signal=IndicatorValue(handle,index,1);
       string signal_str=(signal!=EMPTY_VALUE ? DoubleToString(signal,ind_digits) : "");
       panel.DrawText(signal_str,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,100);
       
    //--- Выводим описание состояния линии индикатора относительно сигнальной линии
       panel.DrawText("Stoch vs Signal", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
       ENUM_LINE_STATE state_signal=LineStateRelative(handle,index,0,signal,IndicatorValue(handle,index+1,1));
       string state_signal_str=(state_signal==LINE_STATE_ABOVE ? "Stoch > Signal" : state_signal==LINE_STATE_UNDER ? "Stoch < Signal" : LineStateDescription(state_signal));
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       color clr=(state_signal==LINE_STATE_CROSS_UP ? clrBlue : state_signal==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
       panel.DrawText(state_signal_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,100);
       
    //--- Выводим описание состояния линии индикатора относительно уровня перекупленности
       string ovb=StringFormat("%+.2f",overbough);
       panel.DrawText("Overbough", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2);
       panel.DrawText(ovb, panel.CellX(1,3,0)+66, panel.CellY(1,3,0)+2);
       ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough);
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       clr=(state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
       string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb));
       panel.DrawText(ovb_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,100);
       
    //--- Выводим описание состояния линии индикатора относительно уровня перепроданности
       panel.DrawText("Oversold", panel.CellX(1,4,0)+2, panel.CellY(1,4,0)+2);
       string ovs=StringFormat("%+.2f",oversold);
       panel.DrawText(ovs, panel.CellX(1,4,0)+68, panel.CellY(1,4,0)+2);
       ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold);
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       clr=(state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE);
       string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs));
       panel.DrawText(ovs_str,panel.CellX(1,4,1)+2,panel.CellY(1,4,1)+2,clr,100);
       
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

    Состояние линии стохастика, нахождение в зонах перекупленности/перепроданности и сигналы индикатора помечаются цветом на панели.

    Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Работа с панелью
    //--- Вызываем обработчик событий панели
       panel.OnChartEvent(id,lparam,dparam,sparam);
    
    //--- Если курсор перемещается или щелчок по графику
       if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
         {
          //--- Объявляем переменные для записи в них координат времени и цены
          datetime time=0;
          double price=0;
          int wnd=0;
          //--- Если координаты курсора преобразованы в дату и время
          if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
            {
             //--- записываем индекс бара, где расположен курсор в глобальную переменную
             mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
             //--- Выводим данные бара под курсором на панель
             DrawData(mouse_bar_index,time);
            }
         }
    
    //--- Если получили пользовательское событие - выводим об этом сообщение в журнал
       if(id>CHARTEVENT_CUSTOM)
         {
          //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
          PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
         }
      }
    


    После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



    Файл советника "TestOscillatorStoch.mq5" можно посмотреть в прикреплённых к статье файлах.


    Triple Exponential Average

    Технический индикатор Тройное Экспоненциальное Среднее (Triple Exponential Average, TRIX) разработан Джеком Хатсоном (Jack Hutson) как осциллятор состояния перекупленности и перепроданности. Он может применяться также и как индикатор моментума. Тройное сглаживание служит для устранения циклических составляющих в движении цены с периодом меньше, чем период индикатора TRIX.

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



    Параметры

    Индикатор имеет два настраиваемых параметра:

    • Период расчёта, по умолчанию — 14,
    • Цена расчёта, по умолчанию — Close.

    Входные и глобальные переменные для использования индикатора в советнике:

    //+------------------------------------------------------------------+
    //|                                           TestOscillatorTRIX.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriod=  14;            /* Period         */ // Период расчёта TRIX
    input ENUM_APPLIED_PRICE   InpPrice =  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта TRIX
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

    При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

    //+------------------------------------------------------------------+
    //|                                           TestOscillatorTRIX.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- includes
    #include <Dashboard\Dashboard.mqh>
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint                 InpPeriod=  14;            /* Period         */ // Период расчёта TRIX
    input ENUM_APPLIED_PRICE   InpPrice =  PRICE_CLOSE;   /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта TRIX
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


    Инициализация

    Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("TRIX(%lu)",period);
       ind_digits=Digits();
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iTriX(Symbol(),PERIOD_CURRENT,period,InpPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    

    При использовании информационной панели, создаётся панель:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("TRIX(%lu)",period);
       ind_digits=Digits();
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iTriX(Symbol(),PERIOD_CURRENT,period,InpPrice);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Панель
    //--- Создаёи панель
       panel=new CDashboard(1,20,20,199,225);
       if(panel==NULL)
         {
          Print("Error. Failed to create panel object");
          return INIT_FAILED;
         }
    //--- Устанавливаем параметры шрифта
       panel.SetFontParams("Calibri",9);
    //--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
       panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
    //--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
       panel.CreateNewTable(0);
    //--- Рисуем таблицу с идентификатором 0 на фоне панели
       panel.DrawGrid(0,2,20,6,2,18,97);
    
    //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
       panel.CreateNewTable(1);
    //--- Получаем координату Y2 таблицы с идентификатором 0 и
    //--- устанавливаем координату Y1 для таблицы с идентификатором 1
       int y1=panel.TableY2(0)+22;
    //--- Рисуем таблицу с идентификатором 1 на фоне панели
       panel.DrawGrid(1,2,y1,3,2,18,97);
       
    //--- Выводим в журнал табличные данные
       panel.GridPrint(0,2);
       panel.GridPrint(1,2);
    //--- Инициализируем переменную с индексом бара указателя мышки
       mouse_bar_index=0;
    //--- Выводим на панель данные текущего бара
       DrawData(mouse_bar_index,TimeCurrent());
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    


    Деинициализация

    В обработчике OnDeinit() советника освобождается хэндл индикатора:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
      }
    

    При использовании информационной панели — удаляется созданный объект панели:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
       
    //--- Если объект панели существует - удаляем его
       if(panel!=NULL)
          delete panel;
      }


    Получение данных

    Общие функции получения данных по хэндлу индикатора:

    //+------------------------------------------------------------------+
    //| Возвращает данные индикатора на указанном баре                   |
    //+------------------------------------------------------------------+
    double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
      {
       double array[1]={0};
       ResetLastError();
       if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
         {
          PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
          return EMPTY_VALUE;
         }
       return array[0];
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии индикатора                            |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
      {
    //--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
       const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Разворот линии вверх (value2>value1 && value0>value1)
       if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_TURN_UP;
    //--- Направление линии вверх (value2<=value1 && value0>value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_UP;
    //--- Остановка направления линии вверх (value2<=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_UP;
    //--- Разворот линии вниз (value2<value1 && value0<value1)
       if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_TURN_DOWN;
    //--- Направление линии вниз (value2>=value1 && value0<value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_DOWN;
    //--- Остановка направления линии вниз (value2>=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_DOWN;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии относительно указанного уровня        |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
      {
    //--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Определяем второй сравниваемый уровень
       double level=(level1==EMPTY_VALUE ? level0 : level1);
    //--- Линия находится под уровнем (value1<level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_UNDER;
    //--- Линия находится над уровнем (value1>level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_ABOVE;
    //--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_CROSS_UP;
    //--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_CROSS_DOWN;
    //--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия равна значению уровня (value1==level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_EQUALS;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает описание состояния линии индикатора                   |
    //+------------------------------------------------------------------+
    string LineStateDescription(const ENUM_LINE_STATE state)
      {
       switch(state)
         {
          case LINE_STATE_UP         :  return "Up";
          case LINE_STATE_STOP_UP    :  return "Stop Up";
          case LINE_STATE_TURN_UP    :  return "Turn Up";
          case LINE_STATE_DOWN       :  return "Down";
          case LINE_STATE_STOP_DOWN  :  return "Stop Down";
          case LINE_STATE_TURN_DOWN  :  return "Turn Down";
          case LINE_STATE_ABOVE      :  return "Above level";
          case LINE_STATE_UNDER      :  return "Under level";
          case LINE_STATE_CROSS_UP   :  return "Crossing Up";
          case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
          case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
          case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
          case LINE_STATE_EQUALS     :  return "Equals";
          default                    :  return "Unknown";
         }
      }
    

    При использовании информационной панели, данные выводятся на панель при помощи функции:

    //+------------------------------------------------------------------+
    //| Выводит данные с указанного индекса таймсерии на панель          |
    //+------------------------------------------------------------------+
    void DrawData(const int index,const datetime time)
      {
    //--- Объявляем переменные для получения в них данных
       MqlTick  tick={0};
       MqlRates rates[1];
    
    //--- Если текущие цены получить не удалось - уходим
       if(!SymbolInfoTick(Symbol(),tick))
          return;
    //--- Если данные бара по указанному индексу получить не удалось - уходим
       if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
          return;
    
    //--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
       int  size=0;
       uint flags=0;
       uint angle=0;
       string name=panel.FontParams(size,flags,angle);
       panel.SetFontParams(name,9,FW_BOLD);
       panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
       panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
    //--- Устанавливаем параметры шрифта для данных бара и индикатора
       panel.SetFontParams(name,9);
    
    //--- Выводим на панель данные указанного бара в таблицу 0
       panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
       panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
       panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
       panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
       panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
       panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);
    
    //--- Выводим на панель данные индикатора с указанного бара в таблицу 1
       panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       double value=IndicatorValue(handle,index,0);
       string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
       
    //--- Выводим описание состояния линии индикатора
       panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
       ENUM_LINE_STATE state=LineState(handle,index,0);
    //--- Цвет надписи меняется в зависимости от значения линии выше/ниже нуля
       color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE);
       panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90);
       
    //--- Выводим описание состояния линии индикатора относительно нуля
       panel.DrawText("TRIX vs Zero", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
       ENUM_LINE_STATE state_zero=LineStateRelative(handle,index,0,0);
       string state_zero_str=
         (
          state_zero==LINE_STATE_ABOVE        ?  "TRIX > 0"  : 
          state_zero==LINE_STATE_UNDER        ?  "TRIX < 0"  : 
          state_zero==LINE_STATE_TOUCH_ABOVE  || 
          state_zero==LINE_STATE_TOUCH_BELOW  ?  "Touch"     :
          LineStateDescription(state_zero)
         );
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       clr=(state_zero==LINE_STATE_CROSS_UP ? clrBlue : state_zero==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE);
       panel.DrawText(state_zero_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90);
       
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

    Состояния линии и её соотношение относительно нуля помечаются цветом на панели.

    Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Работа с панелью
    //--- Вызываем обработчик событий панели
       panel.OnChartEvent(id,lparam,dparam,sparam);
    
    //--- Если курсор перемещается или щелчок по графику
       if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
         {
          //--- Объявляем переменные для записи в них координат времени и цены
          datetime time=0;
          double price=0;
          int wnd=0;
          //--- Если координаты курсора преобразованы в дату и время
          if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
            {
             //--- записываем индекс бара, где расположен курсор в глобальную переменную
             mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
             //--- Выводим данные бара под курсором на панель
             DrawData(mouse_bar_index,time);
            }
         }
    
    //--- Если получили пользовательское событие - выводим об этом сообщение в журнал
       if(id>CHARTEVENT_CUSTOM)
         {
          //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
          PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
         }
      }
    


    После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



    Файл советника "TestOscillatorTRIX.mq5" можно посмотреть в прикреплённых к статье файлах.


    Williams' Percent Range

    Технический индикатор Процентный Диапазон Вильямса (Williams’ Percent Range, %R) — это динамический индикатор, определяющий состояние перекупленности/перепроданности. Williams’ Percent Range очень похож на технический индикатор Stochastic Oscillator. Различие между ними состоит лишь в том, что первый имеет перевернутую шкалу, а второй строится с использованием внутреннего сглаживания.

    Значения индикатора в диапазоне от -80% до -100% указывают на состояние перепроданности. Значения в диапазоне от -0% до -20% свидетельствуют о том, что рынок перекуплен. Для построения индикатора Williams Percent Range в перевернутой шкале его значениям обычно присваивается отрицательный знак (например, -30%). При анализе отрицательный знак можно не учитывать.

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

    У индикатора Williams Percent Range есть любопытная способность загадочным образом предвосхищать ценовые развороты. Он почти всегда образует пик и поворачивает вниз за определенный промежуток времени до того, как цена достигает пика и поворачивает вниз. Точно так же Williams Percent Range обычно образует впадину и заблаговременно поворачивает вверх.



    Параметры

    Индикатор имеет один настраиваемый параметр: период расчёта. Значение по умолчанию — 14.

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

    Входные и глобальные переменные для использования индикатора в советнике:

    //+------------------------------------------------------------------+
    //|                                            TestOscillatorWPR.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint  InpPeriod   =  14;                     /* Period   */ // Период расчёта WPR
    input double               InpOverbough= -20.0;    /* Overbough level*/ // Уровень перекупленности
    input double               InpOversold = -80.0;    /* Oversold level */ // Уровень перепроданности
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта TRIX
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    double   overbough=0;            // Уровень перекупленности
    double   oversold=0;             // Уровень перепроданности
    string   ind_title;              // Описание индикатора
    

    При использовании информационной панели, подключается файл класса панели и добавляются переменные для работы с ней:

    //+------------------------------------------------------------------+
    //|                                            TestOscillatorWPR.mq5 |
    //|                                  Copyright 2023, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2023, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    //--- includes
    #include <Dashboard\Dashboard.mqh>
    //--- enums
    enum ENUM_LINE_STATE
      {
       LINE_STATE_NONE,        // Неопределённое состояние
       LINE_STATE_UP,          // Направление вверх
       LINE_STATE_DOWN,        // Направление вниз
       LINE_STATE_TURN_UP,     // Разворот вверх
       LINE_STATE_TURN_DOWN,   // Разворот вниз
       LINE_STATE_STOP_UP,     // Остановка направления вверх
       LINE_STATE_STOP_DOWN,   // Остановка направления вниз
       LINE_STATE_ABOVE,       // Над значением
       LINE_STATE_UNDER,       // Под значением
       LINE_STATE_CROSS_UP,    // Пересечение значения вверх
       LINE_STATE_CROSS_DOWN,  // Пересечение значения вниз
       LINE_STATE_TOUCH_BELOW, // Касание значения снизу
       LINE_STATE_TOUCH_ABOVE, // Касание значения сверху
       LINE_STATE_EQUALS,      // Равно значению
      };
    //--- input parameters
    input uint  InpPeriod   =  14;                     /* Period   */ // Период расчёта WPR
    input double               InpOverbough= -20.0;    /* Overbough level*/ // Уровень перекупленности
    input double               InpOversold = -80.0;    /* Oversold level */ // Уровень перепроданности
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period=0;               // Период расчёта TRIX
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    double   overbough=0;            // Уровень перекупленности
    double   oversold=0;             // Уровень перепроданности
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


    Инициализация

    Обработчик OnInit() для инициализации параметров индикатора и создания его хэндла:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта и уровни
       period=int(InpPeriod<1 ? 14 : InpPeriod);
       overbough=InpOverbough;
       oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("%%R(%lu)",period);
       ind_digits=2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iWPR(Symbol(),PERIOD_CURRENT,period);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    

    При использовании информационной панели, создаётся панель:

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта и уровни
       period=int(InpPeriod<1 ? 14 : InpPeriod);
       overbough=InpOverbough;
       oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("%%R(%lu)",period);
       ind_digits=2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iWPR(Symbol(),PERIOD_CURRENT,period);
       if(handle==INVALID_HANDLE)
         {
          PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError());
          return INIT_FAILED;
         }
    
    //--- Панель
    //--- Создаёи панель
       panel=new CDashboard(1,20,20,229,243);
       if(panel==NULL)
         {
          Print("Error. Failed to create panel object");
          return INIT_FAILED;
         }
    //--- Устанавливаем параметры шрифта
       panel.SetFontParams("Calibri",9);
    //--- Отображаем панель с текстом в заголовке "Символ, Описание таймфрейма"
       panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7));
    //--- Создаём таблицу с идентификатором 0 для отображения в ней данных бара
       panel.CreateNewTable(0);
    //--- Рисуем таблицу с идентификатором 0 на фоне панели
       panel.DrawGrid(0,2,20,6,2,18,112);
    
    //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
       panel.CreateNewTable(1);
    //--- Получаем координату Y2 таблицы с идентификатором 0 и
    //--- устанавливаем координату Y1 для таблицы с идентификатором 1
       int y1=panel.TableY2(0)+22;
    //--- Рисуем таблицу с идентификатором 1 на фоне панели
       panel.DrawGrid(1,2,y1,4,2,18,112);
       
    //--- Выводим в журнал табличные данные
       panel.GridPrint(0,2);
       panel.GridPrint(1,2);
    //--- Инициализируем переменную с индексом бара указателя мышки
       mouse_bar_index=0;
    //--- Выводим на панель данные текущего бара
       DrawData(mouse_bar_index,TimeCurrent());
    
    //--- Успешная инициализация
       return(INIT_SUCCEEDED);
      }
    


    Деинициализация

    В обработчике OnDeinit() советника освобождается хэндл индикатора:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
      }
    

    При использовании информационной панели — удаляется созданный объект панели:

    //+------------------------------------------------------------------+
    //| Expert deinitialization function                                 |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- destroy timer
       EventKillTimer();
       
    //--- Освобождаем хэндл индикатора
       ResetLastError();
       if(!IndicatorRelease(handle))
          PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError());
    //--- Очищаем все комментарии на графике
       Comment("");
       
    //--- Если объект панели существует - удаляем его
       if(panel!=NULL)
          delete panel;
      }


    Получение данных

    Общие функции получения данных по хэндлу индикатора:

    //+------------------------------------------------------------------+
    //| Возвращает данные индикатора на указанном баре                   |
    //+------------------------------------------------------------------+
    double IndicatorValue(const int ind_handle,const int index,const int buffer_num)
      {
       double array[1]={0};
       ResetLastError();
       if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1)
         {
          PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError());
          return EMPTY_VALUE;
         }
       return array[0];
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии индикатора                            |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num)
      {
    //--- Получаем значения линии индикатора со смещением (0,1,2) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
       const double value2=IndicatorValue(ind_handle,index+2,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Разворот линии вверх (value2>value1 && value0>value1)
       if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_TURN_UP;
    //--- Направление линии вверх (value2<=value1 && value0>value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0)
          return LINE_STATE_UP;
    //--- Остановка направления линии вверх (value2<=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_UP;
    //--- Разворот линии вниз (value2<value1 && value0<value1)
       if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_TURN_DOWN;
    //--- Направление линии вниз (value2>=value1 && value0<value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0)
          return LINE_STATE_DOWN;
    //--- Остановка направления линии вниз (value2>=value1 && value0==value1)
       else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0)
          return LINE_STATE_STOP_DOWN;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает состояние линии относительно указанного уровня        |
    //+------------------------------------------------------------------+
    ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE)
      {
    //--- Получаем значения линии индикатора со смещением (0,1) относительно переданного индекса
       const double value0=IndicatorValue(ind_handle,index,  buffer_num);
       const double value1=IndicatorValue(ind_handle,index+1,buffer_num);
    //--- Если хоть одно из значений получить не удалось - возвращаем неопределённое значение 
       if(value0==EMPTY_VALUE || value1==EMPTY_VALUE)
          return LINE_STATE_NONE;
    //--- Определяем второй сравниваемый уровень
       double level=(level1==EMPTY_VALUE ? level0 : level1);
    //--- Линия находится под уровнем (value1<level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_UNDER;
    //--- Линия находится над уровнем (value1>level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_ABOVE;
    //--- Линия пересекла уровень снизу-вверх (value1<=level && value0>level0)
       if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0)
          return LINE_STATE_CROSS_UP;
    //--- Линия пересекла уровень сверху-вниз (value1>=level && value0<level0)
       if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0)
          return LINE_STATE_CROSS_DOWN;
    //--- Линия коснулась уровня снизу (value1<level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия коснулась уровня сверху (value1>level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_TOUCH_BELOW;
    //--- Линия равна значению уровня (value1==level0 && value0==level0)
       if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0)
          return LINE_STATE_EQUALS;
    //--- Неопределённое состояние
       return LINE_STATE_NONE;
      }
    //+------------------------------------------------------------------+
    //| Возвращает описание состояния линии индикатора                   |
    //+------------------------------------------------------------------+
    string LineStateDescription(const ENUM_LINE_STATE state)
      {
       switch(state)
         {
          case LINE_STATE_UP         :  return "Up";
          case LINE_STATE_STOP_UP    :  return "Stop Up";
          case LINE_STATE_TURN_UP    :  return "Turn Up";
          case LINE_STATE_DOWN       :  return "Down";
          case LINE_STATE_STOP_DOWN  :  return "Stop Down";
          case LINE_STATE_TURN_DOWN  :  return "Turn Down";
          case LINE_STATE_ABOVE      :  return "Above level";
          case LINE_STATE_UNDER      :  return "Under level";
          case LINE_STATE_CROSS_UP   :  return "Crossing Up";
          case LINE_STATE_CROSS_DOWN :  return "Crossing Down";
          case LINE_STATE_TOUCH_BELOW:  return "Touch from Below";
          case LINE_STATE_TOUCH_ABOVE:  return "Touch from Above";
          case LINE_STATE_EQUALS     :  return "Equals";
          default                    :  return "Unknown";
         }
      }
    

    При использовании информационной панели, данные выводятся на панель при помощи функции:

    //+------------------------------------------------------------------+
    //| Выводит данные с указанного индекса таймсерии на панель          |
    //+------------------------------------------------------------------+
    void DrawData(const int index,const datetime time)
      {
    //--- Объявляем переменные для получения в них данных
       MqlTick  tick={0};
       MqlRates rates[1];
    
    //--- Если текущие цены получить не удалось - уходим
       if(!SymbolInfoTick(Symbol(),tick))
          return;
    //--- Если данные бара по указанному индексу получить не удалось - уходим
       if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1)
          return;
    
    //--- Устанавливаем параметры шрифта для заголовков данных бара и индикатора
       int  size=0;
       uint flags=0;
       uint angle=0;
       string name=panel.FontParams(size,flags,angle);
       panel.SetFontParams(name,9,FW_BOLD);
       panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6);
       panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6);
    //--- Устанавливаем параметры шрифта для данных бара и индикатора
       panel.SetFontParams(name,9);
    
    //--- Выводим на панель данные указанного бара в таблицу 0
       panel.DrawText("Date",  panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_DATE),     panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90);
       panel.DrawText("Time",  panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString(  rates[0].time,TIME_MINUTES),  panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90);
       panel.DrawText("Open",  panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()),      panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90);
       panel.DrawText("High",  panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()),      panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90);
       panel.DrawText("Low",   panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()),       panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90);
       panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()),     panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90);
    
    //--- Выводим на панель данные индикатора с указанного бара в таблицу 1
       panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       double value=IndicatorValue(handle,index,0);
       string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
       
    //--- Получаем состояние линии и состояния относительно уровней
       ENUM_LINE_STATE state=LineState(handle,index,0);
       ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough);
       ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold);
    
    //--- Выводим описание состояния линии индикатора относительно уровня перекупленности
       string ovb=StringFormat("%+.2f",overbough);
       panel.DrawText("Overbough", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
       panel.DrawText(ovb, panel.CellX(1,2,0)+66, panel.CellY(1,2,0)+2);
       color clr=clrNONE;
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       clr=
         (
          state_ovb==LINE_STATE_CROSS_DOWN ||
          (state_ovb==LINE_STATE_ABOVE && state==LINE_STATE_TURN_DOWN) ? clrRed : clrNONE
         );
       string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb));
       panel.DrawText(ovb_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90);
       
    //--- Выводим описание состояния линии индикатора относительно уровня перепроданности
       panel.DrawText("Oversold", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2);
       string ovs=StringFormat("%+.2f",oversold);
       panel.DrawText(ovs, panel.CellX(1,3,0)+68, panel.CellY(1,3,0)+2);
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       clr=
         (
          state_ovs==LINE_STATE_CROSS_UP ||
          (state_ovs==LINE_STATE_UNDER && state==LINE_STATE_TURN_UP) ? clrBlue : clrNONE
         );
       string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs));
       panel.DrawText(ovs_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,90);
       
    //--- Выводим описание состояния линии индикатора
       panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
    //--- Цвет надписи меняется в зависимости от нахождения линии в областях перекупленности/перепроданности
       clr=(state_ovb==LINE_STATE_ABOVE ? clrOrangeRed : state_ovs==LINE_STATE_UNDER ? clrDodgerBlue : clrNONE);
       panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90);
    
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

    Нахождение линии индикатора в зонах перекупленности/перепроданности, сигналы индикатора и состояние линии помечаются цветом на панели.

    Кроме того, в обработчике событий OnChartEvent() советника вызывается обработчик событий панели и обрабатываются события для получения индекса бара под курсором:

    //+------------------------------------------------------------------+
    //| ChartEvent function                                              |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id,
                      const long &lparam,
                      const double &dparam,
                      const string &sparam)
      {
    //--- Работа с панелью
    //--- Вызываем обработчик событий панели
       panel.OnChartEvent(id,lparam,dparam,sparam);
    
    //--- Если курсор перемещается или щелчок по графику
       if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK)
         {
          //--- Объявляем переменные для записи в них координат времени и цены
          datetime time=0;
          double price=0;
          int wnd=0;
          //--- Если координаты курсора преобразованы в дату и время
          if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price))
            {
             //--- записываем индекс бара, где расположен курсор в глобальную переменную
             mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time);
             //--- Выводим данные бара под курсором на панель
             DrawData(mouse_bar_index,time);
            }
         }
    
    //--- Если получили пользовательское событие - выводим об этом сообщение в журнал
       if(id>CHARTEVENT_CUSTOM)
         {
          //--- Здесь может быть обработка щелчка по кнопке закрытия на панели
          PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam);
         }
      }
    


    После компиляции советника и запуска его на графике, можем контролировать состояние линии индикатора на панели:



    Файл советника "TestOscillatorWPR.mq5" можно посмотреть в прикреплённых к статье файлах.


    Заключение

    Здесь мы рассмотрели подключение к советникам индикаторов из категории осцилляторов. При помощи информационной панели мы можем контролировать значения и сигналы индикаторов, получаемый общими универсальными функциями. Все предложенные здесь коды легко переносятся в коды советников, и их можно использовать "как есть" при помощи Copy-Paste, либо загрузить из прикреплённых к статье файлов для самостоятельной доработки.

    Далее рассмотрим в том же ключе остальные индикаторы из стандартной поставки терминала.


    Последние комментарии | Перейти к обсуждению на форуме трейдеров (7)
    Valeriy Yastremskiy
    Valeriy Yastremskiy | 7 сент. 2023 в 12:49
    Artyom Trishkin #:

    InpPeriod - входная переменная input. Минус там никак не поставить. Ноль можно. Такие проверки (на меньше единицы) - привычка не сравнивать напрямую числа с нулём - если вдруг в другой подобной конструкции будет входная переменная с типом double, то это мне позволит избежать прямого сравнения двух double-чисел. Да, это "защита от дурака". В первую очередь от себя самого - чтобы делать меньше ошибок при переносе кода в другие места.

    Ну да, уинт же))) Минус не поставить)))

    Alexey Viktorov
    Alexey Viktorov | 7 сент. 2023 в 12:52
    Valeriy Yastremskiy #:

    Ну да, уинт же))) Минус не поставить)))

    А ноль?

    Artyom Trishkin
    Artyom Trishkin | 7 сент. 2023 в 13:06
    Alexey Viktorov #:

    А ноль?

    А ноль можно. Но для индикатора будет установлено значение по умолчанию в этом случае.

    Только вот смысл вопроса твоего был в чём-то ином - ты это и так знаешь про ноль.

    Но гадать не буду

    Alexey Viktorov
    Alexey Viktorov | 7 сент. 2023 в 13:40
    Artyom Trishkin #:

    А ноль можно. Но для индикатора будет установлено значение по умолчанию в этом случае.

    Только вот смысл вопроса твоего был в чём-то ином - ты это и так знаешь про ноль.

    Но гадать не буду

    Правильно. Гадать не нужно. Просто был вопрос Валерию. Минус не поставить, а ноль?

    Valeriy Yastremskiy
    Valeriy Yastremskiy | 14 сент. 2023 в 11:17
    Alexey Viktorov #:

    А ноль?

    Так ноль это текущий))) Как защита от дурака нормальная конструкция)))

    А можно маску на клаву наложить, что бы нажимались только цифры, а остальные клавиши не нажимались))) Для солдат в 90х делал))) И при нажатии на другие клавиши, появлялось предупреждение большими буквами)

    Разработка системы репликации - Моделирование рынка (Часть 11): Появление СИМУЛЯТОРА (I) Разработка системы репликации - Моделирование рынка (Часть 11): Появление СИМУЛЯТОРА (I)
    Для того, чтобы использовать данные, формирующие бары, мы должны отказаться от репликации и заняться разработкой симулятора. Мы будем использовать 1-минутные бары именно потому, что они предлагают минимальный уровень сложности.
    Разработка системы репликации - Моделирование рынка (Часть 10): Только реальные данные для репликации Разработка системы репликации - Моделирование рынка (Часть 10): Только реальные данные для репликации
    Здесь мы рассмотрим, как более надежные данные (торгуемые тики) можно использовать в системе репликации, не беспокоясь о том, скорректированы они или нет.
    Теория категорий в MQL5 (Часть 12): Порядок Теория категорий в MQL5 (Часть 12): Порядок
    Статья является частью серии о реализации графов средствами теории категорий в MQL5 и посвящена отношению порядка (Order Theory). Мы рассмотрим два основных типа упорядочения и исследуем, как концепции отношения порядка могут поддерживать моноидные множества при принятии торговых решений.
    Теория категорий в MQL5 (Часть 11): Графы Теория категорий в MQL5 (Часть 11): Графы
    Статья продолжает серию о реализации теории категорий в MQL5. Здесь мы рассмотрим, как теория графов может быть интегрирована с моноидами и другими структурами данных при разработке стратегии закрытия торговой системы.