English Español Deutsch 日本語 Português
preview
Разработка системы репликации (Часть 44): Проект Chart Trade (III)

Разработка системы репликации (Часть 44): Проект Chart Trade (III)

MetaTrader 5Примеры | 12 августа 2024, 16:35
367 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье Разработка системы репликации (Часть 43): Проект Chart Trade (II), я объяснил, как можно управлять данными шаблона, чтобы использовать их в OBJ_CHART. Там я лишь обозначил тему, не вдаваясь в подробности, поскольку в той версии работа была выполнена очень упрощенным способом. Это сделано для того, чтобы облегчить объяснение содержания, ведь несмотря на кажущуюся простоту многих вещей, некоторые из них не столь очевидны, а без понимания самой простой и основной части, вы не сможете по-настоящему разобраться в том, что мы делаем.

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

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


Рождается новый класс C_AdjustTemplate

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "../Auxiliar/C_Terminal.mqh"
05. //+------------------------------------------------------------------+
06. class C_AdjustTemplate
07. {
08.     private :
09.             string m_szName[];
10.             string m_szFind[];
11.             string m_szReplace[];
12.             string m_szFileName;
13.             int m_maxIndex;
14.             int m_FileIn;
15.             int m_FileOut;
16.             bool m_bSimple;
17.     public  :
18. //+------------------------------------------------------------------+
19.             C_AdjustTemplate(const string szFileNameIn, string szFileNameOut = NULL)
20.                     :m_maxIndex(0),
21.                      m_szFileName(szFileNameIn),
22.                      m_FileIn(INVALID_HANDLE),
23.                      m_FileOut(INVALID_HANDLE)
24.                     {
25.                             ResetLastError();
26.                             if ((m_FileIn = FileOpen(szFileNameIn, FILE_TXT | FILE_READ)) == INVALID_HANDLESetUserError(C_Terminal::ERR_FileAcess);
27.                             if (_LastError == ERR_SUCCESS)
28.                             {
29.                                     if (!(m_bSimple = (StringLen(szFileNameOut) > 0))) szFileNameOut = szFileNameIn + "_T";                                                 
30.                                     if ((m_FileOut = FileOpen(szFileNameOut, FILE_TXT | FILE_WRITE)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess);
31.                             }
32.                     }
33. //+------------------------------------------------------------------+
34.             ~C_AdjustTemplate()
35.                     {
36.                             FileClose(m_FileIn);
37.                             if (m_FileOut != INVALID_HANDLE)
38.                             {
39.                                     FileClose(m_FileOut);
40.                                     if ((!m_bSimple) && (_LastError == ERR_SUCCESS)) FileMove(m_szFileName + "_T", 0, m_szFileName, FILE_REWRITE);
41.                                     if ((!m_bSimple) && (_LastError != ERR_SUCCESS)) FileDelete(m_szFileName + "_T");
42.                             }
43.                             ArrayResize(m_szName, 0);
44.                             ArrayResize(m_szFind, 0);
45.                             ArrayResize(m_szReplace, 0);
46.                     }
47. //+------------------------------------------------------------------+
48.             void Add(const string szName, const string szFind, const string szReplace)
49.                     {
50.                             m_maxIndex++;
51.                             ArrayResize(m_szName, m_maxIndex);
52.                             ArrayResize(m_szFind, m_maxIndex);
53.                             ArrayResize(m_szReplace, m_maxIndex);
54.                             m_szName[m_maxIndex - 1] = szName;
55.                             m_szFind[m_maxIndex - 1] = szFind;
56.                             m_szReplace[m_maxIndex - 1] = szReplace;
57.                     }
58. //+------------------------------------------------------------------+
59.             string Get(const string szName, const string szFind)
60.                     {
61.                             for (int c0 = 0; c0 < m_maxIndex; c0++) if ((m_szName[c0] == szName) && (m_szFind[c0] == szFind)) return m_szReplace[c0];
62.                             
63.                             return NULL;
64.                     }
65. //+------------------------------------------------------------------+
66.             void Execute(void)
67.                     {
68.                             string sz0, tmp, res[];
69.                             int count0 = 0, i0;
70.                             
71.                             if ((m_FileIn != INVALID_HANDLE) && (m_FileOut != INVALID_HANDLE)) while ((!FileIsEnding(m_FileIn)) && (_LastError == ERR_SUCCESS))
72.                             {
73.                                     sz0 = FileReadString(m_FileIn);
74.                                     if (sz0 == "<object>") count0 = 1;
75.                                     if (sz0 == "</object>") count0 = 0;
76.                                     if (count0 > 0) if (StringSplit(sz0, '=', res) > 1)
77.                                     {
78.                                             i0 = (count0 == 1 ? 0 : i0);
79.                                             for (int c0 = 0; (c0 < m_maxIndex) && (count0 == 1); i0 = c0, c0++) count0 = (res[1] == (tmp = m_szName[c0]) ? 2 : count0);
80.                                             for (int c0 = i0; (c0 < m_maxIndex) && (count0 == 2); c0++) if ((res[0] == m_szFind[c0]) && (tmp == m_szName[c0]))
81.                                             {
82.                                                     if (StringLen(m_szReplace[c0])) sz0 =  m_szFind[c0] + "=" + m_szReplace[c0];
83.                                                     else m_szReplace[c0] = res[1];
84.                                             }
85.                                     }
86.                                     FileWriteString(m_FileOut, sz0 + "\r\n");
87.                             };
88.                     }
89. //+------------------------------------------------------------------+
90. };
91. //+------------------------------------------------------------------+

Исходный код: C_AdjustTemplate

Этот код содержит именно то, что нам нужно. Если вы посмотрите на этот код здесь и посмотрите на код класса C_ChartFloatingRAD из предыдущей статьи, вы заметите, что содержимое, присутствующее между строками 38 и 90 класса C_ChartFloatingRAD, есть и здесь, но выгляди по-другому. Это связано с тем, что моделирование данных в этом классе C_AdjustTemplate осуществляется таким образом, чтобы способствовать более эффективному выполнению. Вы заметите это, когда позже в этой статье будет показан новый код класса C_ChartFloatingRAD. Но оставим это на потом. Для начала давайте разберемся, что происходит в этом классе C_AdjustTemplate.

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

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

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

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

Теперь обратите внимание на следующий факт: в строке 25 мы «перезагружаем» константу _LastError. Поэтому, если есть какая-либо ошибка до вызова конструктора, вам следует это проверить; в противном случае — вы потеряете значение, указанное в константе ошибки. Я уже объяснял в прошлых статьях, почему нужно делать именно так, прочтите их для получения более подробной информации.

В строке 26 мы пытаемся открыть исходный файл, который обязательно должен быть указан. Если эта попытка окажется неудачной, мы сообщим об этом в константе _LastError. Если нам удастся открыть файл, константа_LastError будет содержать значение ERR_SUCCESS, и при этом проверка, выполненная в строке 27, будет успешной, что позволит нам перейти к следующему этапу.

На этом этапе мы проверим в строке 29, указано ли имя для файла назначения. Если оно не указано, тогда мы будем работать с временным файлом, имя которого будет основываться на имени исходного файла. Имея имя для файла назначения, мы можем выполнить строку 30, которая попытается создать файл назначения. Если эта попытка окажется неудачной, мы сообщим об этом в константе _LastError. Если все выполняется хорошо, постоянная _LastError будет содержать значение ERR_SUCCESS, и у нас будут идентификаторы файлов. Они будут использоваться для манипулирования файлами, поэтому не следует пытаться внешне делать что-либо с файлами до тех пор, пока дескрипторы (handles) не будут закрыты. Подумайте о том, что машина открыта, и если ее включить, то могут возникнуть проблемы или знаменитое B.O.

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

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

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

Далее у нас есть очень базовая и простая процедура, которая начинается в строке 50, где мы увеличиваем счетчик и сразу после этого выделяем память для хранения данных, которые будем использовать позже. Это выделение выполняется между строками 51 и 53. Обратите также внимание на строки 54 – 56, на способ, которым мы будем хранить информацию. Поскольку этот процесс прост, мы не будем вдаваться в подробности. А в строке 59 у нас есть одна любопытная функция.

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

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

  • Входной файл не может быть прочитан;
  • Выходной файл может быть недоступен;
  • Функция StringSplit может не сработать.

Любая из этих проблем приведет к изменению константы ошибки. Если это произойдет, цикл while, который находится в строке 71, завершится досрочно, что приведет к тому, что весь индикатор не будет корректно работать при размещении на графике. Помните следующее: если константа _LastError содержит значение, отличное от ERR_SUCCESS, в момент выполнения деструктора шаблон не будет обновляться. И если это первый вызов, он не будет создан, а если его нет, индикатор не будет размещен на графике. Вот почему эта процедура Execute является такой деликатной.

Однако представим, что все функционирует идеально, и посмотрим, что происходит внутри цикла while.

В строке 73 мы читаем строку (string) из исходного файла. Эта строка (string) будет состоять из одной полной строки. Читая таким образом, будет намного проще проверить остальное. Далее, в строке 74 мы проверяем, вводится ли определение какого-то объекта, и уже в строке 75 проверяем, завершилось ли оно.

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

Когда мы находимся внутри объекта, в строке 76 у нас есть проверка, позволяющая нам «разделить» строку. Это «разделение» будет выполнено прямо по знаку равенства (=), что означает, что мы определяем некоторый параметр для некоторого свойства объекта. Что ж, если это правда, что мы определяем свойство, эти проверки пройдут, позволяя нам выполнить строку 78. Эта строка просто отрегулирует временную память. Но сам вопрос возникает в следующих строках.

В строке 79 мы пройдемся по всем данным, которые были добавлены во время выполнения вызовов Add (процедура со строки 48). Если случайно мы находим значение параметра в шаблоне, это говорит о том, что мы имеем дело с искомым объектом. При этом мы временно сохраняем имя объекта и указываем, что будем проводить второй уровень анализа. Благодаря тому, что мы проводим второй уровень, строка 79 не будет выполнена повторно в рамках того же объекта. Это обязывает нас гарантировать, что шаблон должен иметь ту же структуру, что и в предыдущей статье Разработка системы репликации (Часть 43): Проект Chart Trade (II). То есть файл должен быть именно таким. Если вы меняете его, убедитесь, что он остается максимально похожим на приводимый ранее.

Ну а поскольку мы уже находимся на втором уровне, на 80-й строке, у нас будет еще один цикл. Важно: циклы в строках 79 и 80 никогда не выполняются вместе. Всегда будет выполняться либо один, либо другой, но никогда оба сразу. Исключение  первый вызов. То, что обе строки 79 и 80 не выполняются, может показаться странным, но на самом деле именно так и происходит. Но если строка 80 все же будет выполнена, внутри цикла у нас будет проверка на наличие желаемого свойства объекта. Обратите внимание, что имя объекта важно, поэтому мы временно записываем его во время цикла в строке 79.

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

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

На этом мы завершаем объяснение класса C_AdjustTemplate. Теперь посмотрим, каким получился класс C_ChartFloatingRAD. Как вы понимаете, он также был модифицирован и стал еще интереснее.


Новый облик класса C_ChartFloatingRAD

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

Ниже можно увидеть полный код класса C_ChartFloatingRAD, вплоть до текущего момента.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "../Auxiliar/C_Mouse.mqh"
005. #include "C_AdjustTemplate.mqh"
006. //+------------------------------------------------------------------+
007. class C_ChartFloatingRAD : private C_Terminal
008. {
009.    private :
010.            enum eObjectsIDE {MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL};
011.            struct st00
012.            {
013.                    int     x, y;
014.                    string  szObj_Chart,
015.                            szFileNameTemplate;
016.                    long    WinHandle;
017.                    double  FinanceTake,
018.                            FinanceStop;
019.                    int     Leverage;
020.                    bool    IsDayTrade,
021.                            IsMaximized;
022.                    struct st01
023.                    {
024.                            int x, y, w, h;
025.                    }Regions[MSG_NULL];
026.            }m_Info;
027. //+------------------------------------------------------------------+
028.            C_Mouse  *m_Mouse;
029. //+------------------------------------------------------------------+
030.            void CreateWindowRAD(int x, int y, int w, int h)
031.                    {
032.                            m_Info.szObj_Chart = (string)ObjectsTotal(GetInfoTerminal().ID);
033.                            ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0);
034.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = x);
035.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = y);
036.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w);
037.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h);
038.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false);
039.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false);
040.                            m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID);
041.                    };
042. //+------------------------------------------------------------------+
043. inline void UpdateChartTemplate(void)
044.                    {
045.                            ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate);
046.                            ChartRedraw(m_Info.WinHandle);
047.                    }
048. //+------------------------------------------------------------------+
049. inline double PointsToFinance(const double Points)
050.                    {                               
051.                            return Points * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade;
052.                    };
053. //+------------------------------------------------------------------+
054. inline void AdjustTemplate(const bool bFirst = false)
055.                    {
056. #define macro_AddAdjust(A) {                         \
057.              (*Template).Add(A, "size_x", NULL);     \
058.              (*Template).Add(A, "size_y", NULL);     \
059.              (*Template).Add(A, "pos_x", NULL);      \
060.              (*Template).Add(A, "pos_y", NULL);      \
061.                            }
062. #define macro_GetAdjust(A) {                                                                          \
063.              m_Info.Regions[A].x = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_x"));  \
064.              m_Info.Regions[A].y = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_y"));  \
065.              m_Info.Regions[A].w = (int) StringToInteger((*Template).Get(EnumToString(A), "size_x")); \
066.              m_Info.Regions[A].h = (int) StringToInteger((*Template).Get(EnumToString(A), "size_y")); \                                               
067.                            }
068.                            
069.                            C_AdjustTemplate *Template;
070.                            
071.                            if (bFirst)
072.                            {
073.                                    Template = new C_AdjustTemplate("Chart Trade/IDE_RAD.tpl", m_Info.szFileNameTemplate = StringFormat("Chart Trade/%u.tpl", GetInfoTerminal().ID));
074.                                    for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_AddAdjust(EnumToString(c0));
075.                            }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate);
076.                            Template.Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol);
077.                            Template.Add("MSG_LEVERAGE_VALUE", "descr", (string)m_Info.Leverage);
078.                            Template.Add("MSG_TAKE_VALUE", "descr", (string)m_Info.FinanceTake);
079.                            Template.Add("MSG_STOP_VALUE", "descr", (string)m_Info.FinanceStop);
080.                            Template.Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0"));
081.                            Template.Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0"));
082.                            Template.Execute();
083.                            if (bFirst) for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_GetAdjust(c0);
084.                            
085.                            delete Template;
086.                            
087.                            UpdateChartTemplate();
088.                                                            
089. #undef macro_AddAdjust
090. #undef macro_GetAdjust
091.                    }
092. //+------------------------------------------------------------------+
093.            eObjectsIDE CheckMousePosition(const int x, const int y)
094.                    {
095.                            int xi, yi, xf, yf;
096.                            
097.                            for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++)
098.                            {
099.                                    xi = m_Info.x + m_Info.Regions[c0].x;
100.                                    yi = m_Info.y + m_Info.Regions[c0].y;
101.                                    xf = xi + m_Info.Regions[c0].w;
102.                                    yf = yi + m_Info.Regions[c0].h;
103.                                    if ((x > xi) && (y > yi) && (x < xf) && (y < yf) && ((c0 == MSG_MAX_MIN) || (c0 == MSG_TITLE_IDE) || (m_Info.IsMaximized))) return c0;
104.                            }
105.                            return MSG_NULL;
106.                    }
107. //+------------------------------------------------------------------+
108.    public  :
109. //+------------------------------------------------------------------+
110.            C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const int Leverage, const double FinanceTake, const double FinanceStop)
111.                    :C_Terminal()
112.                    {
113.                            m_Mouse = MousePtr;
114.                            m_Info.Leverage = (Leverage < 0 ? 1 : Leverage);
115.                            m_Info.FinanceTake = PointsToFinance(FinanceToPoints(MathAbs(FinanceTake), m_Info.Leverage));
116.                            m_Info.FinanceStop = PointsToFinance(FinanceToPoints(MathAbs(FinanceStop), m_Info.Leverage));
117.                            m_Info.IsDayTrade = true;
118.                            m_Info.IsMaximized = true;
119.                            if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown);
120.                            CreateWindowRAD(115, 64, 170, 210);
121.                            AdjustTemplate(true);
122.                    }
123. //+------------------------------------------------------------------+
124.            ~C_ChartFloatingRAD()
125.                    {
126.                            ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Chart);
127.                            FileDelete(m_Info.szFileNameTemplate);
128.                            
129.                            delete m_Mouse;
130.                    }
131. //+------------------------------------------------------------------+
132.            void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
133.                    {
134.                            static int sx = -1, sy = -1;
135.                            int x, y, mx, my;
136.                            static eObjectsIDE it = MSG_NULL;
137.    
138.                            switch (id)
139.                            {
140.                                    case CHARTEVENT_MOUSE_MOVE:
141.                                            if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft))
142.                                            {
143.                                                    switch (it = CheckMousePosition(x = (int)lparam, y = (int)dparam))
144.                                                    {
145.                                                            case MSG_TITLE_IDE:
146.                                                                    if (sx < 0)
147.                                                                    {
148.                                                                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
149.                                                                            sx = x - m_Info.x;
150.                                                                            sy = y - m_Info.y;
151.                                                                    }
152.                                                                    if ((mx = x - sx) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = mx);
153.                                                                    if ((my = y - sy) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = my);
154.                                                                    break;
155.                                                    }
156.                                            }else
157.                                            {
158.                                                    if (it != MSG_NULL)
159.                                                    {
160.                                                            switch (it)
161.                                                            {
162.                                                                    case MSG_MAX_MIN:
163.                                                                            m_Info.IsMaximized = (m_Info.IsMaximized ? false : true);
164.                                                                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6));
165.                                                                            break;
166.                                                                    case MSG_DAY_TRADE:
167.                                                                            m_Info.IsDayTrade = (m_Info.IsDayTrade ? false : true);
168.                                                                            break;
169.                                                            }
170.                                                            it = MSG_NULL;
171.                                                            AdjustTemplate();
172.                                                    }
173.                                                    if (sx > 0)
174.                                                    {
175.                                                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                                                
176.                                                            sx = sy = -1;
177.                                                    }
178.                                            }
179.                                            break;
180.                            }
181.                    }
182. //+------------------------------------------------------------------+
183. };
184. //+------------------------------------------------------------------+

Исходный код класса C_ChartFloatingRAD

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

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

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

При рассмотрении кода, мы видим между строками 22 и 25 структуру, которая будет использоваться переменной, являющейся массивом. Но если посмотреть на количество элементов в массиве, возникает вопрос: что это? У нас нет значения, это что-то другое. Вы можете подумать: «Я не нахожу нигде, что это значит». Спокойно, не нужно паниковать. Эти данные, обозначающие количество элементов в массиве, являются как раз последними данными в перечислении, сделанном в строке 10. Но здесь есть одна деталь. Это последнее значение на самом деле не представляет собой элемент или объект. Если бы представляло, декларация должна была бы быть другой. Но поскольку оно не представляет, мы можем использовать значение так, как это делается.

Следующая строка, заслуживающая некоторого пояснения, находится в пункте 54. Здесь мы действительно получим доступ к шаблону. Но прежде чем приступить к этому объяснению, давайте рассмотрим еще кое-что. Этот процедурный код используется в двух местах. Первое — в строке 121, которая является конструктором, и второе — в строке 171, которая находится в процедуре обработки сообщений. Почему важно это знать? Причина в том, что мы делаем, и что хотим делать в шаблоне.

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

Возможно, это объяснение было несколько путанным, но давайте посмотрим, как работает процедура в строке 54. Может быть это поможет вам лучше понять ситуацию. Между строками 56 и 67 у нас есть определение двух макросов, которые находятся здесь для того, чтобы помочь нам и облегчить программирование. Точно так же, как строки 89 и 90 служат для устранения таких макросов. Обычно мне нравится использовать макросы, когда я повторяю один и тот же код или какой-то параметр несколько раз. В данном конкретном случае повторяется параметр. Но код макросов довольно прост.

Первый, который находится между строками 56 и 61, добавит элементы, которые класс C_AdjustTemplate будет нам возвращать. Во втором макросе, который находится между строками 62 и 67, мы берем значения, которые сообщает нам класс C_AdjustTemplate, и преобразуем их в значение, которое сохраняется в массиве, объявленном в строке 25. Имейте это в виду. Мы не просто угадываем, где находятся объекты, мы спрашиваем у шаблона, где они находятся.

При этом в строке 71 мы проверяем, начинаем ли мы настройку шаблона. Если это не так, мы выполним вызов, присутствующий в строке 75. С другой стороны, если это первый вызов, мы сообщим классу C_AdjustTemplate, какие имена будут использоваться. Это делается в строке 73. Теперь обратите особое внимание на строку 74. Вы видите, что в этой строке мы будем использовать перечисление, чтобы сообщить классу C_AdjustTemplate, какие объекты нам нужны для получения данных. Для этого мы используем цикл. Таким образом, класс C_AdjustTemplate будет знать, какие свойства должны быть зафиксированы.

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

Наконец, в строке 82 мы сообщаем классу C_AdjustTemplate, что он может настроить шаблон в соответствии с предоставленной информацией. Это делается так, как показано в предыдущей теме. Когда вся работа будет выполнена, мы проверим в строке 83, выполняется ли первый вызов. Если это так, мы скорректируем значения массива, объявленного в строке 25. Это делается с помощью цикла, который сообщает классу C_AdjustTemplate, какие объекты и свойства мы хотим знать.

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

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

Теперь давайте посмотрим на другое: строка 93 запускает довольно интересную функцию, которая могла бы разместиться непосредственно там, где будет использоваться. Она создана для того, чтобы сделать код более читабельным. Эта функция включает цикл, который начинается в строке 97 и проходит через каждый из объектов, присутствующих в OBJ_CHART. Помните следующее: объект OBJ_CHART содержит шаблон, и этот шаблон содержит объекты, которые мы будем проверять. Во время проверки мы создадим прямоугольник, который является областью клика для каждого объекта. Это делается между строками 99 и 102.

Получив эту область клика, мы можем сравнить ее с областью, указанной как параметры вызова. Это сравнение производится в строке 103. Теперь обратите внимание, что помимо области существуют еще некоторые дополнительные условия. Если все в порядке, будет возвращен индекс объектов, в противном же случае, мы вернем значение MSG_NULL. Именно по этой причине нам нужно, чтобы перечисление, определенное вначале, включало это значение. Если бы его не было, невозможно было бы сообщить, что клик был сделан по недействительному объекту в индикаторе Chart Trade.

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

Большая часть кода выглядит очень похоже на то, что было показано в предыдущей статье. Однако, есть небольшие различия, которые стоит прокомментировать, чтобы менее опытные могли понять, что происходит. В строках 134–136 мы определяем некоторые переменные. Переменная, определенная в строке 136, особенно интересует нас в этой статье, поскольку остальные уже были объяснены.

Переменная, присутствующая в строке 136, будет использоваться нами как память. Это обусловлено тем, что мы не можем рассчитывать на дополнительную помощь от MetaTrader 5 для решения вопросов, связанных с кликами. Обычно, когда на графике есть объекты, MetaTrader 5 сообщит нам имя объекта, по которому был сделан клик. Это делается через событие CHARTEVENT_OBJECT_CLICK. Но в данном случае у нас нет никаких реальных объектов, кроме OBJ_CHART. Таким образом, любой клик в области индикатора Chart Trade будет интерпретирован MetaTrader 5 как клик по OBJ_CHART.

Единственное событие, с которым мы имеем дело, — это CHARTEVENT_MOUSE_MOVE, и этого нам более чем достаточно. Однако клики будут обрабатываться только в том случае, если индикатор мыши не находится в состоянии исследования. Это проверяется в строке 141. Если индикатор мыши находится в состоянии исследования, или произошло что-то еще, мы перейдем к строке 156. И тут возникает вопрос. Если переменная, объявленная в строке 136, имеет другое значение, что-то должно произойти. Но сначала давайте посмотрим, когда и где эта переменная получит свое значение.

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

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

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

Но вернемся к строке 156. Когда эта строка будет выполнена, в рамках этого условия мы произведем две проверки. Первая установит, получил ли клик какой-либо объект, присутствующий в OBJ_CHART. Если это произошло, то у нас будет то же самое условие, которое было бы выполнено, если бы объект действительно существовал на графике, и MetaTrader 5 сгенерировал бы событие CHARTEVENT_OBJECT_CLICK. То есть мы «эмулируем» работу уже существующей системы. Но для того, чтобы обеспечить адекватное поведение всего индикатора.

Таким образом, если проверка, представленная в строке 158, пройдена, мы сначала выполним соответствующую обработку события. Это делается между строками 160 и 169, а затем, в строке 170, мы удаляем указание объекта, и в строке 171 выполняем обновление текущего состояния шаблона. Таким образом, весь индикатор будет обновлен, создавая иллюзию присутствия объектов на графике, хотя единственным реальным объектом является OBJ_CHART.

Весь остальной код класса C_ChartFloatingRAD уже был объяснен в предыдущей статье, поэтому я не вижу необходимости комментировать его здесь снова.

Демонстрационное видео

Заключение

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

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


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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Алгоритм миграции животных — Animal Migration Optimization (AMO) Алгоритм миграции животных — Animal Migration Optimization (AMO)
Статья посвящена алгоритму AMO, который моделирует процесс сезонной миграции животных в поисках оптимальных условий для жизни и размножения. Основные особенности AMO включают использование топологического соседства и вероятностный механизм обновления, что делает его простым в реализации и гибким для различных оптимизационных задач.
Разбираем примеры торговых стратегий в клиентском терминале Разбираем примеры торговых стратегий в клиентском терминале
В статье рассмотрим наглядно по блок-схемам логику прилагаемых к терминалу учебных советников, расположенных в папке Experts\Free Robots, торгующих по свечным паттернам.
Методы Уильяма Ганна (Часть I): Создаем индикатор углов Ганна Методы Уильяма Ганна (Часть I): Создаем индикатор углов Ганна
В чем суть теории Ганна? Как строятся углы Ганна? Создаем индикатор углов Ганна для MetaTrader 5.
Нейросети в трейдинге: Модели пространства состояний Нейросети в трейдинге: Модели пространства состояний
В основе большого количества рассмотренных нами ранее моделей лежит архитектура Transformer. Однако они могут быть неэффективны при работе с длинными последовательностями. И в этой статье я предлагаю познакомиться с альтернативным направлением прогнозирования временных рядов на основе моделей пространства состояний.