
开发回放系统(第 54 部分):第一个模块的诞生
概述
在上一篇文章"开发回放系统(第 53 部分):事情变得复杂 (五)"中,我解释了一些概念,这些概念从现在起将成为我们编程的一部分,我们将在 MQL5 和 MetaTrader 5 平台中使用这些概念。我意识到,其中许多概念对大多数读者来说都是新的,我也知道,任何有系统编程经验的人(如 Windows 系统编程人员)都会熟悉这些概念。
因此,如果你真的想深入了解我将要向你展示的内容的原因和工作原理,我建议你学习一点 Windows 编程,了解程序之间是如何交换信息的。文章中的这些信息将使我们远离我真正想展示的内容:如何在更高级的水平上开发和使用 MQL5 和 MetaTrader 5。
在 Windows 环境中找到使用此消息传递进行通信的程序不会有任何问题,但正确理解它们需要一些先验知识和扎实的 C 语言编程基础。如果你没有这些知识,我的建议是从学习 C 语言编程开始,然后学习 Windows 中程序之间如何交换消息。这样,就可以为理解我们的下一步工作奠定广泛而坚实的基础。
使事情成为可能
如果你仔细阅读了上一篇文章,你可能会注意到我在很长一段时间内都在努力做某事。然而,即使所有这些元素都部分起作用,它们也不能在更大的系统中共存,或者至少不能以事物发展的方式共存。
也许我最大的错误是几周来忽略了我们的应用程序对 MetaTrader 5 平台中的事件的响应,但错误是不同的:我认为 MetaTrader 5 不是一个平台,而是一个简单的程序,其他程序将在其中运行。
我看到 MetaTrader 5 时的这种缺陷是由于其他平台没有给我们提供与 MetaTrader 5 相同的灵活性。因此,在以更高级的方式开发应用程序时,我损失了一些时间和速度。然而,由于这个回放/模拟器系统已被证明更适合以模块化方式开发(与许多人通常所做的不同),我遇到了一些问题。但这并不是问题所在,而是我忽略了的东西。
您可以在上一篇文章中的视频中看到,MetaTrader 5 提供的功能比许多人探索的要多得多。但今天,我们将把曾经只是一个梦想的东西变成可以实现的东西。我们将开始减少编程,增加构建。从本文开始,我们将探索消息交换,以使 MetaTrader 5 为我们更加努力地工作,只关注使应用程序与图表上的其他内容和谐地工作。
我们要做的第一件事是调整鼠标指标,以开始 MetaTrader 5 应用程序开发的新阶段。
由于代码将发生相当大的变化,因此其中的许多部分都必须更改。但是,如果您一直遵循所有步骤,那么进行这些更改将不会有任何问题。
因此,我们将立即为将来出现的所有应用程序创建一个通用文件。这将使我们能够处理从现在开始可以由我们创建的任何代码处理的消息。我们将控制当两个具有消息处理程序的应用程序出现在图表上时会发生什么。这样,每个应用程序都能正确处理消息。
该文件的初始内容如下所示。它应该以 Defines.mqh 的名称保存。它的位置很快就会显示出来。所以,如果你对编程一无所知,我很抱歉,但从现在开始,你将无法理解我要实现的东西。从这一刻起,你面前有一道屏障,阻止你继续前进。如果你真的想使用我们今天要介绍的内容,你必须具备一些基本的编程知识。
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. int _int[sizeof(double) / sizeof(int)]; // 2 Informations 22. char _char[sizeof(double) / sizeof(char)]; // 8 Informations 23. }; 24. //+------------------------------------------------------------------+ 25. enum EnumEvents { 26. evHideMouse, //Hide mouse price line 27. evShowMouse, //Show mouse price line 28. evHideBarTime, //Hide bar time 29. evShowBarTime, //Show bar time 30. evHideDailyVar, //Hide daily variation 31. evShowDailyVar, //Show daily variation 32. evHidePriceVar, //Hide instantaneous variation 33. evShowPriceVar, //Show instantaneous variation 34. evSetServerTime, //Replay/simulation system timer 35. evCtrlReplayInit //Initialize replay control 36. }; 37. //+------------------------------------------------------------------+
Defines.mqh 文件源代码
在第13行中,我们定义了将在自定义交易品种中使用的名称,以及将由回放/模拟器服务使用的名称。在第 14 行中,我们声明了一些仅供控制指标使用的内容。
这两行以及第 16 行和第 23 行之间的内容已经是之前编写的代码的一部分。不过,由于 InterProcess.mqh 头文件将不复存在,因此有必要将这些信息移到定义文件中。
我们真正感兴趣的是从第 25 行开始。我们将在此声明了一个列表,随着新事件的推出和增加,这个列表也将不断扩大。虽然有一点危险,但如果采取适当的预防措施,危险并不严重:一定要在列表末尾添加一个枚举。这样做的话,就不必重新编译旧代码,但如果在列表中间添加内容,就必须重新编译所有旧代码。这将有助于避免出现问题。
请注意,在每一行中,我们定义了一个值,并对相应的事件给出了简短的注释。
您将看到,当代码显示时,它们将指示这些事件将在何处以及如何发生。但最主要的是只在代码中可见的东西。因此,如果您打算使用我展示的任何内容,请查看消息处理程序。
有了这个,我们开始使整个系统完全模块化。从现在开始,请密切关注代码和图表上显示的内容:是时候像真正的专业人士一样使用 MetaTrader 5 了。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Macros.mqh" 05. #include "..\Defines.mqh" 06. #include "Interprocess.mqh" 07. //+------------------------------------------------------------------+ 08. class C_Terminal 09. { 10. //+------------------------------------------------------------------+ 11. protected: 12. enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance}; 13. //+------------------------------------------------------------------+ 14. struct st_Terminal
C_Terminal.mqh 文件中的代码
请仔细观察第 5 行,该行指定了上述 Defines.mqh 头文件相对于 C_Terminal.mqh 头文件的位置。但我想让你们注意的还不止这些。请注意,第 6 行已从代码中删除,这意味着您现在可以从项目中删除 InterProcess.mqh 头文件,因为它将不再被使用。
由于这一修改相当简单,而且 C_Terminal.mqh 文件代码未作其他修改,因此我认为没有必要复制整个文件。虽然没有重大变化,但需要提到一点,否则在编译将显示的代码时会遇到问题。
第 12 行包含了一个已被赋予新值的枚举。在测试过程中,应使用它来检查图表上是否已经存在相同的指标。因此,有必要对同一头文件 C_Terminal.mqh 再做一个小改动。以下代码显示了这一修改。
157. //+------------------------------------------------------------------+ 158. bool IndicatorCheckPass(const string szShortName) 159. { 160. string szTmp = szShortName + "_TMP"; 161. 162. if (_LastError != ERR_SUCCESS) return false; 163. IndicatorSetString(INDICATOR_SHORTNAME, szTmp); 164. if (ChartWindowFind(m_Infos.ID, szShortName) != -1) 165. { 166. ChartIndicatorDelete(m_Infos.ID, 0, szTmp); 167. Print("Only one instance is allowed..."); 168. SetUserError(C_Terminal::ERR_NoMoreInstance); 169. 170. return false; 171. } 172. IndicatorSetString(INDICATOR_SHORTNAME, szShortName); 173. ResetLastError(); 174. 175. return true; 176. } 177. //+------------------------------------------------------------------+
C_Terminal.mqh 文件中需要更改的部分
我们需要用片段 2 中显示的代码替换 C_Terminal.mqh 文件中的原始函数的修改。因此,在测试过程中,系统将正确设置 _LastError 常量,表明发生了错误。造成这一错误的原因是图表上存在同一指标的另一个实例。除了这两处简单的改动外,我们没有做其他修改,因此可以继续开发鼠标指标类。
以下是 C_Mouse.mqh 头文件的完整代码。由于正确理解代码对某些人来说可能很困难,我将简要解释一下发生了什么。然后你可以更好地看到一切是如何开始模块化的。
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. int X_Adjusted, 022. Y_Adjusted, 023. X_Graphics, 024. Y_Graphics; 025. double Price; 026. datetime dt; 027. }Position; 028. uint ButtonStatus; 029. bool ExecStudy; 030. datetime TimeDevice; 031. }; 032. //+------------------------------------------------------------------+ 033. protected: 034. //+------------------------------------------------------------------+ 035. void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const 036. { 037. if (m_Mem.szShortName != NULL) return; 038. CreateObjectGraphics(szName, OBJ_BUTTON, clrNONE); 039. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 042. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 043. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 044. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 046. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 047. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 048. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 049. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 050. } 051. //+------------------------------------------------------------------+ 052. private : 053. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 054. struct st01 055. { 056. st_Mouse Data; 057. color corLineH, 058. corTrendP, 059. corTrendN; 060. eStudy Study; 061. }m_Info; 062. struct st_Mem 063. { 064. bool CrossHair, 065. IsFull; 066. datetime dt; 067. string szShortName; 068. }m_Mem; 069. bool m_OK; 070. //+------------------------------------------------------------------+ 071. void GetDimensionText(const string szArg, int &w, int &h) 072. { 073. TextSetFont("Lucida Console", -100, FW_NORMAL); 074. TextGetSize(szArg, w, h); 075. h += 5; 076. w += 5; 077. } 078. //+------------------------------------------------------------------+ 079. void CreateStudy(void) 080. { 081. if (m_Mem.IsFull) 082. { 083. CreateObjectGraphics(def_NameObjectLineV, OBJ_VLINE, m_Info.corLineH); 084. CreateObjectGraphics(def_NameObjectLineT, OBJ_TREND, m_Info.corLineH); 085. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_WIDTH, 2); 086. CreateObjToStudy(0, 0, def_NameObjectStudy); 087. } 088. m_Info.Study = eStudyCreate; 089. } 090. //+------------------------------------------------------------------+ 091. void ExecuteStudy(const double memPrice) 092. { 093. double v1 = GetInfoMouse().Position.Price - memPrice; 094. int w, h; 095. 096. if (!CheckClick(eClickLeft)) 097. { 098. m_Info.Study = eStudyNull; 099. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 100. if (m_Mem.IsFull) ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName + "T"); 101. }else if (m_Mem.IsFull) 102. { 103. string sz1 = StringFormat(" %." + (string)GetInfoTerminal().nDigits + "f [ %d ] %02.02f%% ", 104. MathAbs(v1), Bars(GetInfoTerminal().szSymbol, PERIOD_CURRENT, m_Mem.dt, GetInfoMouse().Position.dt) - 1, MathAbs((v1 / memPrice) * 100.0))); 105. GetDimensionText(sz1, w, h); 106. ObjectSetString(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_TEXT, sz1); 107. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 108. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XSIZE, w); 109. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YSIZE, h); 110. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 111. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectStudy, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 112. ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 113. ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 114. } 115. m_Info.Data.ButtonStatus = eKeyNull; 116. } 117. //+------------------------------------------------------------------+ 118. public : 119. //+------------------------------------------------------------------+ 120. C_Mouse(const long id, const string szShortName) 121. :C_Terminal(id), 122. m_OK(false) 123. { 124. m_Mem.szShortName = szShortName; 125. } 126. //+------------------------------------------------------------------+ 127. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 128. :C_Terminal(id) 129. { 130. if (!(m_OK = IndicatorCheckPass(szShortName))) SetUserError(C_Terminal::ERR_Unknown); 131. if (_LastError != ERR_SUCCESS) return; 132. m_Mem.szShortName = NULL; 133. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 134. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 135. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 136. ZeroMemory(m_Info); 137. m_Info.corLineH = corH; 138. m_Info.corTrendP = corP; 139. m_Info.corTrendN = corN; 140. m_Info.Study = eStudyNull; 141. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 142. CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 143. } 144. //+------------------------------------------------------------------+ 145. ~C_Mouse() 146. { 147. if (!m_OK) return; 148. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 149. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, false); 150. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 151. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 152. } 153. //+------------------------------------------------------------------+ 154. inline bool CheckClick(const eBtnMouse value) 155. { 156. return (GetInfoMouse().ButtonStatus & value) == value; 157. } 158. //+------------------------------------------------------------------+ 159. inline const st_Mouse GetInfoMouse(void) 160. { 161. if (m_Mem.szShortName != NULL) 162. { 163. double Buff[]; 164. uCast_Double loc; 165. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 166. 167. ZeroMemory(m_Info.Data); 168. if (CopyBuffer(handle, 0, 0, 6, Buff) == 6) 169. { 170. m_Info.Data.Position.Price = Buff[0]; 171. loc.dValue = Buff[1]; 172. m_Info.Data.Position.dt = loc._datetime; 173. loc.dValue = Buff[2]; 174. m_Info.Data.Position.X_Adjusted = loc._int[0]; 175. m_Info.Data.Position.Y_Adjusted = loc._int[1]; 176. loc.dValue = Buff[3]; 177. m_Info.Data.Position.X_Graphics = loc._int[0]; 178. m_Info.Data.Position.Y_Graphics = loc._int[1]; 179. loc.dValue = Buff[4]; 180. m_Info.Data.ButtonStatus = loc._char[0]; 181. m_Info.Data.TimeDevice = (datetime)Buff[5]; 182. IndicatorRelease(handle); 183. } 184. } 185. 186. return m_Info.Data; 187. } 188. //+------------------------------------------------------------------+ 189. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 190. { 191. int w = 0; 192. static double memPrice = 0; 193. 194. if (m_Mem.szShortName == NULL) 195. { 196. C_Terminal::DispatchMessage(id, lparam, dparam, sparam); 197. switch (id) 198. { 199. case (CHARTEVENT_CUSTOM + evHideMouse): 200. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE); 201. break; 202. case (CHARTEVENT_CUSTOM + evShowMouse): 203. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH); 204. break; 205. case (CHARTEVENT_CUSTOM + evSetServerTime): 206. m_Info.Data.TimeDevice = (datetime)dparam; 207. break; 208. case CHARTEVENT_MOUSE_MOVE: 209. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (int)lparam, m_Info.Data.Position.Y_Graphics = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 210. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price)); 211. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 212. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X_Adjusted, m_Info.Data.Position.Y_Adjusted); 213. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0); 214. m_Info.Data.ButtonStatus = (uint) sparam; 215. if (CheckClick(eClickMiddle)) 216. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 217. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 218. { 219. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 220. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 221. m_Info.Study = eStudyExecute; 222. } 223. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 224. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 225. break; 226. case CHARTEVENT_OBJECT_DELETE: 227. if ((m_Mem.IsFull) && (sparam == def_NameObjectLineH)) CreateObjectGraphics(def_NameObjectLineH, OBJ_HLINE, m_Info.corLineH); 228. break; 229. } 230. } 231. } 232. //+------------------------------------------------------------------+ 233. }; 234. //+------------------------------------------------------------------+ 235. #undef def_NameObjectLineV 236. #undef def_NameObjectLineH 237. #undef def_NameObjectLineT 238. #undef def_NameObjectStudy 239. //+------------------------------------------------------------------+
C_Mouse.mqh 文件的源代码
与以前的版本不同,现在只需要 C_Terminal.mqh 文件。但是,您应该注意这个类中更改的其他细节,如上所示。
如果你注意到了,我们现在在第 30 行有了一个新变量。它只有在使用回放/模拟器服务时才有用。此变量使我们可以访问回放/模拟器服务提供的值,该值之前是通过全局终端变量获得的。虽然该变量是在第 30 行中声明的,但在其他地方也会用到。但由于我们稍后将解释的另一个问题,我们不得不在 C_Mouse 类中添加它。
正如你所看到的,大部分代码都与前一个代码相同,直到第 168 行,我们才发现了一些不同之处。现在,我们将使用六个缓冲区位置。第六个位置应由第 30 行所声明的变量占据。因此,在第 181 行中,当我们需要读取并找出该变量的值时,我们会填入该值。在这里,我必须停下来解释一下。实际使用第 30 行中声明的变量内容的程序只有回放/模拟器服务和鼠标指标。后者将使用该值通知用户下一个柱形打开前的剩余时间。不过,如果我们不想使用它,可以将其从鼠标指标或回放/模拟器服务中移除。这样做不会有任何问题。如果您想使用这些信息,请注意,除非我们使用全局终端变量或其他方法将时间值存储在回放/模拟器服务中,否则该服务将无法知道何时应再次更新这些数据。
因此,我们将该值放入鼠标指标缓冲区。但随后你会想:"用户更改图表时间框架后,当 MetaTrader 5 在图表上恢复指标时,该值不会被删除和重置吗?那么将其存储在指标缓冲区中就没有意义了。事实上,这正是我们想要的。当服务检测到缓冲区中的值不再有效时,就会向鼠标指标发送一个事件,以再次更新该值。通过这种方式,我们将保持一切如预期。
也就是说,MetaTrader 5 强制指标将缓冲区清零的事实使回放/模拟器服务明白,用户做出了一些需要回放/模拟器服务重新评估的更改。这样,我们的应用程序就能很容易地检测到发生的情况,而 MetaTrader 5 会为我们做一切。
但是,如果我们看一下从第 189 行开始的代码,这个问题就变得更加有趣了,因为 C_Mouse 类的消息处理函数就位于该行。
在这个消息处理函数中,我们的模块化系统将开始处理前三条消息。因此,请关注正在发生的事情。首先,为了确保消息处理是由指标完成的,而不是由使用该类的其他代码完成的,我们必须检查我们调用的是否正是指标。该检查在第 194 行执行。如果检查成功,我们就认为我们在处理一个指标,因此我们只需要在图表上设置一个鼠标指标,以避免潜在的利益冲突。我曾在其他文章中介绍过这个问题,现在我们继续往下看。
在第 199 行中,我们处理鼠标指标应隐藏价格线的事件。
在第 202 行中,我们要处理一个事件,告诉鼠标指标价格线应该再次显示在图表上。这样,任何应用程序都能告诉鼠标指标何时显示或隐藏其使用的价格线。
在这两个事件中,无需指定任何其他参数,因此任何想要显示或隐藏鼠标线的应用程序只需使用指定值生成一个自定义事件,并将该事件指向鼠标指标所在的图表即可。稍后我将对此进行更多解释。目前,我们可以这样理解:如果在某个时候图表上出现鼠标指标,而你想隐藏鼠标线,那么为了隐藏或显示鼠标线,你需要生成一个具有所需值的自定义事件。例如,该事件可由 EA 交易触发。
第三个事件,我们也将在这里实现,如第 205 行所示。在这种情况下,我们指定应放置为服务运行时间的值,即实际执行此操作的是回放/模拟器服务。因为只有服务才能在生成此类事件时占据一定优势。但有一点很重要:当这个事件被触发时,它必须指定一个时间值。该值必须包含在 "dparam" 参数中,即一个 double 型数值。
同样,你需要从更广阔的角度看待问题。double 型数值由 8 个字节组成,就像同样由 8 个字节组成的 datetime 型值一样。因此,我们要进行类型转换,以便编译器理解我们在做什么。但对处理器来说,我们所做的只是将 8 个字节放入一个变量,而这些字节的内容并不重要。
理解这一点很重要,因为在有些情况下,我们需要传递整个字符串值,有时甚至是整个结构,而在 MetaTrader 5 中,或者更准确地说,在 MQL5 中,我们不能像在 C/C++ 中那样使用指针,因此我们需要一些技巧来在 MetaTrader 5 中运行的应用程序之间传递这些数据。
好了,第一部分工作已经完成。但我们仍然会把事情做得更好。如果你留心观察,可能会发现还有其他事件与鼠标指标相关联。不过,这些事件并不在头文件 C_Mouse.mqh 中,而是在文件 C_Study.mqh 中。要查看这些事件并了解会发生什么,让我们来看看这段代码。完整代码如下所示:
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. }m_Info; 025. //+------------------------------------------------------------------+ 026. const datetime GetBarTime(void) 027. { 028. datetime dt; 029. int i0 = PeriodSeconds(); 030. 031. if (m_Info.Status == eInReplay) 032. { 033. if ((dt = GetInfoMouse().TimeDevice) == ULONG_MAX) return ULONG_MAX; 034. }else dt = TimeCurrent(); 035. if (m_Info.Rate.time <= dt) 036. m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0; 037. 038. return m_Info.Rate.time - dt; 039. } 040. //+------------------------------------------------------------------+ 041. void Draw(void) 042. { 043. double v1; 044. 045. if (m_Info.bvT) 046. { 047. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 048. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo); 049. } 050. if (m_Info.bvD) 051. { 052. v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / GetInfoMouse().Position.Price) * 100.0), 2); 053. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 054. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 055. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 056. } 057. if (m_Info.bvP) 058. { 059. v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0)) * 100.0), 2); 060. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 061. ObjectSetInteger(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 062. ObjectSetString(GetInfoTerminal().ID, def_ExpansionBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 063. } 064. } 065. //+------------------------------------------------------------------+ 066. inline void CreateObjInfo(EnumEvents arg) 067. { 068. switch (arg) 069. { 070. case evShowBarTime: 071. C_Mouse::CreateObjToStudy(2, 110, def_ExpansionBtn1, clrPaleTurquoise); 072. m_Info.bvT = true; 073. break; 074. case evShowDailyVar: 075. C_Mouse::CreateObjToStudy(2, 53, def_ExpansionBtn2); 076. m_Info.bvD = true; 077. break; 078. case evShowPriceVar: 079. C_Mouse::CreateObjToStudy(58, 53, def_ExpansionBtn3); 080. m_Info.bvP = true; 081. break; 082. } 083. } 084. //+------------------------------------------------------------------+ 085. inline void RemoveObjInfo(EnumEvents arg) 086. { 087. string sz; 088. 089. switch (arg) 090. { 091. case evHideBarTime: 092. sz = def_ExpansionBtn1; 093. m_Info.bvT = false; 094. break; 095. case evHideDailyVar: 096. sz = def_ExpansionBtn2; 097. m_Info.bvD = false; 098. break; 099. case evHidePriceVar: 100. sz = def_ExpansionBtn3; 101. m_Info.bvP = false; 102. break; 103. } 104. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 105. ObjectDelete(GetInfoTerminal().ID, sz); 106. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 107. } 108. //+------------------------------------------------------------------+ 109. public : 110. //+------------------------------------------------------------------+ 111. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 112. :C_Mouse(IdParam, szShortName, corH, corP, corN) 113. { 114. if (_LastError != ERR_SUCCESS) return; 115. ZeroMemory(m_Info); 116. m_Info.Status = eCloseMarket; 117. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 118. m_Info.corP = corP; 119. m_Info.corN = corN; 120. CreateObjInfo(evShowBarTime); 121. CreateObjInfo(evShowDailyVar); 122. CreateObjInfo(evShowPriceVar); 123. } 124. //+------------------------------------------------------------------+ 125. void Update(const eStatusMarket arg) 126. { 127. datetime dt; 128. 129. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 130. { 131. case eCloseMarket : 132. m_Info.szInfo = "Closed Market"; 133. break; 134. case eInReplay : 135. case eInTrading : 136. if ((dt = GetBarTime()) < ULONG_MAX) 137. { 138. m_Info.szInfo = TimeToString(dt, TIME_SECONDS); 139. break; 140. } 141. case eAuction : 142. m_Info.szInfo = "Auction"; 143. break; 144. default : 145. m_Info.szInfo = "ERROR"; 146. } 147. Draw(); 148. } 149. //+------------------------------------------------------------------+ 150. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 151. { 152. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 153. switch (id) 154. { 155. case CHARTEVENT_CUSTOM + evHideBarTime: 156. RemoveObjInfo(evHideBarTime); 157. break; 158. case CHARTEVENT_CUSTOM + evShowBarTime: 159. CreateObjInfo(evShowBarTime); 160. break; 161. case CHARTEVENT_CUSTOM + evHideDailyVar: 162. RemoveObjInfo(evHideDailyVar); 163. break; 164. case CHARTEVENT_CUSTOM + evShowDailyVar: 165. CreateObjInfo(evShowDailyVar); 166. break; 167. case CHARTEVENT_CUSTOM + evHidePriceVar: 168. RemoveObjInfo(evHidePriceVar); 169. break; 170. case CHARTEVENT_CUSTOM + evShowPriceVar: 171. CreateObjInfo(evShowPriceVar); 172. break; 173. case CHARTEVENT_MOUSE_MOVE: 174. Draw(); 175. break; 176. } 177. ChartRedraw(GetInfoTerminal().ID); 178. } 179. //+------------------------------------------------------------------+ 180. }; 181. //+------------------------------------------------------------------+ 182. #undef def_ExpansionBtn3 183. #undef def_ExpansionBtn2 184. #undef def_ExpansionBtn1 185. #undef def_ExpansionPrefix 186. #undef def_MousePrefixName 187. //+------------------------------------------------------------------+
C_Study.mqh 文件源代码
与 C_Mouse.mqh 不同,这里有许多不同之处。让我们从现在正在实现的新变量、检查和其他东西开始。不过,由于其中大部分都很简单,我们将只介绍其中几个最 "不寻常" 的。其中第 33 行,我们可以看到访问终端上全局变量的函数已不再使用。现在,我们要求鼠标指标查找之前在终端全局变量中搜索到的内容。这正是我们想要做的事情:我们想在不使用用户可以控制的东西的情况下做事情,也不需要求助于外部编程。我们的想法是用纯 MQL5 实现一切。
查看代码,可能并不完全清楚我们的实现比以前有更多的设置。我们这样做是为了让我们的鼠标指标成为一种标准,可以在平台上的其他应用程序中使用。这里有一点很重要:作为程序员,你可以向鼠标指标发送消息,以打开或关闭其中已定义的内容。
为此,我们需要隔离代码的某些部分,这样当发生需要打开或关闭某些功能的事件时,我们正在访问的对象就会发生我们想要的变化。在这种情况下,我们谈论的是一些简单的东西,比如如何让图表上出现或不出现某些东西。好吧,本来可能会有更复杂的事情。我们逐渐引入的自由度使许多事情成为可能。要实现它们,您只需要对已经准备好的内容进行小幅修改。这也意味着应用程序的安全性和可靠性将始终保持在最高水平。
您在 C_Study.mqh 文件中看到的大部分代码正是如此。但现在我们真正感兴趣的是从第 150 行开始发生的事情,在那里我们将实现这个头文件中描述的内容。请注意,我们在此不感兴趣的未处理部分会传递给 C_Mouse 类,以便在那里处理事件。这可以从第 152 行看出。
现在请注意一件事,在该处理程序中可以看到的每个用户事件都会打开或关闭鼠标指标中的某些功能,使其看起来有些不同,但这是在运行时进行的,用户无需重新编译代码或配置一大串选项。请注意这一事实,您只需创建小脚本文件,向鼠标指标发送自定义事件,即可在使用过程中改变其外观。
如果你足够有创造力,你只要看看这段代码及其工作原理,就已经在思考和计划不同的技巧了。但我建议你不要着急,因为现阶段还不完全清楚回放/模拟器系统将朝哪个方向发展。这是因为将不同的元素转化为模块开辟了广泛的可能性。系统的发展方式迫使我们在考虑新的可能性时要格外小心。但就目前而言,我的重点是让系统在不使用终端全局变量的情况下开始工作,同时仍然保持模块最初创建时的质量。
好的,现在,所有这些都已显示,我们可以继续编写实际创建鼠标指标的代码,请看下面。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. #property description "This is an indicator for graphical studies using the mouse." 004. #property description "This is an integral part of the Replay / Simulator system." 005. #property description "However it can be used in the real market." 006. #property version "1.54" 007. #property icon "/Images/Market Replay/Icons/Indicators.ico" 008. #property link "https://www.mql5.com/pt/articles/11971" 009. #property indicator_chart_window 010. #property indicator_plots 0 011. #property indicator_buffers 1 012. //+------------------------------------------------------------------+ 013. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 014. //+------------------------------------------------------------------+ 015. C_Study *Study = NULL; 016. //+------------------------------------------------------------------+ 017. input long user00 = 0; //ID 018. input C_Study::eStatusMarket user01 = C_Study::eAuction; //Market Status 019. input color user02 = clrBlack; //Price Line 020. input color user03 = clrPaleGreen; //Positive Study 021. input color user04 = clrLightCoral; //Negative Study 022. //+------------------------------------------------------------------+ 023. C_Study::eStatusMarket m_Status; 024. int m_posBuff = 0; 025. double m_Buff[]; 026. //+------------------------------------------------------------------+ 027. int OnInit() 028. { 029. ResetLastError(); 030. Study = new C_Study(user00, "Indicator Mouse Study", user02, user03, user04); 031. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 032. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 033. { 034. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 035. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 036. m_Status = C_Study::eCloseMarket; 037. }else 038. m_Status = user01; 039. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 040. ArrayInitialize(m_Buff, EMPTY_VALUE); 041. 042. return INIT_SUCCEEDED; 043. } 044. //+------------------------------------------------------------------+ 045. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 046. { 047. m_posBuff = rates_total - 6; 048. (*Study).Update(m_Status); 049. 050. return rates_total; 051. } 052. //+------------------------------------------------------------------+ 053. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 054. { 055. (*Study).DispatchMessage(id, lparam, dparam, sparam); 056. SetBuffer(); 057. 058. ChartRedraw((*Study).GetInfoTerminal().ID); 059. } 060. //+------------------------------------------------------------------+ 061. void OnBookEvent(const string &symbol) 062. { 063. MqlBookInfo book[]; 064. C_Study::eStatusMarket loc = m_Status; 065. 066. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 067. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 068. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 069. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 070. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 071. if (loc != m_Status) (*Study).Update(m_Status); 072. } 073. //+------------------------------------------------------------------+ 074. void OnDeinit(const int reason) 075. { 076. if (reason != REASON_INITFAILED) 077. { 078. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 079. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 080. } 081. delete Study; 082. } 083. //+------------------------------------------------------------------+ 084. inline void SetBuffer(void) 085. { 086. uCast_Double Info; 087. 088. m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff); 089. m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price; 090. Info._datetime = (*Study).GetInfoMouse().Position.dt; 091. m_Buff[m_posBuff + 1] = Info.dValue; 092. Info._int[0] = (*Study).GetInfoMouse().Position.X_Adjusted; 093. Info._int[1] = (*Study).GetInfoMouse().Position.Y_Adjusted; 094. m_Buff[m_posBuff + 2] = Info.dValue; 095. Info._int[0] = (*Study).GetInfoMouse().Position.X_Graphics; 096. Info._int[1] = (*Study).GetInfoMouse().Position.Y_Graphics; 097. m_Buff[m_posBuff + 3] = Info.dValue; 098. Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0); 099. m_Buff[m_posBuff + 4] = Info.dValue; 100. m_Buff[m_posBuff + 5] = (double)(*Study).GetInfoMouse().TimeDevice; 101. } 102. //+------------------------------------------------------------------+
鼠标指标源代码
代码没有发生重大变化,只有一个新增加的内容,可以在第 100 行看到,数据被放入指标缓冲区中。同样,这些数据仅在回放/模拟器服务的早期阶段有用。它没有其他用途,至少我没注意到还有其他用途。
结论
为了帮助大家理解这里发生的事情,我将附上指标的可执行文件以及一些脚本,当指标出现在图表上时,这些脚本将用来修改指标。但也有一些人不想在他们的平台上运行已经编译好的程序。好吧,我明白为什么。在本文末尾的视频中,您可以看到执行脚本时会发生什么。
请注意,这只是我们能力的一小部分。那些持狭隘观点的人会说这是无稽之谈,本可以采取不同的做法,但我不会理会他们。我更喜欢看到那些思考我所展示的可能性的人眼中闪烁的光芒:这个模块化系统可以成为什么,以及一切将如何进一步发展。正是为了他们,我才写了这些文章。在这些文章中,我展示了许多人只是触及了我们可以用 MQL5 实际做什么的表面,MetaTrader 5 实际上是一个伟大的平台,在正确的人手中,它可以做令人难以置信的事情。
然而,我再次向那些实际上没有编程技能的人道歉。很抱歉,如果不了解此处显示的内容,访问此回放/模拟器服务的源代码将变得危险。但我保证不会让你独自面对这个问题,一旦模块版本完成并稳定下来,我就会向你提供可执行文件,并在文章中提供可执行程序。但是,如果你想编译源代码,它总是可以在文章中找到。但是,我知道,如果没有适当的知识,您将无法创建编译系统所需的目录结构。
这正是我想做的,因为我从自己的经验中看到,在之前的文章中,人们试图在不理解它们在说什么的情况下编译一些东西。这种事情不仅有风险,而且很危险。你不应该在不了解其用途的情况下使用它们。
视频 01 - 模块操作演示
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11971



