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

Разработка системы репликации (Часть 55): Модуль управления

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

Введение

В предыдущей статье Разработка системы репликации (Часть 54): Появление первого модуля, мы собрали первый настоящий модуль всей нашей новой системы репликации/моделирования. Помимо возможности использования в разрабатываемой системе, мы также сможем применять модули индивидуально и персонализированно, чтобы избежать необходимости в большом объеме программирования для создания указанной системы. Как бы то ни было, после того, как модуль собран, его можно будет легко настраивать без необходимости новой компиляции. 

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

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

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

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

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

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

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

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

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


Модификация индикатора управления

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

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

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

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

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP           "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay        def_PathBMP + "Play.bmp"
009. #define def_ButtonPause       def_PathBMP + "Pause.bmp"
010. #define def_ButtonLeft        def_PathBMP + "Left.bmp"
011. #define def_ButtonLeftBlock   def_PathBMP + "Left_Block.bmp"
012. #define def_ButtonRight       def_PathBMP + "Right.bmp"
013. #define def_ButtonRightBlock  def_PathBMP + "Right_Block.bmp"
014. #define def_ButtonPin         def_PathBMP + "Pin.bmp"
015. #resource "\\" + def_ButtonPlay
016. #resource "\\" + def_ButtonPause
017. #resource "\\" + def_ButtonLeft
018. #resource "\\" + def_ButtonLeftBlock
019. #resource "\\" + def_ButtonRight
020. #resource "\\" + def_ButtonRightBlock
021. #resource "\\" + def_ButtonPin
022. //+------------------------------------------------------------------+
023. #define def_PrefixCtrlName    "MarketReplayCTRL_"
024. #define def_PosXObjects       120
025. //+------------------------------------------------------------------+
026. #define def_SizeButtons         32
027. #define def_ColorFilter         0xFF00FF
028. //+------------------------------------------------------------------+
029. #include "..\Auxiliar\C_Terminal.mqh"
030. #include "..\Auxiliar\C_Mouse.mqh"
031. //+------------------------------------------------------------------+
032. class C_Controls : private C_Terminal
033. {
034.    protected:
035.    private   :
036. //+------------------------------------------------------------------+
037.       enum eObjectControl {ePlay, eLeft, eRight, ePin, eNull};
038. //+------------------------------------------------------------------+
039.       struct st_00
040.       {
041.          string  szBarSlider,
042.                  szBarSliderBlock;
043.          int     Minimal;
044.       }m_Slider;
045.       struct st_01
046.       {
047.          C_DrawImage *Btn;
048.          bool         state;
049.          int          x, y, w, h;
050.       }m_Section[eObjectControl::eNull];
051.       C_Mouse   *m_MousePtr;
052. //+------------------------------------------------------------------+
053. inline void CreteBarSlider(int x, int size)
054.          {
055.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_PrefixCtrlName + "B1", OBJ_RECTANGLE_LABEL, 0, 0, 0);
056.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
060.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
064.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_PrefixCtrlName + "B2", OBJ_RECTANGLE_LABEL, 0, 0, 0);
065.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
066.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
067.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
068.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
069.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
070.          }
071. //+------------------------------------------------------------------+
072.       void SetPlay(bool state)
073.          {
074.             if (m_Section[ePlay].Btn == NULL)
075.                m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause);
076.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 0 : 1));
077.          }
078. //+------------------------------------------------------------------+
079.       void CreateCtrlSlider(void)
080.          {
081.             CreteBarSlider(77, 436);
082.             m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock);
083.             m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock);
084.             m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePin), def_ColorFilter, "::" + def_ButtonPin);
085.             PositionPinSlider(m_Slider.Minimal);
086.          }
087. //+------------------------------------------------------------------+
088. inline void RemoveCtrlSlider(void)
089.          {         
090.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
091.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
092.             {
093.                delete m_Section[c0].Btn;
094.                m_Section[c0].Btn = NULL;
095.             }
096.             ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName + "B");
097.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
098.          }
099. //+------------------------------------------------------------------+
100. inline void PositionPinSlider(int p)
101.          {
102.             int iL, iR;
103.             
104.             m_Section[ePin].x = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
105.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
106.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
107.             m_Section[ePin].x += def_PosXObjects;
108.              m_Section[ePin].x += 95 - (def_SizeButtons / 2);
109.              for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
110.                m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0)));
111.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
112.          }
113. //+------------------------------------------------------------------+
114. inline eObjectControl CheckPositionMouseClick(int &x, int &y)
115.          {
116.             C_Mouse::st_Mouse InfoMouse;
117.             
118.             InfoMouse = (*m_MousePtr).GetInfoMouse();
119.             x = InfoMouse.Position.X_Graphics;
120.             y = InfoMouse.Position.Y_Graphics;
121.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
122.             {   
123.                if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y))
124.                   return c0;
125.             }
126.             
127.             return eNull;
128.          }
129. //+------------------------------------------------------------------+
130.    public   :
131. //+------------------------------------------------------------------+
132.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
133.          :C_Terminal(Arg0),
134.           m_MousePtr(MousePtr)
135.          {
136.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
137.             if (_LastError != ERR_SUCCESS) return;
138.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
139.             ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName);
140.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
141.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
142.             {
143.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
144.                m_Section[c0].y = 25;
145.                m_Section[c0].Btn = NULL;
146.             }
147.             m_Section[ePlay].x = def_PosXObjects;
148.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
149.             m_Section[eRight].x = m_Section[ePlay].x + 511;
150.             m_Slider.Minimal = INT_MIN;
151.          }
152. //+------------------------------------------------------------------+
153.       ~C_Controls()
154.          {
155.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
156.             ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName);
157.             delete m_MousePtr;
158.          }
159. //+------------------------------------------------------------------+
160.       void SetBuff(const int rates_total, double &Buff[])
161.          {
162.             uCast_Double info;
163.             
164.             info._int[0] = m_Slider.Minimal;
165.             info._int[1] = (m_Section[ePlay].state ? INT_MAX : INT_MIN);
166.             Buff[rates_total - 1] = info.dValue;
167.          }
168. //+------------------------------------------------------------------+
169.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
170.          {
171.             int x, y;
172.             static int iPinPosX = -1, six = -1, sps;
173.             uCast_Double info;
174.             
175.             switch (id)
176.             {
177.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
178.                   info.dValue = dparam;
179.                   iPinPosX = m_Slider.Minimal = info._int[0];
180.                   if (info._int[1] == 0) SetUserError(C_Terminal::ERR_Unknown); else
181.                   {
182.                      SetPlay(info._int[1] == INT_MAX);
183.                      if (info._int[1] == INT_MIN) CreateCtrlSlider();
184.                   }
185.                   break;
186.                case CHARTEVENT_OBJECT_DELETE:
187.                   if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName)
188.                   {
189.                      if (sparam == (def_PrefixCtrlName + EnumToString(ePlay)))
190.                      {
191.                         delete m_Section[ePlay].Btn;
192.                         m_Section[ePlay].Btn = NULL;
193.                         SetPlay(m_Section[ePlay].state);
194.                      }else
195.                      {
196.                         RemoveCtrlSlider();
197.                         CreateCtrlSlider();
198.                      }
199.                   }
200.                   break;
201.                case CHARTEVENT_MOUSE_MOVE:
202.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
203.                   {
204.                      case ePlay:
205.                         SetPlay(!m_Section[ePlay].state);
206.                         if (m_Section[ePlay].state)
207.                         {
208.                            RemoveCtrlSlider();
209.                            m_Slider.Minimal = iPinPosX;
210.                         }else CreateCtrlSlider();
211.                         break;
212.                      case eLeft:
213.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
214.                         break;
215.                      case eRight:
216.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
217.                         break;
218.                      case ePin:
219.                         if (six == -1)
220.                         {
221.                            six = x;
222.                            sps = iPinPosX;
223.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
224.                         }
225.                         iPinPosX = sps + x - six;
226.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
227.                         break;
228.                   }else if (six > 0)
229.                   {
230.                      six = -1;
231.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
232.                   }
233.                   break;
234.             }
235.             ChartRedraw(GetInfoTerminal().ID);
236.          }
237. //+------------------------------------------------------------------+
238. };
239. //+------------------------------------------------------------------+
240. #undef def_PosXObjects
241. #undef def_ButtonPlay
242. #undef def_ButtonPause
243. #undef def_ButtonLeft
244. #undef def_ButtonRight
245. #undef def_ButtonPin
246. #undef def_PrefixCtrlName
247. #undef def_PathBMP
248. //+------------------------------------------------------------------+

Исходный код файла C_Control.mqh

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

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

Для этой упаковки мы используем объединение, объявленное в строке 162. Затем на строке 164 мы помещаем значение, которое будет настраиваться пользователем при манипуляции ползунком. Обратите внимание на следующее: мы будем сохранять положение ползунка, измененное пользователем. В строке 165 мы указываем статус индикатора управления, то есть, находится ли он в режиме воспроизведения (play) или же в режиме паузы (pausa).

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

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

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

Следующий элемент кода, действительно требующий объяснения, — это обработчик сообщений, который начинается со строки 169. В нем есть два момента, которые заслуживают внимания. Начнем с того, который связан с меньшим количеством деталей, но все же относится к тому, что было объяснено выше. Итак, переходим к строке 209.

В этой строке есть кое-что любопытное: мы сохраняем значение, настроенное пользователем при перемещении ползунка, в переменной m_Slider.Minimal. Причиной является упрощение процесса, позволяющее сосредоточить все в ключевых точках кода. Если бы этой строки 209 не существовало, нам пришлось бы выполнять проверку в каком-то месте кода, чтобы передать в буфер установленную пользователем позицию. Или, что еще хуже, пришлось бы найти какой-то способ напрямую передать сервису заданное пользователем значение без использования глобальных переменных терминала. Раньше для этого использовалась глобальная переменная, но теперь мы будем работать с буфером. Поэтому, чтобы избежать повторных проверок и корректировок, мы помещаем значение в легкодоступное место. Обратите внимание: это значение будет сохранено здесь только после того, как пользователь нажмет кнопку воспроизведения.

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

Это пользовательское событие будет запускаться время от времени, но обратите внимание: значение, содержащее данные, будет присутствовать в значении типа double и в упакованном виде. Поэтому нам необходимо перевести эту информацию, обеспечив при этом ее безопасность и надежность. Полученная информация определенным образом следует тому же принципу, что и информация, которую мы сохраняем в буфере. Однако здесь есть нюанс: мы проверяем ее целостность. 

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

Итак, если все в порядке, мы выполним еще два действия. Первое — вызов в строке 182 процедуры, отвечающей за отображение кнопки play или pausa. Второе — это тест, показанный в строке 183. Если значение минимальное, это означает, что мы находимся в режиме паузы, поэтому нам нужно пересоздать ползунок, чтобы пользователь мог что-то настраивать.

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

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

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.55"
07. #property link "https://www.mql5.com/pt/articles/11988"
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;
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)
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).SetBuff(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. //+------------------------------------------------------------------+

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

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

Поскольку мы будем использовать буфер, его нужно объявить. Мы делаем это сначала в строке 18, а затем в строке 31 объявляем буфер так, чтобы он был доступен вне индикатора. В строке 32 мы убеждаемся, что буфер полностью пуст. А вот на следующее обратите пристальное внимание, потому что это важно.

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

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

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

Индикатор же узнает, что делает сервис, через входные параметры, а именно объявленные в строке 16, и пользовательские события, которые обрабатываются в вызове OnChartEvent индикатора.

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

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

Когда индикатор размещается на графике, его буфер изначально будет установлен в ноль. В момент выполнения конструктора класса C_Controls, будет инициализировано специфическое значение в строке 150 (чтобы понять это, посмотрите код класса). Но это значение не будет записано в буфер до тех пор, пока не произойдет вызов OnChartEvent, и тогда, в строке 50 кода индикатора, буфер будет изменен.

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

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

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

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

01. //+------------------------------------------------------------------+
02. #property service
03. #property copyright "Daniel Jose"
04. #property description "Data synchronization demo service."
05. #property version   "1.00"
06. //+------------------------------------------------------------------+
07. #include <Market Replay\Defines.mqh>
08. //+------------------------------------------------------------------+
09. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
10. #resource "\\" + def_IndicatorControl
11. //+------------------------------------------------------------------+
12. input string user00 = "BOVA11";    //Symbol
13. //+------------------------------------------------------------------+
14. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != ""))
15. //+------------------------------------------------------------------+
16. void OnStart()
17. {
18.    uCast_Double info;
19.    long id;
20.    int handle, iPos, iMode;
21.    double Buff[];
22.    
23.    SymbolSelect(user00, true);
24.    id = ChartOpen(user00, PERIOD_H1);            
25.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "::" + def_IndicatorControl, id)) != INVALID_HANDLE)
26.       ChartIndicatorAdd(id, 0, handle);
27.    IndicatorRelease(handle);
28.    if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "\\Indicators\\Mouse Study.ex5", id)) != INVALID_HANDLE)
29.       ChartIndicatorAdd(id, 0, handle);
30.    IndicatorRelease(handle);   
31.    Print("Service maintaining sync state...");
32.    iPos = 0;
33.    iMode = INT_MIN;
34.    while (def_Loop)
35.    {
36.       while (def_Loop && ((handle = ChartIndicatorGet(id, 0, "Market Replay Control")) == INVALID_HANDLE)) Sleep(50);
37.       info.dValue = 0;
38.       if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) info.dValue = Buff[0];
39.       IndicatorRelease(handle);
40.       if (info._int[0] == INT_MIN)
41.       {
42.          info._int[0] = iPos;
43.          info._int[1] = iMode;
44.          EventChartCustom(id, evCtrlReplayInit, 0, info.dValue, "");
45.       }else if (info._int[1] != 0)
46.       {
47.          iPos = info._int[0];
48.          iMode = info._int[1];
49.       }
50.       Sleep(250);
51.    }
52.    ChartClose(id);
53.    Print("Finished service...");   
54. }
55. //+------------------------------------------------------------------+

Исходный код демонстрационного сервиса

Обратите внимание на строку 10, где мы преобразуем индикатор управления в ресурс сервиса. Таким образом, нет необходимости включать его в список индикаторов, поскольку он бесполезен для чего-либо другого, кроме внедряемой системы. В строке 12 мы сообщаем о символе, чтобы протестировать систему. Важно использовать действительный символ, так как его корректность в дальнейшем не будет проверяться. В строке 14 у нас есть определение, которое будет служить для проверки некоторых условий, чтобы иметь возможность надлежащим образом завершить работу сервиса.

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

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

В строке 31 мы сообщаем терминалу, что сервис будет активен и будет следить за тем, что происходит на графике.

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

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

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

Если чтение выполнено, в переменной info.dValue будет доступно некоторое ненулевое значение. В этот момент мы можем проверить состояние индикатора управления. В строке 40 мы проверяем, был ли он уже инициализирован. Так как это впервые, полученный ответ будет отрицательным. В строках 42 и 43 мы формируем значение для передачи индикатору, и отправляем запрос в MetaTrader 5 для генерации пользовательского события на графике. Это событие отображается в строке 44, где мы передаем сообщение индикатору управления для его инициализации.

В любой другой момент мы будем проверять, находится ли индикатор в статусе pausa или play. Если он находится в одном из этих состояний, то строка 45 позволит сохранить настроенные пользователем значения в индикаторе. Таким образом, при изменении таймфрейма пользователем, тест в строке 40 выдаст истинное значение, а значения, сохраненные в сервисе, будут снова переданы индикатору, что позволит ему корректно инициализироваться даже при изменении таймфрейма.

В конце мы закрываем график с помощью строки 52 и выводим сообщение о прекращении работы сервиса в строке 53.

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



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


Заключение

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

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Levison Da Silva Barbosa
Levison Da Silva Barbosa | 14 июл. 2024 в 13:45
Настоящий урок.
Анализ влияния погоды на валюты аграрных стран с использованием Python Анализ влияния погоды на валюты аграрных стран с использованием Python
Как связана погода и валютный рынок? В классической экономической теории долгое время не признавали влияние таких факторов на поведение рынка. Но все изменилось. Давайте попробуем найти связи в состоянии погоды и положения аграрных валют на рынке.
Нейросети в трейдинге: Модели направленной диффузии (DDM) Нейросети в трейдинге: Модели направленной диффузии (DDM)
Предлагаем познакомиться с моделями направленной диффузии, которые используют анизотропные и направленные шумы, зависящие от данных, в процессе прямой диффузии для захвата значимых графовых представлений.
Машинное обучение и Data Science (Часть 23): Почему LightGBM и XGBoost лучше многих ИИ-моделей? Машинное обучение и Data Science (Часть 23): Почему LightGBM и XGBoost лучше многих ИИ-моделей?
LightGBM и XGBoost — продвинутые методы построения деревьев решений с использованием градиентного бустинга, они обеспечивают превосходную производительность и гибкость, что делает их идеальными для финансового моделирования и алгоритмической торговли. В этой статье мы поговорим о том, как использовать эти инструменты для оптимизации торговых стратегий, повышения точности прогнозов и получения выгоды на финансовых рынках.
Возможности SQLite в MQL5: Пример интерактивной панели с торговой статистикой в разрезе символов и магиков Возможности SQLite в MQL5: Пример интерактивной панели с торговой статистикой в разрезе символов и магиков
В статье рассмотрим создание индикатора, отображающего на интерактивной панели статистику торговли по счёту и в разрезе символов и торговых стратегий. Код напишем, основываясь на примерах из Документации и статьи о работе с базами данных.