开发回放系统(第 44 部分):Chart Trader 项目(三)
概述
在上一篇文章开发回放系统(第 43 部分):Chart Trader 项目 (二) 中,我解释了如何操作模板数据以便在 OBJ_CHART 中使用。在那篇文章中,我只是概述了这一主题,并没有深入探讨细节,因为在那个版本中,这项工作是以非常简单的方式完成的。这样做是为了更容易解释内容,因为尽管很多事情表面上很简单,但其中有些并不那么明显,如果不了解最简单、最基本的部分,就无法真正理解全局。
因此,尽管这段代码可以工作(正如我们已经看到的那样),但它仍然无法让我们做某些事情。换句话说,除非在数据建模方面有所改进,否则执行某些任务将更加困难。上述改进涉及到更复杂的编码,但使用的概念是相同的。只是代码会稍微更复杂一些。
除了这一点,我们还要解决一个问题。如果你注意到了,我在文章中也提到过,这段代码的效率并不高,因为在我看来,它包含了太多的设置调用。为了解决这个问题,我们将对代码做一些小改动,以大幅减少调用次数,同时进行更充分的数据建模。
新类的诞生:C_AdjustTemplate
为了实现必要的改进,我们必须创建一个新的类。其完整代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "../Auxiliar/C_Terminal.mqh" 05. //+------------------------------------------------------------------+ 06. class C_AdjustTemplate 07. { 08. private : 09. string m_szName[]; 10. string m_szFind[]; 11. string m_szReplace[]; 12. string m_szFileName; 13. int m_maxIndex; 14. int m_FileIn; 15. int m_FileOut; 16. bool m_bSimple; 17. public : 18. //+------------------------------------------------------------------+ 19. C_AdjustTemplate(const string szFileNameIn, string szFileNameOut = NULL) 20. :m_maxIndex(0), 21. m_szFileName(szFileNameIn), 22. m_FileIn(INVALID_HANDLE), 23. m_FileOut(INVALID_HANDLE) 24. { 25. ResetLastError(); 26. if ((m_FileIn = FileOpen(szFileNameIn, FILE_TXT | FILE_READ)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess); 27. if (_LastError == ERR_SUCCESS) 28. { 29. if (!(m_bSimple = (StringLen(szFileNameOut) > 0))) szFileNameOut = szFileNameIn + "_T"; 30. if ((m_FileOut = FileOpen(szFileNameOut, FILE_TXT | FILE_WRITE)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess); 31. } 32. } 33. //+------------------------------------------------------------------+ 34. ~C_AdjustTemplate() 35. { 36. FileClose(m_FileIn); 37. if (m_FileOut != INVALID_HANDLE) 38. { 39. FileClose(m_FileOut); 40. if ((!m_bSimple) && (_LastError == ERR_SUCCESS)) FileMove(m_szFileName + "_T", 0, m_szFileName, FILE_REWRITE); 41. if ((!m_bSimple) && (_LastError != ERR_SUCCESS)) FileDelete(m_szFileName + "_T"); 42. } 43. ArrayResize(m_szName, 0); 44. ArrayResize(m_szFind, 0); 45. ArrayResize(m_szReplace, 0); 46. } 47. //+------------------------------------------------------------------+ 48. void Add(const string szName, const string szFind, const string szReplace) 49. { 50. m_maxIndex++; 51. ArrayResize(m_szName, m_maxIndex); 52. ArrayResize(m_szFind, m_maxIndex); 53. ArrayResize(m_szReplace, m_maxIndex); 54. m_szName[m_maxIndex - 1] = szName; 55. m_szFind[m_maxIndex - 1] = szFind; 56. m_szReplace[m_maxIndex - 1] = szReplace; 57. } 58. //+------------------------------------------------------------------+ 59. string Get(const string szName, const string szFind) 60. { 61. for (int c0 = 0; c0 < m_maxIndex; c0++) if ((m_szName[c0] == szName) && (m_szFind[c0] == szFind)) return m_szReplace[c0]; 62. 63. return NULL; 64. } 65. //+------------------------------------------------------------------+ 66. void Execute(void) 67. { 68. string sz0, tmp, res[]; 69. int count0 = 0, i0; 70. 71. if ((m_FileIn != INVALID_HANDLE) && (m_FileOut != INVALID_HANDLE)) while ((!FileIsEnding(m_FileIn)) && (_LastError == ERR_SUCCESS)) 72. { 73. sz0 = FileReadString(m_FileIn); 74. if (sz0 == "<object>") count0 = 1; 75. if (sz0 == "</object>") count0 = 0; 76. if (count0 > 0) if (StringSplit(sz0, '=', res) > 1) 77. { 78. i0 = (count0 == 1 ? 0 : i0); 79. for (int c0 = 0; (c0 < m_maxIndex) && (count0 == 1); i0 = c0, c0++) count0 = (res[1] == (tmp = m_szName[c0]) ? 2 : count0); 80. for (int c0 = i0; (c0 < m_maxIndex) && (count0 == 2); c0++) if ((res[0] == m_szFind[c0]) && (tmp == m_szName[c0])) 81. { 82. if (StringLen(m_szReplace[c0])) sz0 = m_szFind[c0] + "=" + m_szReplace[c0]; 83. else m_szReplace[c0] = res[1]; 84. } 85. } 86. FileWriteString(m_FileOut, sz0 + "\r\n"); 87. }; 88. } 89. //+------------------------------------------------------------------+ 90. }; 91. //+------------------------------------------------------------------+
源代码:C_AdjustTemplate
这段代码正包含我们所需要的。如果你看看这里的代码,再看看上一篇文章中的 C_ChartFloatingRAD 类代码,你会发现 C_ChartFloatingRAD 类第 38 行和第 90 行之间的内容也在这里,但看起来有所不同。这是因为该 C_AdjustTemplate 类的数据建模方式有助于提高执行效率。本文稍后将展示新的 C_ChartFloatingRAD 类代码,您将看到这一点。所以,还是留到以后再说吧。首先,让我们了解一下这个 C_AdjustTemplate 类是怎么回事。
虽然 C_AdjustTemplate 类的代码看似复杂难懂,但实际上非常简单。不过,尽管它具有多种功能,却被设计为作为单一函数执行。要真正理解整个理念,请忘掉您正在使用 MetaTrader 或 MQL5 的代码。如果把自己想象成是在处理一台机器的部件,就会更容易理解一切。C_AdjustTemplate 类应被视为一个模板。因此,可以将其视为我们在前一篇文章中讨论过的模板文件。
这样一想,我们就会明白到底发生了什么,也会明白为什么我们今后要以这样的方式来处理这一类问题。因此,当你使用类构造函数时,基本上就是打开了模板,以便对其中的内容进行操作。使用析构函数时,就好像在说:"好了,MetaTrader,现在可以使用模板了"。其他函数则是调整模板的工具。
有鉴于此,让我们来看看这个类的每个部分是如何工作的。让我们从构造函数开始。从第 19 行开始,我们可以看到必须提供一个字符串,而第二个字符串参数是可选的。为什么要这样做?原因很简单:重载。如果不能重载,我们就得写两个构造函数,但既然可以重载,我们就使用这种方法。但事实上,这种重载并不常见,它就是要这样使用的。
完成后,我们将在第 20 行和第 23 行之间预初始化一些变量。这一点对我们很重要,虽然编译器会进行隐式初始化,但最好还是显式初始化。这样,我们就能清楚地知道每个变量的值是多少。
现在请注意以下情况:在第 25 行,我们 "重置" 了 _LastError 常量。因此,如果在调用构造函数之前出现任何错误,就应该进行检查;否则,就会丢失错误常量中指定的值。我在之前的文章中已经解释过为什么要这样做,请阅读这些文章了解更多详细信息。
在第 26 行,我们尝试打开必须指定的源文件。如果尝试失败,我们将在 _LastError 常量中报告。如果我们成功打开了文件,_LastError 常量将包含 ERR_SUCCESS 值,第 27 行中执行的检查也将成功,从而允许我们进入下一步。
此时,我们会在第 29 行检查是否为目标文件指定了名称。如果没有指定,我们将使用一个临时文件,其名称将基于原始文件的名称。有了目标文件的名称,我们就可以执行第 30 行,尝试创建目标文件。如果尝试失败,我们将在 _LastError 常量中报告。成功执行所有这些步骤后,_LastError 常量将包含 ERR_SUCCESS 值,我们将获得文件标识符。这些句柄将用于操作文件,因此在句柄关闭之前,不应尝试对文件执行任何外部操作。这就像机器是开放式的,如果您打开它,可能会出现问题。
因此,让我们按照代码编辑的顺序继续。这就到了第 34 行,类的析构代码就从这里开始。在第 36 行,我们要做的第一件事就是关闭输入文件。请注意:只有当文件句柄有效时,该文件才会被关闭。也就是说,文件必须是打开的。在第 37 行,我们检查输出文件是否已打开。这样做是为了避免不必要地执行下面的代码行。
因此,如果目标文件已打开,我们将在第 40 行检查是否为其指定了名称,以及在设置过程中是否有任何错误。如果一切正常,我们将重命名文件,使它与其余指标所期望的文件一致。总之,在第 41 行,我们删除了出错时使用的临时文件。
在第 43 行和第 45 行之间,我们释放已分配的内存。这样的事情相当重要,但很多人却忘记了去做。根据良好的编程实践,如果分配内存,就应该始终释放内存。这样,MetaTrader 5 就不会过度和不必要地消耗资源。
接下来,我们有一个非常简单的基本过程,从第 50 行开始,我们将计数器递增,紧接着分配内存来存储稍后要用到的数据。这一分配工作在第 51 和 53 行之间进行。还要注意第 54 到 56 行,这是我们存储信息的方式。由于这个过程很简单,我就不细说了。接下来,第 59 行有一个有趣的函数。
从第 59 行开始的这个函数虽然也很简单,但却相当有趣。不是因为它的功能,而是因为它的工作方式。请看第 60 行,这实际上是该函数中的唯一一行。在这个循环中,我们会遍历在第 50 行的函数中添加的所有仓位。问题是,程序员为什么要读取使用第 50 行函数所写入类的信息?这似乎毫无意义。的确,如果只看类代码,这是不合理的,但也有细微的差别。从第 66 行开始的一个小细节。
从第 66 行开始的 Execute 函数非常微妙。原因是很多事情都可能出错。基本上,我们可能会遇到以下错误:
- 无法读取输入文件;
- 输出文件可能不可用;
- StringSplit 函数可能会失败。
任何这些问题都会导致错误常量发生变化。如果出现这种情况,第 71 行的 while 循环将提前终止,导致整个指标在图表上无法正常工作。请记住:如果 _LastError 常量包含 ERR_SUCCESS 以外的值,则在执行析构函数时模板将无法更新。如果是第一次调用,则不会创建。如果没有,指标就不会被放置在图表上。这就是为什么 "Execute" 如此重要。
然而,假设一切运行正常。让我们看看 while 循环内部发生了什么。
在第 73 行,我们从源文件中读取了一个字符串。该字符串将由一个完整的行组成。这样阅读会更容易检查其余部分。接下来,在第 74 行,我们检查是否正在输入某个对象的定义,而在第 75 行,我们检查定义是否已完成。
这些检查对于加快读取和定制模板的过程非常重要。如果不处理任何对象,我们可以直接将信息写入输出文件。该信息是在第 86 行写入的,请注意。因为现在我们将看到如何调整和定制原始模板,以创建我们所需的内容。
进入对象内部后,第 76 行的检查允许我们 "打断" 字符串。这种 "打断" 发生在等号(=)上,这意味着我们正在为对象的某些属性定义参数。如果我们确实在定义一个属性,这些检查就会通过,从而允许我们执行第 78 行。这一行将简单地调整临时内存。但下面的几行代码却提出了一个问题。
在第 79 行中,我们循环查看在 Add 调用(第 48 行的方法)过程中添加的所有数据。如果我们偶然在模板中找到了参数值,这就表明我们正在处理所需的对象。我们暂时保存对象的名称,并表示将进行第二级分析。因为我们执行的是第二级,所以第 79 行不会在同一对象中再次执行。因此,我们必须确保模板的结构与上一篇文章开发回放系统(第 43 部分):Chart Trade 项目(二)中的结构相同。文件必须完全相同。如果要更改,请确保尽可能与您之前提供的版本保持一致。
好吧,既然我们已经进入了第二层,在第 80 行,我们将有另一个循环。重要:第 79 行和第 80 行中的循环永远不会一起执行。总会执行其中一个,但绝不会同时执行两个。唯一的例外 是第一次调用。看似奇怪的是,第 79 行和第 80 行都没有执行,但实际情况就是这样。但如果执行了第 80 行,在循环内部我们就会知道所需的对象属性是否存在。请注意,对象名称很重要,因此我们在第 79 行的循环中暂时写入它。
如果检查结果显示找到了对象属性,我们将执行第 82 行所示的第二次检查。此时,我们将得到第 59 行中函数存在的原因。如果在稍后讨论的编程过程中,您告诉 C_AdjustTemplate 类您不知道在对象的属性中使用什么参数,则第 82 行的检查将导致执行第 83 行,从而固定对象属性中存在的值。如果您指定要使用的值,它将被写入模板。
正是这种功能使 C_AdjustTemplate 类如此有趣,因为您可以要求它报告属性的值,或指定使用哪个值。
关于 C_AdjustTemplate 类的说明到此为止。现在让我们来看看 C_ChartFloatingRAD 类是怎样的。可以想象,它也经过了修改,变得更加有趣。
C_ChartFloatingRAD 类的新外观
虽然我不会在本文中展示该类的最终代码(原因是我想慢慢解释每一个细节),但你会注意到,它现在要比上一篇文章中介绍的代码复杂得多。不过,大部分代码仍保持不变。因此,建议您按顺序阅读文章,否则可能会错过一些细节,影响您的整体理解。
下面是 C_ChartFloatingRAD 类到当前为止的完整代码。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "../Auxiliar/C_Mouse.mqh" 005. #include "C_AdjustTemplate.mqh" 006. //+------------------------------------------------------------------+ 007. class C_ChartFloatingRAD : private C_Terminal 008. { 009. private : 010. enum eObjectsIDE {MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL}; 011. struct st00 012. { 013. int x, y; 014. string szObj_Chart, 015. szFileNameTemplate; 016. long WinHandle; 017. double FinanceTake, 018. FinanceStop; 019. int Leverage; 020. bool IsDayTrade, 021. IsMaximized; 022. struct st01 023. { 024. int x, y, w, h; 025. }Regions[MSG_NULL]; 026. }m_Info; 027. //+------------------------------------------------------------------+ 028. C_Mouse *m_Mouse; 029. //+------------------------------------------------------------------+ 030. void CreateWindowRAD(int x, int y, int w, int h) 031. { 032. m_Info.szObj_Chart = (string)ObjectsTotal(GetInfoTerminal().ID); 033. ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0); 034. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = x); 035. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = y); 036. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w); 037. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h); 038. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false); 039. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false); 040. m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID); 041. }; 042. //+------------------------------------------------------------------+ 043. inline void UpdateChartTemplate(void) 044. { 045. ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate); 046. ChartRedraw(m_Info.WinHandle); 047. } 048. //+------------------------------------------------------------------+ 049. inline double PointsToFinance(const double Points) 050. { 051. return Points * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade; 052. }; 053. //+------------------------------------------------------------------+ 054. inline void AdjustTemplate(const bool bFirst = false) 055. { 056. #define macro_AddAdjust(A) { \ 057. (*Template).Add(A, "size_x", NULL); \ 058. (*Template).Add(A, "size_y", NULL); \ 059. (*Template).Add(A, "pos_x", NULL); \ 060. (*Template).Add(A, "pos_y", NULL); \ 061. } 062. #define macro_GetAdjust(A) { \ 063. m_Info.Regions[A].x = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_x")); \ 064. m_Info.Regions[A].y = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_y")); \ 065. m_Info.Regions[A].w = (int) StringToInteger((*Template).Get(EnumToString(A), "size_x")); \ 066. m_Info.Regions[A].h = (int) StringToInteger((*Template).Get(EnumToString(A), "size_y")); \ 067. } 068. 069. C_AdjustTemplate *Template; 070. 071. if (bFirst) 072. { 073. Template = new C_AdjustTemplate("Chart Trade/IDE_RAD.tpl", m_Info.szFileNameTemplate = StringFormat("Chart Trade/%u.tpl", GetInfoTerminal().ID)); 074. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_AddAdjust(EnumToString(c0)); 075. }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate); 076. Template.Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol); 077. Template.Add("MSG_LEVERAGE_VALUE", "descr", (string)m_Info.Leverage); 078. Template.Add("MSG_TAKE_VALUE", "descr", (string)m_Info.FinanceTake); 079. Template.Add("MSG_STOP_VALUE", "descr", (string)m_Info.FinanceStop); 080. Template.Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0")); 081. Template.Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0")); 082. Template.Execute(); 083. if (bFirst) for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_GetAdjust(c0); 084. 085. delete Template; 086. 087. UpdateChartTemplate(); 088. 089. #undef macro_AddAdjust 090. #undef macro_GetAdjust 091. } 092. //+------------------------------------------------------------------+ 093. eObjectsIDE CheckMousePosition(const int x, const int y) 094. { 095. int xi, yi, xf, yf; 096. 097. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) 098. { 099. xi = m_Info.x + m_Info.Regions[c0].x; 100. yi = m_Info.y + m_Info.Regions[c0].y; 101. xf = xi + m_Info.Regions[c0].w; 102. yf = yi + m_Info.Regions[c0].h; 103. if ((x > xi) && (y > yi) && (x < xf) && (y < yf) && ((c0 == MSG_MAX_MIN) || (c0 == MSG_TITLE_IDE) || (m_Info.IsMaximized))) return c0; 104. } 105. return MSG_NULL; 106. } 107. //+------------------------------------------------------------------+ 108. public : 109. //+------------------------------------------------------------------+ 110. C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const int Leverage, const double FinanceTake, const double FinanceStop) 111. :C_Terminal() 112. { 113. m_Mouse = MousePtr; 114. m_Info.Leverage = (Leverage < 0 ? 1 : Leverage); 115. m_Info.FinanceTake = PointsToFinance(FinanceToPoints(MathAbs(FinanceTake), m_Info.Leverage)); 116. m_Info.FinanceStop = PointsToFinance(FinanceToPoints(MathAbs(FinanceStop), m_Info.Leverage)); 117. m_Info.IsDayTrade = true; 118. m_Info.IsMaximized = true; 119. if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown); 120. CreateWindowRAD(115, 64, 170, 210); 121. AdjustTemplate(true); 122. } 123. //+------------------------------------------------------------------+ 124. ~C_ChartFloatingRAD() 125. { 126. ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Chart); 127. FileDelete(m_Info.szFileNameTemplate); 128. 129. delete m_Mouse; 130. } 131. //+------------------------------------------------------------------+ 132. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 133. { 134. static int sx = -1, sy = -1; 135. int x, y, mx, my; 136. static eObjectsIDE it = MSG_NULL; 137. 138. switch (id) 139. { 140. case CHARTEVENT_MOUSE_MOVE: 141. if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft)) 142. { 143. switch (it = CheckMousePosition(x = (int)lparam, y = (int)dparam)) 144. { 145. case MSG_TITLE_IDE: 146. if (sx < 0) 147. { 148. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 149. sx = x - m_Info.x; 150. sy = y - m_Info.y; 151. } 152. if ((mx = x - sx) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = mx); 153. if ((my = y - sy) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = my); 154. break; 155. } 156. }else 157. { 158. if (it != MSG_NULL) 159. { 160. switch (it) 161. { 162. case MSG_MAX_MIN: 163. m_Info.IsMaximized = (m_Info.IsMaximized ? false : true); 164. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6)); 165. break; 166. case MSG_DAY_TRADE: 167. m_Info.IsDayTrade = (m_Info.IsDayTrade ? false : true); 168. break; 169. } 170. it = MSG_NULL; 171. AdjustTemplate(); 172. } 173. if (sx > 0) 174. { 175. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true); 176. sx = sy = -1; 177. } 178. } 179. break; 180. } 181. } 182. //+------------------------------------------------------------------+ 183. }; 184. //+------------------------------------------------------------------+
C_ChartFloatingRAD 类源代码
我知道这些代码看起来非常混乱和复杂,尤其是对初学者来说。但是,如果您从一开始就一直关注本系列,那么您现在应该已经学到了很多有关 MQL5 编程的知识。总之,这段代码比许多人通常创建的代码要复杂得多。
如果将这段代码与前一篇文章中的代码进行比较,你也会发现它变得更加复杂了。不过,大部分的复杂性都转移到了 C_AdjustTemplate 类中,该类已在上一节中进行了介绍。让我们弄清楚这段代码的作用,因为这正是 Chart Trade 指标的神奇之处。这是因为显示的指标代码在上一篇文章中保持不变。但是,显示的代码在这里已经发生了变化,而且这种变化足以为指标添加新的功能。
首先,我们来看第 10 行,这里有一个枚举,可以让我们更方便地访问模板中的对象。但同一枚举的值 MSG_NULL,是用于控制的。在进一步的解释过程中,这一点将变得很清楚。
查看代码,我们会在第 22 行和第 25 行之间看到一个结构,它将被一个数组变量使用。但是,如果你查看数组中的元素数量,你可能会想知道它是什么。我们没有看到数值,它是别的东西。你可能会想,"我到处都找不到它是什么意思"。冷静下来,不必惊慌。该数据表示数组中的元素个数,是第 10 行中枚举的最后一个数据。但这里有一个细节。最后一个值实际上并不代表一个元素或对象。如果是的话,声明就应该是不同的。
下一行值得解释的是第 54 行。这就是我们实际访问模板的地方。但在解释之前,让我们再看另一件事。该函数可在两个位置访问。第一个在第 121 行,是构造函数,第二个在第 171 行,是消息处理部分。为什么要知道这些?原因在于我们在模板中做了什么和想做什么。
第一种情况发生在构造函数中,我们希望自定义模板,使其按某种方式运行。在第二种情况下,我们将利用我们已有的东西;我们不会改变模板,但我们会使它完全符合我们的要求。
这种解释可能看起来有点混乱,但让我们看看第 54 行的方法是如何工作的。也许这能帮助你更好地了解情况。在第 56 行和第 67 行之间,我们定义了两个宏,用于帮助我们更轻松地编程。就像第 89 行和第 90 行用于消除此类宏定义一样。我通常喜欢在多次重复相同代码或某些参数时使用宏。在这种特殊情况下,一个参数会重复出现。宏代码非常简单。
第一行位于第 56 行和第 61 行之间,将添加 C_AdjustTemplate 类返回给我们的元素。在第 62 行和第 67 行之间的第二个宏中,我们使用 C_AdjustTemplate 类告诉我们的值,并将其转换为另一个值,存储在第 25 行声明的数组中。注意这一点。我们不是简单地猜测对象在哪里,而是询问模板它们在哪里。
在第 71 行,我们检查是否开始调整模板。如果不是这种情况,我们就执行第 75 行的调用。但如果这是第一次调用,我们还要告诉 C_AdjustTemplate 类将使用哪些名称。这是在第 73 行完成的。现在请特别注意第 74 行。可以看到,在这一行中,我们使用了一个枚举来告诉 C_AdjustTemplate 类我们需要从哪些对象中获取数据。为此,我们使用了一个循环。这样,C_AdjustTemplate 类就能知道哪些属性需要修正。
在任何情况下,在第 76 行和第 81 行中,我们都向模板传递了对象属性中应使用的值。每行指定一个对象、一个要更改的属性和一个要使用的值。
最后,在第 82 行中,我们告诉 C_AdjustTemplate 类,它可以根据所提供的信息定制模板。具体做法如上一主题所示。所有工作完成后,我们在第 83 行检查这是否是第一次调用。如果是,我们就调整第 25 行声明的数组中的值。这需要使用一个循环,告诉 C_AdjustTemplate 类我们想要了解哪些对象和属性。
这项工作完成后,我们使用第 85 行来关闭 C_Template 类。最后,在第 87 行,我们要求 OBJ_CHART 对象更新。这样,我们就能看到文章末尾演示视频中展示的神奇效果。
请注意:我们不会在此检查任何错误,而是假定一切正常并按预期运行。但我们会在指标代码中检查是否有错误。因此,如果出现任何故障,不是在这里处理,而是将在指标代码中处理。
现在让我们来看看别的事情:第 93 行运行着一个相当有趣的函数,可以把它放在需要用到的地方。这样做是为了使代码更易读。该函数有一个从第 97 行开始的循环,遍历 OBJ_CHART 中的每个对象。请记住:OBJ_CHART 对象包含一个模板,该模板包含我们要检查的对象。在检查过程中,我们将创建一个矩形,作为每个对象的点击区域。这是在第 99 和 102 行完成的。
获得点击区域后,我们就可以将其与作为调用参数所指定的区域进行比较。这个比较是在第 103 行进行的。除了区域比较,还有一些附加条件。如果一切正常,将返回对象的索引;否则将返回 MSG_NULL。这就是为什么我们需要在开头定义的枚举中包含这个值。如果没有这个值,就无法报告点击的是 Chart Trade 指标中的无效对象。
接下来要解释的是第 132 行。它是事件处理程序。现在,它包含了一些新的部分。但正是这些新部分,才使您在演示视频中看到的一切成为可能。让我们来仔细看看到底发生了什么。请注意,到此为止,除了 OBJ_CHART,我们还没有创建任何其他对象。然而,我们有预想的功能。
大部分代码看起来与前一篇文章中显示的非常相似。不过,有些细微的差别还是值得一说的,以便让那些经验不足的人了解情况。在第 134 到 136 行,我们定义了一些变量。第 136 行定义的变量在本文的框架内特别重要,因为其他变量已经解释过了。
我们将在第 136 行使用该变量作为存储。这是因为我们无法依靠 MetaTrader 5 的额外协助来解决与点击相关的问题。通常,当图表上有对象时,MetaTrader 5 会告诉我们被点击对象的名称。这是通过 CHARTEVENT_OBJECT_CLICK 事件实现的。但在这种情况下,除了 OBJ_CHART,我们没有其他真正的对象。因此,在 Chart Trade 指标区域的任何点击都会被 MetaTrader 5 理解为对 OBJ_CHART 的点击。
我们处理的唯一事件是 CHARTEVENT_MOUSE_MOVE,这对我们来说已经足够了。不过,只有当鼠标指示器不在研究状态时,才会处理点击。第 141 行对此进行了检查。如果鼠标指标处于研究状态,或者发生了其他情况,我们将转到第 156 行。这里有一个问题。如果第 136 行中声明的变量有不同的值,那么一定会发生什么。但首先,让我们来看看这个变量是在何时何地获取其值的。
当鼠标指标处于空闲状态并发生点击时,第 141 行执行的检查将确定单击的位置和内容。这在第 143 行完成。此时,我们要告诉分析函数鼠标点击时的位置。不过,这里有一个小瑕疵,我现在就不多说了,因为它将在下一篇文章中得到纠正,还有其他一些我们还需要做的小事情。在进行检查的同时,函数会返回接收到点击的对象名称。该名称将被写入一个静态变量中。
由于实际原因,我们现在只检查标题对象。如果它被点击了,第 145 行将允许执行拖动代码。我们可以在这里放置其他对象,但这样会使检查逻辑复杂化,至少在现阶段是这样。由于鼠标按钮被按下,对象将继续接收点击消息。
如前所述,我们可以改进这一点。但现在我想让代码尽可能简单,逐步进行修改,因为这里展示的概念与许多人通常的编程大相径庭。
但让我们回到第 156 行。当执行这一行时,我们将在该条件中执行两次检查。第一个选项将确定是否点击了 OBJ_CHART 中的任何对象。如果发生了这种情况,我们就会遇到与对象实际存在于图表上时相同的条件,MetaTrader 5 就会生成 CHARTEVENT_OBJECT_CLICK 事件。也就是说,我们 "模拟" 现有系统的运行。我们这样做是为了确保整个指标的充分运行。
如果第 158 行的检查成功通过,我们将首先执行相应的事件处理。这将在第 160 和 169 行完成,然后在第 170 行移除对象的引用,在第 171 行更新模板的当前状态。这样,整个指标都会被更新,造成图表上存在对象的假象,尽管真正的对象只有 OBJ_CHART。
C_ChartFloatingRAD 类中的所有其他代码都已在上一篇文章中解释过,因此我认为没有必要在此再次进行注释。
演示视频
结论
正如您所看到的,在本文中,我介绍了一种在 OBJ_CHART 中使用模板的方法,通过这种方法,您可以获得与图表上存在真实对象时非常相似的行为。我所展示的功能的最大优势可能是能够利用 MetaTrader 5 本身的元素快速创建界面,而无需使用复杂的 MQL5 编程。
虽然我所演示的内容看起来相当混乱和复杂,但这只是因为它对亲爱的读者你来说是全新的东西。随着时间的推移,当你开始练习时,你会发现这些知识可以广泛应用于各种场景和情况。然而,该系统尚未完成。此外,它还有一个小缺陷。但在下一篇文章中,我们将修复这一问题,最终允许用户直接在Chart Trade 中输入数值。这部分的实现将非常有趣。请不要错过本系列的下一篇文章。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11690
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV)
构建K线图趋势约束模型(第一部分):针对EA和技术指标
如何使用抛物线转向(Parabolic SAR)指标设置跟踪止损(Trailing Stop)