English 中文 Español Deutsch 日本語 Português
preview
Бегущая строка котировок: улучшенная версия

Бегущая строка котировок: улучшенная версия

MetaTrader 5Индикаторы | 20 января 2023, 10:08
2 189 0
Daniel Jose
Daniel Jose

Введение

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

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

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

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


Внедрение новой системы ленты котировок

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

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Object_Base.mqh"
//+------------------------------------------------------------------+
class C_Object_BtnBitMap : public C_Object_Base
{
        public  :
//+------------------------------------------------------------------+
                void Create(string szObjectName, string szResource1)
                        {
                                C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL);
                                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 0, "\\Images\\Widget\\Bitmaps\\" + szResource1 + ".bmp");
                                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, false);
                        };
//+------------------------------------------------------------------+
};

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

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

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

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

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

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

Один из таких способов показан в статье Как сделать график более интересным: Добавление фона, в этой статье мы показали, как можно использовать другой метод для создания изображения непосредственно через ресурс. Хотя мы по-прежнему используем растровый файл (так как он имеет более простую внутреннюю модель конструкции кода), ничто не мешает нам использовать другие форматы, например, GIF-файл с анимацией. Главное, что, при использовании этого метода, мы можем создать и использовать изображение с прозрачным фоном, что невозможно здесь.

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

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

Кроме этого дополнения к коду, у нас есть еще одно изменение, которое происходит в классе C_Object_Edit. Давайте посмотрим, что изменилось в нем:

template < typename T >
void Create(string szObjectName, color corTxt, color corBack, T InfoValue, string szFont = "Lucida Console", int iSize = 10)
        {
                C_Object_Base::Create(szObjectName, OBJ_EDIT);
                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, szFont);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, iSize);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_LEFT);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, corBack);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, corBack);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true);
                SetTextValue(szObjectName, InfoValue, corTxt);
        };

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

template < typename T >
void SetTextValue(string szObjectName, T InfoValue, color cor = clrNONE, const string szSufix = "")
        {
                color clr = (cor != clrNONE ? cor : (color)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR));
                string sz0;
                
                if (typename(T) == "string") sz0 = (string)InfoValue; else
                if (typename(T) == "double")
                {
                        clr = (cor != clrNONE ? cor : ((double)InfoValue < 0.0 ? def_ColorNegative : def_ColoPositive));
                        sz0 = Terminal.ViewDouble((double)InfoValue < 0.0 ? -((double)InfoValue) : (double)InfoValue) + szSufix;
                }else   sz0 = "?#?";
                ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, sz0);
                ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clr);
        };

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

Если цвет указан, он будет использован; если значение не ввели, мы будем использовать цвет, который уже присутствует в объекте. Это очень полезно, потому что мы, возможно, хотим изменить только текст, и в этом случае будет сохранен цвет, который уже имел объект. На этом этапе мы проверяем, является ли тип строкой. Это мы делаем с помощью в RUN TIME, поэтому мы должны убедиться, что всё правильно.

Мы также можем использовать тип double, который в основном используется для указания цены актива, и если такой тип наблюдается в RUN TIME, мы установим значения соответствующим образом и с соответствующим цветом. Теперь у нас наблюдается интересный факт, который позволяет нам немного больше говорить о значении, и для этого мы будем использовать суффикс, который будет сообщен во время вызова. Если что-то не понятно, у нас также есть формат, который позволяет указывать на это. Затем мы выводим текст и настраиваем его цвет.

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


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

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


Новый класс C_Widget

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

#include "Elements\C_Object_Edit.mqh"
#include "Elements\C_Object_BackGround.mqh"
#include "Elements\C_Object_BtnBitMap.mqh"
//+------------------------------------------------------------------+
C_Terminal Terminal;
//+------------------------------------------------------------------+
#define def_PrefixName  "WidgetPrice"
#define def_MaxWidth    160
//+------------------------------------------------------------------+
#define macro_MaxPosition (Terminal.GetWidth() >= (m_Infos.nSymbols * def_MaxWidth) ? Terminal.GetWidth() : m_Infos.nSymbols * def_MaxWidth)
#define macro_ObjectName(A, B) (def_PrefixName + (string)Terminal.GetSubWin() + CharToString(A) + "#" + B)

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

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

Далее переходим к той части, где у нас идут объявления всех приватных элементов в классе объекта:

class C_Widget
{
        protected:
                enum EventCustom {Ev_RollingTo};
        private :
                enum EnumTypeObject {en_Background = 35, en_Symbol, en_Price, en_Percentual, en_Icon, en_Arrow};
                struct st00
                {
                        color CorBackGround,
                              CorSymbol;
                        int   nSymbols,
                              MaxPositionX;
                        struct st01
                        {
                              string szCode;
                        }Symbols[];
                }m_Infos;

// ... Остальной код

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

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

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

void CreateBackGround(void)
        {
                C_Object_BackGround backGround;
                string sz0 = macro_ObjectName(en_Background, "");
                                
                backGround.Create(sz0, m_Infos.CorBackGround);
                backGround.Size(sz0, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH), Terminal.GetHeight());
        }

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

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

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

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

#define macro_ObjectName(A, B) (def_PrefixName + (string)Terminal.GetSubWin() + CharToString(A) + "#" + B)

Первым аргументом макроса будет перечисление, которое мы определили в классе. Это перечисление преобразим в соответствующую строку, т.е. из значения у нас получится символ, и этот символ будет использоваться для придания имени уникальности. Теперь, если мы посмотрим на таблицу ASCII, то увидим, что первое допустимое значение это 32, что и является значением символа «пробел». Поэтому мы можем инициализировать перечисление со значением 32, но делать это с помощью меньшего значения нецелесообразно.

Это происходит только потому, что мы преобразуем значение в символ; если бы значение было приведено к соответствующей строке, мы могли бы инициализировать перечисление со значением 0, поскольку тогда преобразование не вызывало бы никаких проблем. Однако, поскольку мы преобразуем некоторые вещи, то подходящим минимальным значением является 32.

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

void AddSymbolInfo(const string szArg, const bool bRestore = false)
        {
                C_Object_Edit edit;
                C_Object_BtnBitMap bmp;
                string sz0;
                const int x = 9999;

                bmp.Create(sz0 = macro_ObjectName(en_Icon, szArg), szArg);
                bmp.PositionAxleX(sz0, x);
                bmp.PositionAxleY(sz0, 15);
                edit.Create(sz0 = macro_ObjectName(en_Symbol, szArg), m_Infos.CorSymbol, m_Infos.CorBackGround, szArg);
                edit.PositionAxleX(sz0, x);
                edit.PositionAxleY(sz0, 10);
                edit.Size(sz0, 56, 16);
                edit.Create(sz0 = macro_ObjectName(en_Percentual, szArg), m_Infos.CorSymbol, m_Infos.CorBackGround, 0.0, "Lucida Console", 8);
                edit.PositionAxleX(sz0, x);
                edit.PositionAxleY(sz0, 10);
                edit.Size(sz0, 50, 11);
                edit.Create(sz0 = macro_ObjectName(en_Arrow, szArg), m_Infos.CorSymbol, m_Infos.CorBackGround, "", "Wingdings 3", 10);
                edit.PositionAxleX(sz0, x);
                edit.PositionAxleY(sz0, 26);
                edit.Size(sz0, 20, 16);
                edit.Create(sz0 = macro_ObjectName(en_Price, szArg), 0, m_Infos.CorBackGround, 0.0);
                edit.PositionAxleX(sz0, x);
                edit.PositionAxleY(sz0, 26);
                edit.Size(sz0, 60, 16);
                if (!bRestore)
                {
                        ArrayResize(m_Infos.Symbols, m_Infos.nSymbols + 1, 10);
                        m_Infos.Symbols[m_Infos.nSymbols].szCode = szArg;
                        m_Infos.nSymbols++;
                }
        }

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

Почему мы объявляем константу, а затем применяем её к вызовам? Причина в том, что мне нравится играть и исследовать разные возможности вещей. Это забавный способ понять, на что действительно способен язык программирования.

На самом деле, нет необходимости объявлять такую константу, можно использовать определение компиляции (#define) или, проще говоря, значение константы в каждом из вызовов будет одинаковым. Давайте посмотрим на остальную часть функции. Мы увидим, что элементы добавляются так, чтобы их имена всегда были уникальными, т.е. никакие два элемента не будут иметь одинаковое имя. Созщдаются они в следующей последовательности: BitMap, код актива,ежедневное процентное изменение, стрелка, указывающая на изменение и текущая цена актива.

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

Теперь давайте посмотрим, как на самом деле представляются данные.

inline void UpdateSymbolInfo(int x, const string szArg)
        {
                C_Object_Edit edit;
                C_Object_BtnBitMap bmp;
                string sz0;
                double v0[], v1;
                                
                ArraySetAsSeries(v0, true);
                if (CopyClose(szArg, PERIOD_D1, 0, 2, v0) < 2) return;
                v1 = ((v0[0] - v0[1]) / v0[(v0[0] > v0[1] ? 0 : 1)]) * 100.0;
                bmp.PositionAxleX(sz0 = macro_ObjectName(en_Icon, szArg), x);
                x += (int) ObjectGetInteger(Terminal.Get_ID(), sz0, OBJPROP_XSIZE);
                edit.PositionAxleX(macro_ObjectName(en_Symbol, szArg), x + 2);
                edit.PositionAxleX(sz0 = macro_ObjectName(en_Arrow, szArg), x  + 2);
                edit.SetTextValue(sz0, CharToString(v1 >= 0 ? (uchar)230 : (uchar)232), (v1 >= 0 ? def_ColoPositive : def_ColorNegative));
                edit.SetTextValue(sz0 = macro_ObjectName(en_Percentual, szArg), v1 , clrNONE, "%");
                edit.PositionAxleX(sz0, x + 62);
                edit.SetTextValue(sz0 = macro_ObjectName(en_Price, szArg), v0[0] * (v1 >= 0 ? 1 : -1));
                edit.PositionAxleX(sz0, x + 24);
        }

Здесь мы будем следить за тем, чтобы ряд значений всегда шел от самого нового к самому старому. Как только у нас появляется такая уверенность, мы можем фиксировать значения цены закрытия, но обратите внимание, что мы ежедневно делаем отсчет, поскольку изменение, которое нам нужно, на самом деле является дневным изменением. Еще один момент: если количество возвращаемых данных меньше ожидаемого, мы сразу же выйдем из функции, потому что если продолжить, то в итоге поломаем индикатор, о чем сообщается на панели инструментов MetaTrader 5. И мы не хотим ломать индикатор только из-за ошибки, которая обычно носит временный характер.

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

Далее, мы разместим объекты на экране, и первое, что нужно сделать - разместить изображение. Надо обязательно вспомнить, что в момент создания объектов уже говорилось, что размер изображения нужно сообщить в другом месте. Хорошо, место находится именно здесь, хотя единственная информация, которая нам нужна - это ширина изображения, но, как мы уже обсудили, предпочтительнее использовать 32 x 32, так как изменение, особенно в большую сторону, будет означать, что вам придется исправить другие значения.

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

Все последующие значения будут зависеть от данного значения, содержащегося в ширине изображения, поэтому именно оно будет определять ситуацию. Но у нас есть небольшой момент, который для некоторых не имеет большого смысла, потому что у нас есть значение, и оно преобразуется в символ, а символ не появляется на экране так, как вы думали. Что происходит в этот момент? Здесь мы создаем стрелки, которые указывают на то, идет ли цена вниз или вверх, на основе дневного изменения. Сам элемент выглядит запутанным, но всё работает отлично, если помнить, что мы используем не просто любой шрифт, а Wingdings 3. Мы можем использовать другой шрифт, но тогда придется исправить эти значения здесь, чтобы всё отобразилось правильно.

Аналогичным образом, мы видим еще один интересный момент. Следует отметить, что мы отправляем строковое и двойное значения в одну и ту же функцию, поэтому мы могли бы ожидать найти две разные функции. Однако, глядя на код объекта, мы видим только одну функцию, это происходит из-за того, что она перегружена. Но перегрузка создается самим компилятором и компоновщиком. Для этого мы должны были сделать тесты внутри функции, но если мы посмотрим более внимательно, мы увидим, что у нас есть символ, широко используемый для обозначения значений в процентах (%). Теперь давайте перейдем к функции внутри объекта Edit, и мы увидим, где добавляется этот процентный символ, и как мы это сделали.

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

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

#property indicator_separate_window
#property indicator_plots 0
#property indicator_height 32
//+------------------------------------------------------------------+
#include <Widget\Rolling Price\C_Widget.mqh>
//+------------------------------------------------------------------+
input string user00 = "Config.cfg";  //Файл настроек
input int    user01 = -1;            //Смещение
input int    user02 = 10;            //Пауза в миллисекундах
input
 color  user03 = clrWhiteSmoke; //Цвет актива
input color  user04 = clrBlack;      //Цвет фона
//+------------------------------------------------------------------+
C_Widget Widget;
//+------------------------------------------------------------------+
int OnInit()
{
        if (!Widget.Initilize(user00, "Widget Price", user03, user04))
                return INIT_FAILED;
        EventSetMillisecondTimer(user02);
        
        return INIT_SUCCEEDED;
}

Единственный момент, который действительно заслуживает того, чтобы быть указанным в коде индикатора - это тот, где мы устанавливаем значение высоты индикатора, больше нет никаких отличий от кода базовой версии.

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


Дополнительные функции

А как вам идея вывести на панель те активы, которыми мы хотим торговать? И не только это, давайте пойдем немного дальше. Теперь это так.

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

Хорошая идея, не так ли? Вы, наверное, подумаете, что это нечто ультра-супер-пупер-мега сложное. Или что вы должны быть опытнейшем программистом, с огромными знаниями в ядерной физике, или кем-то со знанием инопланетных технологий, но нет. Это делается очень легко и просто, с помощью MQL5 вместе с платформой MetaTrader 5.

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

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
        {
                static int tx = 0;
                string szRet[];
                                                        
                switch (id)
                {

// ... Внутренний код ...

                        case CHARTEVENT_OBJECT_CLICK:
                                if (StringSplit(sparam, '#', szRet) == 2)
                                {
                                        SymbolSelect(szRet[1], true);
                                        szRet[0] = ChartSymbol(Terminal.Get_ID());
                                        if (ChartSetSymbolPeriod(Terminal.Get_ID(), szRet[1], PERIOD_CURRENT)) SymbolSelect(szRet[0], false);
                                        else SymbolSelect(szRet[1], false);
                                }
                                break;
                }
        }

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

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

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

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

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



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

Прикрепленные файлы |
Еще раз о системе Мюррея Еще раз о системе Мюррея
Системы графического анализа цен заслуженно популярны у трейдеров. В данной статье я рассказываю о полной системе Мюррея, включающей не только его знаменитые уровни, но и некоторые другие полезные техники оценки текущего положения цены и принятия решения о сделке.
Популяционные алгоритмы оптимизации: Оптимизация инвазивных сорняков (Invasive Weed Optimization - IWO) Популяционные алгоритмы оптимизации: Оптимизация инвазивных сорняков (Invasive Weed Optimization - IWO)
Удивительная способность сорняков выживать в самых разнообразных условиях послужило идеей создания мощного алгоритма оптимизации. IWO — один из лучших среди рассмотренных ранее.
Как построить советник, работающий автоматически (Часть 01): Концепции и структуры Как построить советник, работающий автоматически (Часть 01): Концепции и структуры
Сегодня посмотрим, как создать советник, просто и безопасно работающий в автоматическом режиме.
Эксперименты с нейросетями (Часть 3): Практическое применение Эксперименты с нейросетями (Часть 3): Практическое применение
Нейросети наше все. Проверяем на практике, так ли это. MetaTrader 5 как самодостаточное средство для использования нейросетей в трейдинге. Простое объяснение.