开发回放系统(第 55 部分):控制模块
概述
在上一篇文章"开发回放系统(第 54 部分):第一个模块的诞生"中,我们已经组装了整个新回放/模拟器系统的第一个真实模块。除了在我们正在开发的系统中使用它们的可能性外,我们还将能够以个性化的方式单独应用这些模块,以避免创建这样一个系统需要大量的编程。无论哪种方式,一旦构建了模块,我们将能够轻松地对其进行自定义,而无需重新编译。
为此,您只需要向模块发送一条消息来更改其外观或工作方式,这可以通过简单的脚本轻松完成。
因此,基于我们在上一篇文章中看到的内容,我们有机会创建一个可以在真实账户和模拟账户上使用的系统。但这不是我们唯一能做的事情;除此之外,我们还可以创建一个回放/模拟器系统,其行为与您在真实或模拟账户上看到的非常相似。
然而,我们将开始使用的这个新模型的主要优势是,您将能够在回放/模拟器系统中以及在 MetaTrader 5 的日常工作中使用相同的工具和应用程序在模拟账户上进行训练或在真实账户上进行交易。
现在我们的鼠标指标已经准备就绪,我们可以开始创建,或者说调整我们的控制指标,以便它以模块化方式开始工作。需要对刚才提到的内容进行简要解释。
直到最近,回放/模拟器系统才使用全局终端变量来提供交互、控制和访问回放/模拟器服务所需的程序之间的一定程度的通信。
由于我们开始使用通过用户事件完成消息传递的模块系统,我们将不再需要使用全局终端变量。考虑到这一点,我们现在可以删除以前使用的所有全局终端变量。然而,在这种情况下,我们需要调整系统,以便信息在程序之间继续流动。
对信息传输系统进行建模是一项需要高度关注和谨慎的任务,因为以后根本不可能读取信息。如果通过自定义事件接收信息时程序或应用程序不在图表上,则该信息将丢失。因此,需要额外的机制来重新发送相同的信息,直到我们确认它已被预期的应用或程序接收。
根据该标准,决定回放/模拟器系统应包括最低限度功能所需的三个核心程序。在这些程序中,只有两个对用户可见:负责服务本身的程序和鼠标指标。控制指标将被视为服务程序的资源,因此,如果不提供服务,则无法使用。
因此,记住这个简短的解释,让我们看一下如何修改控制指标,以便它可以开始执行其工作,即管理回放/模拟器服务。
修改控制指标
需要对控制指标进行的更改不多,因为在我们开始删除终端的全局变量时,其中许多更改已经在上一步中完成。然而,如果不清楚消息交换将如何进行,就不可能理解系统将如何执行其任务。
所以,让我们试着从一开始就把它弄清楚,这样你在阅读未来的文章时就不会迷失。
当指标放置在图表上时,用户可以配置几个参数。这些参数指的是指标输入变量。然而,在某些时刻,这些相同的变量更多的是一种阻碍而不是帮助。请勿误解,我并非试图推动任何根本性的变革。但是,当我们允许用户提前访问变量来配置指标(在本例中)时,我们为潜在的问题打开了大门。
如果你抛开用户可能会意外更改他们不应该更改的东西的事实,这些变量就会变得非常有用。因此我们使用它们将图表 ID 传递给指标。并不是指标真的需要这些信息,但值得记住的是,在指标被放置在图表上的那一刻,它的 ID 可能与对象的预期 ID 不同。这在本系列前面的一篇文章中已经讨论过。
虽然我们可以使用消息系统将 ID 传递给指标,但由于图表是由服务打开的,并且后者知道其 ID,这只会不必要地使服务和指标的代码复杂化。因此,我将保持迄今为止所做的一切。然而,我们需要对控制指标代码做一些小的改动,因为我们不再使用全局终端变量来传递数据。
以下是 C_Control.mqh 文件的完整代码。由于前面的文章已经解释了大部分代码,我们将只关注需要解释的新部分。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\C_DrawImage.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. #define def_PathBMP "Images\\Market Replay\\Control\\" 008. #define def_ButtonPlay def_PathBMP + "Play.bmp" 009. #define def_ButtonPause def_PathBMP + "Pause.bmp" 010. #define def_ButtonLeft def_PathBMP + "Left.bmp" 011. #define def_ButtonLeftBlock def_PathBMP + "Left_Block.bmp" 012. #define def_ButtonRight def_PathBMP + "Right.bmp" 013. #define def_ButtonRightBlock def_PathBMP + "Right_Block.bmp" 014. #define def_ButtonPin def_PathBMP + "Pin.bmp" 015. #resource "\\" + def_ButtonPlay 016. #resource "\\" + def_ButtonPause 017. #resource "\\" + def_ButtonLeft 018. #resource "\\" + def_ButtonLeftBlock 019. #resource "\\" + def_ButtonRight 020. #resource "\\" + def_ButtonRightBlock 021. #resource "\\" + def_ButtonPin 022. //+------------------------------------------------------------------+ 023. #define def_PrefixCtrlName "MarketReplayCTRL_" 024. #define def_PosXObjects 120 025. //+------------------------------------------------------------------+ 026. #define def_SizeButtons 32 027. #define def_ColorFilter 0xFF00FF 028. //+------------------------------------------------------------------+ 029. #include "..\Auxiliar\C_Terminal.mqh" 030. #include "..\Auxiliar\C_Mouse.mqh" 031. //+------------------------------------------------------------------+ 032. class C_Controls : private C_Terminal 033. { 034. protected: 035. private : 036. //+------------------------------------------------------------------+ 037. enum eObjectControl {ePlay, eLeft, eRight, ePin, eNull}; 038. //+------------------------------------------------------------------+ 039. struct st_00 040. { 041. string szBarSlider, 042. szBarSliderBlock; 043. int Minimal; 044. }m_Slider; 045. struct st_01 046. { 047. C_DrawImage *Btn; 048. bool state; 049. int x, y, w, h; 050. }m_Section[eObjectControl::eNull]; 051. C_Mouse *m_MousePtr; 052. //+------------------------------------------------------------------+ 053. inline void CreteBarSlider(int x, int size) 054. { 055. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSlider = def_PrefixCtrlName + "B1", OBJ_RECTANGLE_LABEL, 0, 0, 0); 056. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 057. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Section[ePin].y + 11); 058. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 059. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 060. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 063. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 064. ObjectCreate(GetInfoTerminal().ID, m_Slider.szBarSliderBlock = def_PrefixCtrlName + "B2", OBJ_RECTANGLE_LABEL, 0, 0, 0); 065. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 066. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Section[ePin].y + 6); 067. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 068. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 069. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 070. } 071. //+------------------------------------------------------------------+ 072. void SetPlay(bool state) 073. { 074. if (m_Section[ePlay].Btn == NULL) 075. m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause); 076. m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 0 : 1)); 077. } 078. //+------------------------------------------------------------------+ 079. void CreateCtrlSlider(void) 080. { 081. CreteBarSlider(77, 436); 082. m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock); 083. m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock); 084. m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePin), def_ColorFilter, "::" + def_ButtonPin); 085. PositionPinSlider(m_Slider.Minimal); 086. } 087. //+------------------------------------------------------------------+ 088. inline void RemoveCtrlSlider(void) 089. { 090. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 091. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 092. { 093. delete m_Section[c0].Btn; 094. m_Section[c0].Btn = NULL; 095. } 096. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName + "B"); 097. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 098. } 099. //+------------------------------------------------------------------+ 100. inline void PositionPinSlider(int p) 101. { 102. int iL, iR; 103. 104. m_Section[ePin].x = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 105. iL = (m_Section[ePin].x != m_Slider.Minimal ? 0 : 1); 106. iR = (m_Section[ePin].x < def_MaxPosSlider ? 0 : 1); 107. m_Section[ePin].x += def_PosXObjects; 108. m_Section[ePin].x += 95 - (def_SizeButtons / 2); 109. for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++) 110. m_Section[c0].Btn.Paint(m_Section[c0].x, m_Section[c0].y, m_Section[c0].w, m_Section[c0].h, 20, (c0 == eLeft ? iL : (c0 == eRight ? iR : 0))); 111. ObjectSetInteger(GetInfoTerminal().ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 112. } 113. //+------------------------------------------------------------------+ 114. inline eObjectControl CheckPositionMouseClick(int &x, int &y) 115. { 116. C_Mouse::st_Mouse InfoMouse; 117. 118. InfoMouse = (*m_MousePtr).GetInfoMouse(); 119. x = InfoMouse.Position.X_Graphics; 120. y = InfoMouse.Position.Y_Graphics; 121. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 122. { 123. if ((m_Section[c0].Btn != NULL) && (m_Section[c0].x <= x) && (m_Section[c0].y <= y) && ((m_Section[c0].x + m_Section[c0].w) >= x) && ((m_Section[c0].y + m_Section[c0].h) >= y)) 124. return c0; 125. } 126. 127. return eNull; 128. } 129. //+------------------------------------------------------------------+ 130. public : 131. //+------------------------------------------------------------------+ 132. C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr) 133. :C_Terminal(Arg0), 134. m_MousePtr(MousePtr) 135. { 136. if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown); 137. if (_LastError != ERR_SUCCESS) return; 138. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 139. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 140. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 141. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) 142. { 143. m_Section[c0].h = m_Section[c0].w = def_SizeButtons; 144. m_Section[c0].y = 25; 145. m_Section[c0].Btn = NULL; 146. } 147. m_Section[ePlay].x = def_PosXObjects; 148. m_Section[eLeft].x = m_Section[ePlay].x + 47; 149. m_Section[eRight].x = m_Section[ePlay].x + 511; 150. m_Slider.Minimal = INT_MIN; 151. } 152. //+------------------------------------------------------------------+ 153. ~C_Controls() 154. { 155. for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn; 156. ObjectsDeleteAll(GetInfoTerminal().ID, def_PrefixCtrlName); 157. delete m_MousePtr; 158. } 159. //+------------------------------------------------------------------+ 160. void SetBuff(const int rates_total, double &Buff[]) 161. { 162. uCast_Double info; 163. 164. info._int[0] = m_Slider.Minimal; 165. info._int[1] = (m_Section[ePlay].state ? INT_MAX : INT_MIN); 166. Buff[rates_total - 1] = info.dValue; 167. } 168. //+------------------------------------------------------------------+ 169. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 170. { 171. int x, y; 172. static int iPinPosX = -1, six = -1, sps; 173. uCast_Double info; 174. 175. switch (id) 176. { 177. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 178. info.dValue = dparam; 179. iPinPosX = m_Slider.Minimal = info._int[0]; 180. if (info._int[1] == 0) SetUserError(C_Terminal::ERR_Unknown); else 181. { 182. SetPlay(info._int[1] == INT_MAX); 183. if (info._int[1] == INT_MIN) CreateCtrlSlider(); 184. } 185. break; 186. case CHARTEVENT_OBJECT_DELETE: 187. if (StringSubstr(sparam, 0, StringLen(def_PrefixCtrlName)) == def_PrefixCtrlName) 188. { 189. if (sparam == (def_PrefixCtrlName + EnumToString(ePlay))) 190. { 191. delete m_Section[ePlay].Btn; 192. m_Section[ePlay].Btn = NULL; 193. SetPlay(m_Section[ePlay].state); 194. }else 195. { 196. RemoveCtrlSlider(); 197. CreateCtrlSlider(); 198. } 199. } 200. break; 201. case CHARTEVENT_MOUSE_MOVE: 202. if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft)) switch (CheckPositionMouseClick(x, y)) 203. { 204. case ePlay: 205. SetPlay(!m_Section[ePlay].state); 206. if (m_Section[ePlay].state) 207. { 208. RemoveCtrlSlider(); 209. m_Slider.Minimal = iPinPosX; 210. }else CreateCtrlSlider(); 211. break; 212. case eLeft: 213. PositionPinSlider(iPinPosX = (iPinPosX > m_Slider.Minimal ? iPinPosX - 1 : m_Slider.Minimal)); 214. break; 215. case eRight: 216. PositionPinSlider(iPinPosX = (iPinPosX < def_MaxPosSlider ? iPinPosX + 1 : def_MaxPosSlider)); 217. break; 218. case ePin: 219. if (six == -1) 220. { 221. six = x; 222. sps = iPinPosX; 223. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 224. } 225. iPinPosX = sps + x - six; 226. PositionPinSlider(iPinPosX = (iPinPosX < m_Slider.Minimal ? m_Slider.Minimal : (iPinPosX > def_MaxPosSlider ? def_MaxPosSlider : iPinPosX))); 227. break; 228. }else if (six > 0) 229. { 230. six = -1; 231. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 232. } 233. break; 234. } 235. ChartRedraw(GetInfoTerminal().ID); 236. } 237. //+------------------------------------------------------------------+ 238. }; 239. //+------------------------------------------------------------------+ 240. #undef def_PosXObjects 241. #undef def_ButtonPlay 242. #undef def_ButtonPause 243. #undef def_ButtonLeft 244. #undef def_ButtonRight 245. #undef def_ButtonPin 246. #undef def_PrefixCtrlName 247. #undef def_PathBMP 248. //+------------------------------------------------------------------+
C_Control.mqh 的源代码
代码中有一些看似奇怪的东西。第一个在第 150 行,我们使用 MQL5 中定义的常量 INT_MIN 指定最小滑块偏移值。这是一个负值,是整数变量可能的最小值。我为什么要这么做?现在很难理解原因,所以请耐心等待,因为还有其他事情需要解决,才能完全理解第 150 行。
接下来要注意的是第 160 行,这里我们有一个将数据写入控制指标缓冲区的例程。在此阶段,我们只记录两个值。我不知道我们将来是否需要写入更多的值,但现在这两个值将被压缩为一个双精度值,这样它在缓冲区中只会占据一个位置。
对于这种压缩,我们使用了在第 162 行声明的联合。然后,在第 164 行,我们放置用户在操作滑块时将调整的值。请注意:我们将存储用户改变的滑块位置。在第 165 行中,我们指定控制指标的状态,即它处于播放模式还是暂停模式。
这里有一件重要的事情,当我们指示用户处于播放模式时,即用户按下了播放按钮,将保存某个值。而暂停模式将使用另一个值。我们将使用的值是整数数据类型可能范围的极值。通过这种方式,我们避免了使用空值的歧义,同时保证了信息的完整性,这有助于后续的测试。
这一点非常重要:如果你在构建控制指标的阶段不理解这一点,你可能会在未来的解释中遇到困难。请记住:信息不会直接从控制指标传到服务,它必须经过我们将要使用的通道,即缓冲区。我很快就会回到这一点,因为一些细节需要澄清。
最后,在第 166 行,我们将压缩值保存在指标缓冲区的特定位置。在本系列的另一篇文章中,我已经解释了在这个特定位置保存数据的原因,所以如果你有任何疑问,我建议你阅读之前的文章进行澄清。
下一段真正需要解释的代码是消息处理函数,它从第 169 行开始。它有两点特别值得关注。让我们从涉及较少细节但仍然与上面解释的内容相关的内容开始。因此,让我们跳到第 209 行。
这行代码很有趣:我们将用户在移动滑块时设置的值保存在 m_Slider.Minimal 变量中。原因是通过允许将所有内容集中在代码的关键点来简化流程。如果第 209 行不存在,我们必须在代码中的某个地方执行检查,将用户设置的位置传递给缓冲区。或者,更糟糕的是,我们必须找到一些方法,在不使用终端全局变量的情况下,将用户指定的值直接传递给服务。以前我们为此使用了全局变量,但现在我们将使用缓冲区。因此,为了避免重复检查和调整,我们将值放在易于访问的地方。请注意,只有在用户单击播放按钮后,此值才会保存在此处。
现在,让我们回到前面的代码内容,并查看第 177 行,其中我们有一个自定义事件,其目的是初始化控制指标,以便滑块可以正确定位。
此自定义事件将不时触发,但请注意,包含数据的值将以双精度值和打包形式出现。因此,我们需要翻译这些信息,同时确保其安全性。接收到的信息遵循与我们保存在缓冲区中的信息相同的原理。然而,这里有一个细微差别:我们检查它的完整性。
请注意,第 180 行有一个小测试。它检查指示系统是处于播放模式还是暂停模式的值是否为零。如果它为零,则有问题,控制指标正在接收不正确的数据,并且发生了错误。这就是调用 SetUserError 的原因。通常此函数不会执行,但如果执行,则必须采取适当的措施。我们将在指标代码后面讨论这些措施。
如果一切顺利的话,我们将执行另外两个操作。第一个是第 182 行,我们在这里调用一个负责显示播放或暂停按钮的函数。第二张是第 183 行所示的检查。如果值是最小值,则表示我们处于暂停模式,因此我们需要重新创建滑块以便用户可以调整它。
基本上就是这样。一旦指标添加到图表中,它将无法工作,直到通过自定义事件初始化它。但如何实现这一点,我们稍后再考虑。鼠标指针和控制指标之间的交互将生成有效控制回放/模拟器服务所需的整个消息循环。
现在我们来完整看一下下面的控制指标代码。请密切关注此代码的解释,因为此时我们正在为该系统的所有未来模块奠定基础。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property description "Control indicator for the Replay-Simulator service." 05. #property description "This one doesn't work without the service loaded." 06. #property version "1.55" 07. #property link "https://www.mql5.com/pt/articles/11988" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. #property indicator_buffers 1 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Service Graphics\C_Controls.mqh> 13. //+------------------------------------------------------------------+ 14. C_Controls *control = NULL; 15. //+------------------------------------------------------------------+ 16. input long user00 = 0; //ID 17. //+------------------------------------------------------------------+ 18. double m_Buff[]; 19. int m_RatesTotal; 20. //+------------------------------------------------------------------+ 21. int OnInit() 22. { 23. ResetLastError(); 24. if (CheckPointer(control = new C_Controls(user00, "Market Replay Control", new C_Mouse(user00, "Indicator Mouse Study"))) == POINTER_INVALID) 25. SetUserError(C_Terminal::ERR_PointerInvalid); 26. if (_LastError != ERR_SUCCESS) 27. { 28. Print("Control indicator failed on initialization."); 29. return INIT_FAILED; 30. } 31. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 32. ArrayInitialize(m_Buff, EMPTY_VALUE); 33. 34. return INIT_SUCCEEDED; 35. } 36. //+------------------------------------------------------------------+ 37. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 38. { 39. return m_RatesTotal = rates_total; 40. } 41. //+------------------------------------------------------------------+ 42. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 43. { 44. (*control).DispatchMessage(id, lparam, dparam, sparam); 45. if (_LastError >= ERR_USER_ERROR_FIRST + C_Terminal::ERR_Unknown) 46. { 47. Print("Internal failure in the messaging system..."); 48. ChartClose(user00); 49. } 50. (*control).SetBuff(m_RatesTotal, m_Buff); 51. } 52. //+------------------------------------------------------------------+ 53. void OnDeinit(const int reason) 54. { 55. switch (reason) 56. { 57. case REASON_TEMPLATE: 58. Print("Modified template. Replay/simulation system shutting down."); 59. case REASON_INITFAILED: 60. case REASON_PARAMETERS: 61. case REASON_REMOVE: 62. case REASON_CHARTCLOSE: 63. ChartClose(user00); 64. break; 65. } 66. delete control; 67. } 68. //+------------------------------------------------------------------+
控制指标的源代码
请注意第 10 行,我们在该行告诉 MQL5 我们将需要一个缓冲区,并注意第 9 行,我们在该行指定我们不会在图表上显示任何信息。这意味着缓冲区将位于指标的内部并且对用户不可见,但任何知道如何使用它的代码都可以访问。
因为我们将使用缓冲区,所以我们需要声明它。我们首先在第 18 行执行此操作,然后在第 31 行声明缓冲区,以便可以在指标外部访问它。在第 32 行中,我们确保缓冲区完全为空。现在请密切关注以下内容,因为它很重要。
当用户与 MetaTrader 5 平台交互时(例如通过更改时间框架),MetaTrader 5 会清除图表中的所有内容,然后重新加载。在这种情况下,所有代码都将被重置。对于任何指标来说,这意味着对 OnInit 事件的再次调用,该事件执行其重新初始化的整个过程。我们的控制指标实际上什么也不做,或者更确切地说,它不执行任何具体的计算。其唯一的功能是向用户提供与用于以自定义交易品种在图表上显示柱形的服务进行交互和控制的方法。
因此,当用户改变时间框架时,指标内的所有值都将丢失。我们需要确保一切都以适当的方式进行。指标控制的服务不知道图表上发生了什么,就像指标不知道服务在做什么一样。告知服务和指标各自正在做什么的方式是在它们之间交换消息。
以前,这是通过全局终端变量完成的。但现在我们正在创建一种不同的方式,我们需要确保无论用户在图表上的操作如何,指标和服务都保持一致并了解正在发生的事情。因此,为了向服务通知指标状态,我们使用指标缓冲区,在那里我们放置了与其中发生的进程相关的所有信息。
指标通过输入参数(即第 16 行声明的参数)以及指标的 OnChartEvent 调用中处理的用户事件了解服务的作用。
在数据已经出现在图表上之后,通过输入参数将数据传递给指标是不切实际的。我并不是说这做不到,而是这不切实际,我们不会这么做。一旦服务将指标放置在图表上,您就将失去通过输入参数与其交互的能力。这就是为什么在这里要使用自定义事件。
现在更详细地进行说明,当用户改变时间框架时,指标会丢失有关之前状态和偏移位置的信息。但是,服务中有此信息。但是我们如何确保服务能够将这些数据传递给指标以保持其完整性呢?服务不知道 MetaTrader 5 何时在图表上恢复指标,但服务可以看到指标缓冲区。这就是主要的技巧所在。
当指标放置在图表上时,其缓冲区最初设置为零。当执行 C_Controls 类的构造函数时,在第 150 行初始化一个特定的值(要理解这一点,请查看类代码)。但是,直到调用 OnChartEvent 时,该值才会写入缓冲区,然后在指标代码的第 50 行更新缓冲区。
因此,当 MetaTrader 5 在图表上恢复控制指标后,服务读取缓冲区时,它将看到零值或异常值。此时,将为 MetaTrader 5 启动一个特殊事件,并且服务将通知指标有关更新的值,以便它们正确显示在屏幕上。这将确保按钮和滑块正确显示。
如果我们试图以其他方式做到这一点,我们就必须想出恢复丢失数据的方法。我们最终会创建具有相同结果的不同解决方案:指标初始化。然而,其中一些解决方案可能容易受到用户的操纵,从而使服务和指标之间的通信控制变得复杂。我们的解决方案创建了一个额外的安全层,同时确保信息仅可供实际需要读取和使用的部分访问。
我刚才解释的内容对大多数人来说可能显得非常困惑和陌生,尤其是那些刚刚开始编程的人。消息交换和受控初始化的想法可能是许多人从未听说过的。那么,我们如何证明这在实践中是有效的呢?为此,我们使用控制指标和鼠标指标。但在我们看到真正的系统运行之前,我们需要创建一些东西来演示和理解这个想法本身。
为此,我们将使用更简单但足够有效的代码来阐明主要概念。该代码如下所示。
01. //+------------------------------------------------------------------+ 02. #property service 03. #property copyright "Daniel Jose" 04. #property description "Data synchronization demo service." 05. #property version "1.00" 06. //+------------------------------------------------------------------+ 07. #include <Market Replay\Defines.mqh> 08. //+------------------------------------------------------------------+ 09. #define def_IndicatorControl "Indicators\\Market Replay.ex5" 10. #resource "\\" + def_IndicatorControl 11. //+------------------------------------------------------------------+ 12. input string user00 = "BOVA11"; //Symbol 13. //+------------------------------------------------------------------+ 14. #define def_Loop ((!_StopFlag) && (ChartSymbol(id) != "")) 15. //+------------------------------------------------------------------+ 16. void OnStart() 17. { 18. uCast_Double info; 19. long id; 20. int handle, iPos, iMode; 21. double Buff[]; 22. 23. SymbolSelect(user00, true); 24. id = ChartOpen(user00, PERIOD_H1); 25. if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "::" + def_IndicatorControl, id)) != INVALID_HANDLE) 26. ChartIndicatorAdd(id, 0, handle); 27. IndicatorRelease(handle); 28. if ((handle = iCustom(ChartSymbol(id), ChartPeriod(id), "\\Indicators\\Mouse Study.ex5", id)) != INVALID_HANDLE) 29. ChartIndicatorAdd(id, 0, handle); 30. IndicatorRelease(handle); 31. Print("Service maintaining sync state..."); 32. iPos = 0; 33. iMode = INT_MIN; 34. while (def_Loop) 35. { 36. while (def_Loop && ((handle = ChartIndicatorGet(id, 0, "Market Replay Control")) == INVALID_HANDLE)) Sleep(50); 37. info.dValue = 0; 38. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) info.dValue = Buff[0]; 39. IndicatorRelease(handle); 40. if (info._int[0] == INT_MIN) 41. { 42. info._int[0] = iPos; 43. info._int[1] = iMode; 44. EventChartCustom(id, evCtrlReplayInit, 0, info.dValue, ""); 45. }else if (info._int[1] != 0) 46. { 47. iPos = info._int[0]; 48. iMode = info._int[1]; 49. } 50. Sleep(250); 51. } 52. ChartClose(id); 53. Print("Finished service..."); 54. } 55. //+------------------------------------------------------------------+
演示服务的源代码
注意第 10 行,我们将控制指标转换为服务资源。因此,没有必要将其列入指标清单,因为它对我们的系统以外的任何东西都没有用处。在第 12 行,我们指示要测试系统的交易品种。确保使用有效交易品种,因为稍后将不再检查其有效性。在第 14 行,我们有一个定义,用于检查一些条件以便能够正确关闭服务。
在第 23 行中,我们将交易品种放置在市场观察窗口中,以防它不存在。在第 24 行,我们打开一个图形窗口,其中包含用户(在本例中为您)指定的交易品种。完成后,我们将获得一个图表 ID,并且可以使用它在图表上放置指标。
因此,我们在第 25 行将控制指标放置在新打开的图表上。然后,在第 29 行,我们添加鼠标指标。请注意,鼠标指示器会出现在指标列表中,但控制指标不会。然而,我们需要两者才能进行全面测试。
在第 31 行,我们通知终端该服务将处于活动状态并将监视图表上发生的情况。
此时鼠标指标应该已经可以在图表上看到。然而,控制指标将不可见,尽管它将被列在图表上的指标之中。我之前已经解释过如何初始化控制指标。此时,它的缓冲区将包含对我们没有意义的随机值。因此,我们无法与其互动。但是如果控制指标被正确初始化,并且缓冲区已经被写入,我们将得到一个特定的值。因此,在第 34 行,我们进入一个循环,只要满足第 14 行定义的条件,循环就会继续。
请注意,在第 36 行我们检查控制指标是否实际添加到图表中。但是为什么我们要执行这个检查并等待它完成呢?原因是代码的执行速度比实际发生的速度快得多。因此,我们需要以某种方式让 MetaTrader 5 稳定局面,为此,我们在第 36 行中使用了这个循环。
一旦一切正常,我们就尝试读取控制指标缓冲区。再次提醒您,指标不会显示在图表上。
如果缓冲区读取成功,一些非零值将被添加到 info.dValue 变量中。此时我们可以检查控制指标的状态。在第 40 行,我们检查它是否已经初始化。由于这是第一次,我们看到指标尚未初始化。在第 42 和 43 行中,我们生成一个值传递给指标,并向 MetaTrader 5 发送请求以在图表上生成自定义事件。该事件显示在第 44 行,我们在该行向控制指标传递一条消息以对其进行初始化。
在任何其他时刻,我们都会检查指标是否处于暂停或播放状态。如果它处于这些状态之一,第 45 行将允许用户指定的值保存在指标中。因此,当用户改变时间框架时,第 40 行的检查将返回一个 true 值,并且服务中存储的值将传回指标,即使时间框架发生变化,也允许其正确初始化。
最后,我们使用第 52 行关闭图表,并在第 53 行打印有关服务终止的消息。
如果您还不想尝试的话,您可以在下面的视频中看到系统的运行。我还提供了附加的可执行文件,以便您可以看到它在实践中是如何工作的。
演示视频
结论
在这篇文章中,我们已经开始为未来文章的内容奠定基础。我知道这些材料很密集,所以要冷静地学习,因为再深入一点,一切都会变得更加复杂。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11988
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
使用MQL5开发基于震荡区间突破策略的EA
您应当知道的 MQL5 向导技术(第 18 部分):配合本征向量进行神经架构搜索
自适应社会行为优化(ASBO):Schwefel函数与Box-Muller方法
神经网络变得简单(第 88 部分):时间序列密集编码器(TiDE)