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

Разработка системы репликации (Часть 61): Нажатие кнопки воспроизведения в сервисе (II)

MetaTrader 5Примеры |
231 0
Daniel Jose
Daniel Jose

Введение

В предыдущей статье "Разработка системы репликации (часть 60): Нажатие кнопки воспроизведения в сервисе (I)", мы внесли некоторые изменения, чтобы система репликации/моделирования начала запускать новые данные на графике. Несмотря на то, что мы сделали необходимый минимум для этого, стало ясно, что произошло нечто странное. Система, которая, казалось бы, не претерпела существенных изменений, значительно снизила свою производительность. Создается впечатление, что система перестала быть жизнеспособной, так как внезапно стала очень медленной. Но так ли это на самом деле? И если да, - как мы можем решить эту проблему? Принципы объектно-ориентированного программирования (ООП) всегда сохраняются.

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


Внедряем самые очевидные и простые улучшения

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

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

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

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

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

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

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

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

Первые изменения мы внесем в заголовочный файл C_Controls.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_ObjectCtrlName(A)   "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
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 eMatrixControl {eCtrlPosition, eCtrlStatus};
038.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
039. //+------------------------------------------------------------------+
040.       struct st_00
041.       {
042.          string  szBarSlider,
043.                  szBarSliderBlock;
044.          ushort  Minimal;
045.       }m_Slider;
046.       struct st_01
047.       {
048.          C_DrawImage *Btn;
049.          bool        state;
050.          short       x, y, w, h;
051.       }m_Section[eObjectControl::eNull];
052.       C_Mouse   *m_MousePtr;
053. //+------------------------------------------------------------------+
054. inline void CreteBarSlider(short x, short size)
055.          {
056.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_ObjectCtrlName("B1"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
057.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x);
058.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11);
059.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size);
060.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9);
061.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue);
062.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack);
063.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3);
064.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT);
065.             ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_ObjectCtrlName("B2"), OBJ_RECTANGLE_LABEL, 0, 0, 0);
066.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x);
067.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6);
068.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19);
069.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown);
070.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED);
071.          }
072. //+------------------------------------------------------------------+
073.       void SetPlay(bool state)
074.          {
075.             if (m_Section[ePlay].Btn == NULL)
076.                m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
077.             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) ? 1 : 0));
078.             if (!state) CreateCtrlSlider();
079.          }
080. //+------------------------------------------------------------------+
081.       void CreateCtrlSlider(void)
082.          {
083.             CreteBarSlider(77, 436);
084.             m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock);
085.             m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock);
086.             m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin);
087.             PositionPinSlider(m_Slider.Minimal);
088.          }
089. //+------------------------------------------------------------------+
090. inline void RemoveCtrlSlider(void)
091.          {         
092.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
093.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
094.             {
095.                delete m_Section[c0].Btn;
096.                m_Section[c0].Btn = NULL;
097.             }
098.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
099.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
100.          }
101. //+------------------------------------------------------------------+
102. inline void PositionPinSlider(ushort p)
103.          {
104.             int iL, iR;
105.             
106.             m_Section[ePin].x = (short)(p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p));
107.             iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1);
108.             iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1);
109.             m_Section[ePin].x += def_PosXObjects;
110.             m_Section[ePin].x += 95 - (def_SizeButtons / 2);
111.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
112.                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)));
113.             ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2);
114.          }
115. //+------------------------------------------------------------------+
116. inline eObjectControl CheckPositionMouseClick(short &x, short &y)
117.          {
118.             C_Mouse::st_Mouse InfoMouse;
119.             
120.             InfoMouse = (*m_MousePtr).GetInfoMouse();
121.             x = (short) InfoMouse.Position.X_Graphics;
122.             y = (short) InfoMouse.Position.Y_Graphics;
123.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
124.             {   
125.                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))
126.                   return c0;
127.             }
128.             
129.             return eNull;
130.          }
131. //+------------------------------------------------------------------+
132.    public   :
133. //+------------------------------------------------------------------+
134.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
135.          :C_Terminal(Arg0),
136.           m_MousePtr(MousePtr)
137.          {
138.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
139.             if (_LastError != ERR_SUCCESS) return;
140.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
141.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
142.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
143.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
144.             {
145.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
146.                m_Section[c0].y = 25;
147.                m_Section[c0].Btn = NULL;
148.             }
149.             m_Section[ePlay].x = def_PosXObjects;
150.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
151.             m_Section[eRight].x = m_Section[ePlay].x + 511;
152.             m_Slider.Minimal = eTriState;
153.          }
154. //+------------------------------------------------------------------+
155.       ~C_Controls()
156.          {
157.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
158.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
159.             delete m_MousePtr;
160.          }
161. //+------------------------------------------------------------------+
162.       void SetBuffer(const int rates_total, double &Buff[])
163.          {
164.             uCast_Double info;
165.             
166.             info._16b[eCtrlPosition] = m_Slider.Minimal;
167.             info._16b[eCtrlStatus] = (ushort)(m_Slider.Minimal > def_MaxPosSlider ? m_Slider.Minimal : (m_Section[ePlay].state ? ePlay : ePause));//SHORT_MAX : SHORT_MIN);
168.             if (rates_total > 0)
169.                Buff[rates_total - 1] = info.dValue;
170.          }
171. //+------------------------------------------------------------------+
172.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
173.          {
174.             short x, y;
175.             static ushort iPinPosX = 0;
176.             static short six = -1, sps;
177.             uCast_Double info;
178.             
179.             switch (id)
180.             {
181.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
182.                   info.dValue = dparam;
183.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
184.                   x = (short) info._16b[eCtrlPosition];
185.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
186.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
187.                   break;
188.                case CHARTEVENT_OBJECT_DELETE:
189.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
190.                   {
191.                      if (sparam == def_ObjectCtrlName(ePlay))
192.                      {
193.                         delete m_Section[ePlay].Btn;
194.                         m_Section[ePlay].Btn = NULL;
195.                         SetPlay(m_Section[ePlay].state);
196.                      }else
197.                      {
198.                         RemoveCtrlSlider();
199.                         CreateCtrlSlider();
200.                      }
201.                   }
202.                   break;
203.                case CHARTEVENT_MOUSE_MOVE:
204.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
205.                   {
206.                      case ePlay:
207.                         SetPlay(!m_Section[ePlay].state);
208.                         if (m_Section[ePlay].state)
209.                         {
210.                            RemoveCtrlSlider();
211.                            m_Slider.Minimal = iPinPosX;
212.                         }else CreateCtrlSlider();
213.                         break;
214.                      case eLeft:
215.                         PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal));
216.                         break;
217.                      case eRight:
218.                         PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider));
219.                         break;
220.                      case ePin:
221.                         if (six == -1)
222.                         {
223.                            six = x;
224.                            sps = (short)iPinPosX;
225.                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
226.                         }
227.                         iPinPosX = sps + x - six;
228.                         PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX)));
229.                         break;
230.                   }else if (six > 0)
231.                   {
232.                      six = -1;
233.                      ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                     
234.                   }
235.                   break;
236.             }
237.             ChartRedraw(GetInfoTerminal().ID);
238.          }
239. //+------------------------------------------------------------------+
240. };
241. //+------------------------------------------------------------------+
242. #undef def_PosXObjects
243. #undef def_ButtonPlay
244. #undef def_ButtonPause
245. #undef def_ButtonLeft
246. #undef def_ButtonRight
247. #undef def_ButtonPin
248. #undef def_PathBMP
249. //+------------------------------------------------------------------+

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

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

Теперь обратите внимание на что-то важное: в строках 37 и 38 есть два перечисления. Строка 37 ранее не существовала, но мы ее создали для облегчения доступа к данным в буфере. Можно увидеть, как это делается в процедуре SetBuffer в строке 162. Аналогичная настройка была произведена и в процедуре обработки сообщений, хотя в данном случае реализация несколько отличается. Это можно увидеть между строками 182 и 186. Однако обратите внимание на строку 184, так как мы ее удалили из оригинального кода.

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

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

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


Получаем быстрый доступ к состоянию кнопки

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

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

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

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

Но если проверить код в заголовочном файле C_DrawImage.mqh, то мы не обнаружим модификации нужной переменной в объекте OBJ_BITMAP_LABEL. Это происходит еще до того, как мы попытаемся что-либо сделать в сервисе. Но если проверить код в файле C_Controls.mqh, то можно увидеть, что в строке 77 выполняется запрос на обновление объекта. С данного момента мы можем внести необходимые изменения, чтобы сервис смог извлечь из них пользу. Теоретически, экономия составит несколько машинных циклов на вызов.

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

174. //+------------------------------------------------------------------+
175.       void Paint(const int x, const int y, const int w, const int h, const uchar cView, const int what)
176.          {
177.             
178.             if ((m_szRecName == NULL) || (what < 0) || (what >= def_MaxImages)) return;
179.             ReSizeImage(w, h, cView, what);
180.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_XDISTANCE, x);
181.             ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_YDISTANCE, y);
182.             if (ResourceCreate(m_szRecName, m_Pixels, w, h, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
183.             {
184.                ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, m_szRecName);
185.                ObjectSetString(GetInfoTerminal().ID, m_szObjName, OBJPROP_BMPFILE, what, m_szRecName);
186.                ObjectSetInteger(GetInfoTerminal().ID, m_szObjName, OBJPROP_STATE, what == 1);
187.                ChartRedraw(GetInfoTerminal().ID);
188.             }
189.          }
190. //+------------------------------------------------------------------+

Фрагмент исходного кода C_DrawImage.mqh

Обратите внимание: строка 184 была заменена строкой 185, так как она содержит параметр, указывающий индекс изображения. Однако нас действительно интересует строка 186, в которой обновляется состояние объектной переменной OBJ_BITMAP_LABEL. Теперь переменная OBJPROP_STATE объекта будет напрямую отражать его состояние. Напомним, что есть только два возможных состояния: воспроизведение или пауза.

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

Чтобы сервис понял, что происходит, ему сначала нужно добавить что-то новое. Первое изменение находится чуть ниже:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_ConfigService.mqh"
05. #include "C_Controls.mqh"
06. //+------------------------------------------------------------------+
07. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
08. #resource "\\" + def_IndicatorControl
09. //+------------------------------------------------------------------+
10. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
11. //+------------------------------------------------------------------+
12. #define def_ShortNameIndControl "Market Replay Control"
13. //+------------------------------------------------------------------+
14. class C_Replay : public C_ConfigService
15. {
16.    private   :
17.       struct st00
18.       {
19.          C_Controls::eObjectControl Mode;
20.          uCast_Double               Memory;
21.          ushort                     Position;
22.          int                        Handle;
23.       }m_IndControl;

Фрагмент исходного кода C_Replay.mqh

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

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

131. //+------------------------------------------------------------------+
132.       C_Replay()
133.          :C_ConfigService()
134.          {
135.             Print("************** Market Replay Service **************");
136.             srand(GetTickCount());
137.             SymbolSelect(def_SymbolReplay, false);
138.             CustomSymbolDelete(def_SymbolReplay);
139.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
140.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
141.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
142.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
143.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
144.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
145.             SymbolSelect(def_SymbolReplay, true);
146.             m_Infos.CountReplay = 0;
147.             m_IndControl.Handle = INVALID_HANDLE;
148.             m_IndControl.Mode = C_Controls::ePause;
149.             m_IndControl.Position = 0;
150.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
151.          }
152. //+------------------------------------------------------------------+

Фрагмент исходного кода C_Replay.mqh

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

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

34. //+------------------------------------------------------------------+
35. inline void UpdateIndicatorControl(void)
36.          {
37.             static bool bTest = false;
38.             double Buff[];
39.                                  
40.             if (m_IndControl.Handle == INVALID_HANDLE) return;
41.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
42.             {
43.                if (bTest)
44.                   m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1  ? C_Controls::ePause : C_Controls::ePlay);
45.                else
46.                {
47.                   if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
48.                      m_IndControl.Memory.dValue = Buff[0];
49.                   if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
50.                      if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay))
51.                         m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
52.                }
53.             }else
54.             {
55.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
56.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
57.                m_IndControl.Memory._8b[7] = 'D';
58.                m_IndControl.Memory._8b[6] = 'M';
59.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
60.                bTest = false;
61.             }
62.          }
63. //+------------------------------------------------------------------+

Фрагмент исходного кода C_Replay.mqh

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

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

1) значения инициализируются в конструкторе; 2) первая подпрограмма, вызывающая эту функцию, инициализирует модуль индикатора управления; 3) периодически процедура цикла проверяет состояние кнопки в индикаторе управления.

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

Давайте посмотрим, как работает данный фрагмент. Сначала, в строке 40, мы проверяем, есть ли у нас действующий хэндл. Если всё верно, мы продолжаем. Затем, в строке 41, мы проверяем, совпадает ли значение в памяти с текущей позицией. Если ответ - да, то в строке 43 мы оцениваем, равна ли статическая переменная true. В этом случае мы используем функцию для доступа к значению, которое находится в OBJ_BITMAP_LABEL. Обратите внимание на то, как осуществляется доступ, вам он может показаться необычным, ведь мы обращаемся к тому, что определено в заголовочном файле C_Controls.mqh. Но доступ мы реализовали правильно.

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

Однако после того, как буфер будет прочитан, строка 49 проверит, не находимся ли мы в TriState. Если данное условие выполнено, мы осуществим набор операций в строке 50, прежде чем проверить, находимся ли мы в режиме воспроизведения, что определит, будет ли у статической переменной значение true или false. Данный набор операций, которые на самом деле являются присваиваниями, добавлен таким образом, что команда кажется намного сложнее, чем она есть на самом деле. Однако, поскольку это не влияет на компилятор и значения присваиваются, как и ожидалось, мы можем сделать это таким образом: если строка 50 true, мы сохраняем значение буфера в переменной внутреннего положения. Это происходит в строке 51.

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

С другой стороны, если проверка в строке 41 окажется false, выполним то, что определено между строками 55 и 60. Это вызовет пользовательское событие, чтобы мы могли обновить модуль индикатора управления. И именно так будет работать система в дальнейшем.

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


Существенное повышение производительности

Несмотря на всё вышесказанное, данные модификации не приводят к значительному увеличению производительности сервиса репликации/моделирования. По крайней мере, капитальных изменений не наблюдается. Однако данные изменения делают некоторые части кода более эффективными и, самое главное, более читабельными. Последнее связано с определениями, созданными в файле C_Controls.mqh, которые затем использовались в заголовочном файле C_Replay.mqh. Даже если вы не видели весь код, вероятно, у вас уже имеется представление о том, как изменить некоторые фрагменты, чтобы улучшить читаемость класса C_Replay.

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

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

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_ConfigService.mqh"
005. #include "C_Controls.mqh"
006. //+------------------------------------------------------------------+
007. #define def_IndicatorControl   "Indicators\\Market Replay.ex5"
008. #resource "\\" + def_IndicatorControl
009. //+------------------------------------------------------------------+
010. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_Infos.IdReplay) != ""))
011. //+------------------------------------------------------------------+
012. #define def_ShortNameIndControl "Market Replay Control"
013. //+------------------------------------------------------------------+
014. class C_Replay : public C_ConfigService
015. {
016.    private   :
017.       struct st00
018.       {
019.          C_Controls::eObjectControl Mode;
020.            uCast_Double             Memory;
021.          ushort                     Position;
022.          int                        Handle;
023.       }m_IndControl;
024.       struct st01
025.       {
026.          long     IdReplay;
027.          int      CountReplay;
028.          double   PointsPerTick;
029.          MqlTick  tick[1];
030.          MqlRates Rate[1];
031.       }m_Infos;
032.       stInfoTicks m_MemoryData;
033. //+------------------------------------------------------------------+
034. inline bool MsgError(string sz0) { Print(sz0); return false; }
035. //+------------------------------------------------------------------+
036. inline void UpdateIndicatorControl(void)
037.          {
038.             static bool bTest = false;
039.             double Buff[];
040.                                  
041.             if (m_IndControl.Handle == INVALID_HANDLE) return;
042.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
043.             {
044.                if (bTest)
045.                   m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1  ? C_Controls::ePause : C_Controls::ePlay);
046.                else
047.                {
048.                   if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
049.                      m_IndControl.Memory.dValue = Buff[0];
050.                   if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
051.                      if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay))
052.                         m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
053.                }
054.             }else
055.             {
056.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
057.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
058.                m_IndControl.Memory._8b[7] = 'D';
059.                m_IndControl.Memory._8b[6] = 'M';
060.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
061.                bTest = false;
062.             }
063.          }
064. //+------------------------------------------------------------------+
065.       void SweepAndCloseChart(void)
066.          {
067.             long id;
068.             
069.             if ((id = ChartFirst()) > 0) do
070.             {
071.                if (ChartSymbol(id) == def_SymbolReplay)
072.                   ChartClose(id);
073.             }while ((id = ChartNext(id)) > 0);
074.          }
075. //+------------------------------------------------------------------+
076. inline void CreateBarInReplay(bool bViewTick)
077.          {
078.             bool    bNew;
079.             double dSpread;
080.             int    iRand = rand();
081. 
082.             if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew))
083.             {
084.                m_Infos.tick[0] = m_MemoryData.Info[m_Infos.CountReplay];
085.                if (m_MemoryData.ModePlot == PRICE_EXCHANGE)
086.                {                  
087.                   dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 );
088.                   if (m_Infos.tick[0].last > m_Infos.tick[0].ask)
089.                   {
090.                      m_Infos.tick[0].ask = m_Infos.tick[0].last;
091.                      m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread;
092.                   }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid)
093.                   {
094.                      m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread;
095.                      m_Infos.tick[0].bid = m_Infos.tick[0].last;
096.                   }
097.                }
098.                if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
099.                CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate);
100.             }
101.             m_Infos.CountReplay++;
102.          }
103. //+------------------------------------------------------------------+
104.       void AdjustViewDetails(void)
105.          {
106.             MqlRates rate[1];
107. 
108.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
109.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX);
110.             ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE);
111.             m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE);
112.             CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate);
113.             if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE))
114.                for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++);
115.             if (rate[0].close > 0)
116.             {
117.                if (GetInfoTicks().ModePlot == PRICE_EXCHANGE)
118.                   m_Infos.tick[0].last = rate[0].close;
119.                else
120.                {
121.                   m_Infos.tick[0].bid = rate[0].close;
122.                   m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick);
123.                }               
124.                m_Infos.tick[0].time = rate[0].time;
125.                m_Infos.tick[0].time_msc = rate[0].time * 1000;
126.             }else
127.                m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay];
128.             CustomTicksAdd(def_SymbolReplay, m_Infos.tick);
129.          }
130. //+------------------------------------------------------------------+
131.    public   :
132. //+------------------------------------------------------------------+
133.       C_Replay()
134.          :C_ConfigService()
135.          {
136.             Print("************** Market Replay Service **************");
137.             srand(GetTickCount());
138.             SymbolSelect(def_SymbolReplay, false);
139.             CustomSymbolDelete(def_SymbolReplay);
140.             CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay));
141.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0);
142.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0);
143.             CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0);
144.             CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation");
145.             CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8);
146.             SymbolSelect(def_SymbolReplay, true);
147.             m_Infos.CountReplay = 0;
148.             m_IndControl.Handle = INVALID_HANDLE;
149.             m_IndControl.Mode = C_Controls::ePause;
150.             m_IndControl.Position = 0;
151.             m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = C_Controls::eTriState;
152.          }
153. //+------------------------------------------------------------------+
154.       ~C_Replay()
155.          {
156.             IndicatorRelease(m_IndControl.Handle);
157.             SweepAndCloseChart();
158.             SymbolSelect(def_SymbolReplay, false);
159.             CustomSymbolDelete(def_SymbolReplay);
160.             Print("Finished replay service...");
161.          }
162. //+------------------------------------------------------------------+
163.       bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate)
164.          {
165.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0)
166.                return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket.");
167.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0)
168.                return MsgError("Asset configuration is not complete, need to declare the ticket value.");
169.             if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0)
170.                return MsgError("Asset configuration not complete, need to declare the minimum volume.");
171.             SweepAndCloseChart();
172.             m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1);
173.             if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl"))
174.                Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl");
175.             else
176.                Print("Apply template: ", szNameTemplate, ".tpl");
177. 
178.             return true;
179.          }
180. //+------------------------------------------------------------------+
181.       bool InitBaseControl(const ushort wait = 1000)
182.          {
183.             Print("Waiting for Mouse Indicator...");
184.             Sleep(wait);
185.             while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200);
186.             if (def_CheckLoopService)
187.             {
188.                AdjustViewDetails();
189.                Print("Waiting for Control Indicator...");
190.                if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false;
191.                ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle);
192.                UpdateIndicatorControl();
193.             }
194.             
195.             return def_CheckLoopService;
196.          }
197. //+------------------------------------------------------------------+
198.       bool LoopEventOnTime(void)
199.          {         
200.             int iPos;
201. 
202.             while ((def_CheckLoopService) && (m_IndControl.Mode != C_Controls::ePlay))
203.             {
204.                UpdateIndicatorControl();
205.                Sleep(200);
206.             }
207.             m_MemoryData = GetInfoTicks();
208.             iPos = 0;
209.             while ((m_Infos.CountReplay < m_MemoryData.nTicks) && (def_CheckLoopService))
210.             {
211.                if (m_IndControl.Mode == C_Controls::ePause) return true;
212.                iPos += (int)(m_Infos.CountReplay < (m_MemoryData.nTicks - 1) ? m_MemoryData.Info[m_Infos.CountReplay + 1].time_msc - m_MemoryData.Info[m_Infos.CountReplay].time_msc : 0);
213.                CreateBarInReplay(true);
214.                while ((iPos > 200) && (def_CheckLoopService))
215.                {
216.                   Sleep(195);
217.                   iPos -= 200;
218.                   m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxPosSlider) / m_MemoryData.nTicks);
219.                   UpdateIndicatorControl();
220.                }
221.             }
222. 
223.             return ((m_Infos.CountReplay == m_MemoryData.nTicks) && (def_CheckLoopService));
224.          }
225. //+------------------------------------------------------------------+
226. };
227. //+------------------------------------------------------------------+
228. #undef macroRemoveSec
229. #undef def_SymbolReplay
230. #undef def_CheckLoopService
231. //+------------------------------------------------------------------+

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

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

В строке 32 мы определяем новую приватную переменную класса C_Replay. Однако используемая структура определена в заголовочном файле C_FileTicks.mqh. Подробнее об этом можно прочитать в предыдущей статье. Данная переменная используется в нескольких местах, которые считаются критическими внутри класса C_Replay. Мы выбрали эти точки из-за частоты, с которой они вызываются во время выполнения сервиса. Одна из них - процедура CreateBarInReplay, которая используется для преобразования тиков в бары на графике. Еще один ключевой момент - функция LoopEventOnTime. В строке 207 мы инициализируем эту же переменную.

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

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

Использование указателей для достижения этой цели лучше всего реализовать с помощью функций. Такой подход был продемонстрирован в рассмотренной в предыдущей статье версии. Однако при запуске сервисов репликации/моделирования (особенно в режиме моделирования, когда бары преобразуются в тики, а затем обратно в бары), я наблюдал значительное падение производительности сервиса. Это может быть связано с тем, что на момент написания этой статьи компилятор MQL5 не распознает данную функциональную конструкцию как указатель, позволяющий ограничить доступ к заданной структуре. В данном случае базовый класс C_FileTicks сможет получить доступ к структуре с правами чтения и записи, а у любого другого кода за пределами класса будет доступ только для чтения. Возможно, к тому времени, когда вы прочтете эту статью, разработчики компилятора MQL5 уже исправят данную проблему. Это позволит достичь той же производительности, что и при использовании описанной здесь схемы. Важная рекомендация. Поскольку данная статья была написана некоторое время назад, я предлагаю вам протестировать код перед его внедрением. Но, возможно, больше не будет необходимости делать всё то, что показано здесь.

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

  • Нарушать инкапсуляцию, установленную в предыдущей статье.
  • Реализовать решение, продублировав данные. 

Я решил дублировать данные, поскольку память - дешевый ресурс. Нарушение инкапсуляции усложнило бы разработку и совершенствование кода. Благодаря такому подходу мы добиваемся увеличения скорости выполнения примерно в 4-5 раз, сохраняя при этом тот же уровень безопасности данных. В ходе тестов, проведенных перед внедрением репликации данных, удалось обработать около 3 секунд тиков на примере доллара. Эти результаты можно найти в статьях первого этапа. Теперь, благодаря дублированию, мы можем обрабатывать почти 15 секунд данных в одном и том же контексте. Следует отметить, что данные результаты относятся к моделированию. В случае репликации система теперь строит 1-минутный бар за приемлемое время, т.е. близко к 60 секундам.

Это важная деталь. Можно подумать, что функция Sleep в строке 216 может замедлять работу системы и что ее удаление позволит нам обрабатывать больше данных на графике. Но при работе с реальными тиками функция Sleep в строке 216 незаменима. Если бы она отсутствовала, данные выводились бы на график гораздо быстрее, чем обычно. И это приведет к тому, что воспроизведение, а не моделирование, создаст минутный бар до истечения 60 секунд.


Заключение

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

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

Прикрепленные файлы |
Anexo.zip (420.65 KB)
Переосмысливаем классические стратегии (Часть III): Прогнозирование более высоких максимумов и более низких минимумов Переосмысливаем классические стратегии (Часть III): Прогнозирование более высоких максимумов и более низких минимумов
В статье мы эмпирически проанализируем классические торговые стратегии, чтобы увидеть, можно ли улучшить их с помощью искусственного интеллекта (ИИ). Мы попытаемся предсказать более высокие максимумы и более низкие минимумы, используя модель линейного дискриминантного анализа (Linear Discriminant Analysis).
Собственные векторы и собственные значения: Разведочный анализ данных в MetaTrader 5 Собственные векторы и собственные значения: Разведочный анализ данных в MetaTrader 5
В статье мы рассмотрим различные способы применения собственных векторов и собственных значений в разведочном анализе данных для выявления в них уникальных взаимосвязей.
От начального до среднего уровня: Оператор IF ELSE От начального до среднего уровня: Оператор IF ELSE
В этой статье мы проанализируем, как работать с оператором IF и ее спутником ELSE, Данный оператор - самый важный и значимый из существующих в любом языке программирования. Однако, несмотря на простоту использования, он иногда приводит в замешательство, если у нас нет опыта его применения и связанных с ней понятий. Представленные здесь материалы предназначены только для обучения. Ни в коем случае не рассматривайте его как окончательное приложение, целью которого не является изучение представленных понятий.
Алгоритм эволюционного путешествия во времени — Time Evolution Travel Algorithm (TETA) Алгоритм эволюционного путешествия во времени — Time Evolution Travel Algorithm (TETA)
Мой авторский алгоритм. В этой статье представлен Алгоритм Эволюционного Путешествия во Времени (TETA), вдохновлённый концепцией параллельных вселенных и потоков времени. Основная идея алгоритма заключается в том, что, хотя путешествие во времени в привычном понимании невозможно, мы можем выбирать последовательность событий, которые приводят к различным реальностям.