开发回放系统(第 75 部分):新 Chart Trade(二)
概述
在上一篇文章开发回放系统(第 74 部分):新 Chart Trade(一)中,我主要集中解释 Chart Trade 指标的代码。我们讨论了为什么在编写 Chart Trade 时您可能会选择一种方法而不是另一种方法。我们还介绍了代码的几个方面。然而,我遗漏了一个关键部分:核心代码本身。
我之前没有包含主体代码的原因之一是它删除了超过 100 行代码。所以我必须想一个简单的方法来演示代码中发生了什么变化。我发现的最好的方法就是我在这篇文章中提出的方法。
在这里,我们将仔细研究 Chart Trade 的主要代码。请记住,如果您愿意,您可以将此代码直接嵌入到 EA 交易中。不过,这需要一些调整,我今天会解释。不要忘记上一篇文章中讨论的关于为什么将代码放在指标中而不是 EA 交易中的原因。尽管如此,您可以自由地以最适合您需求的方式使用代码。
那么,事不宜迟,让我们开始吧。
理解 C_ChartFloatingRAD 类的源代码
C_ChartFloatingRAD 类的源代码位于同名的头文件中。下面,您将找到这个类的几乎完整的代码 —— 除了 DispatchMessage 过程之外的所有内容。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "../Auxiliar/C_Mouse.mqh" 005. #include "C_AdjustTemplate.mqh" 006. //+------------------------------------------------------------------+ 007. #define macro_NameGlobalVariable(A) StringFormat("ChartTrade_%u%s", GetInfoTerminal().ID, A) 008. #define macro_CloseIndicator(A) { \ 009. OnDeinit(REASON_INITFAILED); \ 010. SetUserError(A); \ 011. return; \ 012. } 013. //+------------------------------------------------------------------+ 014. class C_ChartFloatingRAD : private C_Terminal 015. { 016. private : 017. enum eObjectsIDE {MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL}; 018. struct st00 019. { 020. short x, y, minx, miny, 021. Leverage; 022. string szObj_Chart, 023. szObj_Editable, 024. szFileNameTemplate; 025. long WinHandle; 026. double FinanceTake, 027. FinanceStop; 028. bool IsMaximized, 029. IsDayTrade, 030. IsSaveState; 031. struct st01 032. { 033. short x, y, w, h; 034. color bgcolor; 035. int FontSize; 036. string FontName; 037. }Regions[MSG_NULL]; 038. }m_Info; 039. C_Mouse *m_Mouse; 040. //+------------------------------------------------------------------+ 041. void CreateWindowRAD(int w, int h) 042. { 043. m_Info.szObj_Chart = "Chart Trade IDE"; 044. m_Info.szObj_Editable = m_Info.szObj_Chart + " > Edit"; 045. ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0); 046. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x); 047. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y); 048. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w); 049. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h); 050. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false); 051. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false); 052. m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID); 053. }; 054. //+------------------------------------------------------------------+ 055. void AdjustEditabled(C_AdjustTemplate &Template, bool bArg) 056. { 057. for (eObjectsIDE c0 = 0; c0 <= MSG_STOP_VALUE; c0++) 058. if (bArg) 059. { 060. Template.Add(EnumToString(c0), "bgcolor", NULL); 061. Template.Add(EnumToString(c0), "fontsz", NULL); 062. Template.Add(EnumToString(c0), "fontnm", NULL); 063. } 064. else 065. { 066. m_Info.Regions[c0].bgcolor = (color) StringToInteger(Template.Get(EnumToString(c0), "bgcolor")); 067. m_Info.Regions[c0].FontSize = (int) StringToInteger(Template.Get(EnumToString(c0), "fontsz")); 068. m_Info.Regions[c0].FontName = Template.Get(EnumToString(c0), "fontnm"); 069. } 070. } 071. //+------------------------------------------------------------------+ 072. inline void AdjustTemplate(const bool bFirst = false) 073. { 074. #define macro_PointsToFinance(A) A * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade 075. 076. C_AdjustTemplate *Template; 077. 078. if (bFirst) 079. { 080. Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl", true); 081. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) 082. { 083. (*Template).Add(EnumToString(c0), "size_x", NULL); 084. (*Template).Add(EnumToString(c0), "size_y", NULL); 085. (*Template).Add(EnumToString(c0), "pos_x", NULL); 086. (*Template).Add(EnumToString(c0), "pos_y", NULL); 087. } 088. AdjustEditabled(Template, true); 089. }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate); 090. if (_LastError >= ERR_USER_ERROR_FIRST) 091. { 092. delete Template; 093. 094. return; 095. } 096. m_Info.Leverage = (m_Info.Leverage <= 0 ? 1 : m_Info.Leverage); 097. m_Info.FinanceTake = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceTake), m_Info.Leverage)); 098. m_Info.FinanceStop = macro_PointsToFinance(FinanceToPoints(MathAbs(m_Info.FinanceStop), m_Info.Leverage)); 099. (*Template).Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol); 100. (*Template).Add("MSG_LEVERAGE_VALUE", "descr", IntegerToString(m_Info.Leverage)); 101. (*Template).Add("MSG_TAKE_VALUE", "descr", DoubleToString(m_Info.FinanceTake, 2)); 102. (*Template).Add("MSG_STOP_VALUE", "descr", DoubleToString(m_Info.FinanceStop, 2)); 103. (*Template).Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0")); 104. (*Template).Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0")); 105. if (!(*Template).Execute()) 106. { 107. delete Template; 108. 109. macro_CloseIndicator(C_Terminal::ERR_FileAcess); 110. }; 111. if (bFirst) 112. { 113. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) 114. { 115. m_Info.Regions[c0].x = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_x")); 116. m_Info.Regions[c0].y = (short) StringToInteger((*Template).Get(EnumToString(c0), "pos_y")); 117. m_Info.Regions[c0].w = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_x")); 118. m_Info.Regions[c0].h = (short) StringToInteger((*Template).Get(EnumToString(c0), "size_y")); 119. } 120. m_Info.Regions[MSG_TITLE_IDE].w = m_Info.Regions[MSG_MAX_MIN].x; 121. AdjustEditabled(Template, false); 122. }; 123. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6)); 124. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, (m_Info.IsMaximized ? m_Info.x : m_Info.minx)); 125. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, (m_Info.IsMaximized ? m_Info.y : m_Info.miny)); 126. 127. delete Template; 128. 129. ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate); 130. ChartRedraw(m_Info.WinHandle); 131. 132. #undef macro_PointsToFinance 133. } 134. //+------------------------------------------------------------------+ 135. eObjectsIDE CheckMousePosition(const short x, const short y) 136. { 137. int xi, yi, xf, yf; 138. 139. for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) 140. { 141. xi = (m_Info.IsMaximized ? m_Info.x : m_Info.minx) + m_Info.Regions[c0].x; 142. yi = (m_Info.IsMaximized ? m_Info.y : m_Info.miny) + m_Info.Regions[c0].y; 143. xf = xi + m_Info.Regions[c0].w; 144. yf = yi + m_Info.Regions[c0].h; 145. if ((x > xi) && (y > yi) && (x < xf) && (y < yf)) return c0; 146. } 147. return MSG_NULL; 148. } 149. //+------------------------------------------------------------------+ 150. inline void DeleteObjectEdit(void) 151. { 152. ChartRedraw(); 153. ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Editable); 154. } 155. //+------------------------------------------------------------------+ 156. template <typename T > 157. void CreateObjectEditable(eObjectsIDE arg, T value) 158. { 159. long id = GetInfoTerminal().ID; 160. 161. DeleteObjectEdit(); 162. CreateObjectGraphics(m_Info.szObj_Editable, OBJ_EDIT, clrBlack, 0); 163. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XDISTANCE, m_Info.Regions[arg].x + m_Info.x + 3); 164. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YDISTANCE, m_Info.Regions[arg].y + m_Info.y + 3); 165. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_XSIZE, m_Info.Regions[arg].w); 166. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_YSIZE, m_Info.Regions[arg].h); 167. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_BGCOLOR, m_Info.Regions[arg].bgcolor); 168. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_ALIGN, ALIGN_CENTER); 169. ObjectSetInteger(id, m_Info.szObj_Editable, OBJPROP_FONTSIZE, m_Info.Regions[arg].FontSize - 1); 170. ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_FONT, m_Info.Regions[arg].FontName); 171. ObjectSetString(id, m_Info.szObj_Editable, OBJPROP_TEXT, (typename(T) == "double" ? DoubleToString(value, 2) : (string) value)); 172. ChartRedraw(); 173. } 174. //+------------------------------------------------------------------+ 175. bool RestoreState(void) 176. { 177. uCast_Double info; 178. bool bRet; 179. C_AdjustTemplate *Template; 180. 181. if (bRet = GlobalVariableGet(macro_NameGlobalVariable("POST"), info.dValue)) 182. { 183. m_Info.x = (short) info._16b[0]; 184. m_Info.y = (short) info._16b[1]; 185. m_Info.minx = (short) info._16b[2]; 186. m_Info.miny = (short) info._16b[3]; 187. Template = new C_AdjustTemplate(m_Info.szFileNameTemplate = IntegerToString(GetInfoTerminal().ID) + ".tpl"); 188. if (_LastError >= ERR_USER_ERROR_FIRST) bRet = false; else 189. { 190. (*Template).Add("MSG_LEVERAGE_VALUE", "descr", NULL); 191. (*Template).Add("MSG_TAKE_VALUE", "descr", NULL); 192. (*Template).Add("MSG_STOP_VALUE", "descr", NULL); 193. (*Template).Add("MSG_DAY_TRADE", "state", NULL); 194. (*Template).Add("MSG_MAX_MIN", "state", NULL); 195. if (!(*Template).Execute()) bRet = false; else 196. { 197. m_Info.IsDayTrade = (bool) StringToInteger((*Template).Get("MSG_DAY_TRADE", "state")) == 1; 198. m_Info.IsMaximized = (bool) StringToInteger((*Template).Get("MSG_MAX_MIN", "state")) == 1; 199. m_Info.Leverage = (short)StringToInteger((*Template).Get("MSG_LEVERAGE_VALUE", "descr")); 200. m_Info.FinanceTake = (double) StringToDouble((*Template).Get("MSG_TAKE_VALUE", "descr")); 201. m_Info.FinanceStop = (double) StringToDouble((*Template).Get("MSG_STOP_VALUE", "descr")); 202. } 203. }; 204. delete Template; 205. }; 206. 207. GlobalVariablesDeleteAll(macro_NameGlobalVariable("")); 208. 209. return bRet; 210. } 211. //+------------------------------------------------------------------+ 212. public : 213. //+------------------------------------------------------------------+ 214. C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const short Leverage, const double FinanceTake, const double FinanceStop) 215. :C_Terminal(0) 216. { 217. m_Mouse = MousePtr; 218. m_Info.IsSaveState = false; 219. if (!IndicatorCheckPass(szShortName)) return; 220. if (!RestoreState()) 221. { 222. m_Info.Leverage = Leverage; 223. m_Info.IsDayTrade = true; 224. m_Info.FinanceTake = FinanceTake; 225. m_Info.FinanceStop = FinanceStop; 226. m_Info.IsMaximized = true; 227. m_Info.minx = m_Info.x = 115; 228. m_Info.miny = m_Info.y = 64; 229. } 230. CreateWindowRAD(170, 210); 231. AdjustTemplate(true); 232. } 233. //+------------------------------------------------------------------+ 234. ~C_ChartFloatingRAD() 235. { 236. ChartRedraw(); 237. ObjectsDeleteAll(GetInfoTerminal().ID, m_Info.szObj_Chart); 238. if (!m_Info.IsSaveState) 239. FileDelete(m_Info.szFileNameTemplate); 240. 241. delete m_Mouse; 242. } 243. //+------------------------------------------------------------------+ 244. void SaveState(void) 245. { 246. #define macro_GlobalVariable(A, B) if (GlobalVariableTemp(A)) GlobalVariableSet(A, B); 247. 248. uCast_Double info; 249. 250. info._16b[0] = m_Info.x; 251. info._16b[1] = m_Info.y; 252. info._16b[2] = m_Info.minx; 253. info._16b[3] = m_Info.miny; 254. macro_GlobalVariable(macro_NameGlobalVariable("POST"), info.dValue); 255. m_Info.IsSaveState = true; 256. 257. #undef macro_GlobalVariable 258. } 259. //+------------------------------------------------------------------+ 260. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 261. { .... The internal code of this procedure will be seen in the next article 386. } 387. //+------------------------------------------------------------------+ 388. }; 389. //+------------------------------------------------------------------+ 390. #undef macro_NameGlobalVariable 391. #undef macro_CloseIndicator 392. //+------------------------------------------------------------------+
C_ChartFloatingRAD.mqh 文件的源代码
此版本的代码已经删除了不必要的行。它还包括必要的更新,以确保一切按预期进行。由于你们中的许多人可能还没有看到原始版本,我鼓励你们密切关注本文中的解释,不仅要了解代码是如何工作的,还要特别了解它是如何使 Chart Trade 运行并出现在图表上的。乍一看,你可能看不到任何你期望从 Chart Trade 中看到的视觉元素。那么这怎么可能呢?看起来几乎不包含图形元素的代码如何在图表上显示图形?这里发生了什么魔法?
嗯,这不是魔法。正是编程和各种技术的巧妙运用,共同使得 Chart Trade 能够按预期进行。此外,这使得 Chart Trade 可靠且安全地运行。
在原始版本中,一些内容是公有的,例如第 17 行中的结构和枚举。然而,我们要注意确保没有变量泄漏到代码之外。因此封装已经成为一个关键的设计原则。但为什么那些公有元素不再可见?这是因为现在的想法是建立一个协议并使用它而不需要 C_ChartFloatingRAD 类来解码数据。我们稍后将在文章中讨论这个问题。
从第 18 行开始,我们需要的所有变量现在都在结构中声明。请密切关注所使用的类型。大多数情况下,我们不使用 32 位类型,或者更具体地说,整数类型。为什么呢?即使用于屏幕坐标的变量也使用 16 位类型(SHORT 组)。
与默认使用 INT 的常见做法不同,我认为这里没有必要这样做。就屏幕分辨率而言,16 位对于现代技术来说已经足够了。最大 65535 足以在 4K 屏幕上绘图。坦率地说,这甚至可以处理 8K 显示器。因此在这里使用 INT 没有任何优势。此外,使用 SHORT 有助于数据压缩。我们稍后会看到这一点。
现在让我们继续。在第 31 行,您将看到在第一个结构中定义的另一个结构。这个内部结构保持了图形元素的相对位置。这些是用户在 Chart Trade 界面中交互的元素。
代码开头定义了两个宏,现在不必担心它们,只要注意它们的位置即可。我们稍后会用到它们。其中一个在第 8 行定义,乍一看可能有点令人困惑,但你很快就会明白它的目的。
不管怎样,这是它的核心。在第 14 行,我们私有继承 C_Terminal 类来扩展其功能并构建 Chart Trade。在第 20 行和第 39 行之间,我们声明了类的私有内部变量。现在我们已经准备好开始审查代码了。为了使事情变得简单,我们将逐一分析它们在代码中出现的函数。我们从第 41 行声明的 CreateWindowRAD 过程开始。
该过程仅从第 230 行调用一次,没有其他地方了。它的作用很简单:在图表上创建一个 OBJ_CHART 对象。就是这些,但是为什么要添加这个 OBJ_CHART 对象呢?通过这样做,我们不需要手动创建所有其他图形对象。对于那些习惯于通过代码绘制图表的人来说,这可能会让人感到困惑,但我在之前的文章中已经对此进行了解释。在本文的最后,我将提供这些资源的链接,以便您可以更深入地研究它们。它们确切地解释了为什么采取这种方法。如果这篇文章没有完全澄清这个话题,那么这些参考资料将是一个有价值的帮助。
我们当前任务的关键部分是第 52 行。一旦创建了 OBJ_CHART 对象,我们就会在那里存储 MetaTrader 5 返回的 ID。我们需要这个 ID,因为它稍后会很重要。现在我们可以进入下一个过程:AdjustEditabled,从第 55 行开始。此过程有何作用?尽管听起来很奇怪,但它的目的是调整和获取将出现在 OBJ_CHART 中的对象的状态。但它是如何工作的呢?为了理解,我们需要仔细研究 AdjustTemplate 过程是如何工作的,因为 AdjustEditabled 就是在这里使用的。让我们来看看第 72 行和第 133 行之间发生了什么。这是类中最复杂的部分之一,也是“魔法”真正发生的地方。
在第 74 行,我们定义了一个仅在此过程中使用的宏。请注意,在过程结束时,在第 132 行,宏未定义,因此不能在其他地方使用。在第 78 行,我们检查需要采取什么行动。如果我们正在构建 Chart Trade,这个条件就会成立,然后一系列步骤就会随之而来。如果 Chart Trade 已经存在,我们将执行第 89 行。在这里,您可以重新阅读上一篇文章,以更好地理解模板中完成的工作类型,因为第 80 行或第 89 行都将启动模板创建。同样,这取决于第 78 行的检查结果。
现在到了第一个棘手的部分。如果我们正在创建模板,第 81 行的循环准备定位对象。但这些对象是什么?这些是添加到 Chart Trade 的对象。注意:我们还不知道这些对象在哪里,但我们知道有多少个,这要归功于第 17 行的枚举。在第 88 行,我们再次调用 AdjustEditabled,确保第 58 行的检查通过。此步骤从模板添加更多数据。
如果 C_AdjustTemplate 类成功处理此问题,则第 90 行将让代码继续。如果失败,执行将返回调用代码,如前一篇文章所示。但请深呼吸,我们实际上还没有做任何重要的事情。我们通过第 90 行所做的就是要求 C_AdjustTemplate 打开模板文件,没别的了。
在第 96 行和第 98 行之间,我们调整了一些显示值。然后,在第 99 行和第 104 行之间,我们准备数据以在第 105 行进行处理。这就是魔法真正发生的时候。为了理解它,请回顾前一篇文章中 C_AdjustTemplate 类的 Execute 函数。我们在这里做的是修改最初存储在模板文件中的值。但我们并没有改变原值,我们正在制作一份副本。这就是为什么第78行的检查是必不可少的,它在第 105 行告诉我们要修改哪个文件。
我不确定你是否完全理解了正在发生的事情,但让我试着解释一下。从第 78 行到第 104 行所做的一切都为 C_AdjustTemplate 准备数据,以编辑包含 Chart Trade 的文件。您稍后在 Chart Trade 中看到的每个元素都定义在第 99 行和 104 行之间。尽管看起来很奇怪,但它确实有效。
所以我的建议是:仔细研究这段代码,真正了解它是如何运作的。每个对象都是使用相同的技术创建和管理的。
现在,让我们假设你已经理解了第 105 行之前的所有内容。如果该行成功,代码将继续。如果它失败了,就会发生一些非常不寻常的事情。请密切关注接下来发生的事情。
如果第 105 行的调用失败,我们需要销毁模板并关闭 Chart Trade。销毁模板很简单:第 107 行处理了这个问题,没什么神秘的。然而,这并不会将指标从图表中删除。为此,我们需要调用 OnDeInit 过程。现在到了下一个复杂的部分。我们来看看下面的 OnDeInit 代码片段:
41. void OnDeinit(const int reason) 42. { 43. switch (reason) 44. { 45. case REASON_INITFAILED: 46. ChartIndicatorDelete(ChartID(), 0, def_ShortName); 47. break; 48. case REASON_CHARTCHANGE: 49. (*chart).SaveState(); 50. break; 51. } 52. 53. delete chart; 54. }
Chart Trade 指标代码片段
这段代码只是为了帮助解释这个过程。因此,让我们澄清一下:当执行第 109 行时,控制将跳转到宏代码中。我们可以在代码的开头看到这个宏,从第 8 行开始。当执行此宏中的第 9 行时,将执行代码段第 46 行中的内容。
同时,摘录的第 53 行也将被执行,这确保 Chart Trade 指标被删除。然而,代码随后将返回,并执行宏的第 10 行。此行确保设置了一个错误标志,以防止代码继续执行。然后,在第 11 行,我们返回到调用者,而不管谁调用了 AdjustTemplate 过程。如果模板修改无法成功执行,则该指标将被删除,而不会出现任何其他问题。这是系统中较为复杂的部分之一。但我们还没有完成,因为还有更多内容要讲。
现在,让我们假设模板修改成功。在这种情况下,我们转到第 111 行的新检查。此检查获取可点击对象的位置。此类操作仅在模板创建期间执行。从第 113 行开始的循环处理此任务:它获取可点击对象的位置,第 120 行和 121 行补充了此过程。
接下来,我们将 OBJ_CHART 对象定位到图表上。这发生在第 123 行和第 125 行之间。第 127 行结束了模板操作,这允许第 129 行将适当的模板分配给 OBJ_CHART。然后第 130 行强制更新对象内容。至此,OBJ_CHART 对象的整个结构就完成了,并且所有组件都显示出来,创建了 Chart Trade 界面。
如果您仍然不清楚模板如何更新,以至于您不需要手动编写 Chart Trade 界面,请返回并查看整个 AdjustTemplate 过程。这是配置和显示图表交易的核心流程。Chart Trade 中出现的每个对象都是在此过程中创建、定位和调整的。
在代码中寻找明确的对象声明(例如按钮、图标、图像或其他任何东西)是没有意义的,因为它们不存在于代码中。代码的作用是定位现有对象并使其可供用户访问。不要试图将其理解为传统的对象创建模式。我在这里做的恰恰相反:我首先设计所需的外观,然后才指示代码使该概念生效。如果不理解这个想法,你可能会发现自己完全迷失了方向。
这个系统非常复杂,您可以更改对象的布局、结构,甚至字体,而无需编写一行代码。您需要做的就是打开 MetaTrader 5,加载图表交易模板,并修改它在图表上的视觉呈现方式。其余部分完全由 AdjustTemplate 程序处理。
当然,总有一些事情会让事情变得复杂。为了支持 RAD(快速应用程序开发)概念,我们需要一些额外的程序。我们接下来要看的是位于第 135 行的 CheckMousePosition 函数。这个函数非常简单,它检查鼠标指针当前是否位于任何可点击或可选择的对象内。如果是,第 145 行返回相应的对象。如果不是,第 147 行报告鼠标不在任何交互元素上。
接下来,我们有两个过程来支持一个独特的案例。这是 AdjustTemplate 程序无法自行处理且无法达到所需质量的问题。本例涉及 OBJ_EDIT 对象,该对象允许用户输入任意文本。
第 150 行的过程负责删除可能已创建的任何 OBJ_EDIT 对象。同时,第 156 行定义的过程负责创建单个 OBJ_EDIT 对象,使用户能够在 Chart Trade 界面中输入值。
CreateObjectEditable 过程的声明可能看起来有点奇怪,但没有什么特别之处。它引入了一个重载,以允许第 171 行调整 OBJ_EDIT 对象以匹配正在编辑的变量类型。例如,如果变量是 double 类型,则该值将显示两位小数。否则,该值将被视为字符串。在这种情况下,显示的文本将与调用者构建的文本完全一致。
这里没有什么真正困难的,至少对熟悉MQL5编程的人来说不是。所做的一切都在 MQL5 的一般知识范围内,即使对于那些使用图形对象经验有限的人来说也是如此。
现在让我们继续下一个函数:RestoreState,在第 175 行声明,并在第 210 行实现。此过程不是单独运行的,它需要 SaveState 过程的支持,该过程从第 244 行开始。这两个函数的运行均超出了传统规则,它们共同作用,暂时存储主要信息。这包括 Chart Trade 界面的最后已知位置。更准确地说,它们存储了图表刷新之前 OBJ_CHART 对象的最后已知位置。
为了存储这些信息,我们使用全局终端变量。请注意,我们只使用一个全局变量来保存值。但是,我希望您注意另一个重要细节。在第 187 行中,我们再次使用 C_AdjustTemplate 类。为什么呢?我们又要修改模板吗?答案是否定的。这一次,我们以不同的方式使用模板。
还记得在模板创建过程中,在从第 72 行开始的 AdjustTemplate 过程中,我说过我们可以在模板中添加或删除项目吗?这正是我们在这里所做的。我们使用现有的、修改过的模板值来恢复图表交易中的数据。为什么这是必要的?OBJ_CHART 对象不加载模板来构建 Chart Trade 界面吗?是的,它会的。但我们恢复这些数据是为了适应用户与可点击对象的可能交互,特别是市场买入和卖出按钮。这些按钮触发稍后将解释的事件。为了避免在这些事件发生时出现不必要的延迟或丢失数据,我们在图表交易重新启动时先发制人地恢复必要的值。
为了从模板中正确还原此信息,我们首先打开它 —— 这是在第 187 行完成的。如果成功,第 188 行上的检查将允许第 190 行到第 194 行运行。这会添加要获取的对象名称和相应的值位置。然后,在第 195 行,我们尝试执行转换。由于我们没有修改或添加任何内容到模板中,因此此行只是检索请求的数据。如果执行成功,第 197 行至第 201 行将恢复 C_ChartFloatingRAD 类的必要私有变量。我们不需要恢复所有内容,只需要恢复处理未来事件所需的关键值。最后,第 204 行关闭模板,第 207 行删除临时全局终端变量。
重要的是要了解这些变量的寿命极短,通常不到一秒钟。它们仅用于临时存储时间框架更改前 OBJ_CHART 所在的图表坐标。
这篇文章的信息量已经相当密集了,不过,在结束之前,让我们快速回顾一下另外三个过程,它们简单易懂。
第 214 行的过程是类构造函数。这基本上是不言自明的,因为它利用了本文中讨论的所有内容。第 218 行标记当前状态是否将被保存。默认情况下,不保存任何内容。第 219 行尝试创建指标。如果失败,控制权将返回给调用者,并且指标终止。在第 220 行,我们尝试恢复之前的状态。如果失败,则使用用户的输入值。第 230 行创建 OBJ_CHART 对象,第 231 行执行最后的调整。
从第 234 行开始的程序是类析构函数,它甚至更简单。第 237 行删除与 OBJ_CHART 关联的任何对象。这是避免图表被不必要的残留物弄乱的关键一步。第 238 行检查是否应该保存当前状态。如果不是,第 239 行将从磁盘中删除模板文件。这意味着 RestoreState 函数(第 175-210 行)将不起作用,因为第 188 行的检查将失败,并且值将默认为用户提供的最后一项。
然后我们有从第 244 行开始的 SaveState 函数。它使用在第 246 行定义并在第 257 行删除的内部宏。这个宏不是严格必要的,但由于全局变量名被使用了两次(一次是临时创建它,一次是存储值),使用宏可以使代码更简洁。这里的关键行是 255,它确保了第 238 行的检查失败,从而保留了模板文件。
最后的探讨
虽然我还没有介绍第 260 行声明并通过第 386 行实现的 DispatchMessage 过程,但我不会在本文中解释它。原因很简单:在这个有限的空间里讨论它太复杂了。当与另一个项目一起使用时,它会得到更好的理解。这是因为该程序负责生成允许用户与 Chart Trade 交互以开立或关闭市场头寸的事件。
这个话题值得更详细和彻底的解释。为了确保你有动力阅读下一篇文章,我故意隐瞒了这个函数的内部细节。这部分将在下一篇文章中详细讨论。现在,将 C_ChartFloatingRAD 类的代码保存在单独的文件中,并彻底研究它,因为它已经可以运行。请继续关注下一篇文章,我将在其中介绍第 260 行和第 386 行之间的代码。
我们下篇文章再见。在此之前,您将在下面的 MQL5 中找到 RAD 编程的参考。这些是我过去写的文章,解释了用于构建 Chart Trade 的概念,如这里所示。我们不显式声明对象,但我们能够在图表上显示它们并与它们交互。但是,如果使用快捷键 CTRL+B 打开“对象”窗口,则只会看到一个列出的对象。
附件包含您所需的一切,以便看到指标的运行并与之交互,如下面随附的视频所示。
MQL5 中的 RAD 文章链接
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12442
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
价格行为分析工具包开发(第八部分):指标看板
重新定义MQL5与MetaTrader 5指标
使用MQL5和Python集成经纪商API与智能交易系统