
Разработка системы репликации (Часть 61): Нажатие кнопки воспроизведения в сервисе (II)
Введение
В предыдущей статье "Разработка системы репликации (часть 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
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования