preview
Готовые шаблоны для подключения индикаторов в экспертах (Часть 2): Индикакторы объёма и Билла Вильямса

Готовые шаблоны для подключения индикаторов в экспертах (Часть 2): Индикакторы объёма и Билла Вильямса

MetaTrader 5Примеры | 19 сентября 2023, 15:04
314 0
Artyom Trishkin
Artyom Trishkin

Содержание


Введение

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

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

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


Индикаторы объемов

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


Accumulation/Distribution

Технический индикатор Накопления/Распределения (Accumulation Distribution, A/D) определяется изменением цены и объема. Объем выступает в роли весового коэффициента при изменении цены — чем больше коэффициент (объем), тем значительнее вклад изменения цены (за данный промежуток времени) в значение индикатора.

Фактически, этот индикатор — вариант более распространенного индикатора Балансового Объема (On Balance Volume). Оба они используются для подтверждения ценовых изменений путем измерения соответствующего объема торгов.

Рост индикатора Accumulation/Distribution (A/D) означает накопление (покупку) ценной бумаги, поскольку подавляющая доля объема торгов связана с восходящим движением цен. Когда индикатор падает, это означает распределение (продажу) ценной бумаги, поскольку подавляющая доля объема торгов связана с нисходящим движением цен.

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



Параметры

Для создания хэндла индикатора используется функция iAD():

Возвращает хэндл индикатора Accumulation/Distribution. Всего один буфер.

int  iAD(
   string               symbol,             // имя символа
   ENUM_TIMEFRAMES      period,             // период
   ENUM_APPLIED_VOLUME  applied_volume      // тип объема для расчета
   );

symbol

[in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

period

[in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

applied_volume

[in]   Используемый объем. Может быть любой из ENUM_APPLIED_VOLUME.

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

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

//+------------------------------------------------------------------+
//|                                                 TestVolumeAD.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 ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта A/D
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора

Перечисление ENUM_LINE_STATE создано для упрощения получения состояния линии индикатора — её формы (фигуры) и расположения относительно линии другого индикатора, либо какого-либо уровня.
Подробнее о перечислении можно прочитать в прошлой статье в разделе параметров индикатора ATR.

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

//+------------------------------------------------------------------+
//|                                                 TestVolumeAD.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 ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта A/D
//--- global variables
int      handle=INVALID_HANDLE;  // Хэндл индикатора
int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
string   ind_title;              // Описание индикатора
//--- переменные для панели
int      mouse_bar_index;        // Индекс бара, с которого берутся данные
CDashboard *panel=NULL;          // Указатель на объект панели


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

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

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

//--- Индикатор
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title="A/D";
   ind_digits=0;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iAD(Symbol(),PERIOD_CURRENT,InpVolume);
   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);

//--- Индикатор
//--- Устанавливаем наименование индикатора и количество знаков после запятой
   ind_title="A/D";
   ind_digits=0;
//--- Создаём хэндл индикатора
   ResetLastError();
   handle=iAD(Symbol(),PERIOD_CURRENT,InpVolume);
   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);
     }
  }

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


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


Money Flow Index

Технический индикатор Индекс Денежных Потоков (Money Flow Index, MFI) показывает интенсивность, с которой деньги вкладываются в ценную бумагу или выводятся из нее. Построение и интерпретация индикатора аналогична Relative Strength Index, с той только разницей, что в MFI учитывается и объем.

При анализе Money Flow Index следует учитывать:

  • расхождения между индикатором и движением цен. Если цены растут, а значение Money Flow Index падает (или наоборот), то велика вероятность разворота цен;
  • значение Money Flow Index выше 80 и ниже 20 сигнализирует соответственно о потенциальной вершине и основании рынка.



    Параметры

    Для создания хэндла индикатора используется функция iMFI():

    int  iMFI(
       string               symbol,             // имя символа
       ENUM_TIMEFRAMES      period,             // период
       int                  ma_period,          // период усреднения
       ENUM_APPLIED_VOLUME  applied_volume      // тип объема для расчета
       );

    symbol

    [in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

    period

    [in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

    ma_period

    [in]  Период(количество баров) для вычисления индикатора.

    applied_volume

    [in]  Используемый объем. Может быть любой из ENUM_APPLIED_VOLUME.

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

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

    //+------------------------------------------------------------------+
    //|                                                TestVolumeMFI.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         */ // Период расчёта MFI
    input ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта MFI
    input double               InpOverbough=  80;            /* Overbough level*/ // Уровень перекупленности
    input double               InpOversold =  20;            /* 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;              // Описание индикатора
    

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

    //+------------------------------------------------------------------+
    //|                                                TestVolumeMFI.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         */ // Период расчёта MFI
    input ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта MFI
    input double               InpOverbough=  80;            /* Overbough level*/ // Уровень перекупленности
    input double               InpOversold =  20;            /* 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;          // Указатель на объект панели
    


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

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

    //+------------------------------------------------------------------+
    //| 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("MFI(%lu)",period);
       ind_digits=Digits();
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iMFI(Symbol(),PERIOD_CURRENT,period,InpVolume);
       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("MFI(%lu)",period);
       ind_digits=Digits();
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iMFI(Symbol(),PERIOD_CURRENT,period,InpVolume);
       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);
         }
      }
    

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


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


    On Balance Volume

    Технический индикатор Балансового Объема (On Balance Volume, OBV) связывает объем и изменение цены, сопровождавшее данный объем. Смысл этого индикатора, придуманного Джозефом Гранвиллем, прост. Если цена закрытия текущего бара выше закрытия предыдущего, значение объема текущего бара прибавляется к предыдущему значению OBV, если закрытие текущего бара ниже предыдущего, текущий объем вычитается из предыдущего значения Балансового Объема.

    Интерпретация индикатора On Balance Volume основана на принципе, что изменения OBV опережают ценовые. Согласно этому принципу, повышение балансового объема свидетельствует о том, что в инструмент вкладывают средства профессионалы. Когда позднее и широкая публика начинает вкладывать, и цена, и показания индикатора OBV начинают стремительно расти.

    Если цена опережает в своем движении индикатор On Balance Volume, возникает так называемое "отсутствие подтверждения". Это может наблюдаться на вершине бычьего рынка (когда цена растет без соответствующего роста Балансового Объема или опережая его) или в основании медвежьего рынка (когда цена падает без соответствующего уменьшения Балансового Объема или опережая его).

    О восходящей тенденции On Balance Volume можно говорить, если каждый новый пик выше предыдущего, и каждая новая впадина выше предыдущей. По аналогии, нисходящая тенденция OBV предполагает последовательное понижение пиков и впадин. Когда OBV движется в горизонтальном коридоре, не образуя последовательно повышающихся или понижающихся пиков и впадин — это неопределенная тенденция.

    Если тенденция установилась, она остается в силе до момента перелома. Перелом в тенденции индикатора On Balance Volume может произойти двумя способами. В первом случае тенденция изменяется с восходящей на нисходящую, или с нисходящей на восходящую.

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

    Когда тенденция индикатора On Balance Volume меняется на восходящую или нисходящую, происходит так называемый «прорыв». Поскольку прорывы индикатора обычно предшествуют ценовым прорывам, инвесторам следует занимать длинные позиции при прорывах OBV вверх и, соответственно, продавать в случае прорыва OBV вниз. Открытые позиции нужно сохранять до тех пор, пока направление тенденции не изменится.



    Параметры

    Для создания хэндла индикатора используется функция iOBV():

    Возвращает хэндл индикатора On Balance Volume. Всего один буфер.

    int  iOBV(
       string                symbol,             // имя символа
       ENUM_TIMEFRAMES       period,             // период
       ENUM_APPLIED_VOLUME   applied_volume      // тип объема для расчета
       );
    symbol

    [in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

    period

    [in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

    applied_volume

    [in]  Используемый объем. Может быть любой из значений перечисления ENUM_APPLIED_VOLUME.

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

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

    //+------------------------------------------------------------------+
    //|                                                TestVolumeOBV.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 ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта OBV
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

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

    //+------------------------------------------------------------------+
    //|                                                TestVolumeOBV.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 ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта OBV
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


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

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

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title="OBV";
       ind_digits=0;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iOBV(Symbol(),PERIOD_CURRENT,InpVolume);
       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);
    
    //--- Индикатор
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title="OBV";
       ind_digits=0;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iOBV(Symbol(),PERIOD_CURRENT,InpVolume);
       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);
         }
      }
    

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


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


    Volumes

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



    Параметры

    Для создания хэндла индикатора используется функция iVolumes():

    Возвращает хэндл индикатора, отображающего объемы . Всего один буфер.

    int  iVolumes(
       string               symbol,             // имя символа
       ENUM_TIMEFRAMES      period,             // период
       ENUM_APPLIED_VOLUME  applied_volume      // тип объема
       )

    symbol

    [in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

    period

    [in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

    applied_volume

    [in]  Используемый объем. Может быть любой из значений перечисления ENUM_APPLIED_VOLUME.

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

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

    //+------------------------------------------------------------------+
    //|                                                TestVolumeOBV.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 ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта Volumes
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

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

    //+------------------------------------------------------------------+
    //|                                                TestVolumeOBV.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 ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта Volumes
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


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

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

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title="Volumes";
       ind_digits=0;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iVolumes(Symbol(),PERIOD_CURRENT,InpVolume);
       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);
    
    //--- Индикатор
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title="Volumes";
       ind_digits=0;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iVolumes(Symbol(),PERIOD_CURRENT,InpVolume);
       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 value0=IndicatorValue(handle,index,  0);
       double value1=IndicatorValue(handle,index+1,0);
       string value_str=(value0!=EMPTY_VALUE ? DoubleToString(value0,ind_digits) : "");
       color clr=(value0>value1 ? clrGreen : value0<value1 ? clrRed : clrNONE);
       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,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);
         }
      }
    

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


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


    Индикаторы Билла Вильямса

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


    Accelerator Oscillator

    Цена — это последний элемент, который изменяется. Прежде чем изменится цена, изменяется движущая сила рынка, а перед тем, как движущая сила изменяет свое направление, ускорение движущей силы должно замедлиться и дойти до нуля. Затем она начинает ускоряться в противоположном направлении до тех пор, пока цена не начнет изменять свое направление.

    Технический индикатор Ускорения/Замедления (Acceleration/Deceleration, Accelerator/Decelerator Oscillator, AC) измеряет ускорение и замедление текущей движущей силы. Этот индикатор будет изменять направление перед изменением движущей силы, а она в свою очередь будет изменять свое направление перед изменением цены. Понимание того, что АС является более ранним предупреждающим сигналом, дает очевидные преимущества.

    Нулевая линия — это, по существу, место, где движущая сила сбалансирована с ускорением. Если Осциллятор Ускорения/Замедления выше нуля, то обычно бывает легче для ускорения продолжить движение наверх (и — наоборот, когда ниже нуля). В отличие от Awesome Oscillator, пересечение нулевой линии не представляет собой сигнала. Единственное, что необходимо делать, чтобы контролировать рынок и принимать решения — это следить за изменением цвета. Чтобы избежать серьезных размышлений, необходимо помнить: при помощи АС нежелательно покупать, когда текущий столбец окрашен в красный цвет, и нежелательно продавать, когда текущий столбец окрашен в зеленый цвет.

    Если входить в рынок в направлении движущей силы (индикатор Acceleration/Deceleration выше нуля, при покупке, или он ниже нуля, при продаже), то необходимы только два зеленых столбца, чтобы купить (два красных столбца, чтобы продать). Если движущая сила против открываемой позиции (индикатор ниже нуля, при покупке, или выше нуля, при продаже), необходимо подтверждение, поэтому требуется дополнительный столбец. В этом случае необходимо, чтобы индикатор показывал три красных столбца выше нулевой линии, для короткой позиции и три зеленых столбца ниже нулевой линии, для длинной.


    Параметры

    Для создания хэндла индикатора используется функция iAC():

    Создает индикатор Accelerator Oscillator и возвращает его хэндл. Всего один буфер.

    int  iAC(
       string           symbol,     // имя символа
       ENUM_TIMEFRAMES  period      // период
       );

    symbol

    [in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

    period

    [in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

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

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

    //+------------------------------------------------------------------+
    //|                                               TestWilliamsAC.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,      // Равно значению
      };
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

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

    //+------------------------------------------------------------------+
    //|                                               TestWilliamsAC.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,      // Равно значению
      };
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


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

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

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title="AC";
       ind_digits=Digits()+2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iAC(Symbol(),PERIOD_CURRENT);
       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);
    
    //--- Индикатор
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title="AC";
       ind_digits=Digits()+2;
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iAC(Symbol(),PERIOD_CURRENT);
       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 value0=IndicatorValue(handle,index,  0);
       double value1=IndicatorValue(handle,index+1,0);
       string value_str=(value0!=EMPTY_VALUE ? DoubleToString(value0,ind_digits) : "");
       color clr=(value0>value1 ? clrGreen : value0<value1 ? clrRed : clrNONE);
       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,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);
         }
      }
    

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


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


    Alligator

    Большую часть времени рынок никуда не движется. Только 15-30% от всего времени рынок образует какие-то тенденции и трейдеры, которые не находятся в биржевом зале, почти все свои прибыли извлекают из трендовых движений. Мой дед часто повторял: "Даже слепая курица найдет зерна, если ее кормить в одно и то же время". Мы называем торговлю по тренду "рынок слепой курицы". Хотя нам понадобились годы, мы все-таки разработали индикатор, который всегда позволяет нам "держать порох сухим" до тех пор, пока мы не окажемся в "рынке слепой курицы".
    Билл Вильямс

    Технический индикатор Alligator — это комбинация Линий Баланса (Скользящих Средних, Moving Averages) , использующих фрактальную геометрию и нелинейную динамику.

    • Синяя линия (Челюсть Аллигатора) — это Линия Баланса для временного периода, который использовался для построения графика (13-периодное сглаженное скользящее среднее, сдвинутое на 8 баров в будущее);
    • Красная линия (Зубы Аллигатора) — это Линия Баланса для значимого временного периода на порядок ниже (8-периодное сглаженное скользящее среднее, сдвинутое на 5 баров в будущее);
    • Зеленая линия (Губы Аллигатора) — это Линия Баланса для значимого временного периода, который ниже еще на один порядок (5-периодное сглаженное скользящее среднее, сдвинутое на 3 бара в будущее).

    Губы, Зубы и Челюсть Аллигатора показывают взаимодействие разных временных периодов. Поскольку тренды на рынке можно выделить лишь в течение 15-30 процентов времени, то необходимо следовать трендам и не работать на рынках, изменяющихся только в пределах определенных ценовых периодов.

    Когда Челюсть, Зубы и Губы закрыты или переплетены, Аллигатор собирается спать или уже спит. Когда он спит, его голод увеличивается — чем дольше он спит, тем более голодным он будет, когда проснется. Когда он просыпается, первое, что он делает, — это открывает свою Пасть и начинает зевать. Затем он начинает чуять запах пищи: мясо быка или мясо медведя, и начинает за ним охотиться. Когда Аллигатор основательно наестся, он начинает терять интерес к пище-цене (Линии Баланса сходятся) — это время для фиксирования прибыли.



    Параметры

    Для создания хэндла индикатора используется функция iAlligator():

    Возвращает хэндл индикатора Alligator.

    int  iAlligator(
       string              symbol,            // имя символа
       ENUM_TIMEFRAMES     period,            // период
       int                 jaw_period,        // период для расчета челюстей
       int                 jaw_shift,         // смещение челюстей по горизонтали
       int                 teeth_period,      // период для расчета зубов
       int                 teeth_shift,       // смещение зубов по горизонтали
       int                 lips_period,       // период для расчета губ
       int                 lips_shift,        // смещение губ по горизонтали
       ENUM_MA_METHOD      ma_method,         // тип сглаживания
       ENUM_APPLIED_PRICE  applied_price      // тип цены или handle
       );

    symbol

    [in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

    period

    [in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

    jaw_period

    [in]  Период усреднения синей линии (челюсти аллигатора).

    jaw_shift

    [in]  Смещение синей линии относительно графика цены.

    teeth_period

    [in]  Период усреднения красной линии (зубов аллигатора).

    teeth_shift

    [in]  Смещение красной линии относительно графика цены.

    lips_period

    [in]  Период усреднения зеленой линии (губ аллигатора).

    lips_shift

    [in]  Смещение зеленой линии относительно графика цены.

    ma_method

    [in]  Метод усреднения. Может быть любым из значений перечисления ENUM_MA_METHOD.

    applied_price

    [in]  Используемая цена. Может быть любой из ценовых констант ENUM_APPLIED_PRICE или хэндлом другого индикатора.

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

    Номера буферов: 0 — GATORJAW_LINE, 1 — GATORTEETH_LINE, 2 — GATORLIPS_LINE.


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

    //+------------------------------------------------------------------+
    //|                                        TestWilliamsAlligator.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                 InpPeriodJaws  =  13;            /* Jaws Period    */ // Период расчёта линии "Челюсти"
    input int                  InpShiftJaws   =  8;             /* Jaws Shift     */ // Сдвиг линии "Челюсти"
    input uint                 InpPeriodTeeth =  8;             /* Teeth Period   */ // Период расчёта линии "Зубы"
    input int                  InpShiftTeeth  =  5;             /* Teeth Shift    */ // Сдвиг линии "Зубы"
    input uint                 InpPeriodLips  =  5;             /* Lips Period    */ // Период расчёта линии "Губы"
    input int                  InpShiftLips   =  3;             /* Lips Shift     */ // Сдвиг линии "Губы"
    input ENUM_MA_METHOD       InpMethod      =  MODE_SMMA;     /* Smoothed       */ // Метод сглаживания
    input ENUM_APPLIED_PRICE   InpAppliedPrice=  PRICE_MEDIAN;  /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period_jaws=0;          // Период расчёта линии "Челюсти"
    int      period_teeth=0;         // Период расчёта линии "Зубы"
    int      period_lips=0;          // Период расчёта линии "Губы"
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    

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

    //+------------------------------------------------------------------+
    //|                                        TestWilliamsAlligator.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                 InpPeriodJaws  =  13;            /* Jaws Period    */ // Период расчёта линии "Челюсти"
    input int                  InpShiftJaws   =  8;             /* Jaws Shift     */ // Сдвиг линии "Челюсти"
    input uint                 InpPeriodTeeth =  8;             /* Teeth Period   */ // Период расчёта линии "Зубы"
    input int                  InpShiftTeeth  =  5;             /* Teeth Shift    */ // Сдвиг линии "Зубы"
    input uint                 InpPeriodLips  =  5;             /* Lips Period    */ // Период расчёта линии "Губы"
    input int                  InpShiftLips   =  3;             /* Lips Shift     */ // Сдвиг линии "Губы"
    input ENUM_MA_METHOD       InpMethod      =  MODE_SMMA;     /* Smoothed       */ // Метод сглаживания
    input ENUM_APPLIED_PRICE   InpAppliedPrice=  PRICE_MEDIAN;  /* Applied Price  */ // Цена расчёта
    //--- global variables
    int      handle=INVALID_HANDLE;  // Хэндл индикатора
    int      period_jaws=0;          // Период расчёта линии "Челюсти"
    int      period_teeth=0;         // Период расчёта линии "Зубы"
    int      period_lips=0;          // Период расчёта линии "Губы"
    int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
    string   ind_title;              // Описание индикатора
    //--- переменные для панели
    int      mouse_bar_index;        // Индекс бара, с которого берутся данные
    CDashboard *panel=NULL;          // Указатель на объект панели
    


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

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

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- create timer
       EventSetTimer(60);
    
    //--- Индикатор
    //--- Устанавливаем и корректируем при необходимости период расчёта
       period_jaws=int(InpPeriodJaws<1 ? 13 : InpPeriodJaws);
       period_teeth=int(InpPeriodTeeth<1 ? 8 : InpPeriodTeeth);
       period_lips=int(InpPeriodLips<1 ? 5 : InpPeriodLips);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("Alligator(%lu,%lu,%lu)",period_jaws,period_teeth,period_lips);
       ind_digits=Digits();
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iAlligator(Symbol(),PERIOD_CURRENT,period_jaws,InpShiftJaws,period_teeth,InpShiftTeeth,period_lips,InpShiftLips,InpMethod,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_jaws=int(InpPeriodJaws<1 ? 13 : InpPeriodJaws);
       period_teeth=int(InpPeriodTeeth<1 ? 8 : InpPeriodTeeth);
       period_lips=int(InpPeriodLips<1 ? 5 : InpPeriodLips);
    //--- Устанавливаем наименование индикатора и количество знаков после запятой
       ind_title=StringFormat("Alligator(%lu,%lu,%lu)",period_jaws,period_teeth,period_lips);
       ind_digits=Digits();
    //--- Создаём хэндл индикатора
       ResetLastError();
       handle=iAlligator(Symbol(),PERIOD_CURRENT,period_jaws,InpShiftJaws,period_teeth,InpShiftTeeth,period_lips,InpShiftLips,InpMethod,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);
    
    //--- Получаем данные линий индикатора
       double value_jaws=IndicatorValue(handle,index,GATORJAW_LINE);
       double value_teeth=IndicatorValue(handle,index,GATORTEETH_LINE);
       double value_lips=IndicatorValue(handle,index,GATORLIPS_LINE);
       
    //--- Выводим на панель данные линии Jaws с указанного бара в таблицу 1
       string jaws_str=StringFormat("Jaws(%lu)",period_jaws);
       panel.DrawText(jaws_str, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
       string value_str=(value_jaws!=EMPTY_VALUE ? DoubleToString(value_jaws,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
    //--- Выводим на панель данные линии Teeth с указанного бара в таблицу 1
       string teeth_str=StringFormat("Teeth(%lu)",period_teeth);
       panel.DrawText(teeth_str, panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
       value_str=(value_teeth!=EMPTY_VALUE ? DoubleToString(value_teeth,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,90);
    //--- Выводим на панель данные линии Lips с указанного бара в таблицу 1
       string lips_str=StringFormat("Lips(%lu)",period_jaws);
       panel.DrawText(lips_str, panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2);
       value_str=(value_lips!=EMPTY_VALUE ? DoubleToString(value_lips,ind_digits) : "");
       panel.DrawText(value_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clrNONE,90);
       
    //--- Выводим описание состояния линии Teeth относительно линии Jaws
       panel.DrawText("Teeth vs Jaws", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2);
       ENUM_LINE_STATE state_tj=LineStateRelative(handle,index,1,value_jaws,IndicatorValue(handle,index+1,GATORJAW_LINE));
       string state_tj_str=
         (
          state_tj==LINE_STATE_ABOVE        ?  "Teeth > Jaws"  : 
          state_tj==LINE_STATE_UNDER        ?  "Teeth < Jaws"  : 
          state_tj==LINE_STATE_TOUCH_ABOVE  || 
          state_tj==LINE_STATE_TOUCH_BELOW  ?  "Touch"     :
          LineStateDescription(state_tj)
         );
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       color clr=(state_tj==LINE_STATE_CROSS_UP || state_tj==LINE_STATE_ABOVE  ? clrBlue : state_tj==LINE_STATE_CROSS_DOWN || state_tj==LINE_STATE_UNDER ? clrRed : clrNONE);
       panel.DrawText(state_tj_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,90);
       
    //--- Выводим описание состояния линии Lips относительно линии Teeth
       panel.DrawText("Lips vs Teeth", panel.CellX(1,4,0)+2, panel.CellY(1,4,0)+2);
       ENUM_LINE_STATE state_lt=LineStateRelative(handle,index,2,value_teeth,IndicatorValue(handle,index+1,GATORTEETH_LINE));
       string state_lt_str=
         (
          state_lt==LINE_STATE_ABOVE        ?  "Lips > Teeth"  : 
          state_lt==LINE_STATE_UNDER        ?  "Lips < Teeth"  : 
          state_lt==LINE_STATE_TOUCH_ABOVE  || 
          state_lt==LINE_STATE_TOUCH_BELOW  ?  "Touch"     :
          LineStateDescription(state_lt)
         );
    //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
       clr=(state_lt==LINE_STATE_CROSS_UP || state_lt==LINE_STATE_ABOVE  ? clrBlue : state_lt==LINE_STATE_CROSS_DOWN || state_lt==LINE_STATE_UNDER ? clrRed : clrNONE);
       panel.DrawText(state_lt_str,panel.CellX(1,4,1)+2,panel.CellY(1,4,1)+2,clr,90);
       
    //--- Перерисовываем график для немедленного отображения всех изменений на панели
       ChartRedraw(ChartID());
      }
    

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

    Кроме того, при использовании информационной панели в обработчике событий 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);
         }
      }
    

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


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


    Awesome Oscillator

    Технический индикатор Чудесный Осциллятор Билла Вильямса (Awesome Oscillator, AO) — это 34-периодное простое скользящее среднее, построенное по средним точкам баров (H+L)/2, которое вычтено из 5-периодного простого скользящего среднего, построенного по центральным точкам баров (H+L)/2. Он точно говорит нам, что происходит в текущий момент времени с движущей силой рынка.

    Сигналы на покупку

    "Блюдце" — это единственный сигнал на покупку, который возникает, когда гистограмма находится выше нулевой линии. Необходимо помнить следующее:
    • сигнал "Блюдце" образуется, когда гистограмма меняет направление с нисходящего на восходящее. Второй столбец ниже первого и он окрашен в красный цвет. Третий столбец выше второго и он зеленый;
    • для образования сигнала "Блюдце" необходимо, по крайней мере, три столбца гистограммы.

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

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

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

    "Два пика" — это единственный сигнал на покупку, который может образоваться, когда значения гистограммы лежат ниже нулевой линии. Здесь необходимо помнить следующее:

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

      Сигналы на продажу

      Сигналы на продажу Awesome Oscillator идентичны сигналам на покупку. Сигнал "Блюдце" перевернут, и находится ниже нуля. "Пересечение нулевой линии" идет по убыванию — первый столбец выше нуля, второй ниже. А "два пика" выше нулевой линии и тоже перевернут.



      Параметры

      Для создания хэндла индикатора используется функция iAO():

      Возвращает хэндл индикатора Awesome Oscillator. Всего один буфер.

      int  iAO(
         string           symbol,     // имя символа
         ENUM_TIMEFRAMES  period      // период
         );

      symbol

      [in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

      period

      [in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

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


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

      //+------------------------------------------------------------------+
      //|                                               TestWilliamsAO.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,      // Равно значению
        };
      //--- global variables
      int      handle=INVALID_HANDLE;  // Хэндл индикатора
      int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
      string   ind_title;              // Описание индикатора
      

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

      //+------------------------------------------------------------------+
      //|                                               TestWilliamsAO.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,      // Равно значению
        };
      //--- global variables
      int      handle=INVALID_HANDLE;  // Хэндл индикатора
      int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
      string   ind_title;              // Описание индикатора
      //--- переменные для панели
      int      mouse_bar_index;        // Индекс бара, с которого берутся данные
      CDashboard *panel=NULL;          // Указатель на объект панели
      


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

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

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //--- create timer
         EventSetTimer(60);
      
      //--- Индикатор
      //--- Устанавливаем наименование индикатора и количество знаков после запятой
         ind_title="AO";
         ind_digits=Digits()+1;
      //--- Создаём хэндл индикатора
         ResetLastError();
         handle=iAO(Symbol(),PERIOD_CURRENT);
         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);
      
      //--- Индикатор
      //--- Устанавливаем наименование индикатора и количество знаков после запятой
         ind_title="AO";
         ind_digits=Digits()+1;
      //--- Создаём хэндл индикатора
         ResetLastError();
         handle=iAO(Symbol(),PERIOD_CURRENT);
         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 value0=IndicatorValue(handle,index,  0);
         double value1=IndicatorValue(handle,index+1,0);
         string value_str=(value0!=EMPTY_VALUE ? DoubleToString(value0,ind_digits) : "");
         color clr=(value0>value1 ? clrGreen : value0<value1 ? clrRed : clrNONE);
         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,clr,90);
         
      //--- Выводим описание состояния линии индикатора относительно нуля
         panel.DrawText("AO 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        ?  "AO > 0"  : 
            state_zero==LINE_STATE_UNDER        ?  "AO < 0"  : 
            state_zero==LINE_STATE_TOUCH_ABOVE  || 
            state_zero==LINE_STATE_TOUCH_BELOW  ?  "Touch"     :
            LineStateDescription(state_zero)
           );
      //--- Цвет надписи меняется в зависимости от значения линии относительно уровня
         clr=(state_zero==LINE_STATE_CROSS_UP ? clrGreen : 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);
           }
        }
      

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


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


      Fractals

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

      Фракталы (Fractals) — это один из пяти индикаторов торговой системы Билла Вильямса, позволяющий обнаруживать дно или вершину. Техническое определение фрактала вверх — это серия из минимум пяти последовательных баров, в которой перед самым высоким максимумом и за ним находятся по два бара с более низкими максимумами. Противоположная конфигурация (серия из пяти баров, в которой перед самым низким минимумом и за ним находятся по два бара с более высокими минимумами) соответствует фракталу вниз. На графике фракталы имеют значения High и Low и отмечены стрелками вверх или вниз.

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



      Параметры

      Для создания хэндла индикатора используется функция iFractals():

      Возвращает хэндл индикатора Fractals.

      int  iFractals(
         string           symbol,     // имя символа
         ENUM_TIMEFRAMES  period      // период
         );

      symbol

      [in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

      period

      [in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

      Возвращаемое значение

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

      Номера буферов: 0 — UPPER_LINE, 1 — LOWER_LINE.


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

      //+------------------------------------------------------------------+
      //|                                         TestWilliamsFractals.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"
      //--- global variables
      int      handle=INVALID_HANDLE;  // Хэндл индикатора
      int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
      string   ind_title;              // Описание индикатора
      

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

      //+------------------------------------------------------------------+
      //|                                         TestWilliamsFractals.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>
      //--- global variables
      int      handle=INVALID_HANDLE;  // Хэндл индикатора
      int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
      string   ind_title;              // Описание индикатора
      //--- переменные для панели
      int      mouse_bar_index;        // Индекс бара, с которого берутся данные
      CDashboard *panel=NULL;          // Указатель на объект панели
      


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

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

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //--- create timer
         EventSetTimer(60);
      
      //--- Индикатор
      //--- Устанавливаем наименование индикатора и количество знаков после запятой
         ind_title="Fractals";
         ind_digits=Digits();
      //--- Создаём хэндл индикатора
         ResetLastError();
         handle=iFractals(Symbol(),PERIOD_CURRENT);
         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);
      
      //--- Индикатор
      //--- Устанавливаем наименование индикатора и количество знаков после запятой
         ind_title="Fractals";
         ind_digits=Digits();
      //--- Создаём хэндл индикатора
         ResetLastError();
         handle=iFractals(Symbol(),PERIOD_CURRENT);
         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+" Up", panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
         double value0=IndicatorValue(handle,index,UPPER_LINE);
         string value_str0=(value0!=EMPTY_VALUE ? DoubleToString(value0,ind_digits) : " ");
         panel.DrawText(value_str0,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90);
      
      //--- Выводим на панель данные индикатора с указанного бара в таблицу 1 (нижний фрактал)
         panel.DrawText(ind_title+" Down", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
         double value1=IndicatorValue(handle,index,LOWER_LINE);
         string value_str1=(value1!=EMPTY_VALUE ? DoubleToString(value1,ind_digits) : " ");
         panel.DrawText(value_str1,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);
           }
        }
      

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


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


      Gator Oscillator

      Gator Oscillator строится на основе Alligator и показывает степень схождения/расхождения его линий баланса (сглаженное скользящее среднее). Верхняя гистограмма — абсолютная разница между значениями синей линии и красной линии. Нижняя гистограмма — абсолютная разница между значениями красной линии и зеленой линии, но со знаком минус, потому что гистограмма рисуется сверху вниз.


      Параметры

      Для создания хэндла индикатора используется функция iGator():

      Возвращает хэндл индикатора Gator. Осциллятор показывает разницу между синей и красной линией Аллигатора (верхняя гистограмма) и разницу между красной и зеленой линией (нижняя гистограмма).

      int  iGator(
         string              symbol,            // имя символа
         ENUM_TIMEFRAMES     period,            // период
         int                 jaw_period,        // период для расчета челюстей
         int                 jaw_shift,         // смещение челюстей по горизонтали
         int                 teeth_period,      // период для расчета зубов
         int                 teeth_shift,       // смещение челюстей по зубов
         int                 lips_period,       // период для расчета губ
         int                 lips_shift,        // смещение губ по горизонтали 
         ENUM_MA_METHOD      ma_method,         // тип сглаживания
         ENUM_APPLIED_PRICE  applied_price      // тип цены или handle
         );

      symbol

      [in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

      period

      [in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

      jaw_period

      [in]  Период усреднения синей линии (челюсти аллигатора).

      jaw_shift

      [in]  Смещение синей линии Аллигатора относительно графика цены. Не имеет напрямую отношения к визуальному смещению гистограммы индикатора.

      teeth_period

      [in]  Период усреднения красной линии (зубов аллигатора).

      teeth_shift

      [in]  Смещение красной линии Аллигатора относительно графика цены. Не имеет напрямую отношения к визуальному смещению гистограммы индикатора.

      lips_period

      [in]  Период усреднения зеленой линии (губ аллигатора).

      lips_shift

      [in]  Смещение зеленой линии Аллигатора относительно графика цены. Не имеет напрямую отношения к визуальному смещению гистограммы индикатора.

      ma_method

      [in]  Метод усреднения. Может быть любым из значений ENUM_MA_METHOD.

      applied_price

      [in]  Используемая цена. Может быть любой из ценовых констант ENUM_APPLIED_PRICE или хендлом другого индикатора.

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

      Номера буферов: 0 — UPPER_HISTOGRAM, 1- цветовой буфер верхней гистограммы, 2 — LOWER_HISTOGRAM, 3- цветовой буфер нижней гистограммы.


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

      //+------------------------------------------------------------------+
      //|                                            TestWilliamsGator.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                 InpPeriodJaws  =  13;            /* Jaws Period    */ // Период расчёта линии "Челюсти"
      input int                  InpShiftJaws   =  8;             /* Jaws Shift     */ // Сдвиг линии "Челюсти"
      input uint                 InpPeriodTeeth =  8;             /* Teeth Period   */ // Период расчёта линии "Зубы"
      input int                  InpShiftTeeth  =  5;             /* Teeth Shift    */ // Сдвиг линии "Зубы"
      input uint                 InpPeriodLips  =  5;             /* Lips Period    */ // Период расчёта линии "Губы"
      input int                  InpShiftLips   =  3;             /* Lips Shift     */ // Сдвиг линии "Губы"
      input ENUM_MA_METHOD       InpMethod      =  MODE_SMMA;     /* Smoothed       */ // Метод сглаживания
      input ENUM_APPLIED_PRICE   InpAppliedPrice=  PRICE_MEDIAN;  /* Applied Price  */ // Цена расчёта
      //--- global variables
      int      handle=INVALID_HANDLE;  // Хэндл индикатора
      int      period_jaws=0;          // Период расчёта линии "Челюсти"
      int      period_teeth=0;         // Период расчёта линии "Зубы"
      int      period_lips=0;          // Период расчёта линии "Губы"
      int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
      string   ind_title;              // Описание индикатора
      

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

      //+------------------------------------------------------------------+
      //|                                            TestWilliamsGator.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                 InpPeriodJaws  =  13;            /* Jaws Period    */ // Период расчёта линии "Челюсти"
      input int                  InpShiftJaws   =  8;             /* Jaws Shift     */ // Сдвиг линии "Челюсти"
      input uint                 InpPeriodTeeth =  8;             /* Teeth Period   */ // Период расчёта линии "Зубы"
      input int                  InpShiftTeeth  =  5;             /* Teeth Shift    */ // Сдвиг линии "Зубы"
      input uint                 InpPeriodLips  =  5;             /* Lips Period    */ // Период расчёта линии "Губы"
      input int                  InpShiftLips   =  3;             /* Lips Shift     */ // Сдвиг линии "Губы"
      input ENUM_MA_METHOD       InpMethod      =  MODE_SMMA;     /* Smoothed       */ // Метод сглаживания
      input ENUM_APPLIED_PRICE   InpAppliedPrice=  PRICE_MEDIAN;  /* Applied Price  */ // Цена расчёта
      //--- global variables
      int      handle=INVALID_HANDLE;  // Хэндл индикатора
      int      period_jaws=0;          // Период расчёта линии "Челюсти"
      int      period_teeth=0;         // Период расчёта линии "Зубы"
      int      period_lips=0;          // Период расчёта линии "Губы"
      int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
      string   ind_title;              // Описание индикатора
      //--- переменные для панели
      int      mouse_bar_index;        // Индекс бара, с которого берутся данные
      CDashboard *panel=NULL;          // Указатель на объект панели
      


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

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

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //--- create timer
         EventSetTimer(60);
      
      //--- Индикатор
      //--- Устанавливаем и корректируем при необходимости период расчёта
         period_jaws=int(InpPeriodJaws<1 ? 13 : InpPeriodJaws);
         period_teeth=int(InpPeriodTeeth<1 ? 8 : InpPeriodTeeth);
         period_lips=int(InpPeriodLips<1 ? 5 : InpPeriodLips);
      //--- Устанавливаем наименование индикатора и количество знаков после запятой
         ind_title=StringFormat("Gator(%lu,%lu,%lu)",period_jaws,period_teeth,period_lips);
         ind_digits=Digits()+1;
      //--- Создаём хэндл индикатора
         ResetLastError();
         handle=iGator(Symbol(),PERIOD_CURRENT,period_jaws,InpShiftJaws,period_teeth,InpShiftTeeth,period_lips,InpShiftLips,InpMethod,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_jaws=int(InpPeriodJaws<1 ? 13 : InpPeriodJaws);
         period_teeth=int(InpPeriodTeeth<1 ? 8 : InpPeriodTeeth);
         period_lips=int(InpPeriodLips<1 ? 5 : InpPeriodLips);
      //--- Устанавливаем наименование индикатора и количество знаков после запятой
         ind_title=StringFormat("Gator(%lu,%lu,%lu)",period_jaws,period_teeth,period_lips);
         ind_digits=Digits()+1;
      //--- Создаём хэндл индикатора
         ResetLastError();
         handle=iGator(Symbol(),PERIOD_CURRENT,period_jaws,InpShiftJaws,period_teeth,InpShiftTeeth,period_lips,InpShiftLips,InpMethod,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,229,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,112);
      
      //--- Создаём таблицу с идентификатором 1 для отображения в ней данных индикатора
         panel.CreateNewTable(1);
      //--- Получаем координату Y2 таблицы с идентификатором 0 и
      //--- устанавливаем координату Y1 для таблицы с идентификатором 1
         int y1=panel.TableY2(0)+22;
      //--- Рисуем таблицу с идентификатором 1 на фоне панели
         panel.DrawGrid(1,2,y1,3,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);
      
      //--- Получаем данные буферов индикатора
         double value0=IndicatorValue(handle,index,UPPER_HISTOGRAM); // Верхняя гистограмма
         double value1=IndicatorValue(handle,index,1);               // Цветовой буфер верхней гистограммы
         double value2=IndicatorValue(handle,index,LOWER_HISTOGRAM); // Нижняя гистограмма
         double value3=IndicatorValue(handle,index,3);               // Цветовой буфер нижней гистограммы
         color clr=clrNONE;
      //--- Выводим на панель данные верхней гистограммы с указанного бара в таблицу 1
         panel.DrawText(ind_title+" Up", panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2);
         string value_str=(value0!=EMPTY_VALUE ? DoubleToString(value0,ind_digits) : "");
         clr=(value1>0 ? clrRed : clrGreen);
         panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clr,100);
         
      //--- Выводим на панель данные нижней гистограммы с указанного бара в таблицу 1
         panel.DrawText(ind_title+" Down", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
         value_str=(value2!=EMPTY_VALUE ? DoubleToString(value2,ind_digits) : "");
         clr=(value3>0 ? clrRed : clrGreen);
         panel.DrawText(value_str,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);
           }
        }
      

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



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


      Market Facilitation Index

      Технический индикатор Индекс Облегчения Рынка (Market Facilitation Index, BW MFI) показывает изменение цены, приходящееся на один тик. Абсолютные величины индикатора сами по себе ничего не значат, смысл имеют лишь изменения индикатора. Билл Вильямс придает большое значение изменениям индикатора и объема:

      • Индикатор Market Facilitation Index вырос и объем вырос — это свидетельствует о том, что: а) все большее количество игроков входит в рынок (растет объем), б) вновь прибывающие игроки открывают позиции в направлении развития бара, т.е., движение началось и набирает скорость.
      • Индикатор Market Facilitation Index упал и объем упал. Это говорит о пропадании интереса у участников рынка.
      • Индикатор Market Facilitation Index вырос, но объем упал. Рынок не поддержан объемом со стороны трейдеров, а цена изменяется благодаря спекуляциям трейдеров "на полу" (посредников — брокеров и дилеров).
      • Индикатор Market Facilitation Index упал, но объем вырос. Происходит сражение быков и медведей, с большим объемом покупок и продаж, но с незначительным движением самой цены вследствие примерно равных сил. Одна из двух противоборствующих сторон (покупатели против продавцов) победит. Обычно, прорыв такого бара дает знать, определяет ли этот бар продолжение тренда или им тренд аннулирован. Билл Вильямс такой бар называет "приседающим".



      Параметры

      Для создания хэндла индикатора используется функция iBWMFI():

      Возвращает хэндл индикатора Market Facilitation Index. Всего один буфер.

      int  iBWMFI(
         string               symbol,             // имя символа
         ENUM_TIMEFRAMES      period,             // период
         ENUM_APPLIED_VOLUME  applied_volume      // тип объема для расчета
         );

      symbol

      [in]  Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.

      period

      [in]  Значение периода может быть одним из значений перечисления ENUM_TIMEFRAMES, 0 означает текущий таймфрейм.

      applied_volume

      [in]  Используемый объем. Может быть любой из значений перечисления ENUM_APPLIED_VOLUME.

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


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

      //+------------------------------------------------------------------+
      //|                                            TestWilliamsBWMFI.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 ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта BWMFI
      //--- global variables
      int      handle=INVALID_HANDLE;  // Хэндл индикатора
      int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
      string   ind_title;              // Описание индикатора
      

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

      //+------------------------------------------------------------------+
      //|                                            TestWilliamsBWMFI.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 ENUM_APPLIED_VOLUME  InpVolume   =  VOLUME_TICK;   /* Applied Volume */ // Применяемый объём для расчёта BWMFI
      //--- global variables
      int      handle=INVALID_HANDLE;  // Хэндл индикатора
      int      ind_digits=0;           // Количество знаков после запятой в значении индикатора
      string   ind_title;              // Описание индикатора
      //--- переменные для панели
      int      mouse_bar_index;        // Индекс бара, с которого берутся данные
      CDashboard *panel=NULL;          // Указатель на объект панели
      


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

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

      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //--- create timer
         EventSetTimer(60);
      
      //--- Индикатор
      //--- Устанавливаем наименование индикатора и количество знаков после запятой
         ind_title="BW MFI";
         ind_digits=Digits();
      //--- Создаём хэндл индикатора
         ResetLastError();
         handle=iBWMFI(Symbol(),PERIOD_CURRENT,InpVolume);
         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);
      
      //--- Индикатор
      //--- Устанавливаем наименование индикатора и количество знаков после запятой
         ind_title="BW MFI";
         ind_digits=Digits();
      //--- Создаём хэндл индикатора
         ResetLastError();
         handle=iBWMFI(Symbol(),PERIOD_CURRENT,InpVolume);
         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);
         
      //--- Создаём хэндл индикатора Volumes
         static bool create=false;
         static int hv=INVALID_HANDLE;
         if(!create)
           {
            ResetLastError();
            hv=iVolumes(Symbol(),PERIOD_CURRENT,InpVolume);
            if(hv==INVALID_HANDLE)
              {
               PrintFormat("%s: Failed to create indicator handle Volumes. Error %ld",__FUNCTION__,GetLastError());
               return;
              }
            create=true;
           }
      
      //--- Получаем состояние индикатора Volumes
         ENUM_LINE_STATE state_vol=LineState(hv,index,0);
      //--- Выводим описание состояния линии индикатора
         panel.DrawText("BW MFI State", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2);
         ENUM_LINE_STATE state=LineState(handle,index,0);
         color clr=clrNONE;
         string state_str=LineStateDescription(state);
         if((state==LINE_STATE_UP || state==LINE_STATE_TURN_UP) && (state_vol==LINE_STATE_UP || state_vol==LINE_STATE_TURN_UP))
           {
            state_str="MFI Up, Vol Up";
            clr=clrGreen;
           }
         if((state==LINE_STATE_DOWN || state==LINE_STATE_TURN_DOWN) && (state_vol==LINE_STATE_DOWN || state_vol==LINE_STATE_TURN_DOWN))
           {
            state_str="MFI Dn, Vol Dn";
            clr=clrSaddleBrown;
           }
         if((state==LINE_STATE_UP || state==LINE_STATE_TURN_UP) && (state_vol==LINE_STATE_DOWN || state_vol==LINE_STATE_TURN_DOWN))
           {
            state_str="MFI Up, Vol Dn";
            clr=clrBlue;
           }
         if((state==LINE_STATE_DOWN || state==LINE_STATE_TURN_DOWN) && (state_vol==LINE_STATE_UP || state_vol==LINE_STATE_TURN_UP))
           {
            state_str="MFI Dn, Vol Up";
            clr=clrLightCoral;
           }
      
      //--- Устанавливаем параметры шрифта для данных состояния индикатора (жирный шрифт)
         name=panel.FontParams(size,flags,angle);
         panel.SetFontParams(name,9,FW_BOLD);
         panel.DrawText(state_str,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90);
      //--- Восстанавливаем нормальную толщину шрифта панели
         panel.SetFontParams(name,9);
         
      //--- Перерисовываем график для немедленного отображения всех изменений на панели
         ChartRedraw(ChartID());
        }
      

      Здесь стоит отметить, что получение данных индикатора BW MFI возможно обычным способом — посредством предоставленных здесь универсальных функций. Но для интерпретации показаний столбцов индикатора нужен ещё один индикатор — индикатор объёмов (Volumes), так как для расцветки столбцов гистограммы сравниваются два показателя — значение столбца гистограммы и значение объёма относительно их предыдущих значений. Для получения объёма в функции создаётся хэндл индикатора Volumes (единожды при первом обращении), и далее сравниваются состояния линий индикатора BW MFI и Volumes. Описание их взаимного соотношения выводятся на панель в виде текста.

      Кроме того, при использовании информационной панели в обработчике событий 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);
           }
        }
      

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

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


      Доработка классов информационной панели. Обзор

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

      Теперь каждая создаваемая табличка на панели может возвращать свои координаты: X1, Y1 — левый верхний угол, X2 и Y2 — правый нижний угол. Каждой табличке присваивается свой идентификатор и наименование, по которым можно обратиться к ним для получения данных.

      В класс табличных данных CTableData были добавлены приватные переменные и публичные методы для записи и возврата этих значений:

      //+------------------------------------------------------------------+
      //| Класс данных таблиц                                              |
      //+------------------------------------------------------------------+
      class CTableData : public CObject
        {
      private:
         CArrayObj         m_list_rows;               // Список строк
         uint              m_id;                      // Идентификатор таблицы
         int               m_x1;                      // Координата X1
         int               m_y1;                      // Координата Y1
         int               m_x2;                      // Координата X2
         int               m_y2;                      // Координата Y2
         int               m_w;                       // Ширина
         int               m_h;                       // Высота
         string            m_name;                    // Наименование таблицы
      public:
      //--- Устанавливает наименование таблицы
         void              SetName(const string name) { this.m_name=name;              }
      //--- Возвращает (1) идентификатор, (2) наименование таблицы
         uint              ID(void)             const { return this.m_id;              }
         string            Name(void)           const { return this.m_name;            }
         
      //--- Устанавливает координату (1) X1, (2) X2
         void              SetX1(const uint x1)       { this.m_x1=(int)x1;             }
         void              SetX2(const uint x2)       { this.m_x2=(int)x2;             }
      //--- Устанавливает координату (1) Y1, (2) Y2
         void              SetY1(const uint y1)       { this.m_y1=(int)y1;             }
         void              SetY2(const uint y2)       { this.m_y2=(int)y2;             }
      //--- Устанавливает координаты таблицы
         void              SetCoords(const int x1,const int y1,const int x2,const int y2)
                             {
                              this.SetX1(x1);
                              this.SetY1(y1);
                              this.SetX2(x2);
                              this.SetY2(y2);
                             }
         
      //--- Возвращает координату (1) X1, (2) X2
         int               X1(void)             const { return this.m_x1;              }
         int               X2(void)             const { return this.m_x2;              }
      //--- Возвращает координату (1) Y1, (2) Y2
         int               Y1(void)             const { return this.m_y1;              }
         int               Y2(void)             const { return this.m_y2;              }
         
      //--- Возвращает (1) ширину, (2) высоту
         int               Width(void)          const { return this.m_x2-this.m_x1+1;  }
         int               Height(void)         const { return this.m_y2-this.m_y1+1;  }
         
      //--- Возвращает список строк таблицы

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

         int               ColumnsInRow(const int row_index)
                             {
                              //--- Если в списке нет ни одной строки - возвращаем 0
                              if(this.RowsTotal()==0)
                                 return 0;
                              //--- Получаем указатель на указанную строку и возвращаем количество ячеек в ней
                              CTableRow *row=this.GetRow(row_index);
                              return(row!=NULL ? row.CellsTotal() : 0);
                             }
      //--- Возвращает общее количество ячеек таблицы

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

      //--- Возвращает общее количество ячеек таблицы
         int               CellsTotal(void)
                             {
                              //--- Если в списке нет ни одной строки - возвращаем 0
                              if(this.RowsTotal()==0)
                                 return 0;
                              //---
                              int num=0;
                              int total=this.RowsTotal();
                              for(int i=0;i<total;i++)
                                 num+=this.ColumnsInRow(i);
                              return num;
                             }
      //--- Очищает списки строк и ячеек таблицы

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

      В класс добавлен виртуальный метод Compare, позволяющий сравнивть таблички по идентификаторам (режим сравнения mode = 0), либо по наименованиям (mode != 0):

      //--- Виртуальный метод сравнения двух объектов
         virtual int       Compare(const CObject *node,const int mode=0) const
                             {
                              const CTableData *compared=node;
                              if(mode==0)
                                 return(this.ID()>compared.ID() ? 1 : this.ID()<compared.ID() ? -1 : 0);
                              else
                                 return(this.Name()==compared.Name() ? 0 : this.Name()>compared.Name() ? 1 : -1);
                             }

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

      //--- Конструктор/деструктор
                           CTableData(const uint id) : m_id(id){ this.m_list_rows.Clear(); this.m_name="";  }
                          ~CTableData(void)                    { this.m_list_rows.Clear();                  }


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

      //+------------------------------------------------------------------+
      //| Класс Dashboard                                                  |
      //+------------------------------------------------------------------+
      class CDashboard : public CObject
        {
      private:
         CCanvas           m_canvas;                  // Канвас
         CCanvas           m_workspace;               // Рабочая область
         CArrayObj         m_list_table;              // Список таблиц
         ENUM_PROGRAM_TYPE m_program_type;            // Тип программы
         ENUM_MOUSE_STATE  m_mouse_state;             // Состояние кнопок мышки

      Для создания имён файлов для сохранения пикселей фона и рабочей области в файл в приватной секции объявлены переменные:

         string            m_name_gv_m;               // Наименование глобальной переменной терминала, хранящей флаг свёрнутости панели
         string            m_name_gv_u;               // Наименование глобальной переменной терминала, хранящей флаг закреплённой панели
         string            m_filename_bg;             // Наименование файла для сохранения пикселей фона
         string            m_filename_ws;             // Наименование файла для сохранения пикселей рабочей области
         
         uint              m_array_wpx[];             // Массив пикселей для сохранения/восстановления рабочей области
         uint              m_array_ppx[];             // Массив пикселей для сохранения/восстановления фона панели

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

      //--- Устанавливает параметры шрифта панели по умолчанию
         void              SetFontParams(const string name,const int size,const uint flags=0,const uint angle=0);
      //--- Возвращает установленные параметры шрифта панели
         string            FontParams(int &size,uint &flags,uint &angle);
      //--- Возвращает установленные (1) шрифт, (2) размер, (3) флаги шрифта панели
         string            FontName(void)    const { return this.m_workspace.FontNameGet();  }
         int               FontSize(void)    const { return this.m_workspace.FontSizeGet();  }
         uint              FontFlags(void)   const { return this.m_workspace.FontFlagsGet(); }
      //--- Выводит текстовое сообщение в указанные координаты
         void              DrawText(const string text,const int x,const int y,const color clr=clrNONE,const int width=WRONG_VALUE,const int height=WRONG_VALUE);
      //--- Создаёт новую таблицу
         bool              CreateNewTable(const int id=WRONG_VALUE);
      //--- Возвращает объект табличных данных по (1) идентификатору, (2) наименованию
         CTableData       *GetTable(const uint id);
         CTableData       *GetTable(const string name);
      //--- Рисует (1) фоновую сетку, (2) с автоматическим размером ячеек
         void              DrawGrid(const uint table_id,const uint x,const uint y,const uint rows,const uint columns,const uint row_size,const uint col_size,const color line_color=clrNONE,bool alternating_color=true);
         void              DrawGridAutoFill(const uint table_id,const uint border,const uint rows,const uint columns,const color line_color=clrNONE,bool alternating_color=true);
      //--- Распечатывает данные сетки (координаты пересечения линий)
         void              GridPrint(const uint table_id,const uint indent=0)
                             {
                              CTableData *table=this.GetTable(table_id);
                              if(table==NULL)
                                {
                                 ::PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id);
                                 return;
                                }
                              table.Print(indent);
                             }
      //--- Записывает в переменные значения координат X и Y указанной ячейки таблицы
         void              CellXY(const uint table_id,const uint row,const uint column, int &x, int &y)
                             {
                              CTableData *table=this.GetTable(table_id);
                              if(table==NULL)
                                {
                                 ::PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id);
                                 return;
                                }
                              table.CellXY(row,column,x,y);
                             }
      //--- Возвращает координату (1) X, (2) Y указанной ячейки таблицы
         int               CellX(const uint table_id,const uint row,const uint column)
                             {
                              CTableData *table=this.GetTable(table_id);
                              if(table==NULL)
                                {
                                 ::PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id);
                                 return WRONG_VALUE;
                                }
                              return table.CellX(row,column);
                             }
         int               CellY(const uint table_id,const uint row,const uint column)
                             {
                              CTableData *table=this.GetTable(table_id);
                              if(table==NULL)
                                {
                                 ::PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id);
                                 return WRONG_VALUE;
                                }
                              return table.CellY(row,column);
                             }
      //--- Записывает в переменные значения координат X1 и Y1, X2 и Y2 указанной таблицы
         void              TableCoords(const uint table_id,int &x1,int &y1,int &x2,int &y2)
                             {
                              x1=y1=x2=y2=WRONG_VALUE;
                              CTableData *table=this.GetTable(table_id);
                              if(table==NULL)
                                 return;
                              x1=table.X1();
                              y1=table.Y1();
                              x2=table.X2();
                              y2=table.Y2();
                             }
      
      //--- Возвращает координату (1) X1, (2) Y1, (3) X2, (4) Y2 указанной таблицы
         int               TableX1(const uint table_id)
                             {
                              CTableData *table=this.GetTable(table_id);
                              return(table!=NULL ? table.X1() : WRONG_VALUE);
                             }
         int               TableY1(const uint table_id)
                             {
                              CTableData *table=this.GetTable(table_id);
                              return(table!=NULL ? table.Y1() : WRONG_VALUE);
                             }
         int               TableX2(const uint table_id)
                             {
                              CTableData *table=this.GetTable(table_id);
                              return(table!=NULL ? table.X2() : WRONG_VALUE);
                             }
         int               TableY2(const uint table_id)
                             {
                              CTableData *table=this.GetTable(table_id);
                              return(table!=NULL ? table.Y2() : WRONG_VALUE);
                             }
      
      //--- Обработчик событий
         void              OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
      //--- Конструктор/Деструктор
                           CDashboard(const uint id,const int x,const int y, const int w,const int h,const int wnd=-1);
                          ~CDashboard();


      В кострукторе класса создаются имена файлов для сохранения фона и рабочей области:

      //--- Задаём имена глобальным переменным терминала для хранения координат панели, состояния свёрнуто/развернуто и закрепления
         this.m_name_gv_x=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_X";
         this.m_name_gv_y=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_Y";
         this.m_name_gv_m=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_Minimize";
         this.m_name_gv_u=this.m_program_name+"_id_"+(string)this.m_id+"_"+(string)this.m_chart_id+"_Unpin";
      
      //--- Задаём имена файлов для сохранения пикселей фона и рабочей области
         this.m_filename_bg=this.m_program_name+"\\Dashboard"+(string)this.m_id+"\\background.bin";
         this.m_filename_ws=this.m_program_name+"\\Dashboard"+(string)this.m_id+"\\workspace.bin";
         

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

      //--- Если стоит флаг свёрнутой панели - загружаем в массивы пиксели фона и рабочей области из файлов
         if(this.m_minimized)
           {
            if(::FileIsExist(this.m_filename_bg))
               this.FileLoadBackground();
            if(::FileIsExist(this.m_filename_ws))
               this.FileLoadWorkspace();
           }
        }

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

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

      //+------------------------------------------------------------------+
      //| Деструктор                                                       |
      //+------------------------------------------------------------------+
      CDashboard::~CDashboard()
        {
      //--- Записываем текущие значения в глобальные переменные терминала
         ::GlobalVariableSet(this.m_name_gv_x,this.m_x);
         ::GlobalVariableSet(this.m_name_gv_y,this.m_y);
         ::GlobalVariableSet(this.m_name_gv_m,this.m_minimized);
         ::GlobalVariableSet(this.m_name_gv_u,this.m_movable);
      
      //--- Если панель свёрнута,
      //--- разворачиваем панель, сохраняем внешний вид в массивы пикселей и сворачиваем панель
         if(this.m_minimized)
           {
            this.Expand();
            this.SaveBackground();
            this.SaveWorkspace();
            this.Collapse();
           }
      //--- иначе, если панель развёрнута,
      //--- сохраняем внешний вид в массивы пикселей
         else
           {
            this.SaveBackground();
            this.SaveWorkspace();
           }
      //--- Сохраняем массивы пикселей в файлы
         this.FileSaveBackground();
         this.FileSaveWorkspace();
      
      //--- Удаляем объекты панели
         this.m_canvas.Destroy();
         this.m_workspace.Destroy();
        }


      В блоке обработки нажатия на кнопку сворачивания/разворачивания панели проверяем флаг и сохраняем фон и рабочую область в массивы пикселей в случае, если панель развёрнута:

            //--- Если нажата кнопка сворачивания/разворачивания панели
            else if(state==MOUSE_STATE_PRESSED_INSIDE_MINIMIZE)
              {
               //--- Отключаем прокрутку графика, меню правой кнопки и перекрестие
               this.SetChartsTool(false);
               //--- Если панель не свёрнута - сохраняем фон и рабочую область в массивы пикселей
               if(!this.m_minimized)
                 {
                  this.SaveWorkspace();
                  this.SaveBackground();
                 }
               //--- "переворачиваем" флаг свёрнутости панели,
               this.m_minimized=!this.m_minimized;
               //--- перерисовываем панель с учётом нового состояния флага,
               this.Draw(this.m_title);
               //--- перерисовываем область заголовка панели
               this.RedrawHeaderArea();
               //--- Если панель закреплена и развёрнута - переместим её в запомненные координаты расположения
               if(this.m_minimized && !this.m_movable)
                  this.Move(this.m_x_dock,this.m_y_dock);
               //--- Обновляем канвас с перерисовкой графика и
               this.m_canvas.Update();
               //--- записываем в глобальную переменную терминала состояние флага свёрнутости панели
               ::GlobalVariableSet(this.m_name_gv_m,this.m_minimized);
              }


      Из метода сворачивания панели удалены строки сохранения массива пикселей — теперь сохранение пикселей делается только при нажатии кнопки сворачивания/разворачивания:

      //+------------------------------------------------------------------+
      //| Сворачивает панель                                               |
      //+------------------------------------------------------------------+
      void CDashboard::Collapse(void)
        {
      //--- Сохраняем в массивы пиксели рабочей области и фона панели
         this.SaveWorkspace();
         this.SaveBackground();
      //--- Запоминаем текущую высоту панели
         int h=this.m_h;
      //--- Изменяем размеры (высоту) канваса и рабочей области
         if(!this.SetSizes(this.m_canvas.Width(),this.m_header_h))
            return;
      //--- Рисуем область заголовка
         this.DrawHeaderArea(this.m_title);
      //--- Возвращаем в переменную запомненную высоту панели
         this.m_h=h;
        }


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

      //+------------------------------------------------------------------+
      //| Возвращает установленные параметры шрифта панели                 |
      //+------------------------------------------------------------------+
      string CDashboard::FontParams(int &size,uint &flags,uint &angle)
        {
         size=this.m_workspace.FontSizeGet();
         flags=this.m_workspace.FontFlagsGet();
         angle=this.m_workspace.FontAngleGet();
         return this.m_workspace.FontNameGet();
        }

      Метод возвращает наименование шрифта, а в переданные по ссылке переменные записываются размер шрифта, его флаги и угол.

      В метод рисования текста теперь передаётся и цвет рисуемого текста. По умолчанию — clrNONE, что означает ранее установленный цвет текстов:

      //+------------------------------------------------------------------+
      //| Выводит текстовое сообщение в указанные координаты               |
      //+------------------------------------------------------------------+
      void CDashboard::DrawText(const string text,const int x,const int y,const color clr=clrNONE,const int width=WRONG_VALUE,const int height=WRONG_VALUE)
        {
      //--- Объявим переменные для записи в них ширины и высоты текста
         int w=width;
         int h=height;
      //--- Если ширина и высота текста, переданные в метод, имеют нулевые значения,
      //--- то полностью очищается всё паространство рабочей области прозрачным цветом
         if(width==0 && height==0)
            this.m_workspace.Erase(0x00FFFFFF);
      //--- Иначе
         else
           {
            //--- Если переданные ширина и высота имеют значения по умолчанию (-1) - получаем из текста его ширину и высоту
            if(width==WRONG_VALUE && height==WRONG_VALUE)
               this.m_workspace.TextSize(text,w,h);
            //--- иначе,
            else
              {
               //--- если ширина, переданная в метод, имеет значение по умолчанию (-1) - получаем ширину из текста, либо
               //--- если ширина, переданная в метод, имеет значение больше нуля - используем переданную в метод ширину, либо
               //--- если ширина, переданная в метод, имеет нулевое значение, используем значение 1 для ширины
               w=(width ==WRONG_VALUE ? this.m_workspace.TextWidth(text)  : width>0  ? width  : 1);
               //--- если высота, переданная в метод, имеет значение по умолчанию (-1) - получаем высоту из текста, либо
               //--- если высота, переданная в метод, имеет значение больше нуля - используем переданную в метод высоту, либо
               //--- если высота, переданная в метод, имеет нулевое значение, используем значение 1 для высоты
               h=(height==WRONG_VALUE ? this.m_workspace.TextHeight(text) : height>0 ? height : 1);
              }
            //--- Заполняем пространство по указанным координатам и полученной шириной и высотой прозрачным цветом (стираем прошлую запись)
            this.m_workspace.FillRectangle(x,y,x+w,y+h,0x00FFFFFF);
           }
      //--- Выводим текст на очищенное от прошлого текста места и обновляем рабочую область без перерисовки экрана
         this.m_workspace.TextOut(x,y,text,::ColorToARGB(clr==clrNONE ? this.m_fore_color : clr));
         this.m_workspace.Update(false);
        }


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

      //+------------------------------------------------------------------+
      //| Создаёт новую таблицу                                            |
      //+------------------------------------------------------------------+
      bool CDashboard::CreateNewTable(const int id=WRONG_VALUE)
        {
         uint num=(id>WRONG_VALUE ? id : this.m_list_table.Total());
         CTableData *table=new CTableData(num);
         this.m_list_table.Sort();
         if(this.m_list_table.Search(table)!=WRONG_VALUE)
           {
            PrintFormat("%s: Error. Table with id %lu already exists in the list",__FUNCTION__,num);
            delete table;
            return false;
           }
         if(!this.m_list_table.Add(table))
           {
            PrintFormat("%s: Error. Failed to add table with id %lu to the list",__FUNCTION__,num);
            delete table;
            return false;
           }
         return true;
        }
      //+------------------------------------------------------------------+
      //| Возвращает объект табличных данных по идентификатору             |
      //+------------------------------------------------------------------+
      CTableData *CDashboard::GetTable(const uint id)
        {
         if(this.m_list_table.Total()==0)
           {
            PrintFormat("%s: Error. The list of tables is empty. First you need to create a table using CreateNewTable",__FUNCTION__);
            return NULL;
           }
         CTableData *table=new CTableData(id);
         if(table==NULL)
           {
            ::PrintFormat("%s: Error. Failed to create table object with id %lu",__FUNCTION__,id);
            return NULL;
           }
         this.m_list_table.Sort();
         int index=this.m_list_table.Search(table);
         delete table;
         return this.m_list_table.At(index);
        }
      //+------------------------------------------------------------------+
      //| Возвращает объект табличных данных по наименованию               |
      //+------------------------------------------------------------------+
      CTableData *CDashboard::GetTable(const string name)
        {
         if(this.m_list_table.Total()==0)
           {
            PrintFormat("%s: Error. The list of tables is empty. First you need to create a table using CreateNewTable",__FUNCTION__);
            return NULL;
           }
         CTableData *table=new CTableData(0);
         if(table==NULL)
           {
            ::PrintFormat("%s: Error. Failed to create table object");
            return NULL;
           }
         table.SetName(name);
         this.m_list_table.Sort(1);
         int index=this.m_list_table.Search(table);
         delete table;
         return this.m_list_table.At(index);
        }


      Изменения в методах рисования таблиц:

      //+------------------------------------------------------------------+
      //| Рисует фоновую сетку                                             |
      //+------------------------------------------------------------------+
      void CDashboard::DrawGrid(const uint table_id,
                                const uint x,const uint y,const uint rows,const uint columns,const uint row_size,const uint col_size,
                                const color line_color=clrNONE,bool alternating_color=true)
        {
      //--- Получаем объект таблицы по идентификатору
         CTableData *table=this.GetTable(table_id);
         if(table==NULL)
           {
            PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id);
            return;
           }
      //--- Очищаем все списки объекта табличных данных (удаляем ячейки из строк и все строки)
         table.Clear();
      //--- Высота строки не может быть меньше 2
         int row_h=int(row_size<2 ? 2 : row_size);
      //--- Ширина столбца не может быть меньше 2
         int col_w=int(col_size<2 ? 2 : col_size);
         
      //--- Координата X1 (слева) таблицы не может быть меньше 1 (чтобы оставить один пиксель по периметру панели для рамки)
         int x1=int(x<1 ? 1 : x);
      //--- Рассчитываем координату X2 (справа) в зависимости от количества столбцов и их ширины
         int x2=x1+col_w*int(columns>0 ? columns : 1);
      //--- Координата Y1 находится под областью заголовка панели
         int y1=this.m_header_h+(int)y;
      //--- Рассчитываем координату Y2 (снизу) в зависимости от количества строк и их высоты
         int y2=y1+row_h*int(rows>0 ? rows : 1);
      //--- Устанавливаем координаты таблицы
         table.SetCoords(x1,y1-this.m_header_h,x2,y2-this.m_header_h);
         
      //--- Получаем цвет линий сетки таблицы, либо по умолчанию, либо переданный в метод
         color clr=(line_color==clrNONE ? C'200,200,200' : line_color);
      //--- Если начальная координата X больше 1 - рисуем рамку таблицы
      //--- (при координате 1 рамкой таблицы выступает рамка панели)
         if(x1>1)
            this.m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(clr,this.m_alpha));
      //--- В цикле во строкам таблицы
         for(int i=0;i<(int)rows;i++)
           {
            //--- рассчитываем координату Y очередной горизонтальной линии сетки (координата Y очередной строки таблицы)
            int row_y=y1+row_h*i;
            //--- если передан флаг "чередующихся" цветов строк и строка чётная
            if(alternating_color && i%2==0)
              {
               //--- осветляем цвет фона таблицы и рисуем фоновый прямоугольник
               color new_color=this.NewColor(clr,45,45,45);
               this.m_canvas.FillRectangle(x1+1,row_y+1,x2-1,row_y+row_h-1,::ColorToARGB(new_color,this.m_alpha));
              }
            //--- Рисуем горизонтальную линию сетки таблицы
            this.m_canvas.Line(x1,row_y,x2,row_y,::ColorToARGB(clr,this.m_alpha));
            
            //--- Создаём новый объект строки таблицы
            CTableRow *row_obj=new CTableRow(i);
            if(row_obj==NULL)
              {
               ::PrintFormat("%s: Failed to create table row object at index %lu",(string)__FUNCTION__,i);
               continue;
              }
            //--- Добавляем его в список строк объекта табличных данных
            //--- (если добавить объект не удалось - удаляем созданный объект)
            if(!table.AddRow(row_obj))
               delete row_obj;
            //--- Устанавливаем в созданном объекте-строке его координату Y с учётом смещения от заголовка панели
            row_obj.SetY(row_y-this.m_header_h);
           }
           
      //--- В цикле по столбцам таблицы
         for(int i=0;i<(int)columns;i++)
           {
            //--- рассчитываем координату X очередной вертикальной линии сетки (координата X очередного столбца таблицы)
            int col_x=x1+col_w*i;
            //--- Если линия сетки вышла за пределы панели - прерываем цикл
            if(x1==1 && col_x>=x1+m_canvas.Width()-2)
               break;
            //--- Рисуем вертикальную линию сетки таблицы
            this.m_canvas.Line(col_x,y1,col_x,y2,::ColorToARGB(clr,this.m_alpha));
            
            //--- Получаем из объекта табличных данных количество созданных строк
            int total=table.RowsTotal();
            //--- В цикле по строкам таблицы
            for(int j=0;j<total;j++)
              {
               //--- получаем очередную строку
               CTableRow *row=table.GetRow(j);
               if(row==NULL)
                  continue;
               //--- Создаём новую ячейку таблицы
               CTableCell *cell=new CTableCell(row.Row(),i);
               if(cell==NULL)
                 {
                  ::PrintFormat("%s: Failed to create table cell object at index %lu",(string)__FUNCTION__,i);
                  continue;
                 }
               //--- Добавляем созданную ячейку в строку
               //--- (если добавить объект не удалось - удаляем созданный объект)
               if(!row.AddCell(cell))
                 {
                  delete cell;
                  continue;
                 }
               //--- Устанавливаем в созданном объекте-ячейке его координату X и координату Y из объекта-строки
               cell.SetXY(col_x,row.Y());
              }
           }
      //--- Обновляем канвас без перерисовки графика
         this.m_canvas.Update(false);
        }
      //+------------------------------------------------------------------+
      //| Рисует фоновую сетку с автоматическим размером ячеек             |
      //+------------------------------------------------------------------+
      void CDashboard::DrawGridAutoFill(const uint table_id,const uint border,const uint rows,const uint columns,const color line_color=clrNONE,bool alternating_color=true)
        {
      //--- Получаем объект таблицы по идентификатору
         CTableData *table=this.GetTable(table_id);
         if(table==NULL)
           {
            PrintFormat("%s: Error. Failed to get table object with id %lu",__FUNCTION__,table_id);
            return;
           }
      //--- Координата X1 (левая) таблицы
         int x1=(int)border;
      //--- Координата X2 (правая) таблицы
         int x2=this.m_canvas.Width()-(int)border-1;
      //--- Координата Y1 (верхняя) таблицы
         int y1=this.m_header_h+(int)border;
      //--- Координата Y2 (нижняя) таблицы
         int y2=this.m_canvas.Height()-(int)border-1;
      //--- Устанавливаем координаты таблицы
         table.SetCoords(x1,y1,x2,y2);
      
      //--- Получаем цвет линий сетки таблицы, либо по умолчанию, либо переданный в метод
         color clr=(line_color==clrNONE ? C'200,200,200' : line_color);
      //--- Если отступ от края панели больше нуля - рисуем рамку таблицы
      //--- иначе - рамкой таблицы выступает рамка панели
         if(border>0)
            this.m_canvas.Rectangle(x1,y1,x2,y2,::ColorToARGB(clr,this.m_alpha));
      
      //--- Высота всей сетки таблицы
         int greed_h=y2-y1;
      //--- Рассчитываем высоту строки в зависимости от высоты таблицы и количества строк
         int row_h=(int)::round((double)greed_h/(double)rows);
      //--- В цикле по количеству строк
         for(int i=0;i<(int)rows;i++)
           {
            //--- рассчитываем координату Y очередной горизонтальной линии сетки (координата Y очередной строки таблицы)
            int row_y=y1+row_h*i;
            //--- если передан флаг "чередующихся" цветов строк и строка чётная
            if(alternating_color && i%2==0)
              {
               //--- осветляем цвет фона таблицы и рисуем фоновый прямоугольник
               color new_color=this.NewColor(clr,45,45,45);
               this.m_canvas.FillRectangle(x1+1,row_y+1,x2-1,row_y+row_h-1,::ColorToARGB(new_color,this.m_alpha));
              }
            //--- Рисуем горизонтальную линию сетки таблицы
            this.m_canvas.Line(x1,row_y,x2,row_y,::ColorToARGB(clr,this.m_alpha));
            
            //--- Создаём новый объект строки таблицы
            CTableRow *row_obj=new CTableRow(i);
            if(row_obj==NULL)
              {
               ::PrintFormat("%s: Failed to create table row object at index %lu",(string)__FUNCTION__,i);
               continue;
              }
            //--- Добавляем его в список строк объекта табличных данных
            //--- (если добавить объект не удалось - удаляем созданный объект)
            if(!table.AddRow(row_obj))
               delete row_obj;
            //--- Устанавливаем в созданном объекте-строке его координату Y с учётом смещения от заголовка панели
            row_obj.SetY(row_y-this.m_header_h);
           }
           
      //--- Ширина сетки таблицы
         int greed_w=x2-x1;
      //--- Рассчитываем ширину столбца в зависимости от ширины таблицы и количества столбцов
         int col_w=(int)::round((double)greed_w/(double)columns);
      //--- В цикле по столбцам таблицы
         for(int i=0;i<(int)columns;i++)
           {
            //--- рассчитываем координату X очередной вертикальной линии сетки (координата X очередного столбца таблицы)
            int col_x=x1+col_w*i;
            //--- Если это не самая первая вертикальная линия - рисуем её
            //--- (первой вертикальной линией выступает либо рамка таблицы, либо рамка панели)
            if(i>0)
               this.m_canvas.Line(col_x,y1,col_x,y2,::ColorToARGB(clr,this.m_alpha));
            
            //--- Получаем из объекта табличных данных количество созданных строк
            int total=table.RowsTotal();
            //--- В цикле по строкам таблицы
            for(int j=0;j<total;j++)
              {
               //--- получаем очередную строку
               CTableRow *row=table.GetRow(j);
               if(row==NULL)
                  continue;
               //--- Создаём новую ячейку таблицы
               CTableCell *cell=new CTableCell(row.Row(),i);
               if(cell==NULL)
                 {
                  ::PrintFormat("%s: Failed to create table cell object at index %lu",(string)__FUNCTION__,i);
                  continue;
                 }
               //--- Добавляем созданную ячейку в строку
               //--- (если добавить объект не удалось - удаляем созданный объект)
               if(!row.AddCell(cell))
                 {
                  delete cell;
                  continue;
                 }
               //--- Устанавливаем в созданном объекте-ячейке его координату X и координату Y из объекта-строки
               cell.SetXY(col_x,row.Y());
              }
           }
      //--- Обновляем канвас без перерисовки графика
         this.m_canvas.Update(false);
        }


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

      //+------------------------------------------------------------------+
      //| Сохраняет массив пикселей рабочей области в файл                 |
      //+------------------------------------------------------------------+
      bool CDashboard::FileSaveWorkspace(void)
        {
      //--- Если сохраняемый массив пустой - сообщаем об этом и возвращаем false
         if(this.m_array_wpx.Size()==0)
           {
            ::PrintFormat("%s: Error. The workspace pixel array is empty.",__FUNCTION__);
            return false;
           }
      //--- Если массив не удалось сохранить в файл - сообщаем об этом и возвращаем false
         if(!::FileSave(this.m_filename_ws,this.m_array_wpx))
           {
            ::PrintFormat("%s: FileSave '%s' failed. Error %lu",__FUNCTION__,this.m_filename_ws,::GetLastError());
            return false;
           }
      //--- Успешно, возвращаем true
         return true;
        }
      //+------------------------------------------------------------------+
      //| Сохраняет массив пикселей фона панели в файл                     |
      //+------------------------------------------------------------------+
      bool CDashboard::FileSaveBackground(void)
        {
      //--- Если сохраняемый массив пустой - сообщаем об этом и возвращаем false
         if(this.m_array_ppx.Size()==0)
           {
            ::PrintFormat("%s: Error. The background pixel array is empty.",__FUNCTION__);
            return false;
           }
      //--- Если массив не удалось сохранить в файл - сообщаем об этом и возвращаем false
         if(!::FileSave(this.m_filename_bg,this.m_array_ppx))
           {
            ::PrintFormat("%s: FileSave '%s' failed. Error %lu",__FUNCTION__,this.m_filename_bg,::GetLastError());
            return false;
           }
      //--- Успешно, возвращаем true
         return true;
        }
      //+------------------------------------------------------------------+
      //| Загружает массив пикселей рабочей области из файла               |
      //+------------------------------------------------------------------+
      bool CDashboard::FileLoadWorkspace(void)
        {
      //--- Если не удалось загрузить данные из файла в массив, сообщаем об этом и возвращаем false
         if(::FileLoad(this.m_filename_ws,this.m_array_wpx)==WRONG_VALUE)
           {
            ::PrintFormat("%s: FileLoad '%s' failed. Error %lu",__FUNCTION__,this.m_filename_ws,::GetLastError());
            return false;
           }
      //--- Успешно, возвращаем true
         return true;
        }
      //+------------------------------------------------------------------+
      //| Загружает массив пикселей фона панели из файла                   |
      //+------------------------------------------------------------------+
      bool CDashboard::FileLoadBackground(void)
        {
         if(::FileLoad(this.m_filename_bg,this.m_array_ppx)==WRONG_VALUE)
           {
            ::PrintFormat("%s: FileLoad '%s' failed. Error %lu",__FUNCTION__,this.m_filename_bg,::GetLastError());
            return false;
           }
      //--- Успешно, возвращаем true
         return true;
        }


      Заключение

      В данной статье мы рассмотрели подключение к советникам индикаторов объёма и Билла Вильямса. Все приведённые в статье коды могут быть использованы "как есть" для вставки их в код своих программ. Далее рассмотрим последнюю категорию индикаторов — трендовые, для их подключения и использования в советниках.

      Все файлы — тестовых советников и классов панели, можно загрузить из списка прикреплённых к статье файлов. Класс панели должен располагаться по адресу \MQL5\Include\Dashboard\Dashboard.mqh.


      Прикрепленные файлы |
      TestVolumeAD.mq5 (31.09 KB)
      TestVolumeMFI.mq5 (35.79 KB)
      TestVolumeOBV.mq5 (31.09 KB)
      TestWilliamsAC.mq5 (31.06 KB)
      TestWilliamsAO.mq5 (32.63 KB)
      Dashboard.mqh (218.88 KB)
      Язык визуального программиования ДРАКОН (Drakon) — средство общения для разработчика MQL и заказчика Язык визуального программиования ДРАКОН (Drakon) — средство общения для разработчика MQL и заказчика
      ДРАКОН — язык визуального программирования, специально разработанный для упрощения взаимодействия между специалистами разных отраслей (биологами, физиками, инженерами...) с программистами в российских космических проектах (например, при создании создание комплекса "Буран"). В этой статье я расскажу о том, как ДРАКОН делает создание алгоритмов доступным и интуитивно понятным, даже если вы никогда не сталкивались с кодом, а также - как заказчику легче объяснить свои мысли при заказе торговых роботов, а программисту - совершать меньше ошибок в сложных функциях.
      Популяционные алгоритмы оптимизации: Тасующий алгоритм прыгающих лягушек (Shuffled Frog-Leaping, SFL) Популяционные алгоритмы оптимизации: Тасующий алгоритм прыгающих лягушек (Shuffled Frog-Leaping, SFL)
      Статья представляет подробное описание алгоритма прыгающих лягушек (SFL) и его возможности в решении задач оптимизации. SFL-алгоритм вдохновлен поведением лягушек в естественной среде и предлагает новый подход к оптимизации функций. SFL-алгоритм является эффективным и гибким инструментом, способным обрабатывать разнообразные типы данных и достигать оптимальных решений.
      Объектно-ориентированное программирование (ООП) в MQL5 Объектно-ориентированное программирование (ООП) в MQL5
      Как разработчикам, нам необходимо научиться создавать и разрабатывать программное обеспечение, которое можно использовать многократно и гибко, без дублирования кода, особенно если у нас есть разные объекты с разным поведением. Это можно легко сделать, используя методы и принципы объектно-ориентированного программирования. В этой статье представлены основы объектно-ориентированного программирования в MQL5.
      Разработка пользовательского канала Дончиана с помощью MQL5 Разработка пользовательского канала Дончиана с помощью MQL5
      Существует множество технических инструментов, которые можно использовать для визуализации ценового канала. Одним из таких инструментов является канал Дончиана (Donchian Channel). В этой статье мы узнаем, как создать канал Дончиана и как использовать его в качестве пользовательского индикатора в составе советника.