English 中文 Español Deutsch 日本語 Português
preview
Разработка системы репликации (Часть 59): Новое будущее

Разработка системы репликации (Часть 59): Новое будущее

MetaTrader 5Примеры |
329 1
Daniel Jose
Daniel Jose

Введение

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

Чтобы добиться правильного понимания того, о чем я буду говорить, давайте посмотрим видео 01.


Видеодемонстрация того, как всё может пойти не так

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


Давайте разберемся в причинах закрытия сервиса

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

53. void OnDeinit(const int reason)
54. {
55.    switch (reason)
56.    {
57.       case REASON_TEMPLATE:
58.          Print("Modified template. Replay // simulation system shutting down.");
59.       case REASON_INITFAILED:
60.       case REASON_PARAMETERS:
61.       case REASON_REMOVE:
62.       case REASON_CHARTCLOSE:
63.          ChartClose(user00);
64.          break;
65.    }
66.    delete control;
67. }

Фрагмент кода модуля управления

Обратите внимание, что в строке 63 есть вызов, который сообщает MetaTrader 5 о закрытии графика. Но кто на самом деле выполняет этот вызов? Это связано с тем, что если дело было бы в изменении шаблона, то сообщение в строке 58 должно было появиться в окне сообщений MetaTrader 5, но оно так и не появилось. Поэтому можно подумать, что закрытие графика происходит не из-за смены шаблона в MetaTrader 5.

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

И происходит это так: в определенный момент MetaTrader 5 завершает применение шаблона на графике. Когда это происходит, MetaTrader 5 отправляет индикатору событие Deinit. Данное событие будет обработано функцией OnDeinit, но значение, присутствующее в переменной reason, будет не REASON_TEMPLATE, а REASON_REMOVE. Это происходит, потому что MetaTrader 5 удаляет индикатор управления с графика, тем самым завершая применение шаблона. По этой причине сообщение в строке 58 не выводится в окно сообщений терминала.

Однако, теперь возникает вопрос: почему MetaTrader 5 не применяет шаблон синхронно? Иными словами, почему шаблон не применяется полностью к графику в тот самый момент, когда выполняется вызов ChartApplyTemplate, расположенный в строке 87 заголовочного файла C_Replay.mqh? Причина в скорости. В шаблоне может быть несколько индикаторов или даже советник, требующий наличия определенного количества баров на графике для получения полезных показаний.

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

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

Другой вопрос: почему бы не решить данную проблему по-другому? Например, почему бы не поместить индикатор управления непосредственно в шаблон. Хотя это и решит одну проблему, но создаст другую. В предыдущих версиях, чтобы предотвратить появление индикатора управления на ненужном графике, мы использовали глобальную переменную терминала. Однако мы больше не используем данный подход. Форма блокировки изменилась. Поэтому мы не можем позволить шаблону содержать индикатор управления, так как это может привести к проблемам, которые тяжело решаются.

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


Новый код для сервиса репликации/моделирования

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico"
04. #property description "Control indicator for the Replay-Simulator service."
05. #property description "This one doesn't work without the service loaded."
06. #property version   "1.59"
07. #property link "https://www.mql5.com/pt/articles/12075"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. #property indicator_buffers 1
11. //+------------------------------------------------------------------+
12. #include <Market Replay\Service Graphics\C_Controls.mqh>
13. //+------------------------------------------------------------------+
14. C_Controls *control = NULL;
15. //+------------------------------------------------------------------+
16. input long user00 = 0;      //ID
17. //+------------------------------------------------------------------+
18. double m_Buff[];
19. int    m_RatesTotal = 0;
20. //+------------------------------------------------------------------+
21. int OnInit()
22. {
23.    ResetLastError();   
24.    if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID)
25.       SetUserError(C_Terminal::ERR_PointerInvalid);
26.    if ((_LastError != ERR_SUCCESS) || (user00 == 0))
27.    {
28.       Print("Control indicator failed on initialization.");
29.       return INIT_FAILED;
30.    }
31.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
32.    ArrayInitialize(m_Buff, EMPTY_VALUE);
33.    
34.    return INIT_SUCCEEDED;
35. }
36. //+------------------------------------------------------------------+
37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
38. {
39.    return m_RatesTotal = rates_total;
40. }
41. //+------------------------------------------------------------------+
42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
43. {
44.    (*control).DispatchMessage(id, lparam, dparam, sparam);
45.    if (_LastError >= ERR_USER_ERROR_FIRST + C_Terminal::ERR_Unknown)
46.    {
47.       Print("Internal failure in the messaging system...");
48.       ChartClose(user00);
49.    }
50.    (*control).SetBuffer(m_RatesTotal, m_Buff);
51. }
52. //+------------------------------------------------------------------+
53. void OnDeinit(const int reason)
54. {
55.    switch (reason)
56.    {
57.       case REASON_TEMPLATE:
58.          Print("Modified template. Replay // simulation system shutting down.");
59.       case REASON_INITFAILED:
60.       case REASON_PARAMETERS:
61.       case REASON_REMOVE:
62.       case REASON_CHARTCLOSE:
63.          ChartClose(user00);
64.          break;
65.    }
66.    delete control;
67. }
68. //+------------------------------------------------------------------+
69. 

Исходный код индикатора управления

Вы, наверное, заметили, что я не показываю код в заголовочном файле, поскольку он не был изменен. Хотя мы можем ознакомиться со всем кодом модуля управления, приведенным выше, мы сосредоточимся на измененных фрагментах. Единственное различие - в строке 26. В соответствии с этим, проверим два условия. Одно из них - значение _LastError, а другое - значение user00. Этот user00 определяется в строке 16 и отвечает за получение от сервиса репликации/моделирования значения ID графика, на котором должен присутствовать модуль управления.

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

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

01. <indicator>
02. name=Custom Indicator
03. path=Services\Market Replay.ex5::Indicators\Market Replay.ex5
04. apply=0
05. show_data=1
06. scale_inherit=0
07. scale_line=0
08. scale_line_percent=50
09. scale_line_value=0.000000
10. scale_fix_min=0
11. scale_fix_min_val=0.000000
12. scale_fix_max=0
13. scale_fix_max_val=0.000000
14. expertmode=1610613824
15. fixed_height=-1
16. 
17. <graph>
18. name=
19. draw=0
20. style=0
21. width=1
22. color=
23. </graph>
24. <inputs>
25. user00=130652731570824061
26. </inputs>
27. </indicator>

Фрагмент файла шаблона

Точное положение данного фрагмента здесь не имеет значения. Поэтому нумерация линий - это всего лишь вспомогательное средство для обозначения элементов и облегчения объяснения.

Обратите внимание, что в строке 1 находится открывающая метка, а в строке 27 - метка, которая закрывает структуру. Между этими двумя метками находится различная информация, например, положение индикатора на графике. Это можно увидеть в строке 3. Отлично. Теперь обратите внимание на строку 24. Здесь у нас есть метка, которая открывает раздел ожидаемых записей для индикатора, объявленного в строке 3. Аналогично, строка 26 закрывает соответствующую часть записей.

В строке 25 мы ссылаемся на запись, которая в исходном коде индикатора объявлена в строке 16 для получения идентификатора графика. Обратите внимание, что в 25 строке фрагмента мы посылаем значение индикатора управления, чтобы условие, проверенное в строке 26 исходного кода индикатора, было false. Затем индикатор загрузится, и всё будет работать правильно, не так ли? И снова вы ошибаетесь. Как только сервис репликации/моделирования разместит индикатор управления на графике правильным образом, код индикатора обнаружит, что там уже запущен другой экземпляр. Когда это произойдет, строка 24 кода индикатора управления выдаст результат, приводящий к выполнению строки 25. И это приведет к тому, что у _LastError больше не будет ожидаемого значения в строке 26. Это значит, что вся система выйдет из строя, и MetaTrader 5 сгенерирует событие Deinit. Данное событие должно быть обработано процедурой, присутствующей в строке 53, т.е. OnDeinit. Там должен быть выполнен поток, начинающийся с REASON_INITFAILED, который закроет график и приведет к остановке сервиса репликации/моделирования.

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

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


Маленькие изменения ради больших целей

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "Macros.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. class C_Terminal
008. {
009. //+------------------------------------------------------------------+
010.    protected:
011.       enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance};
012. //+------------------------------------------------------------------+
013.       struct st_Terminal
014.       {
015.          ENUM_SYMBOL_CHART_MODE    ChartMode;
016.          ENUM_ACCOUNT_MARGIN_MODE  TypeAccount;
017.          long           ID;
018.          string         szSymbol;
019.          int            Width,
020.                         Height,
021.                         nDigits,
022.                         SubWin;
023.          double         PointPerTick,
024.                         ValuePerPoint,
025.                         VolumeMinimal,
026.                         AdjustToTrade;
027.       };
028. //+------------------------------------------------------------------+
029.    private   :
030.       st_Terminal m_Infos;
031.       struct mem
032.       {
033.          long   Show_Descr,
034.                 Show_Date;
035.          bool   AccountLock;
036.       }m_Mem;
037. //+------------------------------------------------------------------+
038.       void CurrentSymbol(void)
039.          {
040.             MqlDateTime mdt1;
041.             string sz0, sz1;
042.             datetime dt = macroGetDate(TimeCurrent(mdt1));
043.             enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER;
044.       
045.             sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);
046.             for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS);
047.             switch (eTS)
048.             {
049.                case DOL   :
050.                case WDO   : sz1 = "FGHJKMNQUVXZ"; break;
051.                case IND   :
052.                case WIN   : sz1 = "GJMQVZ";       break;
053.                default    : return;
054.             }
055.             for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0))
056.                if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break;
057.          }
058. //+------------------------------------------------------------------+
059.    public   :
060. //+------------------------------------------------------------------+      
061.       C_Terminal(const long id = 0, const uchar sub = 0)
062.          {
063.             m_Infos.ID = (id == 0 ? ChartID() : id);
064.             m_Mem.AccountLock = false;
065.             m_Infos.SubWin = (int) sub;
066.             CurrentSymbol();
067.             m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);
068.             m_Mem.Show_Date  = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE);
069.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false);
070.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true);
071.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true);
072.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false);
073.             m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS);
074.             m_Infos.Width   = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
075.             m_Infos.Height  = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
076.             m_Infos.PointPerTick  = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE);
077.             m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE);
078.             m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP);
079.             m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick;
080.             m_Infos.ChartMode   = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE);
081.             if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE));
082.             ResetLastError();
083.          }
084. //+------------------------------------------------------------------+
085.       ~C_Terminal()
086.          {
087.             ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date);
088.             ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr);
089.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false);
090.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false);
091.          }
092. //+------------------------------------------------------------------+
093. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg)
094.          {
095.             if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true;
096.             m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING);
097.          }
098. //+------------------------------------------------------------------+
099. inline const st_Terminal GetInfoTerminal(void) const
100.          {
101.             return m_Infos;
102.          }
103. //+------------------------------------------------------------------+
104. const double AdjustPrice(const double arg) const
105.          {
106.             return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits);
107.          }
108. //+------------------------------------------------------------------+
109. inline datetime AdjustTime(const datetime arg)
110.          {
111.             int nSeconds= PeriodSeconds();
112.             datetime   dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
113.             
114.             return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
115.          }
116. //+------------------------------------------------------------------+
117. inline double FinanceToPoints(const double Finance, const uint Leverage)
118.          {
119.             double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1));
120.             
121.             return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade)));
122.          };
123. //+------------------------------------------------------------------+
124.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
125.          {
126.             static string st_str = "";
127.             
128.             switch (id)
129.             {
130.                case CHARTEVENT_CHART_CHANGE:
131.                   m_Infos.Width  = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS);
132.                   m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS);
133.                   break;
134.                case CHARTEVENT_OBJECT_CLICK:
135.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
136.                   if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true)
137.                      ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true);
138.                   break;
139.                case CHARTEVENT_OBJECT_CREATE:
140.                   if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false);
141.                   st_str = sparam;
142.                   break;
143.             }
144.          }
145. //+------------------------------------------------------------------+
146. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const
147.          {
148.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false);
149.             ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0);
150.             ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n");
151.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false);
152.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor);
153.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false);
154.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false);
155.             ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder);
156.             ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true);
157.          }
158. //+------------------------------------------------------------------+
159.       bool IndicatorCheckPass(const string szShortName)
160.          {
161.             string szTmp = szShortName + "_TMP";
162.             
163.             if (_LastError != ERR_SUCCESS) return false;            
164.             IndicatorSetString(INDICATOR_SHORTNAME, szTmp);
165.             m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin);
166.             if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE)
167.             {
168.                ChartIndicatorDelete(m_Infos.ID, 0, szTmp);
169.                Print("Only one instance is allowed...");
170.                SetUserError(C_Terminal::ERR_NoMoreInstance);
171.                
172.                return false;
173.             }
174.             IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
175.             ResetLastError();
176.    
177.             return true;
178.          }
179. //+------------------------------------------------------------------+
180. };

Исходный код заголовочного файла C_Terminal.mqh

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

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

Поскольку данная переменная была добавлена и может быть прочитана за пределами класса, мы должны убедиться в том, что она имеет соответствующее значение с самого начала. По этой причине в строке 61 мы внесли несколько изменений в конструктор класса. Обратите внимание, что теперь конструктор получает дополнительный параметр. Данный параметр может принимать значения от 0 до 255, что является достаточным, поскольку мы редко размещаем на графике более 2-3 подокон. Но есть одна деталь, которую мы решаем с помощью явного объявления преобразования типов при рассмотрении строки 65. А почему бы нам не использовать тип uchar непосредственно в переменной вместо того, чтобы выполнять это преобразование здесь, в строке 65? Причина в обратной совместимости. MQL5 ожидает получения целочисленного типа со знаком. Если мы объявим переменную с таким типом, это значительно упростит нашу дальнейшую работу. Аналогично этому, мы можем легко ограничить количество подокон до 255 благодаря тому, что передаваемый параметр имеет тип uchar. По этой причине всё делается именно так.

Теперь, перейдя к строке 149, мы можем увидеть, где используется данное значение. Обратите внимание, что это значение будет использоваться для оповещения MetaTrader 5 о том, в каком окне графика должен быть размещен объект. Подобные моменты не являются новостью для тех, у кого уже есть некоторые знания о MQL5. Кстати, это довольно частое явление. Однако, если посмотреть на функцию CheckPassIndicator, которая начинается в строке 159, ситуация меняется.

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

Чтобы быстро определить, что это за окно, мы используем строку 164, где присваиваем временное имя тому индикатору, который разместили. Затем, в строке 165, мы используем вызов MQL5 ChartWindowFind, чтобы MetaTrader 5 сообщил нам с точностью, в каком окне находится индикатор. Помните, что мы используем временное имя и что ни один другой индикатор не должен иметь похожее имя, в противном случае мы можем получить ложное срабатывание. Если мы не получаем положительного значения, то исправим индикацию, чтобы она указывала на главное окно, то есть на окно с нулевым индексом. После этого, в строке 166, мы убедимся в том, что в графике присутствует только один экземпляр. Остальная часть функции осталась прежней, поэтому не нуждается в дальнейших комментариях.

Хорошо, теперь мы можем разместить модуль указателя мыши в любом подокне. Но для того, чтобы это хоть как-то работало, придется кое-что изменить в коде классов. Давайте посмотрим, что изменилось в заголовочном файле C_Mouse.mqh. Можно ознакомиться с ним ниже.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_Terminal.mqh"
005. //+------------------------------------------------------------------+
006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_"
007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0))
008. //+------------------------------------------------------------------+
009. class C_Mouse : public C_Terminal
010. {
011.    public   :
012.       enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
013.       enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
014.       struct st_Mouse
015.       {
016.          struct st00
017.          {
018.             short    X_Adjusted,
019.                      Y_Adjusted,
020.                      X_Graphics,
021.                      Y_Graphics;
022.             double   Price;
023.             datetime dt;
024.          }Position;
025.          uchar      ButtonStatus;
026.          bool       ExecStudy;
027.       };
028. //+------------------------------------------------------------------+
029.    protected:
030. //+------------------------------------------------------------------+
031.       void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const
032.          {
033.             if (!m_OK) return;
034.             CreateObjectGraphics(szName, OBJ_BUTTON);
035.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true);
036.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
037.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack);
038.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor);
039.             ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console");
040.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10);
041.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
042.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x);
043.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1);
044.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 
045.             ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18);
046.          }
047. //+------------------------------------------------------------------+
048.    private   :
049.       enum eStudy {eStudyNull, eStudyCreate, eStudyExecute};
050.       struct st01
051.       {
052.          st_Mouse Data;
053.          color    corLineH,
054.                   corTrendP,
055.                   corTrendN;
056.          eStudy   Study;
057.       }m_Info;
058.       struct st_Mem
059.       {
060.          bool     CrossHair,
061.                   IsFull;
062.          datetime dt;
063.          string   szShortName,
064.                   szLineH,
065.                   szLineV,
066.                   szLineT,
067.                   szBtnS;
068.       }m_Mem;
069.       bool m_OK;
070. //+------------------------------------------------------------------+
071.       void GetDimensionText(const string szArg, int &w, int &h)
072.          {
073.             TextSetFont("Lucida Console", -100, FW_NORMAL);
074.             TextGetSize(szArg, w, h);
075.             h += 5;
076.             w += 5;
077.          }
078. //+------------------------------------------------------------------+
079.       void CreateStudy(void)
080.          {
081.             if (m_Mem.IsFull)
082.             {
083.                CreateObjectGraphics(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH);
084.                CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH);
085.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_WIDTH, 2);
086.                CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_NameObjectStudy);
087.             }
088.             m_Info.Study = eStudyCreate;
089.          }
090. //+------------------------------------------------------------------+
091.       void ExecuteStudy(const double memPrice)
092.          {
093.             double v1 = GetInfoMouse().Position.Price - memPrice;
094.             int w, h;
095.             
096.             if (!CheckClick(eClickLeft))
097.             {
098.                m_Info.Study = eStudyNull;
099.                ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
100.                if (m_Mem.IsFull)   ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T");
101.             }else if (m_Mem.IsFull)
102.             {
103.                string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ",
104.                   MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0));
105.                GetDimensionText(sz1, w, h);
106.                ObjectSetString(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_TEXT, sz1);                                                
107.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP));
108.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XSIZE, w);
109.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YSIZE, h);
110.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w);
111.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h));            
112.                ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price);
113.                ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP));
114.             }
115.             m_Info.Data.ButtonStatus = eKeyNull;
116.          }
117. //+------------------------------------------------------------------+
118. inline void DecodeAlls(int xi, int yi)
119.          {
120.             int w = 0;
121. 
122.             xi = (xi > 0 ? xi : 0);
123.             yi = (yi > 0 ? yi : 0);
124.             ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short)xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
125.             m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
126.             m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price);
127.             ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi);
128.             yi -= (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WINDOW_YDISTANCE, GetInfoTerminal().SubWin);
129.             m_Info.Data.Position.X_Adjusted = (short) xi;
130.             m_Info.Data.Position.Y_Adjusted = (short) yi;
131.          }
132. //+------------------------------------------------------------------+
133.    public   :
134. //+------------------------------------------------------------------+
135.       C_Mouse(const long id, const string szShortName)
136.          :C_Terminal(id),
137.          m_OK(false)
138.          {
139.             m_Mem.szShortName = szShortName;
140.          }
141. //+------------------------------------------------------------------+
142.       C_Mouse(const long id, const string szShortName, color corH, color corP, color corN)
143.          :C_Terminal(id)
144.          {
145.             if (!(m_OK = IndicatorCheckPass(m_Mem.szShortName = szShortName))) SetUserError(C_Terminal::ERR_Unknown);
146.             if (_LastError != ERR_SUCCESS) return;
147.             m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL);
148.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true);
149.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false);
150.             ZeroMemory(m_Info);
151.             m_Info.corLineH  = corH;
152.             m_Info.corTrendP = corP;
153.             m_Info.corTrendN = corN;
154.             m_Info.Study = eStudyNull;
155.             if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE))
156.                CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH);
157.             ChartRedraw(GetInfoTerminal().ID);
158.          }
159. //+------------------------------------------------------------------+
160.       ~C_Mouse()
161.          {
162.             if (!m_OK) return;
163.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
164.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(GetInfoTerminal().ID, m_Mem.szShortName) != -1);
165.              ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair);
166.             ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName);
167.          }
168. //+------------------------------------------------------------------+
169. inline bool CheckClick(const eBtnMouse value) 
170.          {
171.             return (GetInfoMouse().ButtonStatus & value) == value;
172.          }
173. //+------------------------------------------------------------------+
174. inline const st_Mouse GetInfoMouse(void)
175.          {
176.             if (!m_OK)
177.             {
178.                double Buff[];
179.                uCast_Double loc;
180.                int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName);
181. 
182.                ZeroMemory(m_Info.Data);
183.                if (CopyBuffer(handle, 0, 0, 1, Buff) == 1)
184.                {
185.                   loc.dValue = Buff[0];
186.                   m_Info.Data.ButtonStatus = loc._8b[0];
187.                   DecodeAlls((int)loc._16b[1], (int)loc._16b[2]);
188.                }
189.                IndicatorRelease(handle);
190.             }
191. 
192.             return m_Info.Data;
193.          }
194. //+------------------------------------------------------------------+
195. inline void SetBuffer(const int rates_total, double &Buff[])
196.          {
197.             uCast_Double info;
198.             
199.             info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0);
200.             info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics;
201.             info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics;
202.             Buff[rates_total - 1] = info.dValue;
203.          }
204. //+------------------------------------------------------------------+
205.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
206.          {
207.             int w = 0;
208.             static double memPrice = 0;
209.       
210.             if (m_OK)      
211.             {
212.                C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
213.                switch (id)
214.                {
215.                   case (CHARTEVENT_CUSTOM + evHideMouse):
216.                      if (m_Mem.IsFull)   ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, clrNONE);
217.                      break;
218.                   case (CHARTEVENT_CUSTOM + evShowMouse):
219.                      if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH);
220.                      break;
221.                   case CHARTEVENT_MOUSE_MOVE:
222.                      DecodeAlls((int)lparam, (int)dparam);
223.                      if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price);
224.                      if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0);
225.                      m_Info.Data.ButtonStatus = (uchar) sparam;
226.                      if (CheckClick(eClickMiddle))
227.                         if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
228.                      if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
229.                      {
230.                         ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
231.                         if (m_Mem.IsFull)   ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price);
232.                         m_Info.Study = eStudyExecute;
233.                      }
234.                      if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
235.                      m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
236.                      break;
237.                   case CHARTEVENT_OBJECT_DELETE:
238.                      if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH))
239.                         CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH);
240.                      break;
241.                }
242.             }
243.          }
244. //+------------------------------------------------------------------+
245. };
246. //+------------------------------------------------------------------+
247. #undef macro_NameObjectStudy
248. //+------------------------------------------------------------------+

Исходный код заголовочного файла C_Mouse.mqh

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

В этом коде есть несколько существенных отличий, но ничего такого, что заслуживало бы особого внимания. Однако между строками 64 и 67 объявляются новые переменные. Они используются для хранения имен создаваемых объектов. Чтобы облегчить понимание того, как это будет сделано, давайте посмотрим в строке 83 на пример присвоения имени одному из объектов. В этой строке хорошо видно, как используется макрос для генерации и присвоения имени одной из переменных.

Большая часть кода ничем не примечательна, потому что изменения были минимальны и мы их внесли только для лучшей поддержки того, что нам нужно. Есть одна часть кода, которая действительно заслуживает внимания. Даже если это не на 100% так, как нам хотелось бы, этого достаточно, чтобы добиться желаемого. Я имею в виду процедуру, которая начинается со строки 118, DecodeAlls.

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

Но самая важная деталь, которая может вас по настоящему удивить и дезориентировать, находится в строке 128. Почему данная строка существует? И почему ее не было раньше? Чтобы понять это, нужно разобраться с еще одной деталью. Изначально мы разработали модуль указателя мыши для отображения только в окне с индексом ноль. Точка отсчета в этом окне находится на оси Y в верхней части окна, то есть значение Y всегда начинается с 0. Однако при добавлении дополнительных окон Y главного окна не изменяется, в то время как Y дополнительных окон - изменяется. Эти значения смещены на определенную величину. Для операционной системы, то есть Windows, данное смещение не имеет значения и сообщает MetaTrader 5 точное положение мыши.

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

Изображение 01

Изображение 01 - Изучаем рабочее пространство

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

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

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

Значение, которое возвращает вызов в строке 128, вычитается из преобразованного значения. Напомним, что преобразованное значение - это то, что MetaTrader 5 сообщает в качестве окна. Без исправления, внесенного в строке 128, когда указатель мыши попадает в позицию, которую MetaTrader 5 идентифицирует как часть подокна, мы получали бы ложное представление о положении горизонтальной линии. Но с данным исправлением такая проблема не возникнет. По крайней мере, не так, как это обычно происходит.

Еще один важный момент находится в строке 145, где мы храним короткое имя индикатора. Зачем нам это? Причина становится понятной в строке 164. Не зная названия указателя мыши, мы не сможем протестировать и проверить, можем ли мы уже сейчас сообщить MetaTrader 5 о прекращении отправки событий мыши. Кто-то может спросить, не могли бы мы просто поддержать событие в активированном виде? Однако оставлять событие активированным, не используя его, было бы в некотором смысле пустой тратой времени. Причина в том, что при каждом движении мыши MetaTrader 5 должен будет вызывать событие мыши, а если никто не пользуется мышью, то это только добавит лишние элементы в очередь событий. Чтобы избежать этого, мы отключаем отправку событий мыши, как только они перестают быть нужными. Но без знания имени модуля указателя мыши, как мы можем узнать, есть ли указатель, который должен получить данное событие? Это просто невозможно. По этой причине мы сохраняем имя индикатора и используем его для проверки возможности отключения события мыши.

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\C_Mouse.mqh"
005. //+------------------------------------------------------------------+
006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_"
007. //+------------------------------------------------------------------+
008. class C_Study : public C_Mouse
009. {
010.    private   :
011. //+------------------------------------------------------------------+
012.       struct st00
013.       {
014.          eStatusMarket  Status;
015.          MqlRates       Rate;
016.          string         szInfo,
017.                         szBtn1,
018.                         szBtn2,
019.                         szBtn3;
020.          color          corP,
021.                         corN;
022.          int            HeightText;
023.          bool           bvT, bvD, bvP;
024.          datetime       TimeDevice;
025.       }m_Info;
026. //+------------------------------------------------------------------+
027.       const datetime GetBarTime(void)
028.          {
029.             datetime dt;
030.             int i0 = PeriodSeconds();
031.             
032.             if (m_Info.Status == eInReplay)
033.             {
034.                if ((dt = m_Info.TimeDevice) == ULONG_MAX) return ULONG_MAX;
035.             }else dt = TimeCurrent();
036.             if (m_Info.Rate.time <= dt)
037.                m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0;
038. 
039.             return m_Info.Rate.time - dt;
040.          }
041. //+------------------------------------------------------------------+
042.       void Draw(void)
043.          {
044.             double v1;
045.             
046.             if (m_Info.bvT)
047.             {
048.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18);
049.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo);
050.             }
051.             if (m_Info.bvD)
052.             {
053.                v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
054.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
055.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
056.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
057.             }
058.             if (m_Info.bvP)
059.             {
060.                v1 = NormalizeDouble((((iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0) - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2);
061.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1);
062.                ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
063.                ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1)));
064.             }
065.          }
066. //+------------------------------------------------------------------+
067. inline void CreateObjInfo(EnumEvents arg)
068.          {
069.             switch (arg)
070.             {
071.                case evShowBarTime:
072.                   C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise);
073.                   m_Info.bvT = true;
074.                   break;
075.                case evShowDailyVar:
076.                   C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
077.                   m_Info.bvD = true;
078.                   break;
079.                case evShowPriceVar:
080.                   C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0)));
081.                   m_Info.bvP = true;
082.                   break;
083.             }
084.          }
085. //+------------------------------------------------------------------+
086. inline void RemoveObjInfo(EnumEvents arg)
087.          {
088.             string sz;
089.             
090.             switch (arg)
091.             {
092.                case evHideBarTime:
093.                   sz = m_Info.szBtn1;
094.                   m_Info.bvT = false;
095.                   break;
096.                case evHideDailyVar:
097.                   sz = m_Info.szBtn2;
098.                   m_Info.bvD   = false;
099.                   break;
100.                case evHidePriceVar:
101.                   sz = m_Info.szBtn3;
102.                   m_Info.bvP = false;
103.                   break;
104.             }
105.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
106.             ObjectDelete(GetInfoTerminal().ID, sz);
107.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
108.          }
109. //+------------------------------------------------------------------+
110.    public   :
111. //+------------------------------------------------------------------+
112.       C_Study(long IdParam, string szShortName, color corH, color corP, color corN)
113.          :C_Mouse(IdParam, szShortName, corH, corP, corN)
114.          {
115.             if (_LastError != ERR_SUCCESS) return;
116.             ZeroMemory(m_Info);
117.             m_Info.Status = eCloseMarket;
118.             m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1));
119.             m_Info.corP = corP;
120.             m_Info.corN = corN;
121.             CreateObjInfo(evShowBarTime);
122.             CreateObjInfo(evShowDailyVar);
123.             CreateObjInfo(evShowPriceVar);
124.          }
125. //+------------------------------------------------------------------+
126.       void Update(const eStatusMarket arg)
127.          {
128.             datetime dt;
129.             
130.             switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
131.             {
132.                case eCloseMarket   :
133.                   m_Info.szInfo = "Closed Market";
134.                   break;
135.                case eInReplay      :
136.                case eInTrading   :
137.                   if ((dt = GetBarTime()) < ULONG_MAX)
138.                   {
139.                      m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
140.                      break;
141.                   }
142.                case eAuction      :
143.                   m_Info.szInfo = "Auction";
144.                   break;
145.                default            :
146.                   m_Info.szInfo = "ERROR";
147.             }
148.             Draw();
149.          }
150. //+------------------------------------------------------------------+
151. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
152.          {
153.             C_Mouse::DispatchMessage(id, lparam, dparam, sparam);
154.             switch (id)
155.             {
156.                case CHARTEVENT_CUSTOM + evHideBarTime:
157.                   RemoveObjInfo(evHideBarTime);
158.                   break;
159.                case CHARTEVENT_CUSTOM + evShowBarTime:
160.                   CreateObjInfo(evShowBarTime);
161.                   break;
162.                case CHARTEVENT_CUSTOM + evHideDailyVar:
163.                   RemoveObjInfo(evHideDailyVar);
164.                   break;
165.                case CHARTEVENT_CUSTOM + evShowDailyVar:
166.                   CreateObjInfo(evShowDailyVar);
167.                   break;
168.                case CHARTEVENT_CUSTOM + evHidePriceVar:
169.                   RemoveObjInfo(evHidePriceVar);
170.                   break;
171.                case CHARTEVENT_CUSTOM + evShowPriceVar:
172.                   CreateObjInfo(evShowPriceVar);
173.                   break;
174.                case (CHARTEVENT_CUSTOM + evSetServerTime):
175.                   m_Info.TimeDevice = (datetime)dparam;
176.                   break;
177.                case CHARTEVENT_MOUSE_MOVE:
178.                   Draw();
179.                   break;
180.             }
181.             ChartRedraw(GetInfoTerminal().ID);
182.          }
183. //+------------------------------------------------------------------+
184. };
185. //+------------------------------------------------------------------+
186. #undef def_ExpansionPrefix
187. #undef def_MousePrefixName
188. //+------------------------------------------------------------------+

Исходный код заголовочного файла C_Study.mqh

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

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "This is an indicator for graphical studies using the mouse."
04. #property description "This is an integral part of the Replay / Simulator system."
05. #property description "However it can be used in the real market."
06. #property version "1.59"
07. #property icon "/Images/Market Replay/Icons/Indicators.ico"
08. #property link "https://www.mql5.com/pt/articles/12075"
09. #property indicator_chart_window
10. #property indicator_plots 0
11. #property indicator_buffers 1
12. //+------------------------------------------------------------------+
13. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
14. //+------------------------------------------------------------------+
15. C_Study *Study       = NULL;
16. //+------------------------------------------------------------------+
17. input color user02   = clrBlack;                       //Price Line
18. input color user03   = clrPaleGreen;                   //Positive Study
19. input color user04   = clrLightCoral;                  //Negative Study
20. //+------------------------------------------------------------------+
21. C_Study::eStatusMarket m_Status;
22. int m_posBuff = 0;
23. double m_Buff[];
24. //+------------------------------------------------------------------+
25. int OnInit()
26. {
27.    ResetLastError();
28.    Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04);
29.    if (_LastError != ERR_SUCCESS) return INIT_FAILED;
30.    if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
31.    {
32.       MarketBookAdd((*Study).GetInfoTerminal().szSymbol);
33.       OnBookEvent((*Study).GetInfoTerminal().szSymbol);
34.       m_Status = C_Study::eCloseMarket;
35.    }else
36.       m_Status = C_Study::eInReplay;
37.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
38.    ArrayInitialize(m_Buff, EMPTY_VALUE);
39.    
40.    return INIT_SUCCEEDED;
41. }
42. //+------------------------------------------------------------------+
43. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
44. {
45.    m_posBuff = rates_total;
46.    (*Study).Update(m_Status);   
47.    
48.    return rates_total;
49. }
50. //+------------------------------------------------------------------+
51. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
52. {
53.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
54.    (*Study).SetBuffer(m_posBuff, m_Buff);
55.    
56.    ChartRedraw((*Study).GetInfoTerminal().ID);
57. }
58. //+------------------------------------------------------------------+
59. void OnBookEvent(const string &symbol)
60. {
61.    MqlBookInfo book[];
62.    C_Study::eStatusMarket loc = m_Status;
63.    
64.    if (symbol != (*Study).GetInfoTerminal().szSymbol) return;
65.    MarketBookGet((*Study).GetInfoTerminal().szSymbol, book);
66.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
67.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
68.       if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
69.    if (loc != m_Status) (*Study).Update(m_Status);
70. }
71. //+------------------------------------------------------------------+
72. void OnDeinit(const int reason)
73. {
74.    if (reason != REASON_INITFAILED)
75.    {
76.       if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay)
77.          MarketBookRelease((*Study).GetInfoTerminal().szSymbol);
78.    }
79.    delete Study;
80. }
81. //+------------------------------------------------------------------+
82. 

Исходный код указателя мыши

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

Обратите внимание, что в строке 36 мы вносим небольшую поправку. Если данный модуль размещен на графике символа, используемом сервисом репликации/моделирования, строка 36 автоматически изменит настройки. Теперь пользователю больше не придется вносить изменения, которые требовались ранее, например, сообщать ID графика. Дополнительное примечание: несмотря на то, что сообщать ID больше не нужно, всё, что было описано в предыдущих статьях о вызовах, остается в силе, по крайней мере, на момент написания статьи.


Заключение

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

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

В завершение статьи мы приведем видео, в котором показано, как работает модуль указателя мыши. Обратите внимание на видео: вы увидите, что в нем есть ошибка. Я знаю о ее существовании. Но поскольку ошибка не столь важна, я исправлю ее в другой раз, когда буду лучше понимать, как работает MetaTrader 5 в некоторых аспектах, которые я еще не до конца осознал.

Я хочу, чтобы вы приняли участие в этом процессе обучения. Поэтому я продолжу рассказывать о том, как разработать систему, которая будет использоваться для сервиса репликации/моделирования, а также будет совместима с реальным рынком и демо-счетами.


Видеодемонстрация нового индикатора мыши в действии

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Уроборос
Уроборос | 5 авг. 2024 в 12:42
У меня есть предложение.
На основе воспроизведения вашего рынка сделайте что-то немного другое, что может быть полезно и интересно не только вам, но и многим другим людям.
Я бы сделал это сам, но я не профессиональный программист. И я тщательно изучаю все ваши коды. А реализовать свою идею на их основе я не могу из-за нехватки времени.
И если вы сочтете мое предложение интересным и/или задумаете сделать коммерческий продукт на основе этой идеи, то я хотел бы получить бесплатный доступ к нему в качестве платы за идею.
И если вы согласны, то я готов это озвучить.
Алгоритм циклического партеногенеза — Cyclic Parthenogenesis Algorithm (CPA) Алгоритм циклического партеногенеза — Cyclic Parthenogenesis Algorithm (CPA)
В данной статье рассмотрим новый популяционный алгоритм оптимизации CPA (Cyclic Parthenogenesis Algorithm), вдохновленный уникальной репродуктивной стратегией тлей. Алгоритм сочетает два механизма размножения — партеногенез и половое, а также использует колониальную структуру популяции с возможностью миграции между колониями. Ключевыми особенностями алгоритма являются адаптивное переключение между различными стратегиями размножения и система обмена информацией между колониями через механизм перелета.
Нейросети в трейдинге: Контекстно-зависимое обучение, дополненное памятью (MacroHFT) Нейросети в трейдинге: Контекстно-зависимое обучение, дополненное памятью (MacroHFT)
Предлагаю познакомиться с фреймворком MacroHFT, который применяет контекстно зависимое обучение с подкреплением и память, для улучшения решений в высокочастотной торговле криптовалютами, используя макроэкономические данные и адаптивные агенты.
От начального до среднего уровня: Операторы От начального до среднего уровня: Операторы
В этой статье мы рассмотрим основных операторов. Хотя тема проста для понимания, есть определенные моменты, которые имеют большое значение, когда речь идет о включении математических выражений в формат кода. Без адекватного понимания этих деталей, программисты с небольшим опытом или вообще без него в итоге отказываются от попыток создать собственных решений.
От начального до среднего уровня: Переменные (III) От начального до среднего уровня: Переменные (III)
Сегодня мы рассмотрим, как использовать переменные и константы, предопределенные языком MQL5. Кроме того, мы проанализируем еще один особый тип переменных: функции. Умение правильно работать с этими переменными может определить разницу между работающим и неработающим приложением. Для того, чтобы понять представленное здесь, необходимо разобраться с материалом, который был рассмотрен в предыдущих статьях.