
Разработка системы репликации (Часть 56): Адаптация модулей
Введение
В предыдущей статье Разработка системы репликации (Часть 55): Модуль управления мы реализовали некоторые изменения, которые позволили нам создать индикатор управления таким образом, чтобы использование глобальных переменных терминала стало необязательным. По крайней мере в части хранения информации и настроек, регулируемых пользователем.
Хотя все работало прекрасно и было достаточно стабильным, когда система размещалась на графике с определенным количеством баров, она продолжала давать сбои из-за превышения предела диапазона при использовании данных, относящихся к пользовательскому символу.
И причина вовсе не в том, что пользовательский символ на самом деле неисправен или вызывает проблемы. Это связано с самой базой данных, которая зачастую оказывается недостаточной для корректного функционирования всей системы. И основным проблемным фактором является буфер.
Возможно, сейчас вы недоумеваете: «Как это возможно, что буфер является проблемой? Система работает, когда мы используем реальный символ, но когда мы используем пользовательский символ, она дает сбой, и причина этого сбоя — буфер индикатора?!»
Да, причина в буфере. Но не потому, что вы, возможно, себе представляете. А потому, что когда мы создаем пользовательский символ, возможно, буфер не имеет минимального размера, необходимого для хранения данных, которые нам нужно в него поместить.
Увидев это, можно подумать, что достаточно выделить больше памяти, и проблема решится. Однако, когда дело касается MQL5, все не так просто. Выделение памяти для данных в буфере индикатора происходит не так, как вы, вероятно, себе представляете. Распределение памяти зависит от количества баров, присутствующих на графике. Поэтому нет смысла использовать какую-либо функцию для выделения памяти, поскольку на самом деле она не будет использоваться так, как вы себе представляете или желаете.
Суть проблемы
Настоящая проблема заключена не в индикаторе управления, а в индикаторе мыши. И в процессе исправления этой аномалии, мы создадим решение, которое также затронет и индикатор управления, хотя изменения станут видны позже в этой же статье. Но сначала давайте разберемся в природе возникающего сбоя и в том, как он происходит.
Если вы воспользуетесь индикатором мыши, представленным в предыдущих статьях, и разместите его на графике пользовательского символа, имеющего, например, 60 одноминутных баров, у вас не возникнет проблем на таймфреймах, равных или меньших 10 минут. Однако, если вы попытаетесь использовать таймфрейм более 10 минут, вы получите сообщение от MetaTrader 5: «Индикатор мыши: ошибка диапазона».
Но почему так происходит? Причина в том, что индикатору мыши, представленному в предыдущей статье, требуется 6 позиций для хранения данных в буфере индикатора. Поэтому на вопрос, что же произошло на самом деле, нам ответит математика. С 60 барами в одну минуту вы можете изменить таймфрейм до 10 минут, что даст нам 6 баров на графике. Эти шесть баров обеспечат необходимые шесть позиций в буфере для размещения данных. Однако, когда мы размещаем более длительный таймфрейм, количество баров на графике будет меньше шести.
В этот момент индикатор мыши выдаст ошибку диапазона, так как он попытается записать данные в позицию памяти, которую MetaTrader 5 не выделил для записи в буфер.
Вот в чем заключается ошибка, и есть два способа ее исправления. Первый — разместить на пользовательском символе достаточное количество баров, чтобы на любом таймфрейме на графике их было не менее шести. Это в каком-то смысле не самое подходящее решение, так как чтобы получить доступ к месячному таймфрейму, нам нужно было бы загрузить хотя бы шесть месяцев минутных баров на график пользовательского символа, чтобы не пришлось модифицировать индикатор мыши. Это нужно только для того, чтобы предотвратить возникновение ошибки диапазона.
Лично я думаю, и уверен, что многие с этим согласятся, что это далеко не самое лучшее решение, особенно когда речь идет о системе репликации/моделирования. Если бы система была ориентирована исключительно на репликацию, возможно, это решение могло бы работать, при условии, что исследования проводились бы только на одном таймфрейме или на более низких таймфреймах. Но поскольку мы можем использовать систему для моделирования движений рынка, это решение совершенно неприемлемо, и необходимо что-то более элегантное.
И именно это мы и сделаем. Модифицируем индикатор мыши таким образом, что информация будет компактно умещаться в одной позиции внутри буфера. Таким образом, нам понадобится всего один бар на графике, чтобы индикатор мог выполнять свою функцию.
Начало реализации решения
Решение о компактном размещении информации в одной позиции обусловлено тем, что системе репликации/моделирования будет значительно проще добавлять и поддерживать хотя бы один бар на графике, чем делать что-либо еще.
Однако основная причина заключается в том, что мы можем захотеть создать симуляцию рынка, и при этом нам не потребуется использовать огромное количество баров. Мы сможем использовать только то количество, которое нам нужно и хочется, а сервис репликации/моделирования сам обеспечит системе минимально необходимую стабильность. Это позволит нам использовать те же средства, как на реальном счете, так и на демонстрационном.
Подводя итог: мы сделаем буфер индикатора мыши гораздо меньше, там будет использоваться только одна позиция, но при этом мы получим тот же объем информации, которая будет нам возвращена после запроса к индикатору для чтения буфера. Один момент, который стоит подчеркнуть здесь и сейчас: если вы читаете непосредственно из буфера индикатора мыши, вы получите данные только в одной позиции этого буфера. Эти данные необходимо будет перевести, чтобы они могли быть действительно полезны, но с помощью класса C_Mouse мы можем использовать функцию, которая будет доступна, чтобы этот перевод выполнялся правильно.
С учетом вышесказанного, мы переходим к фактическому этапу реализации. И первое, что необходимо изменить, — это заголовочный файл. Его код приводится ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. //+------------------------------------------------------------------+ 16. union uCast_Double 17. { 18. double dValue; 19. long _long; // 1 Information 20. datetime _datetime; // 1 Information 21. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 22. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 23. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 24. }; 25. //+------------------------------------------------------------------+ 26. enum EnumEvents { 27. evHideMouse, //Hide mouse price line 28. evShowMouse, //Show mouse price line 29. evHideBarTime, //Hide bar time 30. evShowBarTime, //Show bar time 31. evHideDailyVar, //Hide daily variation 32. evShowDailyVar, //Show daily variation 33. evHidePriceVar, //Hide instantaneous variation 34. evShowPriceVar, //Show instantaneous variation 35. evSetServerTime, //Replay/simulation system timer 36. evCtrlReplayInit //Initialize replay control 37. }; 38. //+------------------------------------------------------------------+
Исходный код файла Defines.mqh
По большому счету, больших различий здесь практически нет. Но если присмотреться, то можно увидеть, что между строками 21 и 23 произошли некоторые изменения. Эти изменения направлены на более эффективное использование битов. Итак, чтобы было проще определить тип информации, с которой мы имеем дело, я использовал простую запись, где _32b означает 32 бита, _16b означает 16 бит, а _8b означает 8 бит. Таким образом, когда осуществляется тот или иной тип доступа, мы будем точно знать, какое количество битов мы на самом деле будем использовать. Учитывая, что значение double представляет 64 бита, каждый из пакетов будет иметь совместимую длину, соответствующую этим 64 битам.
Таким образом, _32b может содержать 2 значения, _16b — 4 значения, а _8b — 8 значений. Но обратите внимание на то, что мы используем объединение, поэтому можем комбинировать эти наборы. Однако, чтобы сделать это правильно, вы должны понимать, что весь массив в MQL5 основан на системе счисления, используемой в C/C++, то есть, мы всегда начинаем с нуля, и каждая новая позиция всегда увеличивается на одну единицу.
На этом этапе многие могут начать путаться, если у них нет хотя бы базовых знаний о том, как все работает в C/C++, поскольку термин «единица» может не так выразительно описывать то, что нам на самом деле придется использовать при увеличении значения индекса, чтобы правильно разместить данные в пакете. Непонимание этой концепции может привести к тому, что вы полностью потеряетесь или, по крайней мере, не поймете, как на самом деле сжимается информация.
Прежде чем мы приступим к рассмотрению изменений, внесенных в индикатор мыши, мы быстро пройдемся по индикатору управления. Причина в том, что в индикаторе управления нужно было лишь адаптировать функции, процедуры и переменные к новым типам, которые присутствуют в заголовочном файле Defines.mqh. Давайте посмотрим на код класса, показанный ниже.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\C_DrawImage.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_PathBMP "Images\\Market Replay\\Control\\" 008. #define def_ButtonPlay def_PathBMP + "Play.bmp" 009. #define def_ButtonPause def_PathBMP + "Pause.bmp" 010. #define def_ButtonLeft def_PathBMP + "Left.bmp" 011. #define def_ButtonLeftBlock def_PathBMP + "Left_Block.bmp" 012. #define def_ButtonRight def_PathBMP + "Right.bmp" 013. #define def_ButtonRightBlock def_PathBMP + "Right_Block.bmp" 014. #define def_ButtonPin def_PathBMP + "Pin.bmp" 015. #resource "\\" + def_ButtonPlay 016. #resource "\\" + def_ButtonPause 017. #resource "\\" + def_ButtonLeft 018. #resource "\\" + def_ButtonLeftBlock 019. #resource "\\" + def_ButtonRight 020. #resource "\\" + def_ButtonRightBlock 021. #resource "\\" + def_ButtonPin 022. //+------------------------------------------------------------------+ 023. #define def_PrefixCtrlName "MarketReplayCTRL_" 024. #define def_PosXObjects 120 025. //+------------------------------------------------------------------+ 026. #define def_SizeButtons 32 027. #define def_ColorFilter 0xFF00FF 028. //+------------------------------------------------------------------+ 029. #include "..\Auxiliar\C_Terminal.mqh" 030. #include "..\Auxiliar\C_Mouse.mqh" 031. //+------------------------------------------------------------------+ 032. class C_Controls : private C_Terminal 033. { 034. protected: 035. private : 036. //+------------------------------------------------------------------+ 037. enum eObjectControl {ePlay, eLeft, eRight, ePin, eNull}; 038. //+------------------------------------------------------------------+ 039. struct st_00 040. { 041. string szBarSlider, 042. szBarSliderBlock; 043. short Minimal; 044. }m_Slider; 045. struct st_01 046. { 047. C_DrawImage *Btn; 048. bool state; 049. short x, y, w, h; 050. }m_Section[eObjectControl::eNull]; 051. C_Mouse *m_MousePtr; 052. //+------------------------------------------------------------------+ 053. inline void CreteBarSlider(short x, short size) 054. { 055. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_PrefixCtrlName + "B1", OBJ_RECTANGLE_LABEL, 0, 0, 0); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 064. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_PrefixCtrlName + "B2", OBJ_RECTANGLE_LABEL, 0, 0, 0); 065. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 066. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 067. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 068. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 069. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 070. } 071. //+------------------------------------------------------------------+ 072. void SetPlay(bool state) 073. { 074. if (m_Section[ePlay].Btn == NULL) 075. m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause); 076. m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 0 : 1)); 077. } 078. //+------------------------------------------------------------------+ 079. void CreateCtrlSlider(void) 080. { 081. CreteBarSlider(77, 436); 082. m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock); 083. m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock); 084. m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePin), def_ColorFilter, "::" + def_ButtonPin); 085. PositionPinSlider(m_Slider.Minimal); 086. } 087. //+------------------------------------------------------------------+ 088. inline void RemoveCtrlSlider(void) 089. { 090. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 091. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 092. { 093. delete m_Section[c0].Btn; 094. m_Section[c0].Btn = NULL; 095. } 096. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName + "B"); 097. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 098. } 099. //+------------------------------------------------------------------+ 100. inline void PositionPinSlider(short p) 101. { 102. int iL, iR; 103. 104. m_Section[ePin].x = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 105. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 106. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 107. m_Section[ePin].x += def_PosXObjects; 108. m_Section[ePin].x += 95 - (def_SizeButtons / 2); 109. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 110. m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0))); 111. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 112. } 113. //+------------------------------------------------------------------+ 114. inline eObjectControl CheckPositionMouseClick(short &x, short &y) 115. { 116. C_Mouse::st_Mouse InfoMouse; 117. 118. InfoMouse = (*m_MousePtr).GetInfoMouse(); 119. x = (short) InfoMouse.Position.X_Graphics; 120. y = (short) InfoMouse.Position.Y_Graphics; 121. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 122. { 123. if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y)) 124. return c0; 125. } 126. 127. return eNull; 128. } 129. //+------------------------------------------------------------------+ 130. public : 131. //+------------------------------------------------------------------+ 132. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 133. :C_Terminal(Arg0), 134. m_MousePtr(MousePtr) 135. { 136. if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown); 137. if (_LastError != ERR_SUCCESS) return; 138. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 139. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 140. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 141. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 142. { 143. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 144. m_Section[c0].y = 25; 145. m_Section[c0].Btn = NULL; 146. } 147. m_Section[ePlay].x = def_PosXObjects; 148. m_Section[eLeft].x = m_Section[ePlay].x + 47; 149. m_Section[eRight].x = m_Section[ePlay].x + 511; 150. m_Slider.Minimal = SHORT_MIN; 151. } 152. //+------------------------------------------------------------------+ 153. ~C_Controls() 154. { 155. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 156. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 157. delete m_MousePtr; 158. } 159. //+------------------------------------------------------------------+ 160. void SetBuffer(const int rates_total, double &Buff[]) 161. { 162. uCast_Double info; 163. 164. info._16b[0] = (ushort) m_Slider.Minimal; 165. info._16b[1] = (ushort) (m_Section[ePlay].state ? SHORT_MAX : SHORT_MIN); 166. if (rates_total > 0) 167. Buff[rates_total - 1] = info.dValue; 168. } 169. //+------------------------------------------------------------------+ 170. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 171. { 172. short x, y; 173. static short iPinPosX = -1, six = -1, sps; 174. uCast_Double info; 175. 176. switch (id) 177. { 178. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 179. info.dValue = dparam; 180. iPinPosX = m_Slider.Minimal = (short) info._16b[0]; 181. if (info._16b[1] == 0) SetUserError(C_Terminal::ERR_Unknown); else 182. { 183. SetPlay((short)(info._16b[1]) == SHORT_MAX); 184. if ((short)(info._16b[1]) == SHORT_MIN) CreateCtrlSlider(); 185. } 186. break; 187. case CHARTEVENT_OBJECT_DELETE: 188. if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName) 189. { 190. if (sparam == (def_PrefixCtrlName + EnumToString(ePlay))) 191. { 192. delete m_Section[ePlay].Btn; 193. m_Section[ePlay].Btn = NULL; 194. SetPlay(m_Section[ePlay].state); 195. }else 196. { 197. RemoveCtrlSlider(); 198. CreateCtrlSlider(); 199. } 200. } 201. break; 202. case CHARTEVENT_MOUSE_MOVE: 203. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 204. { 205. case ePlay: 206. SetPlay(!m_Section[ePlay].state); 207. if (m_Section[ePlay].state) 208. { 209. RemoveCtrlSlider(); 210. m_Slider.Minimal = iPinPosX; 211. }else CreateCtrlSlider(); 212. break; 213. case eLeft: 214. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 215. break; 216. case eRight: 217. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 218. break; 219. case ePin: 220. if (six == -1) 221. { 222. six = x; 223. sps = iPinPosX; 224. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 225. } 226. iPinPosX = sps + x - six; 227. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 228. break; 229. }else if (six > 0) 230. { 231. six = -1; 232. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 233. } 234. break; 235. } 236. ChartRedraw(GetInfoTerminal().ID); 237. } 238. //+------------------------------------------------------------------+ 239. }; 240. //+------------------------------------------------------------------+ 241. #undef def_PosXObjects 242. #undef def_ButtonPlay 243. #undef def_ButtonPause 244. #undef def_ButtonLeft 245. #undef def_ButtonRight 246. #undef def_ButtonPin 247. #undef def_PrefixCtrlName 248. #undef def_PathBMP 249. //+------------------------------------------------------------------+
Исходный код файла C_Control.mqh
Как видим, между показанным выше кодом и тем же кодом, представленным в последней статье, в которой использовался этот заголовочный файл, нет никаких различий, по крайней мере, заметных. Однако некоторые части кода все же претерпели незначительные изменения. Вы можете видеть, что мы используем явное преобразование типов. Одно из таких мест, где это можно увидеть, — строки 164 и 165, где мы сообщаем компилятору, что хотим явно использовать тип данных.
Обратите внимание на изменение в строке 165. Раньше мы использовали целочисленные константы; теперь мы используем короткие константы. Но несмотря на использование знаковых констант, то есть таких, которые могут быть представлены отрицательно, значение, которое необходимо поместить в массив, будет незнакового типа. В этот момент может показаться, что это вызовет ошибку понимания значений. Но так покажется только тем, кто не совсем понимает, как значения представляются в бинарном виде. Им я посоветую изучить бинарное представление значений, чтобы понять, почему мы можем поместить отрицательные значения в систему, которая их не представляет, и тем не менее, передать информацию без потери качества.
В этом же коде, в строке 180, вы можете увидеть еще одно явное преобразование типов. Теперь значение, находящееся в незнаковой переменной, помещается в знаковую переменную. Такое преобразование типов позволяет соответствующим образом представлять отрицательные значения. Обратите внимание, что во всем коде обработки пользовательского события, для инициализации индикатора управления используется этот тип моделирования, так что внимательно изучите фрагмент между строками 178 и 186, так как здесь происходит интенсивное использование этого типа преобразования.
Правда, сжатие данных, выполненное здесь, в индикаторе управления, было не настолько глубоким, чтобы использовать биты в double более адекватно, но это потому, что информация здесь не требует такой продвинутой степени. И последнее, касаемо индикатора управления. Поскольку изменения были внесены только в номер версии и ссылку в коде самого индикатора, я не буду снова повторять здесь один и тот же код. Таким образом, все, что вам нужно будет сделать, это заменить заголовочный файл, упомянутый в предыдущей статье, на представленный в этой статье. Код индикатора останется прежним, без каких-либо других отличий, кроме упомянутых. Его можно использовать без проблем.
Что касается индикатора мыши, здесь все немного сложнее. По этой причине нам действительно понадобятся три файла, которые будут использоваться для создания индикатора. Они будут рассмотрены и объяснены в следующей теме.
Реализация решения в индикаторе мыши
Как видим, в предыдущем разделе нам необходимо было внести некоторые небольшие изменения в код индикатора управления, но это касалось только заголовочного файла. Однако здесь, в индикаторе мыши, ситуация совершенно иная и значительно сложнее.
Итак, давайте рассмотрим изменения. Прежде всего, давайте посмотрим, как теперь выглядит новый класс C_Mouse, который можно увидеть ниже.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_MousePrefixName "MouseBase_" 007. #define def_NameObjectLineH def_MousePrefixName + "H" 008. #define def_NameObjectLineV def_MousePrefixName + "TV" 009. #define def_NameObjectLineT def_MousePrefixName + "TT" 010. #define def_NameObjectStudy def_MousePrefixName + "TB" 011. //+------------------------------------------------------------------+ 012. class C_Mouse : public C_Terminal 013. { 014. public : 015. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 016. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 017. struct st_Mouse 018. { 019. struct st00 020. { 021. short X_Adjusted, 022. Y_Adjusted, 023. X_Graphics, 024. Y_Graphics; 025. double Price; 026. datetime dt; 027. }Position; 028. uchar ButtonStatus; 029. bool ExecStudy; 030. }; 031. //+------------------------------------------------------------------+ 032. protected: 033. //+------------------------------------------------------------------+ 034. void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const 035. { 036. if (m_Mem.szShortName != NULL) return; 037. CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE); 038. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 039. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 042. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 043. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 044. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 046. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 047. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 048. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 049. } 050. //+------------------------------------------------------------------+ 051. private : 052. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 053. struct st01 054. { 055. st_Mouse Data; 056. color corLineH, 057. corTrendP, 058. corTrendN; 059. eStudy Study; 060. }m_Info; 061. struct st_Mem 062. { 063. bool CrossHair, 064. IsFull; 065. datetime dt; 066. string szShortName; 067. }m_Mem; 068. bool m_OK; 069. //+------------------------------------------------------------------+ 070. void GetDimensionText(const string szArg, int &w, int &h) 071. { 072. TextSetFont("Lucida Console", -100, FW_NORMAL); 073. TextGetSize(szArg, w, h); 074. h += 5; 075. w += 5; 076. } 077. //+------------------------------------------------------------------+ 078. void CreateStudy(void) 079. { 080. if (m_Mem.IsFull) 081. { 082. CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH); 083. CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH); 084. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2); 085. CreateObjToStudy(0, 0, def_NameObjectStudy); 086. } 087. m_Info.Study = eStudyCreate; 088. } 089. //+------------------------------------------------------------------+ 090. void ExecuteStudy(const double memPrice) 091. { 092. double v1 = GetInfoMouse().Position.Price - memPrice; 093. int w, h; 094. 095. if (!CheckClick(eClickLeft)) 096. { 097. m_Info.Study = eStudyNull; 098. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 099. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 100. }else if (m_Mem.IsFull) 101. { 102. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 103. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0)); 104. GetDimensionText(sz1, w, h); 105. ObjectSetString(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_TEXT, sz1); 106. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 107. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XSIZE, w); 108. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YSIZE, h); 109. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 110. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 111. ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 112. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 113. } 114. m_Info.Data.ButtonStatus = eKeyNull; 115. } 116. //+------------------------------------------------------------------+ 117. inline void DecodeAlls(int xi, int yi) 118. { 119. int w = 0; 120. 121. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short) xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 122. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 123. m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price); 124. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi); 125. m_Info.Data.Position.X_Adjusted = (short) xi; 126. m_Info.Data.Position.Y_Adjusted = (short) yi; 127. } 128. //+------------------------------------------------------------------+ 129. public : 130. //+------------------------------------------------------------------+ 131. C_Mouse(const long id, const string szShortName) 132. :C_Terminal(id), 133. m_OK(false) 134. { 135. m_Mem.szShortName = szShortName; 136. } 137. //+------------------------------------------------------------------+ 138. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 139. :C_Terminal(id) 140. { 141. if (!(m_OK = IndicatorCheckPass(szShortName))) SetUserError(C_Terminal::ERR_Unknown); 142. if (_LastError != ERR_SUCCESS) return; 143. m_Mem.szShortName = NULL; 144. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 145. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 146. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 147. ZeroMemory(m_Info); 148. m_Info.corLineH = corH; 149. m_Info.corTrendP = corP; 150. m_Info.corTrendN = corN; 151. m_Info.Study = eStudyNull; 152. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 153. CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 154. } 155. //+------------------------------------------------------------------+ 156. ~C_Mouse() 157. { 158. if (!m_OK) return; 159. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 160. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false); 161. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 162. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 163. } 164. //+------------------------------------------------------------------+ 165. inline bool CheckClick(const eBtnMouse value) 166. { 167. return (GetInfoMouse().ButtonStatus & value) == value; 168. } 169. //+------------------------------------------------------------------+ 170. inline const st_Mouse GetInfoMouse(void) 171. { 172. if (m_Mem.szShortName != NULL) 173. { 174. double Buff[]; 175. uCast_Double loc; 176. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 177. 178. ZeroMemory(m_Info.Data); 179. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) 180. { 181. loc.dValue = Buff[0]; 182. m_Info.Data.ButtonStatus = loc._8b[0]; 183. DecodeAlls((int)loc._16b[1], (int)loc._16b[2]); 184. } 185. IndicatorRelease(handle); 186. } 187. 188. return m_Info.Data; 189. } 190. //+------------------------------------------------------------------+ 191. inline void SetBuffer(const int rates_total, double &Buff[]) 192. { 193. uCast_Double info; 194. 195. info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0); 196. info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics; 197. info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics; 198. Buff[rates_total - 1] = info.dValue; 199. } 200. //+------------------------------------------------------------------+ 201. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 202. { 203. int w = 0; 204. static double memPrice = 0; 205. 206. if (m_Mem.szShortName == NULL) 207. { 208. C_Terminal::DispatchMessage(id, lparam, dparam, sparam); 209. switch (id) 210. { 211. case (CHARTEVENT_CUSTOM + evHideMouse): 212. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE); 213. break; 214. case (CHARTEVENT_CUSTOM + evShowMouse): 215. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH); 216. break; 217. case CHARTEVENT_MOUSE_MOVE: 218. DecodeAlls((int)lparam, (int)dparam); 219. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); 220. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0); 221. m_Info.Data.ButtonStatus = (uchar) sparam; //Mudança no tipo ... 222. if (CheckClick(eClickMiddle)) 223. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 224. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 225. { 226. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 227. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 228. m_Info.Study = eStudyExecute; 229. } 230. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 231. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 232. break; 233. case CHARTEVENT_OBJECT_DELETE: 234. if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 235. break; 236. } 237. } 238. } 239. //+------------------------------------------------------------------+ 240. }; 241. //+------------------------------------------------------------------+ 242. #undef def_NameObjectLineV 243. #undef def_NameObjectLineH 244. #undef def_NameObjectLineT 245. #undef def_NameObjectStudy 246. //+------------------------------------------------------------------+
Исходный код файла C_Mouse.mqh
Возможно, вы не сразу заметите произошедшие изменения, так как большинство из них затрагивает только типизацию переменных. Но, тем не менее, они заслуживают упоминания, и правильное их понимание поможет вам определить ограничения этого индикатора мыши. Да, у него есть ограничения, но вы скоро поймете, в чем они состоят, и тогда сможете использовать индикатор соответствующим образом, чтобы ограничения не влияли на его работу.
Во-первых, сразу видно, что между строками 21 и 24 мы используем тип SHORT, ранее это был тип INT. Также был изменен и тип переменной в строке 28.
Прежде чем вы начнете паниковать, воображая, что мы создаем полный хаос, делая такие изменения типов, я хочу напомнить о некоторых особенностях и различиях между типами SHORT и INT.
Тип INT в MQL5 — это 32-битный тип, то есть, значение может варьироваться от -2 147 483 648 до 2 147 483 647, если это знаковое значение. Если оно беззнаковое, то есть, только положительное, значение может варьироваться от 0 до 4 294 967 295. Тип SHORT представляет собой 16-битное значение, то есть может варьироваться от -32 768 до 32 767, если это знаковое значение, или от 0 до 65 535, если беззнаковое.
Теперь я хочу, чтобы вы обратили внимание на один факт и ответили мне: «Каков размер вашего монитора в пикселях? И почему я об этом спрашиваю?». Причина в том, что использование INT для представления декартовых координат (X и Y) на мониторе — чистое невежество. Не поймите меня неправильно, но вы буквально теряете много информации. Чтобы было понятней: монитор с разрешением 8K, который является мощным монитором, имеет 7680 пикселей по горизонтали и 4320 пикселей по вертикали. То есть, монитор с невероятно высоким разрешением имеет менее 2 в 13-й степени позиций как по горизонтали, так и по вертикали. Это значение прекрасно можно представить переменной типа SHORT, даже знаковой, в которой у нас есть 2 в 16-й степени возможных позиций. Таким образом, у нас еще остается 3 свободных бита, чтобы что-то сделать.
Но поскольку нам не нужно столько битов, то простой оптимизации, подобной проводимой, будет более чем достаточно. Напомню еще раз, что если бы мы использовали INT, мы могли бы использовать только 2, но используя SHORT, мы можем использовать 4. Это в пределах той же длины типа double, которая составляет 64 бита. Поскольку нам нужны только 2 значения SHORT для представления позиций (X и Y), которые будут указывать положение мыши на мониторе, у нас остается еще 2 SHORT для других целей. Не забываем также, что у нас остается еще по 3 лишних бита в каждом из SHORT, используемых для представления координат мыши на мониторе, и это для экрана с разрешением 8K.
Как видите, позиция не является для нас ограничением. Наше ограничение совсем в другом. Если вы следили за этой серией статей, вы, возможно, заметили, что в нескольких местах я упоминал, что мы позволим индикатору мыши предоставлять нам некоторую информацию для использования в исследованиях более быстрым и простым способом. И именно здесь кроется ограничение: значения цены и времени не могут быть напрямую помещены в буфер индикатора мыши. Это связано с тем, что для каждого такого значения потребуется по 64 бита, и мы не можем позволить себе добавить для этого две позиции. По этой причине нам нужно сделать кое-что другое, и этим обусловлены некоторые изменения в коде класса C_Mouse.
Первое изменение, которое вы заметите, находится в строке 117. Обратите внимание на следующий факт: чтобы не пришлось глубоко модифицировать все типы, присутствующие в индикаторе мыши, я решил сохранить позиции в INT, по крайней мере, внутри класса. Но как только мы выходим из класса, вещи начинают адаптироваться к тому, что нам действительно нужно.
Таким образом, процедура в строке 117 преобразует координаты экрана, сообщаемые операционной системой, в координаты, скорректированные MQL5, чтобы они были совместимы с тем, что отображается на графике. Обратите на это особое внимание. Эта процедура является частной для класса, то есть никакой код вне класса не будет иметь к ней доступа. Тем не менее, она выполняет перевод данных, обеспечивая функционирование, идентичное тому, что было ранее. То есть, изменения будут прозрачны для системы. Если вы уже использовали этот индикатор и работали с классом для перевода данных, у вас не должно возникнуть никаких проблем.
Чтобы понять это, необходимо посмотреть на строку 170. Эта функция будет переводить данные индикатора. Обратите внимание, что ничего не изменилось, хотя внутренний код этой функции претерпел изменения, поскольку раньше мы ожидали шесть возвращаемых значений, а теперь ожидаем только одно. И обратите пристальное внимание на то, как мы сейчас работаем. В строке 179 мы ищем значение в буфере индикатора, и, если какое-либо значение возвращается, в строке 181 мы присваиваем его системе перевода. В строке 182 мы осуществляем захват и перевод значения, чтобы узнать статус кнопок. Обратите внимание на используемый индекс и длину в битах. В строке 183 мы отправляем данные процедуре, которая преобразует экранные координаты, то есть X и Y, в другие типы, которые мы будем использовать, это внутри MetaTrader 5 или через любое другое приложение, использующее индикатор мыши в качестве вспомогательного. Вы увидите, что, несмотря на изменения, они строго остаются внутри функции, не влияя ни на что за пределами класса.
Теперь перейдем к несколько более сложному, но необходимому для работы вопросу. Это строка 191, где мы объявляем процедуру, которая будет помещать данные в буфер индикатора. Раньше эта же процедура была частью кода индикатора, однако, из практических соображений, я решил разместить ее здесь, в коде класса. И основной мотивацией для этого является ограничение доступа к данным класса.
Итак, посмотрим, что получилось. В строке 193 мы объявляем нашу переменную сжатия. В строке 195 мы начинаем компактизацию данных. Теперь обратите пристальное внимание на то, что на самом деле произойдет, чтобы вы понимали, какие индексы используются для доступа к массивам.
Массив _8b содержит 8 позиций, а массив _16b — 4 позиции. Оба начинаются с индекса ноль. Важный момент: индекс ноль в массиве _16b соответствует индексам ноль и один в массиве _8b. То есть, когда на строке 195 мы используем индекс ноль в массиве _8b, мы занимаем индекс ноль в массиве _16b, но поскольку информация, которую нам нужно разместить, требует только индекса ноль, индекс один в массиве _8b останется свободным. Но даже если индекс один свободен, вы не сможете использовать индекс ноль в массиве _16b, именно по той причине, что он требует два _8b для своего формирования, как я недавно объяснил.
Таким образом, у нас остается пустой индекс один из массива _8b, а начиная с индекса два массива _8b мы будем сохранять значения положения мыши. Поскольку индекс 2 будет ссылаться на индекс 1 массива _16b, в строке 196 мы ссылаемся на этот индекс, так что все остается разделенным логически и функционально. Строка 197 следует тому же принципу индексации. Вы можете подумать, что мы занимаем все биты, но на самом деле это не так. У нас полностью свободны биты индекса 3 массива _16b, а также свободен индекс 1 массива _8b, не считая 6 бит, которые также будут свободны в двух коротких сообщениях SHORT, которые мы используем для хранения X и позиции Y. Но поскольку система обеспечивает нас всем необходимым, я просто информирую вас о том, что у нас реально есть в плане использования, а чего нет, на случай, если кто-то захочет использовать указанные биты как-то иначе.
Вы можете лучше представить себе это, посмотрев на изображение ниже.
На этом изображении вы можете увидеть содержимое буфера, байт за байтом. Имейте в виду, что каждый байт представляет собой 8 бит. Обратите внимание, что области, окрашенные в синий цвет, будут заняты некоторой информацией, а белые области могут быть свободны для получения дополнительных данных или информации в будущем. X обозначает графическую координату X, то же самое относится и к Y. Надеюсь, посмотрев на это изображение, вы лучше поймете, что я на самом деле делаю.
Что касается остальной части кода индикатора мыши, то здесь практически не потребуется особых пояснений, поскольку сам код будет достаточно прост для понимания. Однако, поскольку он также был изменен, я хочу зарегистрировать новый код. Посмотрите, как он должен выглядеть, чтобы все правильно функционировало.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\C_Mouse.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_ExpansionPrefix def_MousePrefixName + "Expansion_" 007. #define def_ExpansionBtn1 def_ExpansionPrefix + "B1" 008. #define def_ExpansionBtn2 def_ExpansionPrefix + "B2" 009. #define def_ExpansionBtn3 def_ExpansionPrefix + "B3" 010. //+------------------------------------------------------------------+ 011. class C_Study : public C_Mouse 012. { 013. private : 014. //+------------------------------------------------------------------+ 015. struct st00 016. { 017. eStatusMarket Status; 018. MqlRates Rate; 019. string szInfo; 020. color corP, 021. corN; 022. int HeightText; 023. bool bvT, bvD, bvP; 024. datetime TimeDevice; 025. }m_Info; 026. //+------------------------------------------------------------------+ 027. const datetime GetBarTime(void) 028. { 029. datetime dt; 030. int i0 = PeriodSeconds(); 031. 032. if (m_Info.Status == eInReplay) 033. { 034. if ((dt = m_Info.TimeDevice) == ULONG_MAX) return ULONG_MAX; 035. }else dt = TimeCurrent(); 036. if (m_Info.Rate.time <= dt) 037. m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0; 038. 039. return m_Info.Rate.time - dt; 040. } 041. //+------------------------------------------------------------------+ 042. void Draw(void) 043. { 044. double v1; 045. 046. if (m_Info.bvT) 047. { 048. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 049. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo); 050. } 051. if (m_Info.bvD) 052. { 053. v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 054. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 055. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 056. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 057. } 058. if (m_Info.bvP) 059. { 060. v1 = NormalizeDouble((((iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0) - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 061. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 062. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 063. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 064. } 065. } 066. //+------------------------------------------------------------------+ 067. inline void CreateObjInfo(EnumEvents arg) 068. { 069. switch (arg) 070. { 071. case evShowBarTime: 072. C_Mouse::CreateObjToStudy(2, 110, def_ExpansionBtn1, clrPaleTurquoise); 073. m_Info.bvT = true; 074. break; 075. case evShowDailyVar: 076. C_Mouse::CreateObjToStudy(2, 53, def_ExpansionBtn2); 077. m_Info.bvD = true; 078. break; 079. case evShowPriceVar: 080. C_Mouse::CreateObjToStudy(58, 53, def_ExpansionBtn3); 081. m_Info.bvP = true; 082. break; 083. } 084. } 085. //+------------------------------------------------------------------+ 086. inline void RemoveObjInfo(EnumEvents arg) 087. { 088. string sz; 089. 090. switch (arg) 091. { 092. case evHideBarTime: 093. sz = def_ExpansionBtn1; 094. m_Info.bvT = false; 095. break; 096. case evHideDailyVar: 097. sz = def_ExpansionBtn2; 098. m_Info.bvD = false; 099. break; 100. case evHidePriceVar: 101. sz = def_ExpansionBtn3; 102. m_Info.bvP = false; 103. break; 104. } 105. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 106. ObjectDelete(GetInfoTerminal().ID, sz); 107. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 108. } 109. //+------------------------------------------------------------------+ 110. public : 111. //+------------------------------------------------------------------+ 112. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 113. :C_Mouse(IdParam, szShortName, corH, corP, corN) 114. { 115. if (_LastError != ERR_SUCCESS) return; 116. ZeroMemory(m_Info); 117. m_Info.Status = eCloseMarket; 118. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 119. m_Info.corP = corP; 120. m_Info.corN = corN; 121. CreateObjInfo(evShowBarTime); 122. CreateObjInfo(evShowDailyVar); 123. CreateObjInfo(evShowPriceVar); 124. } 125. //+------------------------------------------------------------------+ 126. void Update(const eStatusMarket arg) 127. { 128. datetime dt; 129. 130. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 131. { 132. case eCloseMarket : 133. m_Info.szInfo = "Closed Market"; 134. break; 135. case eInReplay : 136. case eInTrading : 137. if ((dt = GetBarTime()) < ULONG_MAX) 138. { 139. m_Info.szInfo = TimeToString(dt, TIME_SECONDS); 140. break; 141. } 142. case eAuction : 143. m_Info.szInfo = "Auction"; 144. break; 145. default : 146. m_Info.szInfo = "ERROR"; 147. } 148. Draw(); 149. } 150. //+------------------------------------------------------------------+ 151. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 152. { 153. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 154. switch (id) 155. { 156. case CHARTEVENT_CUSTOM + evHideBarTime: 157. RemoveObjInfo(evHideBarTime); 158. break; 159. case CHARTEVENT_CUSTOM + evShowBarTime: 160. CreateObjInfo(evShowBarTime); 161. break; 162. case CHARTEVENT_CUSTOM + evHideDailyVar: 163. RemoveObjInfo(evHideDailyVar); 164. break; 165. case CHARTEVENT_CUSTOM + evShowDailyVar: 166. CreateObjInfo(evShowDailyVar); 167. break; 168. case CHARTEVENT_CUSTOM + evHidePriceVar: 169. RemoveObjInfo(evHidePriceVar); 170. break; 171. case CHARTEVENT_CUSTOM + evShowPriceVar: 172. CreateObjInfo(evShowPriceVar); 173. break; 174. case (CHARTEVENT_CUSTOM + evSetServerTime): 175. m_Info.TimeDevice = (datetime)dparam; 176. break; 177. case CHARTEVENT_MOUSE_MOVE: 178. Draw(); 179. break; 180. } 181. ChartRedraw(GetInfoTerminal().ID); 182. } 183. //+------------------------------------------------------------------+ 184. }; 185. //+------------------------------------------------------------------+ 186. #undef def_ExpansionBtn3 187. #undef def_ExpansionBtn2 188. #undef def_ExpansionBtn1 189. #undef def_ExpansionPrefix 190. #undef def_MousePrefixName 191. //+------------------------------------------------------------------+
Исходный код файла C_Study.mqh
Следует отметить, что класс C_Study практически не изменился. Поэтому я не вижу смысла для дальнейших разъяснений по этому поводу. Давайте теперь посмотрим на код индикатора, показанный ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "This is an indicator for graphical studies using the mouse." 04. #property description "This is an integral part of the Replay / Simulator system." 05. #property description "However it can be used in the real market." 06. #property version "1.56" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/pt/articles/12000" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. #property indicator_buffers 1 12. //+------------------------------------------------------------------+ 13. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 14. //+------------------------------------------------------------------+ 15. C_Study *Study = NULL; 16. //+------------------------------------------------------------------+ 17. input long user00 = 0; //ID 18. input C_Study::eStatusMarket user01 = C_Study::eAuction; //Market Status 19. input color user02 = clrBlack; //Price Line 20. input color user03 = clrPaleGreen; //Positive Study 21. input color user04 = clrLightCoral; //Negative Study 22. //+------------------------------------------------------------------+ 23. C_Study::eStatusMarket m_Status; 24. int m_posBuff = 0; 25. double m_Buff[]; 26. //+------------------------------------------------------------------+ 27. int OnInit() 28. { 29. ResetLastError(); 30. Study = new C_Study(user00, "Indicator Mouse Study", user02, user03, user04); 31. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 32. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 33. { 34. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 35. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 36. m_Status = C_Study::eCloseMarket; 37. }else 38. m_Status = user01; 39. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 40. ArrayInitialize(m_Buff, EMPTY_VALUE); 41. 42. return INIT_SUCCEEDED; 43. } 44. //+------------------------------------------------------------------+ 45. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 46. { 47. m_posBuff = rates_total; 48. (*Study).Update(m_Status); 49. 50. return rates_total; 51. } 52. //+------------------------------------------------------------------+ 53. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 54. { 55. (*Study).DispatchMessage(id, lparam, dparam, sparam); 56. (*Study).SetBuffer(m_posBuff, m_Buff); 57. 58. ChartRedraw((*Study).GetInfoTerminal().ID); 59. } 60. //+------------------------------------------------------------------+ 61. void OnBookEvent(const string &symbol) 62. { 63. MqlBookInfo book[]; 64. C_Study::eStatusMarket loc = m_Status; 65. 66. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 67. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 68. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 69. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 70. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 71. if (loc != m_Status) (*Study).Update(m_Status); 72. } 73. //+------------------------------------------------------------------+ 74. void OnDeinit(const int reason) 75. { 76. if (reason != REASON_INITFAILED) 77. { 78. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 79. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 80. } 81. delete Study; 82. } 83. //+------------------------------------------------------------------+
Исходный код индикатора мыши
Обратите внимание, что здесь есть некоторые отличия от того кода, который был раньше. Но они не настолько существенны, чтобы вы не могли понять код. Однако есть два момента, которые, на мой взгляд, заслуживают краткого комментария. Это строки 47 и 56.
В строке 47 вы можете видеть, что, в отличие от того, что было раньше, теперь мы сохраняем только то значение, которое нам сообщит MetaTrader 5, и не вносим в него никаких изменений, поскольку это будет сделано в другом месте.
Уже в строке 56 мы передаем информацию в класс C_Mouse, где будем корректировать ситуацию и записывать в буфер индикатора. Заметьте, что здесь все стало намного проще именно потому, что мы переместили всю сложность внутрь класса.
Заключение
Благодаря этим модификациям и адаптации кода, у нас появилась возможность практического использования индикатора мыши в разрабатываемой нами системе репликации/моделирования. Теперь мы можем использовать систему отправки сообщений между задействованными приложениями и, в то же время, использовать чтение буфера для передачи информации между ними. Единственное ограничение, которое у нас есть, это то, что мы всегда должны использовать одну единственную позицию в буфере. Это необходимо для того, чтобы не перегружать систему событий. Но это уже будет темой для рассмотрения в будущем.
На видео ниже вы можете увидеть демонстрацию работы системы. В приложении вам будет доступен уже скомпилированный код, чтобы вы могли провести тесты и понять, что происходит на самом деле. Прежде чем я забуду упомянуть об этом, вы можете проверить ошибку диапазона, заменив индикатор мыши, который будет в этом приложении, на тот, что был в предыдущей статье, и запустив работу сервиса из этой статьи. При этом вы увидите, как появится упомянутая ошибка.
И последний момент: хотя в этой статье не упоминается код сервиса, который будет использоваться, я оставлю его объяснение для следующей, поскольку мы будем использовать этот же код как трамплин для того, что на самом деле разрабатываем. Так что наберитесь терпения и ожидайте следующей статьи, ведь с каждым днем все становится все более и более интересным.
Демонстрационное видео
Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/12000





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