English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Взгляни на рынок через готовые классы

Взгляни на рынок через готовые классы

MetaTrader 5Примеры | 26 октября 2010, 15:13
8 367 6
Dmitriy Skub
Dmitriy Skub

Введение

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

В качестве основы для разработки библиотеки взят набор стандартных классов, поставляемых вместе с терминалом MetaTrader 5. Для начала, рассмотрим этот набор и затем решим, как нужно его дополнить и расширить, чтобы выполнить поставленную задачу.


Стандартные классы для создания графических объектов

Интересующий нас набор классов находится в директориях Include и Include\ChartObjects.

Это следующие файлы:

  • Object.mqh - содержит один базовый класс CObject для построения всех остальных, связанных с графическими объектами.
  • ChartObject.mqh - также содержит один класс CChartObject, порожденный от CObject, расширяющий функциональность и инкапсулирующий общие данные и методы всех графических объектов.
  • ChartObjectsTxtControls.mqh - содержит ряд классов, предназначенных для вывода на экран различных графических объектов, содержащих текст. Это базовый для них класс CChartObjectText и классы-потомки CChartObjectLabel, CChartObjectEdit и CChartObjectButton.

Рассмотрим эти классы подробней.


Базовый класс общего назначения - CObject

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

class CObject
{
protected:
   CObject          *m_prev;
   CObject          *m_next;

public:
                     CObject();

   CObject          *Prev()                { return(m_prev); }
   void              Prev(CObject *node)   { m_prev=node;    }
   CObject          *Next()                { return(m_next); }
   void              Next(CObject *node)   { m_next=node;    }

   virtual bool      Save(int file_handle) { return(true);   }
   virtual bool      Load(int file_handle) { return(true);   }

   virtual int       Type() const          { return(0);      }

protected:
   virtual int       Compare(const CObject *node,int mode=0) const { return(0); }
};

//+------------------------------------------------------------------+
void CObject::CObject()
{
   m_prev=NULL;
   m_next=NULL;
}

Как видим, данный класс содержит только данные и методы общего назначения, не имеющие прямого отношения к выводу на экран терминала. Зато он обладает очень важным свойством - его можно использовать для построения односвязных и двусвязных списков. Такое свойство ему дают поля-данные CObject::m_prev и CObject::m_next типа CObject* и методы их чтения/записи. Поле CObject::m_prev указывает на предыдущий элемент списка, а CObject::m_next - на следующий. Более подробно о построении списков будет рассказано дальше.

Кроме того, есть метод сравнения двух объектов типа CObject* - это метод CObject::Compare, который может использоваться при сортировке элементов внутри списка. Есть еще два полезных метода, позволяющих сохранять/считывать поля данных в файлах - это методы CObject::Save и CObject::Load, соответственно. Данные методы нужно переопределять в классах-потомках для получения нужной функциональности. Еще один метод - идентификация типа объекта - CObject::Type. Данный метод бывает полезен при манипулировании списком, содержащим объекты разного типа.

Итак, базовый класс CObject (и его экземпляры) обладает следующими свойствами:

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

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


Базовый класс для построения графических объектов - CChartObject

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

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

protected:
  long       m_chart_id;    // идентификатор графика, на котором лежит 
                                // данный графический объект (0 - текущий график)
  int        m_window;      // номер подокна ( 0 - главное окно )
  string     m_name;        // уникальное (в пределах графика) имя объекта
  int        m_num_points;  // число точек для привязки объекта

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

bool CChartObject::Attach(long chart_id,string name,int window,int points)
{
  if(ObjectFind(chart_id,name)<0)
  {
    return(false);
  }

  if(chart_id==0) chart_id=ChartID();

  m_chart_id  =chart_id;
  m_window    =window;
  m_name      =name;
  m_num_points=points;

  return(true);
}

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

Здесь уже реализованы методы сохранения CChartObject::Save и считывания CChartObject::Load свойств графического объекта. В классе-потомке следует сначала вызывать родительские методы сохранения/чтения, затем собственные.

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

  • привязка реального графического объекта к своему программному "образу".
  • чтение и модификация общих для всех графических объектов свойств.
  • удаление графического объекта с экрана.
  • перемещение графического объекта по экрану (позиционирование).


Класс для построения текстовых графических объектов -
CChartObjectText

Обратимся теперь к файлу ChartObjectsTxtControls.mqh. Здесь находятся описания классов, предназначенных для вывода на экран различных графических объектов, содержащих текст. Рассмотрим их основные особенности.

Базовый класс для них - CChartObjectText. Он инкапсулирует в себе свойства и методы, связанные с отображением текста.

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

class CChartObjectText : public CChartObject
{
public:
   double            Angle() const;
   bool              Angle(double angle);
   string            Font() const;
   bool              Font(string font);
   int               FontSize() const;
   bool              FontSize(int size);
   ENUM_ANCHOR_POINT Anchor() const;
   bool              Anchor(ENUM_ANCHOR_POINT anchor);

   bool              Create(long chart_id,string name,int window,datetime time,double price);

   virtual int       Type() const { return(OBJ_TEXT); }

   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
};

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

Его реализация следующая:

bool CChartObjectText::Create(long chart_id,string name,int window,datetime time,double price)
{
  bool result = ObjectCreate( chart_id, name, OBJ_TEXT, window, time, price );
  if(result)
  {
    result &= Attach(chart_id, name, window, 1 );
  }

  return(result);
}

В случае успешного создания графического объекта (функция ObjectCreate возвращает истинное значение), вызывается метод CChartObject::Attach, который мы рассмотрели ранее.

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

  • чтение и модификация свойств текстовых графических объектов.
  • создание реального графического объекта на экране терминала типа OBJ_TEXT.


Класс для создания графического объекта типа "Текстовая метка" - CChartObjectLabel

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

Приведем описание данного класса:

class CChartObjectLabel : public CChartObjectText
{
public:
   int               X_Distance() const;
   bool              X_Distance(int X);
   int               Y_Distance() const;
   bool              Y_Distance(int Y);
   int               X_Size() const;
   int               Y_Size() const;
   ENUM_BASE_CORNER  Corner() const;
   bool              Corner(ENUM_BASE_CORNER corner);

   bool              Time(datetime time) { return(false);     }
   bool              Price(double price) { return(false);     }

   bool              Create(long chart_id,string name,int window,int X,int Y);

   virtual int       Type() const        { return(OBJ_LABEL); }

   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
};

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

По сравнению с родительским, класс CChartObjectLabel содержит следующие отличительные свойства:

  • позиционирование графического объекта производится в экранных координатах окна.
  • чтение и модификация угла привязки, относительно которого будут задаваться координаты графического объекта. Фактически, это задание начала координат в одном из четырех углов окна графика.
  • создание реального графического объекта на экране терминала типа OBJ_LABEL.


Класс для создания графического объекта типа "Поле ввода" - CChartObjectEdit

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

class CChartObjectEdit : public CChartObjectLabel
{
public:
   bool              X_Size(int X);
   bool              Y_Size(int Y);
   color             BackColor() const;
   bool              BackColor(color new_color);
   bool              ReadOnly() const;
   bool              ReadOnly(bool flag);

   bool              Angle(double angle) { return(false);    }

   bool              Create(long chart_id,string name,int window,int X,int Y,int sizeX,int sizeY);

   virtual int       Type() const        { return(OBJ_EDIT); }

   virtual bool      Save(int file_handle);
   virtual bool      Load(int file_handle);
};

Отличия поля ввода типа OBJ_EDIT от текстовой метки следующие:

  • поле ввода имеет свойства ширина и высота (задается в экранных пикселях), которые ограничивают размер графического объекта на экране. Размер текстовой метки же подстраивается автоматически так, чтобы полностью был виден текст.
  • имеются методы разрешения/запрещения модификации текста - CChartObjectEdit::ReadOnly.
  • добавлен метод, позволяющий менять цвет фона участка, занимаемый графическим объектом.
  • отсутствует возможность изменения угла, под которым отображается объект. Поле ввода может отображаться только в горизонтальном направлении.
  • создание реального графического объекта на экране терминала типа OBJ_EDIT.


Класс для создания графического объекта типа "Кнопка" - CChartObjectButton

Еще один класс в иерархии текстовых графических объектов - CChartObjectButton. Этот объект называется кнопка, он предназначен для создания управляющего элемента на экране в виде нажимающейся кнопки. Этот класс является потомком класса CChartObjectEdit и наследует его функциональность:

class CChartObjectButton : public CChartObjectEdit
{
public:
  bool             State() const;
  bool             State(bool state);

  virtual int       Type() const { return(OBJ_BUTTON); }

  virtual bool      Save(int file_handle);
  virtual bool      Load(int file_handle);
};

Отличия кнопки типа OBJ_BUTTON от поля ввода следующие:

  • внешнее отображение в виде нажимающейся кнопки, аналогичной используемых в диалоговых окнах Windows.
  • новые методы для считывания/модификации состояния кнопки (нажата/отжата) - CChartObjectButton::State.
  • создание реального графического объекта на экране терминала типа OBJ_BUTTON.


Общая структура стандартных классов для отображения графических текстовых объектов

В графическом виде структуру (иерархию) стандартных классов можно представить в следующем виде:

Общая структура стандартных классов

Рисунок 1. Общая структура стандартных классов

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


Расширение функциональности стандартных классов

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

ЗАГОЛОВОК ИНФОРМАЦИОННЫЙ ТЕКСТ


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

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

Пример структурированного вывода текстовой информации

Рисунок 2. Пример структурированного вывода текстовой информации

Здесь используется шесть информационных полей предложенной выше структуры. Некоторые элементы структуры могут отсутствовать. Например, на рисунке 1 в самом верхнем поле отсутствует заголовок. В остальных полях есть и заголовок, и информационный текст. Данный индикатор содержится в приложенном к статье файле PricelInfo.mq5.


Позиционирование графических объектов

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

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

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


Автоматическая генерация неповторяющихся имен объектов

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

Предлагается следующий способ генерации имени (строка, состоящая из полей):

Дата Время
Число миллисекунд


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

TimeToString(TimeGMT(), TIME_DATE|TIME_MINUTES|TIME_SECONDS);

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

DoubleToString(GetTickCount(), 0);

Но, даже если измерить время с точностью до миллисекунд, вполне возможна ситуация, когда вызвав последовательно два или более раз функцию GetTickCount(), мы получим одно и тоже значение. Это обусловлено ограничением дискретности внутренних таймеров операционной системы и процессора. Таким образом, надо принять дополнительные меры для обнаружения такой ситуации.

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

string GetUniqName()
{
  static uint prev_count = 0;

  uint count = GetTickCount();
  while(1)
  {
    if(prev_count == UINT_MAX)
    {
      prev_count = 0;
    }
    if(count <= prev_count)
    {
      prev_count++;
      count = prev_count;
    }
    else
    {
      prev_count = count;
    }

//  Проверим, нет ли уже объекта с таким именем:
    string name = TimeToString(TimeGMT(), TIME_DATE|TIME_MINUTES|TIME_SECONDS)+" "+DoubleToString(count, 0);
    if(ObjectFind(0, name) < 0)
    {
      return(name);
    }
  }

  return(NULL);
}

Ограничение такого способа: генерация не более 4 294 967 295 (UINT_MAX) уникальных имен за одну секунду. Видимо, этого должно быть вполне достаточно на практике. На всякий случай, дополнительно проверяется существование графического объекта с таким именем.


Отображение заголовка информационной структуры - класс TTitleDisplay

Класс для вывода заголовка на экран терминала представлен ниже:

class TTitleDisplay : public CChartObjectLabel
{
protected:
  long    chart_id;
  int     sub_window;
  long    chart_width;       // ширина графика в пикселях
  long    chart_height;      // высота графика в пикселях
  long    chart_width_step;  // шаг координатной сетки по горизонтали
  long    chart_height_step; // шаг координатной сетки по вертикали
  int     columns_number;    // число колонок
  int     lines_number;      // число строк
  int     curr_column;
  int     curr_row;

protected:
  void    SetParams(long chart_id, int window, int cols, int lines);// задать параметры объекта

protected:
  string  GetUniqName();    // получить уникальное имя
  bool    Create(long chart_id, int window, int cols, int lines, int col, int row);
  void    RecalcAndRedraw();// пересчитать координаты и перерисовать

        
public:
  void    TTitleDisplay();  // конструктор
  void    ~TTitleDisplay(); // деструктор
};

Основной метод, создающий графический текстовый объект:

bool Create(long chart_id, int window, int _cols, int _lines, int _col, int _row);

Назначение входных параметров следующее:

  • chart_id - идентификатор окна (0 - главное окно);
  • window - номер подокна (0 - основное);
  • cols - максимальное число графических текстовых объектов по горизонтали (число колонок);
  • lines - максимальное число графических текстовых объектов по вертикали (число строк);
  • col - координата графического объекта по горизонтали (может меняться в пределах от нуля до cols - 1);
  • row - координата графического объекта по вертикали (может меняться в пределах от нуля до lines - 1);

Метод TTitleDisplay::SetParams рассчитывает параметры объекта, связанные с позиционированием на экране.

Реализация следующая:

void TTitleDisplay::SetParams(long _chart_id, int _window, int _cols, int _lines)
{
  this.chart_id = _chart_id;
  this.sub_window = _window;
  this.columns_number = _cols;
  this.lines_number = _lines;

//  Определим размеры окна в пикселях:
  this.chart_width = GetSystemMetrics(SM_CXFULLSCREEN);
  this.chart_height = GetSystemMetrics(SM_CYFULLSCREEN);

//  Рассчитаем шаг координатной сетки:
  this.chart_width_step = this.chart_width/_cols;
  this.chart_height_step = this.chart_height/_lines;
}

Здесь используется вызов функции WinAPI для получения текущих параметров экрана (разрешения рабочей области) - GetSystemMetrics. Эта функция импортируется из системной библиотеки  Windows user32.dll.

Класс для создания информационного текста TFieldDisplay строится аналогично. Подробности можно посмотреть в приложенном к статье файле библиотеки TextDisplay.mqh.


Манипуляция объектами в списке - стандартный класс CList

Теперь рассмотрим еще один стандартный класс, который нам понадобится при реализации задуманного. Это класс, позволяющий группировать объекты в списки. Он находится в файле Include\Arrays\List.mqh. В этом файле описан и реализован стандартный класс CList, являющийся наследником базового класса CObject, который мы рассмотрели в начале статьи. Он содержит набор методов для манипуляции объектами в списке. Это добавление в список, удаление из списка, доступ к произвольному элементу списка и очистка списка.

Рассмотрим основные методы:

  • добавление в список:
   int Add(CObject *new_node);
   int Insert(CObject *new_node,int index);

Это два метода для добавления нового элемента в список. Первый из них CList::Add позволяет добавить новый элемент new_node в конец списка. Второй CList::Insert позволяет вставить новый элемент new_node в произвольное место списка, которое указывается индексом index.

  • удаление из списка:
   bool  Delete(int index);

Метод позволяет удалить из списка элемент с заданным индексом index. При этом, кроме удаления элемента из списка, освобождается также память, занимаемая элементом типа CObject. То есть, происходит и "физическое" удаление объекта.

  • доступ к произвольному элементу списка:
   int       IndexOf(CObject* node);
   CObject*  GetNodeAtIndex(int index);
   CObject*  GetFirstNode();
   CObject*  GetPrevNode();
   CObject*  GetNextNode();
   CObject*  GetLastNode();

Метод CList::IndexOf возвращает индекс заданного элемента в списке. Нумерация индексов идет с нуля - первый элемент имеет нулевой индекс. Метод, выполняющий обратную операцию - по индексу элемента возвращается указатель на элемент. Если элемента с таким индексом в списке нет, то возвращается -1.

Еще четыре метода выполняют навигацию по списку - CList::GetFirstNode возвращвет предыдущий элемент, CList::GetNextNode - следующий, CList::GetLastNode - последний элемент в списке.

  • очистка списка:
   void  Clear();

Метод позволяет удалить все элементы из списка и также освобождается память, занимаемая объектами.

Это основные методы класса CList, остальные описаны в разделе справки, посвященном стандартной библиотеке.


Создание таблицы для вывода текста на экран - класс TableDisplay

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

class TableDisplay : public CList
{
protected:
  long  chart_id;
  int   sub_window;

public:
  void  SetParams(long _chart_id, int _window, ENUM_BASE_CORNER _corner = CORNER_LEFT_UPPER);
  int   AddTitleObject(int _cols, int _lines, int _col, int _row, 
                      string _title, color _color, string _fontname = "Arial", int _fontsize = 8);
  int   AddFieldObject(int _cols, int _lines, int _col, int _row, 
                          color _color, string _fontname = "Arial", int _fontsize = 8);
  bool  SetColor(int _index, color _color);
  bool  SetFont(int _index, string _fontname, int _fontsize);
  bool  SetText(int _index, string _text);

public:
  void  TableDisplay();
  void  ~TableDisplay();
};

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

Полный текст разработанной нами библиотеки находится в приложенном к статье файле TextDisplay.mqh.


3. Пример создания экранной таблицы

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

Выглядеть должно примерно в таком виде:

Пример экранной таблицы

Рисунок 3. Пример экранной таблицы

  • Шаг 1 - включение библиотеки в исходный текст индикатора:
#include  <TextDisplay.mqh>
  • Шаг 2 - создание массивов с названиями и координатами для заголовков:
#define  NUMBER  8
//---------------------------------------------------------------------
string  names[NUMBER]   = {"EURUSD", "GBPUSD", "AUDUSD", "NZDUSD", "USDCHF", "USDCAD", "USDJPY", "EURJPY"};
int     coord_y[NUMBER] = {2,        3,        4,        5,        6,      7,        8,       9};
Координаты отсчитываются начиная с нуля.
  • Шаг 3 - создание табличного объекта типа TableDisplay для хранения всех отображаемых текстовых объектов:
TableDisplay  Table1;
  • Шаг 4 - добавление в таблицу объектов-заголовков и объектов-информационных полей:
int OnInit()
{
//  Создание элемента отображения:
  Table1.SetParams(0, 0);

  for(int i=0; i<NUMBER; i++)
  {
    Table1.AddFieldObject(40, 40, 3, coord_y[i], Yellow);
  }

  for(int i=0; i<NUMBER; i++)
  {
    Table1.AddTitleObject(40, 40, 1, coord_y[i], names[i]+":", White);
  }

  ChartRedraw(0);
  EventSetTimer(1);

  return(0);
}

Оптимально это делать в обработчике события инициализации OnInit. Сначала добавляются информационные поля с помощью метода TableDisplay::AddFieldObject. Затем добавляются заголовки для них методом TableDisplay::AddTitleObject.

Добавление происходит в отдельных циклах по двум причинам: во-первых, число заголовков в общем случае может не совпадать с числом информационных полей; во-вторых, доступ к обновляемым информационным полям проще организовать когда индексация их идет подряд (в данном случае, от нуля до значения NUMBER - 1).

  • Шаг 5 - добавление кода для обновления динамической информации:

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

Текст его представлен ниже:

//---------------------------------------------------------------------
double    rates[NUMBER];
datetime  times[NUMBER];
MqlTick   tick;
//---------------------------------------------------------------------
//      Обработчик события от таймера:
//---------------------------------------------------------------------
void OnTimer()
{
  for(int i=0; i<NUMBER; i++)
  {
//  Получим ценовые данные:
    ResetLastError();
    if(SymbolInfoTick(names[i], tick) != true)
    {
      Table1.SetText(i,"Err "+DoubleToString(GetLastError(),0));
      Table1.SetColor(i,Yellow);
      continue;
    }

    if(tick.time>times[i])
    {
       Table1.SetText(i, DoubleToString(tick.bid, (int)(SymbolInfoInteger(names[i], SYMBOL_DIGITS))));

       if(tick.bid>rates[i])
       {
         Table1.SetColor(i, Lime);
       }
       else if(tick.bid<rates[i])
       {
         Table1.SetColor(i, Red);
       }
       else
       {
         Table1.SetColor(i, Yellow);
       }
       rates[i] = tick.bid;
       times[i] = tick.time;
    }
  }

  ChartRedraw(0);
}

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

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

Все, что нужно - это добавить дополнительные информационные поля в таблицу:

  for(int i=0; i<NUMBER; i++)
  {
    Table1.AddFieldObject(40, 40, 5, coord_y[i], Yellow);
  }

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

  Table1.SetText(i+NUMBER, DoubleToString((tick.ask-tick.bid)/SymbolInfoDouble(names[i], SYMBOL_POINT), 0));

В результате получаем следующую картинку:

Цены со спрэдом

Рисунок 4. Цены со спрэдом

Обратите внимание, что индекс полей для вывода спрэда начинается от значения NUMBER (при i, равном нулю).

  • Шаг 6 - удаление созданных объектов:
void OnDeinit(const int _reason)
{
  EventKillTimer();

//  Удаление элемента отображения:
  Table1.Clear();
}

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

Полный текст данного индикатора находится в приложенном к статье файле PriceList.mq5. Приложена "усовершенствованная" версия индикатора, выводящая спрэд и имеющая ряд внешних параметров для задания цвета заголовков и положения таблицы в окне терминала.


Заключение

В приложенном файле MarketWatch.mq5 (и включаемом MarketWatch.mqh) находится индикатор для вывода основных параметров по торговым инструментам в виде сводной таблицы. Для каждого инструмента выводится информация, аналогичная рис.2. Кроме того, отображается процентное изменение цены за заданные временные интервалы. Набор инструментов (не более 16) и временных интервалов задаются в виде строки с элементами, разделенными точкой с запятой.

Результаты работы данного индикатора представлены на рисунке 5:

Индикатор обзора рынка

Рисунок 5. Индикатор обзора рынка

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

Прикрепленные файлы |
priceinfo.mq5 (8.54 KB)
pricelist.mq5 (7.22 KB)
marketwatch.mq5 (11.04 KB)
textdisplay.mqh (15.43 KB)
marketwatch_doc.zip (379.28 KB)
marketwatch.mqh (9.47 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Dmitriy Skub
Dmitriy Skub | 6 апр. 2013 в 22:15
Какого-то особенного смысла в этом нет - была мысль наподобие ЕМА использовать. Сейчас уже использую просто СДД как среднее арифметическое - так общепринято и разница не существенная.
Nikolai Karetnikov
Nikolai Karetnikov | 6 апр. 2013 в 23:48
Dima_S:
Какого-то особенного смысла в этом нет - была мысль наподобие ЕМА использовать. Сейчас уже использую просто СДД как среднее арифметическое - так общепринято и разница не существенная.

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

Нет ли подобного кода?

Спасибо! 

Dmitriy Skub
Dmitriy Skub | 8 апр. 2013 в 09:21
ns_k:

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

Нет ли подобного кода?

Спасибо! 

Есть, но только в виде гистограммы (индикатора) и только под МТ4.
Nikolai Karetnikov
Nikolai Karetnikov | 19 апр. 2013 в 12:07
Dima_S:
Есть, но только в виде гистограммы (индикатора) и только под МТ4.

Спасибо!  Но я тяготею к использованию исключительно МТ5 для работы :) 

Dmitriy Skub
Dmitriy Skub | 19 апр. 2013 в 14:47
ns_k:

Спасибо!  Но я тяготею к использованию исключительно МТ5 для работы :) 

Если есть идеи по использованию, то можно и переписать на МТ5))
Работа по Накоплению/Распределению и что из этого можно сделать Работа по Накоплению/Распределению и что из этого можно сделать
Индикатор Накопления/Распределения A/D имеет одно интересное свойство - пробитие трендовой линии, построенной на графике данного индикатора с определённой долей вероятности говорит нам о скором пробое линии тренда на графике цены. Данная статья будет полезна и интересна людям, только начинающим программировать на MQL4, поэтому я постарался изложить всё в наиболее доступной для понимания форме и использовать самые простые конструкции построения кода.
Усреднение ценовых рядов без дополнительных буферов для промежуточных расчетов Усреднение ценовых рядов без дополнительных буферов для промежуточных расчетов
Статья о традиционных и не совсем традиционных алгоритмах усреднения, упакованных в максимально простые и достаточно однотипные классы. Они задумывались для универсального использования в практических разработках индикаторов. Надеюсь, что предложенные классы в определенных ситуациях могут оказаться достаточно актуальной альтернативой громоздким, в некотором смысле, вызовам пользовательских и технических индикаторов.
Владимир Цирульник: "Суть моей программы - импровизация!" (ATC 2010) Владимир Цирульник: "Суть моей программы - импровизация!" (ATC 2010)
На счету у Владимира Цирульника один из самых ярких взлетов текущего Чемпионата. К концу третьей торговой недели эксперт Владимира пришел шестым и, похоже, не собирается сдавать позиций. Алгоритм IMEX, который лежит в основе советника, был создан самим разработчиком. Чтобы узнать об этом алгоритме побольше, мы связались с Владимиром.
Строим анализатор спектра Строим анализатор спектра
Данная статья призвана познакомить читателя с возможным вариантом использования графических объектов языка MQL5. В статье рассматривается индикатор, в котором при помощи графических объектов создана панель управления простейшим анализатором спектра. Статья рассчитана на читателя, знакомого с основами языка MQL5.