
开发回放系统(第 59 部分):新的未来
概述
在上一篇文章“开发回放系统(第 58 部分):重返服务工作“中,我提到系统已经发生了一些变化,有理由在将模板应用于日程表和服务更新日程表之间加入一个小延迟。如果不这样做,模块将强制服务提前关闭。你们中的一些人可能会想知道这是怎么发生的。在本文中,我们将更详细地探讨这个问题。
为了正确解释这一点并确保您完全理解这个概念,请观看下面的视频 01。
演示视频:事情怎么会失败
这段视频提供了一个未经编辑、未经更改的演示,展示了实际发生的情况。但为什么会出现这个问题呢?为了理解这个解释,必须重新回顾上一篇文章的要点。我将在这里简要总结一下。
了解服务关闭的原因
MetaTrader 5 应用模板后,服务过早关闭是有原因的。解释很简单:指标 — 或者更准确地说,控制模块 — 导致图表关闭。仍不清楚吗?让我们检查一下负责此行为的控制指标代码的相关部分。您可以在下面找到代码片段。
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. }
控制模块代码片段
观察第 63 行,这里有一处调用指示 MetaTrader 5 关闭图表。但实际上是谁调用的呢?如果它仅由模板的变化触发,则第 58 行的消息将出现在 MetaTrader 5 消息框中。然而,您可能已经注意到,该消息未被打印。这表明图表的关闭不是由模板变化直接引起的。
我理解你为什么可能会质疑这一点,甚至怀疑这个解释。然而事实是,在 MetaTrader 5 中应用模板确实会从图表中删除控制指标,最终导致图表关闭。关键在于这个过程是异步发生的,这意味着事件不会按照您预期的确定顺序展开。
实际情况是,在某个时候,MetaTrader 5 完成了模板的应用。当这种情况发生时,MetaTrader 5 会向指标发送一个 Deinit 事件,该事件由 OnDeinit 函数处理。然而,与您预期的相反,原因变量并不包含值 REASON_TEMPLATE,而是 REASON_REMOVE。这是因为 MetaTrader 5 在模板应用过程中正在主动从图表中删除控制指标。这就是为什么第 58 行的消息从未打印在终端的消息窗口中的原因。
一个自然的问题出现了:为什么 MetaTrader 5 不同步应用模板?换句话说,为什么执行 C_Replay.mqh 头文件第 87 行的 ChartApplyTemplate 函数不能立即将模板完全应用到图表上?答案在于性能。模板可能包含多个指标,甚至包含一个 EA 交易,该 EA 要求图表上存在一定数量的柱形才能提供有用的数据。
如果 MetaTrader 5 等待 ChartApplyTemplate 同步完成,平台可能会冻结几秒钟,甚至如果模板中的某些内容可能导致严重故障而崩溃。通过异步处理此过程,MetaTrader 5 提高了性能,但也给经验不足的程序员带来了挑战。其中一个挑战是了解哪些函数异步执行、哪些函数立即执行。
由于使用 ChartRedraw 强制立即更新图表并不能确保立即应用模板,因此我们需要一种替代方法。演示视频阐释了所实现的解决方案。
您可能会问:为什么不直接在模板中包含控制指标来解决这个问题呢?虽然这可以防止过早关闭的问题,但也会产生另一个问题。在之前的版本中,我们使用了一个全局终端变量来防止控制指标出现在意外的图表上。然而,我们不再依赖这种方法。相反,我们现在使用不同的阻塞机制。这意味着我们不能允许模板包含控制指标,因为这样做会带来难以解决的复杂问题。
如前一篇文章所述,控制和鼠标模块都发生了变化。这是我们重新关注回放/模拟服务时做出的调整的结果。现在,让我们看一下这两个指标的更新代码,重点关注关键的修改。
回放/模拟服务更新代码
下面,您将能找到控制模块源代码的最新版本。
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.59" 07. #property link "https://www.mql5.com/en/articles/12075" 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 = 0; 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) || (user00 == 0)) 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).SetBuffer(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. //+------------------------------------------------------------------+ 69.
控制指标的源代码
您可能已经注意到,我没有展示头文件中的代码,因为它没有被改变。虽然我们可以查看上面的整个控制模块代码,但我们将重点关注修改的部分。唯一的区别是在第26行。在此,我们检查两个条件。其中一个是 _LastError 值,另一个是 user00 值。这个 user00 在第 16 行定义,负责从回放/模拟服务接收控制模块应该运行的图表的 ID。
现在请小心。通常,我们作为用户无法确定应在第 16 行开头分配的值。这是自然发生的。但是,如果我们试图欺骗系统并将整个配置保存为模板文件呢?这可以让一切正常工作,省去我们使用模板的麻烦,对吧?不,这不会像你期望的那样。
如果在服务加载完所有内容并且 MetaTrader 5 稳定了图表后,我们将此完成的图表保存为模板,那么当我们打开模板文件时,我们会注意到一些事情。从下面的片段可以看出这一点。
01. <indicator> 02. name=Custom Indicator 03. path=Services\Market Replay.ex5::Indicators\Market Replay.ex5 04. apply=0 05. show_data=1 06. scale_inherit=0 07. scale_line=0 08. scale_line_percent=50 09. scale_line_value=0.000000 10. scale_fix_min=0 11. scale_fix_min_val=0.000000 12. scale_fix_max=0 13. scale_fix_max_val=0.000000 14. expertmode=1610613824 15. fixed_height=-1 16. 17. <graph> 18. name= 19. draw=0 20. style=0 21. width=1 22. color= 23. </graph> 24. <inputs> 25. user00=130652731570824061 26. </inputs> 27. </indicator>
模板文件片段
此代码片段在文件中的确切位置在此不相关。行号只是帮助我们更清楚地解释事情的参考。
请注意,第 1 行包含一个开始标记,而第 27 行包含一个结束结构。在这两个标签之间指定了各种细节,包括要应用于图表的指标的位置,该位置在第 3 行指示。好的,现在,请密切关注第 24 行。此处,标签打开了第 3 行定义的指标所需的输入参数部分。同样,第 26 行标志着本部分的结束。
在第 25 行,我们引用指标源代码中第 16 行声明的输入参数,用于获取图表 ID。此行给控制指标分配一个值,使得指标源代码第 26 行测试的条件为 false,从而允许指标加载并正常运行。对吧?遗憾的是,不行。当回放/模拟服务正确地将控制指标应用于图表时,指标代码将检测到另一个实例已在运行。当这种情况发生时,控制指标代码中的第 24 行将产生触发第 25 行的结果。因此,_LastError 将不再保持第 26 行的预期值,从而导致系统出错。因此,MetaTrader 5 将生成一个 Deinit 事件,该事件将由第 53 行的 OnDeinit 函数处理。此时,执行将继续进行 REASON_INITFAILED,关闭图表并终止回放/模拟服务。
这表现了当我们充分利用 MetaTrader 5 平台的功能时,一切都可以无缝运行。但这是控制指标的问题,鼠标指标怎么样?
鼠标指标的情况稍微有趣一些。我们决定允许该模块执行特定操作,但并非独占性的操作。相反,它将率先启动这一进程。因此,鼠标指标模块内需要进行一些添加和删除。为了正确定义这些调整,我们将在下一部分中介绍它们。
小改动带来大改进
虽然鼠标模块最初是用于放置在主窗口中的,但 MQL5 允许我们做更多的事情。老实说,我最初没有考虑过实现某些功能。然而,由于 MetaTrader 5 提供了配置和定制选项,使我们能够更有效地执行特定操作,我决定引入一些修改。以下部分介绍了从 C_Terminal 类开始进行的更新和添加,如下所示。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Macros.mqh" 005. #include "..\Defines.mqh" 006. //+------------------------------------------------------------------+ 007. class C_Terminal 008. { 009. //+------------------------------------------------------------------+ 010. protected: 011. enum eErrUser {ERR_Unknown, ERR_FileAcess, ERR_PointerInvalid, ERR_NoMoreInstance}; 012. //+------------------------------------------------------------------+ 013. struct st_Terminal 014. { 015. ENUM_SYMBOL_CHART_MODE ChartMode; 016. ENUM_ACCOUNT_MARGIN_MODE TypeAccount; 017. long ID; 018. string szSymbol; 019. int Width, 020. Height, 021. nDigits, 022. SubWin; 023. double PointPerTick, 024. ValuePerPoint, 025. VolumeMinimal, 026. AdjustToTrade; 027. }; 028. //+------------------------------------------------------------------+ 029. private : 030. st_Terminal m_Infos; 031. struct mem 032. { 033. long Show_Descr, 034. Show_Date; 035. bool AccountLock; 036. }m_Mem; 037. //+------------------------------------------------------------------+ 038. void CurrentSymbol(void) 039. { 040. MqlDateTime mdt1; 041. string sz0, sz1; 042. datetime dt = macroGetDate(TimeCurrent(mdt1)); 043. enum eTypeSymbol {WIN, IND, WDO, DOL, OTHER} eTS = OTHER; 044. 045. sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); 046. for (eTypeSymbol c0 = 0; (c0 < OTHER) && (eTS == OTHER); c0++) eTS = (EnumToString(c0) == sz0 ? c0 : eTS); 047. switch (eTS) 048. { 049. case DOL : 050. case WDO : sz1 = "FGHJKMNQUVXZ"; break; 051. case IND : 052. case WIN : sz1 = "GJMQVZ"; break; 053. default : return; 054. } 055. for (int i0 = 0, i1 = mdt1.year - 2000, imax = StringLen(sz1);; i0 = ((++i0) < imax ? i0 : 0), i1 += (i0 == 0 ? 1 : 0)) 056. if (dt < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1), SYMBOL_EXPIRATION_TIME))) break; 057. } 058. //+------------------------------------------------------------------+ 059. public : 060. //+------------------------------------------------------------------+ 061. C_Terminal(const long id = 0, const uchar sub = 0) 062. { 063. m_Infos.ID = (id == 0 ? ChartID() : id); 064. m_Mem.AccountLock = false; 065. m_Infos.SubWin = (int) sub; 066. CurrentSymbol(); 067. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 068. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 069. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 070. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, true); 071. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, true); 072. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 073. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 074. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 075. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 076. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 077. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 078. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 079. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 080. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 081. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 082. ResetLastError(); 083. } 084. //+------------------------------------------------------------------+ 085. ~C_Terminal() 086. { 087. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, m_Mem.Show_Date); 088. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, m_Mem.Show_Descr); 089. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, false); 090. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, false); 091. } 092. //+------------------------------------------------------------------+ 093. inline void SetTypeAccount(const ENUM_ACCOUNT_MARGIN_MODE arg) 094. { 095. if (m_Mem.AccountLock) return; else m_Mem.AccountLock = true; 096. m_Infos.TypeAccount = (arg == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING ? arg : ACCOUNT_MARGIN_MODE_RETAIL_NETTING); 097. } 098. //+------------------------------------------------------------------+ 099. inline const st_Terminal GetInfoTerminal(void) const 100. { 101. return m_Infos; 102. } 103. //+------------------------------------------------------------------+ 104. const double AdjustPrice(const double arg) const 105. { 106. return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); 107. } 108. //+------------------------------------------------------------------+ 109. inline datetime AdjustTime(const datetime arg) 110. { 111. int nSeconds= PeriodSeconds(); 112. datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0); 113. 114. return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt))); 115. } 116. //+------------------------------------------------------------------+ 117. inline double FinanceToPoints(const double Finance, const uint Leverage) 118. { 119. double volume = m_Infos.VolumeMinimal + (m_Infos.VolumeMinimal * (Leverage - 1)); 120. 121. return AdjustPrice(MathAbs(((Finance / volume) / m_Infos.AdjustToTrade))); 122. }; 123. //+------------------------------------------------------------------+ 124. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 125. { 126. static string st_str = ""; 127. 128. switch (id) 129. { 130. case CHARTEVENT_CHART_CHANGE: 131. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 132. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 133. break; 134. case CHARTEVENT_OBJECT_CLICK: 135. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 136. if (ObjectGetInteger(m_Infos.ID, sparam, OBJPROP_SELECTABLE) == true) 137. ObjectSetInteger(m_Infos.ID, st_str = sparam, OBJPROP_SELECTED, true); 138. break; 139. case CHARTEVENT_OBJECT_CREATE: 140. if (st_str != sparam) ObjectSetInteger(m_Infos.ID, st_str, OBJPROP_SELECTED, false); 141. st_str = sparam; 142. break; 143. } 144. } 145. //+------------------------------------------------------------------+ 146. inline void CreateObjectGraphics(const string szName, const ENUM_OBJECT obj, const color cor = clrNONE, const int zOrder = -1) const 147. { 148. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, false); 149. ObjectCreate(m_Infos.ID, szName, obj, m_Infos.SubWin, 0, 0); 150. ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n"); 151. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, false); 152. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor); 153. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, false); 154. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, false); 155. ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder); 156. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 157. } 158. //+------------------------------------------------------------------+ 159. bool IndicatorCheckPass(const string szShortName) 160. { 161. string szTmp = szShortName + "_TMP"; 162. 163. if (_LastError != ERR_SUCCESS) return false; 164. IndicatorSetString(INDICATOR_SHORTNAME, szTmp); 165. m_Infos.SubWin = ((m_Infos.SubWin = ChartWindowFind(m_Infos.ID, szTmp)) < 0 ? 0 : m_Infos.SubWin); 166. if (ChartIndicatorGet(m_Infos.ID, m_Infos.SubWin, szShortName) != INVALID_HANDLE) 167. { 168. ChartIndicatorDelete(m_Infos.ID, 0, szTmp); 169. Print("Only one instance is allowed..."); 170. SetUserError(C_Terminal::ERR_NoMoreInstance); 171. 172. return false; 173. } 174. IndicatorSetString(INDICATOR_SHORTNAME, szShortName); 175. ResetLastError(); 176. 177. return true; 178. } 179. //+------------------------------------------------------------------+ 180. };
C_Terminal.mqh 头文件的源代码
当查看包含 C_Terminal 类的头文件中的代码时,您可能不会立即注意到所引入的细微变化。这些修改虽然很小,但使我们能够完成很多事情,特别是我们在不久的将来完成的任务。
所有修改源自第 22 行,其中引入了一个新变量。以前,这个变量不存在,但现在添加了它,以便我们将对象定向到特定的子窗口。
由于这个变量可以在类外访问,我们必须确保它有一个正确的初始值。为了实现这一点,我们对第 61 行的类构造函数进行了修改。现在,构造函数接收一个额外的参数,该参数可以保存 0 到 255 之间的值。考虑到图表很少包含超过两三个子窗口,这个范围就足够了。然而,还有一个重要的细节需要解决。查看第 65 行,我们通过显式类型转换来处理此问题。但是为什么不从一开始就将变量声明为 uchar,而是在这里进行转换呢?答案在于向后兼容性。MQL5 需要有符号整数类型,保持这种方式可以简化我们以后的工作。同时,使用 uchar 作为参数可以方便地将子窗口的数量限制为 255 个。这种方法既确保了兼容性,又确保了灵活性。
移至第 149 行,我们可以看到该值的使用方式。它通知 MetaTrader 5 应该在哪个图表窗口显示该对象。对于熟悉 MQL5 的人来说,这是一种常见的技术。然而,当我们检查从第 159 行开始的 IndicadorCheckPass 函数时,事情变得更加有趣。
通常,在图表上放置指标时,我们不需要指定在哪个窗口中绘制它。然而,在处理图形对象时,事情变得更加复杂,因为我们必须明确地确定哪个窗口将包含该对象。如果没有此信息,第 149 行的函数调用将始终将对象放置在错误的窗口中。那么,我们如何确定正确的窗口?
为了有效地做到这一点,我们在第 164 行使用了一个简单的技巧:为指标分配一个临时名称。然后,在第 165 行,我们使用 MQL5 中的 ChartWindowFind 函数。该函数允许 MetaTrader 5 准确地告诉我们哪个窗口包含指标。这里的关键点是其它指标不应有类似的临时名称,因为这可能会导致误报。如果 ChartWindowFind 没有返回有效的窗口索引,我们默认为主窗口(索引 0)。最后,在第 166 行,我们确保图表上只存在指标的单个实例。函数的其余部分保持不变,不需要进一步讨论。
通过这些调整,我们现在可以将鼠标指标模块定位在任何子窗口中。然而,为了使其正确运行,我们还需要修改类代码的某些方面。接下来,我们将快速回顾一下对 C_Mouse.mqh 头文件所做的修改。请看下面。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_Terminal.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_MousePrefixName "MouseBase" + (string)GetInfoTerminal().SubWin + "_" 007. #define macro_NameObjectStudy (def_MousePrefixName + "T" + (string)ObjectsTotal(0)) 008. //+------------------------------------------------------------------+ 009. class C_Mouse : public C_Terminal 010. { 011. public : 012. enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay}; 013. enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10}; 014. struct st_Mouse 015. { 016. struct st00 017. { 018. short X_Adjusted, 019. Y_Adjusted, 020. X_Graphics, 021. Y_Graphics; 022. double Price; 023. datetime dt; 024. }Position; 025. uchar ButtonStatus; 026. bool ExecStudy; 027. }; 028. //+------------------------------------------------------------------+ 029. protected: 030. //+------------------------------------------------------------------+ 031. void CreateObjToStudy(int x, int w, string szName, color backColor = clrNONE) const 032. { 033. if (!m_OK) return; 034. CreateObjectGraphics(szName, OBJ_BUTTON); 035. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_STATE, true); 036. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BORDER_COLOR, clrBlack); 037. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, clrBlack); 038. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BGCOLOR, backColor); 039. ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_FONT, "Lucida Console"); 040. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_FONTSIZE, 10); 041. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 042. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XDISTANCE, x); 043. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YDISTANCE, TerminalInfoInteger(TERMINAL_SCREEN_HEIGHT) + 1); 044. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_XSIZE, w); 045. ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_YSIZE, 18); 046. } 047. //+------------------------------------------------------------------+ 048. private : 049. enum eStudy {eStudyNull, eStudyCreate, eStudyExecute}; 050. struct st01 051. { 052. st_Mouse Data; 053. color corLineH, 054. corTrendP, 055. corTrendN; 056. eStudy Study; 057. }m_Info; 058. struct st_Mem 059. { 060. bool CrossHair, 061. IsFull; 062. datetime dt; 063. string szShortName, 064. szLineH, 065. szLineV, 066. szLineT, 067. szBtnS; 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(m_Mem.szLineV = macro_NameObjectStudy, OBJ_VLINE, m_Info.corLineH); 084. CreateObjectGraphics(m_Mem.szLineT = macro_NameObjectStudy, OBJ_TREND, m_Info.corLineH); 085. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_WIDTH, 2); 086. CreateObjToStudy(0, 0, m_Mem.szBtnS = macro_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, m_Mem.szBtnS, OBJPROP_TEXT, sz1); 107. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corTrendN : m_Info.corTrendP)); 108. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XSIZE, w); 109. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YSIZE, h); 110. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_XDISTANCE, GetInfoMouse().Position.X_Adjusted - w); 111. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szBtnS, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - (v1 < 0 ? 1 : h)); 112. ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 1, GetInfoMouse().Position.dt, GetInfoMouse().Position.Price); 113. ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineT, OBJPROP_COLOR, (memPrice > GetInfoMouse().Position.Price ? m_Info.corTrendN : m_Info.corTrendP)); 114. } 115. m_Info.Data.ButtonStatus = eKeyNull; 116. } 117. //+------------------------------------------------------------------+ 118. inline void DecodeAlls(int xi, int yi) 119. { 120. int w = 0; 121. 122. xi = (xi > 0 ? xi : 0); 123. yi = (yi > 0 ? yi : 0); 124. ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X_Graphics = (short)xi, m_Info.Data.Position.Y_Graphics = (short)yi, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price); 125. m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt); 126. m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price); 127. ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, xi, yi); 128. yi -= (int)ChartGetInteger(GetInfoTerminal().ID, CHART_WINDOW_YDISTANCE, GetInfoTerminal().SubWin); 129. m_Info.Data.Position.X_Adjusted = (short) xi; 130. m_Info.Data.Position.Y_Adjusted = (short) yi; 131. } 132. //+------------------------------------------------------------------+ 133. public : 134. //+------------------------------------------------------------------+ 135. C_Mouse(const long id, const string szShortName) 136. :C_Terminal(id), 137. m_OK(false) 138. { 139. m_Mem.szShortName = szShortName; 140. } 141. //+------------------------------------------------------------------+ 142. C_Mouse(const long id, const string szShortName, color corH, color corP, color corN) 143. :C_Terminal(id) 144. { 145. if (!(m_OK = IndicatorCheckPass(m_Mem.szShortName = szShortName))) SetUserError(C_Terminal::ERR_Unknown); 146. if (_LastError != ERR_SUCCESS) return; 147. m_Mem.CrossHair = (bool)ChartGetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL); 148. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, true); 149. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, false); 150. ZeroMemory(m_Info); 151. m_Info.corLineH = corH; 152. m_Info.corTrendP = corP; 153. m_Info.corTrendN = corN; 154. m_Info.Study = eStudyNull; 155. if (m_Mem.IsFull = (corP != clrNONE) && (corH != clrNONE) && (corN != clrNONE)) 156. CreateObjectGraphics(m_Mem.szLineH = (def_MousePrefixName + (string)ObjectsTotal(0)), OBJ_HLINE, m_Info.corLineH); 157. ChartRedraw(GetInfoTerminal().ID); 158. } 159. //+------------------------------------------------------------------+ 160. ~C_Mouse() 161. { 162. if (!m_OK) return; 163. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 164. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_MOUSE_MOVE, ChartWindowFind(GetInfoTerminal().ID, m_Mem.szShortName) != -1); 165. ChartSetInteger(GetInfoTerminal().ID, CHART_CROSSHAIR_TOOL, m_Mem.CrossHair); 166. ObjectsDeleteAll(GetInfoTerminal().ID, def_MousePrefixName); 167. } 168. //+------------------------------------------------------------------+ 169. inline bool CheckClick(const eBtnMouse value) 170. { 171. return (GetInfoMouse().ButtonStatus & value) == value; 172. } 173. //+------------------------------------------------------------------+ 174. inline const st_Mouse GetInfoMouse(void) 175. { 176. if (!m_OK) 177. { 178. double Buff[]; 179. uCast_Double loc; 180. int handle = ChartIndicatorGet(GetInfoTerminal().ID, 0, m_Mem.szShortName); 181. 182. ZeroMemory(m_Info.Data); 183. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) 184. { 185. loc.dValue = Buff[0]; 186. m_Info.Data.ButtonStatus = loc._8b[0]; 187. DecodeAlls((int)loc._16b[1], (int)loc._16b[2]); 188. } 189. IndicatorRelease(handle); 190. } 191. 192. return m_Info.Data; 193. } 194. //+------------------------------------------------------------------+ 195. inline void SetBuffer(const int rates_total, double &Buff[]) 196. { 197. uCast_Double info; 198. 199. info._8b[0] = (uchar)(m_Info.Study == C_Mouse::eStudyNull ? m_Info.Data.ButtonStatus : 0); 200. info._16b[1] = (ushort) m_Info.Data.Position.X_Graphics; 201. info._16b[2] = (ushort) m_Info.Data.Position.Y_Graphics; 202. Buff[rates_total - 1] = info.dValue; 203. } 204. //+------------------------------------------------------------------+ 205. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 206. { 207. int w = 0; 208. static double memPrice = 0; 209. 210. if (m_OK) 211. { 212. C_Terminal::DispatchMessage(id, lparam, dparam, sparam); 213. switch (id) 214. { 215. case (CHARTEVENT_CUSTOM + evHideMouse): 216. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, clrNONE); 217. break; 218. case (CHARTEVENT_CUSTOM + evShowMouse): 219. if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR, m_Info.corLineH); 220. break; 221. case CHARTEVENT_MOUSE_MOVE: 222. DecodeAlls((int)lparam, (int)dparam); 223. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineH, 0, 0, m_Info.Data.Position.Price); 224. if ((m_Info.Study != eStudyNull) && (m_Mem.IsFull)) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineV, 0, m_Info.Data.Position.dt, 0); 225. m_Info.Data.ButtonStatus = (uchar) sparam; 226. if (CheckClick(eClickMiddle)) 227. if ((!m_Mem.IsFull) || ((color)ObjectGetInteger(GetInfoTerminal().ID, m_Mem.szLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy(); 228. if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate)) 229. { 230. ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false); 231. if (m_Mem.IsFull) ObjectMove(GetInfoTerminal().ID, m_Mem.szLineT, 0, m_Mem.dt = GetInfoMouse().Position.dt, memPrice = GetInfoMouse().Position.Price); 232. m_Info.Study = eStudyExecute; 233. } 234. if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice); 235. m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute; 236. break; 237. case CHARTEVENT_OBJECT_DELETE: 238. if ((m_Mem.IsFull) && (sparam == m_Mem.szLineH)) 239. CreateObjectGraphics(m_Mem.szLineH, OBJ_HLINE, m_Info.corLineH); 240. break; 241. } 242. } 243. } 244. //+------------------------------------------------------------------+ 245. }; 246. //+------------------------------------------------------------------+ 247. #undef macro_NameObjectStudy 248. //+------------------------------------------------------------------+
C_Mouse.mqh 头文件的源代码
首先引起我们注意的是第 6 行和第 7 行。请注意,与以前不同,我们实现了略有不同的命名约定。需要进行此调整以避免与图表上现有的对象名称冲突。因此,名称现在取决于窗口和图表上存在的对象数量。换句话说,每个名字现在都是唯一的。
代码中有一些细微的差异,但没有什么真正值得关注的。然而,在第 64 行到第 67 行之间,声明了新的变量。这些变量用于存储要创建的对象的名称。为了帮助您理解其工作原理,请查看第 83 行,其中提供了一个命名对象的示例。在这里,很清楚如何使用宏来生成变量之一并为其命名。
虽然大部分代码不需要特别强调,因为修改很微妙,旨在提高对我们需求的支持,但有一部分确实需要一些解释。虽然它并不完美,但足以实现我们的目标。我指的是从第 118 行开始的函数,DecodeAlls。
当鼠标指标模块位于主窗口(即索引为零的窗口)时,此过程运行良好。然而,当我们将鼠标指标放在不同的窗口时,问题就开始出现。虽然我们已经解决了其中的许多问题,但还有一些问题仍然存在,正如您将在本文末尾的视频中看到的那样。
亲爱的读者,关键在于第 128 行,这可能会让你完全困惑和迷失方向。为什么会有这一行,之前为什么没有呢?要理解这一点,必须了解另一件事。鼠标指标模块最初旨在仅显示在主窗口中。此窗口的初始 Y 位置位于顶部,这意味着 Y 始终从零开始。但是,当我们添加额外的窗口时,主窗口的 Y 位置保持不变,而额外窗口的 Y 位置则根据特定值偏移。但对于操作系统(Windows)来说,这并不重要,它会告知 MetaTrader 5 鼠标的确切位置。
然后,MetaTrader 5 调整鼠标位置,使其保持在图表窗口内。因此,窗口外的值可能是负的或正的。如果鼠标指针位于窗口工作区区域上方,则会出现负值。如果您不熟悉窗口工作区的构成,请参考图 01,其中包含位图的整个区域被视为工作区。
图 01 - 了解工作空间区域
请注意,边框和标题栏不是窗口工作区的一部分。因此,如果鼠标指针进入标题栏区域,则 MetaTrader 5 (而不是操作系统)会更正该值以使其成为负数。至关重要的是要理解,当指针进入标题栏区域时,Y 值变为负值,这并不是因为操作系统这样做,而是因为 MetaTrader 5 修正了该值以将其保持在工作区区域内。
当您在主窗口中添加创建区域的元素时,例如图表不再属于的柱形,MetaTrader 5 不会将其识别为单独的窗口。即使我们这样定义它,MetaTrader 5 仍然将整个窗口视为一个实体,所有工作区都保持在主窗口的边界内。幸运的是,MetaTrader 5 允许我们知道该地区从哪里开始,尽管它没有提供关于它在哪里结束的信息。我们必须找到一种方法来确定这一点。尽管如此,仅仅知道该区域的起点就已经很有益处了。
为了确定区域的起点,我们调用 ChartGetInteger,传递常量 CHART_WINDOW_YDISTANCE 和子窗口编号。虽然我们使用术语“子窗口”来简化问题,但这个术语并不完全准确。
要从转换后的值中减去第 128 行调用所返回的值,请记住,转换后的值代表 MetaTrader 5 的报告的值,因此它保留在窗口内。如果第 128 行没有进行此项修正,则当鼠标指针进入 MetaTrader 5 解释为子窗口区域一部分的位置时,我们就会得到水平线位置的错误指示。然而,通过这一修正,这个问题就被避免了。至少,不是以通常发生的方式。
另一个值得一提的重要地方是第 145 行,我们在那里存储指标的短名称。但我们为什么要这么做呢?原因在第 164 行变得清楚起来。如果不知道鼠标指标名称,我们就无法检查并确定是否可以告诉 MetaTrader 5 停止发送鼠标事件。有些人可能会质疑我们是否可以简单地保持事件始终启用,但这是不必要的。每次鼠标移动时,MetaTrader 5 都会触发鼠标事件,如果没有人使用它,这只会向事件队列添加一些无用的东西。为了避免这种情况,一旦我们不再需要该事件,我们就会禁用它。但是,如果不知道鼠标指标的名称,我们如何判断是否还有指示器需要该事件呢?没有办法。因此,为了使事情变得简单,我们存储指标名称并使用它来检查是否可以关闭鼠标事件。
除了上面提到的修改之外,还有其他的修改,但由于它们相对简单,我就不详细说了。为了找到它们,只需将此代码与 C_Mouse.mqh 头文件的先前版本进行比较。这将是你的家庭作业,以了解更多有关如何编码和修改事物而不会在最终代码中引入重大问题的知识。
正如预期的那样,C_Study.mqh 文件也经历了一些修改。然而,正如 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. //+------------------------------------------------------------------+ 008. class C_Study : public C_Mouse 009. { 010. private : 011. //+------------------------------------------------------------------+ 012. struct st00 013. { 014. eStatusMarket Status; 015. MqlRates Rate; 016. string szInfo, 017. szBtn1, 018. szBtn2, 019. szBtn3; 020. color corP, 021. corN; 022. int HeightText; 023. bool bvT, bvD, bvP; 024. datetime TimeDevice; 025. }m_Info; 026. //+------------------------------------------------------------------+ 027. const datetime GetBarTime(void) 028. { 029. datetime dt; 030. int i0 = PeriodSeconds(); 031. 032. if (m_Info.Status == eInReplay) 033. { 034. if ((dt = m_Info.TimeDevice) == ULONG_MAX) return ULONG_MAX; 035. }else dt = TimeCurrent(); 036. if (m_Info.Rate.time <= dt) 037. m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0) + i0; 038. 039. return m_Info.Rate.time - dt; 040. } 041. //+------------------------------------------------------------------+ 042. void Draw(void) 043. { 044. double v1; 045. 046. if (m_Info.bvT) 047. { 048. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 18); 049. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn1, OBJPROP_TEXT, m_Info.szInfo); 050. } 051. if (m_Info.bvD) 052. { 053. v1 = NormalizeDouble((((GetInfoMouse().Position.Price - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 054. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 055. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 056. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn2, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 057. } 058. if (m_Info.bvP) 059. { 060. v1 = NormalizeDouble((((iClose(GetInfoTerminal().szSymbol, PERIOD_D1, 0) - m_Info.Rate.close) / m_Info.Rate.close) * 100.0), 2); 061. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_YDISTANCE, GetInfoMouse().Position.Y_Adjusted - 1); 062. ObjectSetInteger(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP)); 063. ObjectSetString(GetInfoTerminal().ID, m_Info.szBtn3, OBJPROP_TEXT, StringFormat("%.2f%%", MathAbs(v1))); 064. } 065. } 066. //+------------------------------------------------------------------+ 067. inline void CreateObjInfo(EnumEvents arg) 068. { 069. switch (arg) 070. { 071. case evShowBarTime: 072. C_Mouse::CreateObjToStudy(2, 110, m_Info.szBtn1 = (def_ExpansionPrefix + (string)ObjectsTotal(0)), clrPaleTurquoise); 073. m_Info.bvT = true; 074. break; 075. case evShowDailyVar: 076. C_Mouse::CreateObjToStudy(2, 53, m_Info.szBtn2 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 077. m_Info.bvD = true; 078. break; 079. case evShowPriceVar: 080. C_Mouse::CreateObjToStudy(58, 53, m_Info.szBtn3 = (def_ExpansionPrefix + (string)ObjectsTotal(0))); 081. m_Info.bvP = true; 082. break; 083. } 084. } 085. //+------------------------------------------------------------------+ 086. inline void RemoveObjInfo(EnumEvents arg) 087. { 088. string sz; 089. 090. switch (arg) 091. { 092. case evHideBarTime: 093. sz = m_Info.szBtn1; 094. m_Info.bvT = false; 095. break; 096. case evHideDailyVar: 097. sz = m_Info.szBtn2; 098. m_Info.bvD = false; 099. break; 100. case evHidePriceVar: 101. sz = m_Info.szBtn3; 102. m_Info.bvP = false; 103. break; 104. } 105. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false); 106. ObjectDelete(GetInfoTerminal().ID, sz); 107. ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true); 108. } 109. //+------------------------------------------------------------------+ 110. public : 111. //+------------------------------------------------------------------+ 112. C_Study(long IdParam, string szShortName, color corH, color corP, color corN) 113. :C_Mouse(IdParam, szShortName, corH, corP, corN) 114. { 115. if (_LastError != ERR_SUCCESS) return; 116. ZeroMemory(m_Info); 117. m_Info.Status = eCloseMarket; 118. m_Info.Rate.close = iClose(GetInfoTerminal().szSymbol, PERIOD_D1, ((GetInfoTerminal().szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(GetInfoTerminal().szSymbol, PERIOD_D1, 0))) ? 0 : 1)); 119. m_Info.corP = corP; 120. m_Info.corN = corN; 121. CreateObjInfo(evShowBarTime); 122. CreateObjInfo(evShowDailyVar); 123. CreateObjInfo(evShowPriceVar); 124. } 125. //+------------------------------------------------------------------+ 126. void Update(const eStatusMarket arg) 127. { 128. datetime dt; 129. 130. switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status)) 131. { 132. case eCloseMarket : 133. m_Info.szInfo = "Closed Market"; 134. break; 135. case eInReplay : 136. case eInTrading : 137. if ((dt = GetBarTime()) < ULONG_MAX) 138. { 139. m_Info.szInfo = TimeToString(dt, TIME_SECONDS); 140. break; 141. } 142. case eAuction : 143. m_Info.szInfo = "Auction"; 144. break; 145. default : 146. m_Info.szInfo = "ERROR"; 147. } 148. Draw(); 149. } 150. //+------------------------------------------------------------------+ 151. virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 152. { 153. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 154. switch (id) 155. { 156. case CHARTEVENT_CUSTOM + evHideBarTime: 157. RemoveObjInfo(evHideBarTime); 158. break; 159. case CHARTEVENT_CUSTOM + evShowBarTime: 160. CreateObjInfo(evShowBarTime); 161. break; 162. case CHARTEVENT_CUSTOM + evHideDailyVar: 163. RemoveObjInfo(evHideDailyVar); 164. break; 165. case CHARTEVENT_CUSTOM + evShowDailyVar: 166. CreateObjInfo(evShowDailyVar); 167. break; 168. case CHARTEVENT_CUSTOM + evHidePriceVar: 169. RemoveObjInfo(evHidePriceVar); 170. break; 171. case CHARTEVENT_CUSTOM + evShowPriceVar: 172. CreateObjInfo(evShowPriceVar); 173. break; 174. case (CHARTEVENT_CUSTOM + evSetServerTime): 175. m_Info.TimeDevice = (datetime)dparam; 176. break; 177. case CHARTEVENT_MOUSE_MOVE: 178. Draw(); 179. break; 180. } 181. ChartRedraw(GetInfoTerminal().ID); 182. } 183. //+------------------------------------------------------------------+ 184. }; 185. //+------------------------------------------------------------------+ 186. #undef def_ExpansionPrefix 187. #undef def_MousePrefixName 188. //+------------------------------------------------------------------+
C_Study.mqh 类的源代码
但是,在本文中,我想展示下面的代码。这是该指标的源代码。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "This is an indicator for graphical studies using the mouse." 04. #property description "This is an integral part of the Replay / Simulator system." 05. #property description "However it can be used in the real market." 06. #property version "1.59" 07. #property icon "/Images/Market Replay/Icons/Indicators.ico" 08. #property link "https://www.mql5.com/pt/articles/12075" 09. #property indicator_chart_window 10. #property indicator_plots 0 11. #property indicator_buffers 1 12. //+------------------------------------------------------------------+ 13. #include <Market Replay\Auxiliar\Study\C_Study.mqh> 14. //+------------------------------------------------------------------+ 15. C_Study *Study = NULL; 16. //+------------------------------------------------------------------+ 17. input color user02 = clrBlack; //Price Line 18. input color user03 = clrPaleGreen; //Positive Study 19. input color user04 = clrLightCoral; //Negative Study 20. //+------------------------------------------------------------------+ 21. C_Study::eStatusMarket m_Status; 22. int m_posBuff = 0; 23. double m_Buff[]; 24. //+------------------------------------------------------------------+ 25. int OnInit() 26. { 27. ResetLastError(); 28. Study = new C_Study(0, "Indicator Mouse Study", user02, user03, user04); 29. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 30. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 31. { 32. MarketBookAdd((*Study).GetInfoTerminal().szSymbol); 33. OnBookEvent((*Study).GetInfoTerminal().szSymbol); 34. m_Status = C_Study::eCloseMarket; 35. }else 36. m_Status = C_Study::eInReplay; 37. SetIndexBuffer(0, m_Buff, INDICATOR_DATA); 38. ArrayInitialize(m_Buff, EMPTY_VALUE); 39. 40. return INIT_SUCCEEDED; 41. } 42. //+------------------------------------------------------------------+ 43. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 44. { 45. m_posBuff = rates_total; 46. (*Study).Update(m_Status); 47. 48. return rates_total; 49. } 50. //+------------------------------------------------------------------+ 51. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 52. { 53. (*Study).DispatchMessage(id, lparam, dparam, sparam); 54. (*Study).SetBuffer(m_posBuff, m_Buff); 55. 56. ChartRedraw((*Study).GetInfoTerminal().ID); 57. } 58. //+------------------------------------------------------------------+ 59. void OnBookEvent(const string &symbol) 60. { 61. MqlBookInfo book[]; 62. C_Study::eStatusMarket loc = m_Status; 63. 64. if (symbol != (*Study).GetInfoTerminal().szSymbol) return; 65. MarketBookGet((*Study).GetInfoTerminal().szSymbol, book); 66. m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading); 67. for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++) 68. if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction; 69. if (loc != m_Status) (*Study).Update(m_Status); 70. } 71. //+------------------------------------------------------------------+ 72. void OnDeinit(const int reason) 73. { 74. if (reason != REASON_INITFAILED) 75. { 76. if ((*Study).GetInfoTerminal().szSymbol != def_SymbolReplay) 77. MarketBookRelease((*Study).GetInfoTerminal().szSymbol); 78. } 79. delete Study; 80. } 81. //+------------------------------------------------------------------+ 82.
鼠标指针源代码
在这段代码中,从一开始你就会注意到我们不再使用某些输入参数。这意味着我们处理该指标的方式已经改变。进行这种修改的原因是为了让您可以自由地将指标放置在您认为合适的任何位置。它的用途超出了我最初的设想。因此,我将不再标明它的具体位置。因此,以前需要用户设置图表 ID 和资产状态的输入不再需要。
不过,我希望您注意第 36 行的一个小修改。现在,如果将此模块添加到回放/模拟器服务正在使用的资产图表中,第 36 行将自动调整内容。这意味着用户将不再有权进行以前需要的修改,例如指定图表 ID。备注:虽然不再需要提供图表 ID,但是之前文章中讨论的有关函数调用的所有内容仍然适用,至少在我撰写本文时是如此。
结论
通过一些工作和利用先前的知识,我已经能够演示 MetaTrader 5 中的某些功能是如何工作的。确实,如果你一直在关注这些文章,你可能会觉得我们没有取得多大进展。事实上,我们一直在取得进展,尽管速度较慢。这是因为我不得不提出并测试一些我不确定在 MetaTrader 5 中是否可以实际完成的事情。
许多自称是程序员的人只是声称 MetaTrader 5 无法实现某些事情 – 该平台缺少这个或那个功能。然而,我发现这些人经常被误导。
在本文的最后,我将给您留下一个视频,展示鼠标指标模块如何运行。仔细观察视频,会发现其中有一处缺陷。我知道这一点,但由于它并不重要,我将把修复留到下次,等我对 MetaTrader 5 的真正工作原理有了更好的理解后,特别是那些我还没有完全理解的方面。
亲爱的读者,我希望你能够参与到这个学习过程。因此,我将继续展示如何开发用于回放/模拟的系统以用于真实市场和模拟账户。
演示视频:新的鼠标指标在起作用
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12075
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。



在你复制市场的基础上,做一些与众不同的事情,不仅对你,而且对许多其他人都有用、有趣。
我会自己做,但我不是专业程序员。我仔细研究了你的所有代码。由于时间不够,我无法在它们的基础上实现我的想法。
如果你们觉得我的提议很有趣,并且/或者考虑在此基础上制作商业产品,那么我希望能免费获得该产品,作为对我想法的回报。
如果你们同意,我愿意发表意见。