English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Объектно-ориентированный подход к построению мультитаймфреймовых и мультивалютных панелей

Объектно-ориентированный подход к построению мультитаймфреймовых и мультивалютных панелей

MetaTrader 5Примеры | 20 декабря 2011, 13:15
5 010 0
Marcin Konieczny
Marcin Konieczny

Введение

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

Предлагаемое решение работает в 2 режимах:

  1. Multi-timeframe mode позволяет отображать содержимое таблицы, рассчитанное для текущего символа на различных таймфреймах;
  2. Multi-currency mode позволяет отображать содержимое таблицы, рассчитанное для текущего таймфрейма на различных символах.

Работа панели в этих двух режимах показана на рисунках 1 и 2.

На рис. 1 панель работает в режиме multi-timeframe mode и отображает данные:

  1. Значения цен;
  2. Изменение цен на текущем баре;
  3. Изменение цен на текущем баре в процентах;
  4. Изменение цен на текущем баре в виде стрелок (вверх/вниз);
  5. Значения индикатора RSI(14);
  6. Значения индикатора RSI(10);
  7. Выполнение условия: SMA(20) > текущая цена.

Рис. 1. Работа панели в режиме Mult-timeframe mode

Рис. 1. Работа панели в режиме Mult-timeframe mode


На втором рисунке показана работа панели в режиме multi-currency mode, на которой отображаются:

  1. Текущие цены;
  2. Изменения цен на текущем баре;
  3. Изменения цен на текущем баре в процентах;
  4. Изменение цен на текущем баре в виде стрелок (вверх/вниз);
  5. Значения индикатора RSI(14);
  6. Значения индикатора RSI(10);
  7. Выполнение условия: SMA(20) > текущая цена.

Figure 2. Mult-currency mode

Рис. 2. Работа панели в режиме Mult-currency mode


1. Реализация

Программную реализацию панели описывает следующая диаграмма классов:

Рис. 3. Диаграмма классов панели

Рис. 3. Диаграмма классов панели


Приведем описание элементов диаграммы:

  1. CTable. Основной класс панели. Отвечает за отображение панели и управление ее элементами.
  2. SpyAgent. Индикатор-агент, который используется для оповещения о событиях по другим символам ('spy'). Для каждого символа создается агент и устанавливается на график инструмента. Агент реагирует на событие OnCalculate при появлении нового тика по символу и отправляет событие CHARTEVENT_CUSTOM, которое информирует объект CTable о необходимости обновления данных. Более подробно этот подход описан в статье "Реализация мультивалютного режима в MetaTrader 5", там можно найти все детали.
  3. CRow. Основной класс для всех индикаторов и условий, которые используются в панели. Все необходимые компоненты панели могут быть построены путем расширения этого класса.
  4. CPriceRow. Простой класс - расширение класса CRow, который используется для отображения текущей цены Bid.
  5. CPriceChangeRow. Класс - потомок класса CRow, используемый для отображения изменения цены текущего бара. Может отображать изменение цены, изменение цены в процентах или отображение в виде стрелок.
  6. CRSIRow. Класс - потомок класса CRow, используемый для отображения текущего значения индикатора RSI (Relative Strength Index).
  7. CPriceMARow. Класс - потомок класса CRow, показывающий выполнение условия: значение индикатора SMA > текущей цены.

Наряду с индикатором SpyAgent, классы CTable и CRow являются ключевыми элементами панели. Классы CPriceRow, CPriceChangeRow, CRSIRow и CPriceMARow отвечают за ее содержимое. Класс CRow является базовым классом, путем расширения которого реализуется необходимый функционал. Четыре представленных класса - лишь простая иллюстрация того, что и каким образом можно сделать.


2. Индикатор SpyAgent

Начнем с индикатора SpyAgent. Он используется только в режиме multi-currency mode (мультивалютный режим) и необходим для обновления данных панели в случае прихода нового тика по инструментам, отличным от инструмента текущего графика. Подробности реализации можно найти в статье в "Реализация мультивалютного режима в MetaTrader 5".

Индикатор SpyAgent запускается на графике указанного символа и отправляет два события: initialization event (инициализация) и new tick event (приход нового тика). Оба события имеют тип CHARTEVENT_CUSTOM . Для их обработки нужно использовать обработчик события OnChartEvent(...), его мы рассмотрим позже.

Посмотрим на код SpyAgent:

//+------------------------------------------------------------------+
//|                                                     SpyAgent.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property indicator_chart_window
#property indicator_plots 0

input long   chart_id=0;        // id графика
input ushort custom_event_id=0; // id события
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {

   if(prev_calculated==0)
      EventChartCustom(chart_id,0,0,0.0,_Symbol); // посылает событие инициализации
   else
      EventChartCustom(chart_id,(ushort)(custom_event_id+1),0,0.0,_Symbol); // посылает событие new tick

   return(rates_total);
  }
//+------------------------------------------------------------------+
Он очень простой. Единственное, что он делает - при поступлении нового тика посылает события CHARTEVENT_CUSTOM.


3. Класс CTable

CTable является ключевым классом панели. Он хранит информацию о настройках панели и управляет ее содержимым. В случае необходимости он обновляет (перерисовывает) содержимое панели.

Посмотрим на описание класса CTable:

//+------------------------------------------------------------------+
//| Класс CTable                                                     |
//+------------------------------------------------------------------+
class CTable
  {
private:
   int               xDistance;    // расстояние от правого угла графика
   int               yDistance;    // расстояние от верхней границы графика
   int               cellHeight;   // высота ячейки таблицы
   int               cellWidth;    // ширина ячейки таблицы
   string            font;         // наименование шрифта
   int               fontSize;
   color             fontColor;

   CList            *rowList;      // список объектов 
   bool              tfMode;       // флаг режима multi-timeframe

   ENUM_TIMEFRAMES   timeframes[];  // массив таймфреймов для режима multi-timeframe
   string            symbols[];    // массив валютных пар для режима multi-currency

   //--- private-методы
   //--- установка параметров таблицы по умолчанию
   void              Init();
   //--- отображает текстовую метку указанной ячейки таблицы
   void              DrawLabel(int x,int y,string text,string font,color col);
   //--- возвращает таймфрейм в виде строки
   string            PeriodToString(ENUM_TIMEFRAMES period);

public:
   //--- конструктор для режима multi-timeframe
                     CTable(ENUM_TIMEFRAMES &tfs[]);
   //--- конструктор для режима multi-currency
                     CTable(string &symb[]);
   //--- деструктор
                    ~CTable();
   //--- перерисовка таблицы
   void              Update();
   //--- методы установки параметров таблицы
   void              SetDistance(int xDist,int yDist);
   void              SetCellSize(int cellW,int cellH);
   void              SetFont(string fnt,int size,color clr);
   //--- добавляет объект CRow в таблицу
   void              AddRow(CRow *row);
  };

Как видите, все компоненты (строки) панели хранятся в списке указателей на объекты CRow, поэтому каждый компонент, который мы захотим добавить в панель должен быть наследником класса CRow. Сам класс CRow можно рассматривать как способ связи панели и ее компонентов. Класс CTable не содержит кода для расчета своих ячеек, за это отвечают классы, являющиеся расширениями класса CRow. CTable - это структура для хранения компонентов типа CRow и их отображения в случае необходимости.

Пройдемся теперь по методам класса CTable. У класса два конструктора. Первый используется для режима multi-timeframe mode, он совсем простой. Нам лишь нужно предоставить ему массив из таймфреймов, данные по которым необходимо отображать.

//+------------------------------------------------------------------+
//| Конструктор для режима Multi-timeframe                           |
//+------------------------------------------------------------------+
CTable::CTable(ENUM_TIMEFRAMES &tfs[])
  {
//--- копируем таймфреймы в свой массив
   ArrayResize(timeframes,ArraySize(tfs),0);
   ArrayCopy(timeframes,tfs);
   tfMode=true;

//--- заполнение массива symbols текущим символом
   ArrayResize(symbols,ArraySize(tfs),0);
   for(int i=0; i<ArraySize(tfs); i++)
      symbols[i]=Symbol();

//--- установка параметров по умолчанию
   Init();
  }

Второй конструктор используется для режима multi-currency mode и имеет дело с массивом символов (инструментов). Также он устанавливает индикаторы-агенты SpyAgent, присоединяя их один за другим на соответствующие графики.

//+------------------------------------------------------------------+
//| Конструктор для режима Multi-currency                            |
//+------------------------------------------------------------------+
CTable::CTable(string &symb[])
  {
//--- копируем символы в свой массив
   ArrayResize(symbols,ArraySize(symb),0);

   ArrayCopy(symbols,symb);
   tfMode=false;

//--- установка параметров по умолчанию
   ArrayResize(timeframes,ArraySize(symb),0);
   ArrayInitialize(timeframes,Period());

//--- установка параметров по умолчанию
   Init();

//--- установка индикаторов SpyAgents для всех запращиваемых символов
   for(int x=0; x<ArraySize(symbols); x++)
      if(symbols[x]!=Symbol()) // не нужно устанавливать их для текущего графика
         if(iCustom(symbols[x],0,"SpyAgent",ChartID(),0)==INVALID_HANDLE)
           {
            Print("Ошибка в установке индикатора SpyAgent на символ "+symbols[x]);
            return;
           }
  }

В методе Init() создается список строк (в виде объекта CList - динамического списка объектов типа CObject) и устанавливает значения по умолчанию для внутренних переменных класса CTable (шрифт, его размер и цвет, размер ячеек и позиция относительно правого верхнего угла графика).

//+------------------------------------------------------------------+
//| Установка значений по умолчанию параметров таблицы               |
//+------------------------------------------------------------------+
CTable::Init()
  {
//--- создаем список для хранения объектов строк Row
   rowList=new CList;

//--- установка значений по умолчанию
   xDistance = 10;
   yDistance = 10;
   cellWidth = 60;
   cellHeight= 20;
   font="Arial";
   fontSize=10;
   fontColor=clrWhite;
  }

Деструктор простой - он производит удаление списка строк и всех объектов на графике, созданных панелью.

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CTable::~CTable()
  {
   int total=ObjectsTotal(0);

//--- удаление всех текстовых меток (label) с графика (всех объектов с префиксом nameBase)
   for(int i=total-1; i>=0; i--)
      if(StringFind(ObjectName(0,i),nameBase)!=-1)
         ObjectDelete(0,ObjectName(0,i));

//--- удаление списка строк и освобождение памяти
   delete(rowList);
  }

Метод AddRow(...) добавляет новую строку к списку строк. Отметим, что rowList является объектом CList, который автоматически изменяет свой размер.

//+------------------------------------------------------------------+
//| Добавляет новый объект row в конец таблицы                       |
//+------------------------------------------------------------------+
CTable::AddRow(CRow *row)
  {
   rowList.Add(row);
   row.Init(symbols,timeframes);
  }

Метод Update() является более сложным, он используется для отображения (отрисовки) панели.

Он состоит из трех частей:

  • Отображение первой колонки (наименования строк)
  • Отображение первой строки (наименования таймфреймов или символов, в зависимости от выбранного режима работы)
  • Отображение внутренних ячеек (значений компонентов таблицы)

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

//+------------------------------------------------------------------+
//| Метод перерисовки таблицы                                        |
//+------------------------------------------------------------------+
CTable::Update()
  {
   CRow *row;
   string symbol;
   ENUM_TIMEFRAMES tf;

   int rows=rowList.Total(); // число строк
   int columns;              // число столбцов

   if(tfMode)
      columns=ArraySize(timeframes);
   else
      columns=ArraySize(symbols);

//--- отображение первого стоблца(имена строк)
   for(int y=0; y<rows; y++)
     {
      row=(CRow*)rowList.GetNodeAtIndex(y);
      //--- примечание: запращиваем наименование объекта методом GetName()
      DrawLabel(columns,y+1,row.GetName(),font,fontColor);
     }

//--- отображение первой строки (наименования таймфреймов или валютных пар)
   for(int x=0; x<columns; x++)
     {
      if(tfMode)
         DrawLabel(columns-x-1,0,PeriodToString(timeframes[x]),font,fontColor);
      else
         DrawLabel(columns-x-1,0,symbols[x],font,fontColor);
     }

//--- отображение внутренних ячеек таблицы
   for(int y=0; y<rows; y++)
      for(int x=0; x<columns; x++)
        {
         row=(CRow*)rowList.GetNodeAtIndex(y);

         if(tfMode)
           {
            //--- в режиме multi-timeframe используем текущий символ и несколько таймфреймов
            tf=timeframes[x];
            symbol=_Symbol;
           }
         else
           {
            //--- в режиме multi-currency используем текущий таймфрейм и несколько символов
            tf=Period();
            symbol=symbols[x];
           }

         //--- примечание: запрашиваются шрифт объекта, 
         //--- цвет и текущее значение, вычисленное для заданного таймфрейма и символа
         DrawLabel(columns-x-1,y+1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf));
        }

//--- принудительная перерисовка графика
   ChartRedraw();
  }

Метод DrawLabel(...) используется для отображения текстовых меток заданной ячейки панели. Сначала он проверяет наличие метки, в случае ее отсутствия создается новый объект OBJ_LABEL.

Затем он устанавливает необходимые свойства метки и ее текст.

//+------------------------------------------------------------------+
//| Отображает текстовую метку (label) заданной ячейки таблицы       |
//+------------------------------------------------------------------+
CTable::DrawLabel(int x,int y,string text,string font,color col)
  {
//--- создание уникального наименования ячейки
   string name=nameBase+IntegerToString(x)+":"+IntegerToString(y);

//--- создание объекта типа OBJ_LABEL
   if(ObjectFind(0,name)<0)
      ObjectCreate(0,name,OBJ_LABEL,0,0,0);

//--- установка свойств объекта
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xDistance+x*cellWidth);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,yDistance+y*cellHeight);
   ObjectSetString(0,name,OBJPROP_FONT,font);
   ObjectSetInteger(0,name,OBJPROP_COLOR,col);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontSize);

//--- установка текста объекта
   ObjectSetString(0,name,OBJPROP_TEXT,text);
  }

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


4. Расширение класса CRow

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

Посмотрим на код класса CRow:

//+------------------------------------------------------------------+
//| Класс CRow                                                       |
//+------------------------------------------------------------------+
//| Базовый класс для создания строк таблицы                         |
//| Для создания собственных строк таблицы следует                   |
//| переопределить (перекрыть) один или более методов класса CRow    |
//+------------------------------------------------------------------+
class CRow : public CObject
  {
public:
   //--- метод инициализации
   virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { }

   //--- метод по умолчанию для получения значения строки таблицы (для отображения строки)
   virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf) { return("-"); }

   //--- метод по умолчанию для получения цвета ячейки таблицы
   virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf) { return(clrWhite); }
   
   //--- метод по умолчанию для получения наименования строки
   virtual string GetName() { return("-"); }

   //--- метод по умолчанию для получения шрифта ячейки таблицы
   virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf) { return("Arial"); }
  };

Класс CRow унаследован от класса CObject, поскольку только CObject и его потомки могут храниться в списке CList. У него 4 метода, которые почти пустые, точнее они возвращают значения по умолчанию. Эти методы должны быть переопределены при расширении класса CRow. Полное переопределение всех из них нам не требуется, можно ограничиться лишь нужными методами.

В качестве пример создадим самый простой компонент панели - компонент для показа текущей цены Bid. Он может быть использован в мультивалютном режиме (multi-currency mode) для отображения текущих цен различных инструментов.

Чтобы сделать это, создадим класс CPriceRow:

//+------------------------------------------------------------------+
//| Класс CPriceRow                                                  |
//+------------------------------------------------------------------+
class CPriceRow : public CRow
  {
public:
   //--- переопределение метода GetValue(..) класса CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- переопределение метода GetName() класса CRow
   virtual string    GetName();

  };
//+------------------------------------------------------------------+
//| Переопределение метода GetValue(..) класса CRow                  |
//+------------------------------------------------------------------+
string CPriceRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   MqlTick tick;

//--- получаем значение текущей цены
   if(!SymbolInfoTick(symbol,tick)) return("-");

   return(DoubleToString(tick.bid,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }
//+------------------------------------------------------------------+
//| Переопределение метода GetName() класса CRow                     |
//+------------------------------------------------------------------+
string CPriceRow::GetName()
  {
   return("Price");
  }

В данном случае мы переопределили методы GetValue(...) и GetName(). Метод GetName просто возвращает наименование этой строки, которая будет отображаться в первой колонке панели. Метод GetValue получает текущую цену по указанному символу и возвращает значение последней цены Bid. Это то, что нам нужно.

Этот был простой пример. Теперь посложнее - мы построим компонент, который будет показывать текущее значение индикатора RSI (Relative Strenth Index).

Его код:

//+------------------------------------------------------------------+
//| Класс CRSIRow                                                    |
//+------------------------------------------------------------------+
class CRSIRow : public CRow
  {
private:
   int               rsiPeriod;       // период RSI
   string            symbols[];       // массив symbols
   ENUM_TIMEFRAMES   timeframes[];     // массив timeframes
   int               handles[];      // массив хендлов индикаторов RSI

   //--- ищет хендл индикатора по символу и таймфрейму
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- конструктор
                     CRSIRow(int period);

   //--- переопределение метода GetValue(..) класса CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- переопределение метода GetName() класса CRow
   virtual string    GetName();

   //--- переопределение метода Init(..) класса CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| Конструктор класса CRSIRow                                       |
//+------------------------------------------------------------------+
CRSIRow::CRSIRow(int period)
  {
   rsiPeriod=period;
  }
//+------------------------------------------------------------------+
//| Переопределение метода Init(..) класса CRow                      |
//+------------------------------------------------------------------+
void CRSIRow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
   ArrayInitialize(handles,-1);
   
//--- копируем содержимое массивов во внутренние массивы
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- получаем хендл индикатора RSI всех символов и таймфреймов
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Переопределение метода GetValue(..)класса CRow                   |
//+------------------------------------------------------------------+
string CRSIRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];

//--- получаем хендл индикатора RSI
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- получаем последнее значение индикатора RSI
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");

   return(DoubleToString(value[0],2));
  }
//+------------------------------------------------------------------+
//| Переопределение метода GetName() класса CRow                     |
//+------------------------------------------------------------------+
string CRSIRow::GetName()
  {
   return("RSI("+IntegerToString(rsiPeriod)+")");
  }
//+------------------------------------------------------------------+
//| Возврашает хендл индикатора для заданного символа и таймфрейма   |
//+------------------------------------------------------------------+
int CRSIRow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

Здесь у нас несколько методов. В конструкторе производится установка периода индикатора RSI и его сохранение в переменной класса. Метод Init(...) используется для создания хендлов индикаторов RSI, они сохраняются в массиве handles[]. Метод GetValue() копирует и возвращает последнее значение из буфера соответствующего индикатора RSI. Метод GetHandle(...) используется для нахождения соответствующего хендла индикатора в массиве индикаторов handles[]. Наименование метода GetName() говорит само за себя.

Как вы можете видеть, строить компоненты панели довольно легко.  Таким же образом мы можем создавать компоненты для почти всех нужных значений, необязательно индикаторных. Ниже приведен пример специального значения (условия), основанного на SMA. Он проверяет положение текущей цены относительно скользящей средней и выводит 'Yes' или 'No' в зависимости от того, находится ли текущая цена выше скользящей средней.

//+------------------------------------------------------------------+
//| Класс CPriceMARow                                                |
//+------------------------------------------------------------------+
class CPriceMARow : public CRow
  {
private:
   int               maPeriod;     // период Moving Average
   int               maShift;      // сдвиг Moving Average
   ENUM_MA_METHOD    maType;        // SMA, EMA, SMMA или LWMA
   string            symbols[];    // массив symbols
   ENUM_TIMEFRAMES   timeframes[];  // массив timeframes
   int               handles[];   // массив хендлов MA

   //--- ищет хендл индикатора по символу и таймфрейму
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- конструктор
                     CPriceMARow(ENUM_MA_METHOD type,int period,int shift);

   //--- переопределение метода GetValue(..) класса CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- переопределение метода GetName() класса CRow
   virtual string    GetName();

   //--- переопределение метода Init(..) класса CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| Конструктор класса CPriceMARow                                   |
//+------------------------------------------------------------------+
CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,int period,int shift)
  {
   maPeriod= period;
   maShift = shift;
   maType=type;
  }
//+------------------------------------------------------------------+
//| Переопределение метода Init(..) класса CRow                      |
//+------------------------------------------------------------------+
void CPriceMARow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);

   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);

   ArrayInitialize(handles,-1);

//--- копируем содержимое массивов во внутренние массивы
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);

//--- получаем хендл индикатора MA всех символов и таймфреймов
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Переопределение метода GetValue(..) класса CRow                  |
//+------------------------------------------------------------------+
string CPriceMARow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];
   MqlTick tick;

//--- получаем хендл индикатора MA
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- получаем последнее значение индикатора MA
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");
//--- получаем последие цены
   if(!SymbolInfoTick(symbol,tick)) return("-");

//--- проверка условия: price > MA
   if(tick.bid>value[0])
      return("Yes");
   else
      return("No");
  }
//+------------------------------------------------------------------+
//| Переопределение метода GetName() класса CRow                     |
//+------------------------------------------------------------------+
string CPriceMARow::GetName()
  {
   string name;

   switch(maType)
     {
      case MODE_SMA: name = "SMA"; break;
      case MODE_EMA: name = "EMA"; break;
      case MODE_SMMA: name = "SMMA"; break;
      case MODE_LWMA: name = "LWMA"; break;
     }

   return("Price>"+name+"("+IntegerToString(maPeriod)+")");
  }
//+------------------------------------------------------------------+
//| Возврашает хендл индикатора для заданного символа и таймфрейма   |
//+------------------------------------------------------------------+
int CPriceMARow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

Здесь кода больше, поскольку индикатор Moving Average имеет 3 параметра: период, сдвиг и тип цены. Метод GetName() немного более сложный, он строит наименование по типу средней и периоду. Метод GetValue(...) работает почти так же, как и в классе CRSIRow, однако вместо значения индикатора возвращает 'Yes', если цены выше SMA или 'No' в случае, если цена ниже SMA.

Последний пример более сложный. Это класс CPriceChangeRow, который показывает изменение цены текущего бара. Он работает в трех режимах:

  • Отображение стрелок (зеленой "вверх" или красной "вниз");
  • Отображение значения изменения цены (зеленым или красным цветом);
  • Отображение изменения цены в процентах (зеленым или красным цветом).

Код выглядит следующим образом:

//+------------------------------------------------------------------+
//| Класс CPriceChangeRow                                            |
//+------------------------------------------------------------------+
class CPriceChangeRow : public CRow
  {
private:
   bool              percentChange;
   bool              useArrows;

public:
   //--- конструктор
                     CPriceChangeRow(bool arrows,bool percent=false);

   //--- переопределение метода GetName() класса CRow
   virtual string    GetName();

   //--- переопределение метода GetFont() класса CRow
   virtual string    GetFont(string symbol,ENUM_TIMEFRAMES tf);

   //--- переопределение метода GetValue(..) класса CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- переопределение метода GetColor(..) класса CRow
   virtual color     GetColor(string symbol,ENUM_TIMEFRAMES tf);

  };
//+------------------------------------------------------------------+
//| Конструктор класса CPriceChangeRow                               |
//+------------------------------------------------------------------+
CPriceChangeRow::CPriceChangeRow(bool arrows,bool percent=false)
  {
   percentChange=percent;
   useArrows=arrows;
  }
//+------------------------------------------------------------------+
//| Переопределение метода GetName() класса CRow                     |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetName()
  {
   return("PriceChg");
  }
//+------------------------------------------------------------------+
//| Переопределение метода GetFont() класса CRow                     |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetFont(string symbol,ENUM_TIMEFRAMES tf)
  {
//--- для отображение стрелок up/down мы используем шрифт Wingdings 
   if(useArrows)
      return("Wingdings");
   else
      return("Arial");
  }
//+------------------------------------------------------------------+
//| Переопределение метода GetValue(..) класса CRow                  |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- получаем цены open и close текущего бара
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(" ");
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(" ");

//--- изменение цены текущего бара
   double change=close[0]-open[0];

   if(useArrows)
     {
      if(change > 0) return(CharToString(233)); // код стрелки "up"
      if(change < 0) return(CharToString(234)); // код стрелки "down"
      return(" ");
        }
    else
    {
      if(percentChange)
        {
         //--- расчет изменения в процентах
         return(DoubleToString(change/open[0]*100.0,3)+"%");
        }
      else
        {
         return(DoubleToString(change,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
        }
     }
  }
//+------------------------------------------------------------------+
//| Переопределение метода GetColor(..) класса CRow                  |
//+------------------------------------------------------------------+
color CPriceChangeRow::GetColor(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- получаем цены open и close текущего бара
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(clrWhite);
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(clrWhite);

   if(close[0] > open[0]) return(clrLime);
   if(close[0] < open[0]) return(clrRed);
   return(clrWhite);
  }

Конструктор имеет два параметра. Первый определяет необходимость рисования стрелок. Если его значение равно true, второй параметр игнорируется. Если он равен false, второй параметр определяет необходимость показа процентного изменения цен либо их значений.

В этом классе я решил переопределить 4 метода класса CRow: GetName, GetValue, GetColor и GetFont. Самым простым из них является метод GetName(), который просто возвращает наименование. Метод GetFont(...) дает возможность вывода стрелок или других символов шрифта Wingdings. Метод GetColor(...) возвращает цвет Lime, если цена возрастает и Red, если цена уменьшается.

Цвет White возвращается в случаях, если цена не изменилась или при ошибке. В методе GetValue(...) запрашиваются цены открытия и закрытия последнего бара, вычисляется их разность и возвращается требуемое значение. В режиме вывода стрелок он возвращает коды стрелок вверх/вниз шрифта Wingdings.


5. Как все это использовать

Для использования панели нам нужно создать новый индикатор. Назовем его TableSample

Список событий, которые мы должны обрабатывать:

Также нам нужен указатель на объект типа CTable, который будет создан динамически в функции OnInit(). Сначала нам нужно указать режим, который мы будем использовать (multi-timeframe или multi-currency mode). В коде, приведенном ниже, используется режим multi-currency mode, пример режима multi-timeframe mode закомментирован. Для режима multi-currency mode нужно создать массив инструментов и передать его в конструктор класса CTable. Для режима multi-timeframe mode нужно создать массив таймфреймов и также передать его во второй тип конструктора.

После этого нам нужно создать все необходимые компоненты и добавить их панель при помощи метода AddRow(...). При желании параметры панели могут быть изменены. Затем нам нужно отобразить панель, поэтому в конце функции OnInit() мы вызваем метод Update. Метод OnDeinit() простой, единственное, что требуется - удаление объекта CTable, которое приводит к вызову деструктора класса CTable.

Методы OnCalculate и OnChartEvent идентичны, они вызывают метод Update(). Метод OnChartEvent(...) необходим только для работы панели в режиме multi-currency mode. В мультивалютном режиме он обрабатывает события, вызванные агентами SpyAgent. В мультитаймфреймовом режиме требуется лишь метод OnCalculate(), поскольку нам требуется наблюдение лишь за символом текущего графика.

//+------------------------------------------------------------------+
//|                                                  TableSample.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0

#include <Table.mqh>
#include <PriceRow.mqh>
#include <PriceChangeRow.mqh>
#include <RSIRow.mqh>
#include <PriceMARow.mqh>

CTable *table; // указатель на объект CTable
//+------------------------------------------------------------------+
//| Indicator initialization function                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- таймфреймы, которые будут использоваться в таблице (в режиме multi-timeframe mode)
   ENUM_TIMEFRAMES timeframes[4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1};

//--- символы, которые будут использоваться в таблице (в режиме multi-currency mode)
   string symbols[4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" };
//-- создание объекта CTable
//   table = new CTable(timeframes); // режим multi-timeframe mode
   table=new CTable(symbols);    // режим multi-currency mode

//--- добавление строк в таблицу
   table.AddRow(new CPriceRow());                 // отображение текущую цену
   table.AddRow(new CPriceChangeRow(false));      // отображение изменения цены текущего бара
   table.AddRow(new CPriceChangeRow(false,true));  // отображение изменения цены текущего бара (в процентах)
   table.AddRow(new CPriceChangeRow(true));       // отображение изменения цены в виде стрелок
   table.AddRow(new CRSIRow(14));                 // отображение значения RSI(14)
   table.AddRow(new CRSIRow(10));                 // отображение значения RSI(10)
   table.AddRow(new CPriceMARow(MODE_SMA,20,0));   // отображение выполнения условия SMA(20) > текущей цены

//--- установка параметров таблицы
   table.SetFont("Arial",10,clrYellow);  // шрифт, размер, цвет
   table.SetCellSize(60, 20);            // ширина и высота
   table.SetDistance(10, 10);            // расстояние от верхнего правого угла графика

   table.Update(); // принудительное обновление (перерисовка) таблицы

   return(0);
  }
//+------------------------------------------------------------------+
//| Indicator deinitialization function                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- вызываем деструктор table и освобождаем память
   delete(table);
  }
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- обновление таблицы: перерасчет и перерисовка
   table.Update();
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Обработчик события OnChartEvent                                  |
//| Обрабатывает событие CHARTEVENT_CUSTOM,отправленное индикаторами |
//| SpyAgent (требуется только для режима multi-currency mode)       |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   table.Update(); // обновление таблицы: перерасчет и перерисовка
  }
//+------------------------------------------------------------------+

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


6. Установка

Все файлы нужно скомпилировать. Индикаторы SpyAgent и TableSample нужно скопировать в папку: каталог_данных_терминала\MQL5\Indicators. Остальные файлы являются файлами библиотек, их нужно скопировать в папку: каталог_данных_терминала\MQL5\Include. Для запуска панели запустите индикатор TableSample на любом графике. Запуск индикаторов SpyAgent не требуется, они будут запущены автоматически.


Заключение

В статье приведен пример объектной реализации мультитаймфреймовых и мультивалютных панелей для MetaTrader 5 и показано, как легко и просто создавать собственные панели.

Все коды, представленные в статье, можно скачать в приложении.


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/357

Прикрепленные файлы |
Используйте EX5-библиотеки для продвижения своих разработок Используйте EX5-библиотеки для продвижения своих разработок
С помощью сокрытия реализации функций/классов в ex5-файл вы сможете делиться своими ноу-хау алгоритмами с другими программистами, создавать общие проекты и продвигать их в сети. И пока команда MetaQuotes всеми силами приближает возможность прямого наследования классов из ex5‑библиотек, мы реализуем данную возможность уже сейчас.
Интервью с Андреа Дзани (ATC 2011) Интервью с Андреа Дзани (ATC 2011)
На предпоследней неделе участник чемпионата Андреа Дзани (sbraer) вплотную подобрался к пятерке лидеров чемпионата. Он занимает 6-е место с результатом около 47 000 USD. За все время чемпионата советник Андреа "AZXY" совершил только одну убыточную сделку в самом начале. С тех пор его кривая эквити неуклонно растет.
Создай свои графические панели на MQL5 Создай свои графические панели на MQL5
Удобство пользования MQL5-программой определяется не только её богатой функциональностью, но и продуманным графическим интерфейсом. Визуальное восприятие иногда гораздо важнее, чем быстрая и стабильная работа. Перед вами пошаговое руководство по самостоятельному созданию индикаторных панелей на основе классов Стандартной библиотеки.
Интервью с Ли Фаном (ATC 2011) Интервью с Ли Фаном (ATC 2011)
На седьмой неделе Чемпионата советник Ли Фана (lf8749) установил рекорд - за 10 трейдов он заработал более $100 000. Именно эта удачная серия сделок обеспечила экспертописателю двухнедельное пребывание на первой строчке рейтинга. Каким образом удалось совершить такой подвиг, мы решили выяснить у самого Ли Фана.