
开发回放系统(第 61 部分):玩转服务(二)
概述
在上一篇文章,开发回放系统(第 60 部分):玩转服务(一)中,我们做了一些调整,以使回放/模拟器服务开始在图表上生成新数据。尽管我们只做了最小的改动,让系统开始发布数据,但很快就发现发生了一些不寻常的事情。尽管没有进行重大修改,但该系统似乎遭受了重大挫折。这种情况给人的印象是,该系统已经变得不可行,因为它突然大幅放缓。真的是这样吗?如果是这样,我们如何解决这个问题?重要的是要记住,我们的目标是保持一切与面向对象的编程原则保持一致。
尽管性能确实有所下降,但我们可以通过理解和适当调整代码的某些方面来解决大部分问题。在本文中,我可能会开始演示如何使用 MetaEditor 中提供的一些工具,这些工具极大地促进了改进代码的过程。事后看来,我应该在几篇文章之前介绍这个话题。然而,我没有看到像现在这样的必要性,因为理解代码的操作方式以及为什么它的性能会如此显著地下降是至关重要的。
实施最明显和最直接的改进
对于 MetaTrader 5 和 MQL5 的功能,误解或缺乏深入的解释往往会在某些实现中造成重大障碍。幸运的是,在社区内,我们可以巩固知识并有效地分享,即使它不能立即解决我们当前的实现挑战。无论如何,拥有准确和高质量的知识总是有益的。
其中一个关键方面正是我将要解释的。当您积极使用 MQL5 时,我讨论的大部分内容都更容易掌握,使您在 MetaTrader 5 中完成的任务比大多数开发人员通常实现或尝试的要多得多。
对于许多 MQL5 程序员来说,最容易误解的方面之一可能是图形对象。许多人认为,这些对象只能通过图表上直接存在的东西来访问、操纵和调整:指标、脚本,甚至是 EA 交易系统。但事实并非如此。
到目前为止,我们的工作方式避免了在自定义资产图表窗口中显示的内容与 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 行的枚举以前并不存在,但创建它是为了简化对缓冲区中的数据的访问。您可以通过检查位于第 162 行的 SetBuffer 过程来了解其工作原理。类似的方法被应用于消息处理过程,尽管在这种情况下,实现略有不同。请查看第 182 行和第 186 行之间的内容。但请特别注意第 184 行:它已从原始代码中删除。
回到枚举的主题,观察第 38 行上的枚举与之前的版本相比已被修改。进行此更改是为了提高代码的可读性。例如,第 44 行的变量以前是有符号类型,现在则是无符号类型。这种调整可以实现微小的修改,例如第 152 行所见的修改,或类似于第 186 行所观察到的修改。
所有这些改进都有助于使代码更具可读性,因为目标是引入一种与以前略有不同的方法。
现在,让我们来看看到底要做什么。随着时间的推移,这些修改可能会节省一些 CPU 周期。但首先,让我们了解一个关键方面。第 77 行,我们请求更改图形对象上显示的图像。此对象是一个按钮,指示我们是处于播放模式还是暂停模式。然而,该服务会不断监控控制指标缓冲区,尽管我们可以采取不同的方法来确定我们是处于播放模式还是暂停模式。这种方法直接涉及第 77 行操作的对象。
快速访问按钮状态
如前一节所述,这些简单的更改并没有提供足够大的性能提升,因此无法单独实现。然而,当我们分析服务最需要性能改进的领域时,情况发生了变化。
在上一篇文章中,我展示了在哪些方面需要进行优化。为了刷新你的记忆,关键点在于 LoopEventOnTime。此函数定期调用另一个函数来检查控制指标按钮的状态,并确定我们是处于暂停模式还是播放模式。
最初,通过检查存储在控制指标缓冲区中的数据来执行此验证。然而,存在一种稍微优雅的方法(尽管它引入了额外的复杂性):直接检查控制对象本身。请记住,控制对象是 OBJ_BITAP_LABEL,这是一种具有两种可能状态的对象,我们可以通过检查其中的特定变量来检查它。
通过检查图表上显示的 OBJ_BITMAP_LABEL 对象中特定变量的值,我们可以在确定是播放还是暂停向图表传输数据时,使服务绕过从缓冲区读取。
但是,如果您查看 C_DrawImage.mqh 头文件,您将不会在 OBJ_BITAP_LABEL 对象中找到对所需变量的任何修改。即使在尝试对服务进行任何更改之前,情况也是如此。然而,通过分析 C_Controls.mqh 文件,您会注意到,在第 77 行,发出了更新对象的请求。这为我们实现必要的修改提供了一个起点,以便服务可以利用它们。理论上,这应该会为每次调用节省一些 CPU 周期。
由于这些更改很小,我不会在这里包含整个头文件。相反,打开 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 对象,我们需要稍微修改 C_Replay 类中已经存在的 UpdateIndicatorControl 代码。修改内容可见以下片段:
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 行,我们检查是否有有效的处理程序。如果为 true,则该过程继续。下一步是验证内存值是否与位置匹配,这在第 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 类的可读性。
现在,让我们探索一种真正提高性能的修改。目标是在预期时间内恢复生成一分钟柱形的功能。
起初,拟议的改变可能看起来很奇怪,完全违反直觉。不过,相信我,你可以自己测试一下。这种简单的修改带来了显著的性能提升。为了查看其实际作用,让我们检查一下 C_Replay.mqh 头文件的完整代码。以下是其完整版本。行号将与前面的代码片段略有不同,但不要关注这一点。让我们研究一下代码。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12121

