
开发回放系统(第 58 部分):重返服务工作
概述
在上一篇文章开发回放系统(第 57 部分):测试服务详解中,我详细解释了所需的源代码,以展示我们将在回放/模拟器系统中使用的模块之间可能的交互方式。
虽然这段代码让我们了解了实际需要实现什么,但它仍然缺少一个对我们的系统真正有用的重要细节 — 使用模板的能力。如果您不使用或不完全理解模板在编码和 MetaTrader 5 设置方面给我们带来的好处,您可能会认为这并不重要。
然而,了解、理解和应用模板大大减少了我们的工作量。有些事情很容易用模板完成,但如果你试图直接对它们进行编程,就会变得极其复杂和难以实现。也许将来我会告诉你如何只使用模板做一些事情,但现在我们还有其他更紧迫的任务。
说实话,我以为我已经达到了控制和鼠标模块不需要任何改进的程度。然而,由于我们将在后续文章中看到的一些细节,这两个模块仍需进行细微更改。我们稍后会看到这一点,但现在,在本文中,我们将找出如何将上一篇文章中获得的知识转化为可行和实用的东西。为此,让我们转向一个新的话题。
修改旧的回放/模拟器服务
虽然距离我们上次对回放/模拟器代码进行修改或改进已经有一段时间了,但是构建回放/模拟器可执行文件所涉及的某些头文件已经发生了变化。也许最显著的变化是删除了 InterProcess.mqh 头文件,并将其替换为 Defines.mqh,后者是一个用途更广泛的文件。
由于我们已经对控制和鼠标模块进行了调整以适应这个新的头文件,因此我们现在必须将相同的更改应用于回放/模拟服务。因此,试图使用更新的头文件结构编译回放/模拟服务将导致编译错误,如图 01 所示。
图 01.尝试编译复制/建模服务
在可能出现的各种错误中,您应该首先解决突出显示的两个错误。为了解决这些问题,请打开 C_Simulation.mqh 头文件并修改代码,如下面的代码片段所示。所需的修改很小 – 只需删除第 04 行并将其替换为第 05 行中显示的调整。此修改确保 C_Simulation.mqh 符合我们正在实现的新框架。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "..\..\Auxiliar\Interprocess.mqh" 05. #include "..\..\Defines.mqh" 06. //+------------------------------------------------------------------+ 07. #define def_MaxSizeArray 16777216 // 16 Mbytes of positions 08. //+------------------------------------------------------------------+ 09. class C_Simulation 10. { 11. private : 12. //+------------------------------------------------------------------+ 13. int m_NDigits; 14. bool m_IsPriceBID;
C_Simulation.mqh 文件源代码片段
就像我们在 C_Simulation.mqh 头文件中所做的那样,我们需要在 C_FilesBars.mqh 文件中做类似的事情。为此,打开 C_FilesBars.mqh 头文件并修改代码,如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "..\..\Auxiliar\Interprocess.mqh" 05. #include "..\..\Defines.mqh" 06. //+------------------------------------------------------------------+ 07. #define def_BarsDiary 1440 08. //+------------------------------------------------------------------+ 09. class C_FileBars 10. { 11. private : 12. int m_file;
C_FilesBars.mqh 文件源代码片段
在这两个代码片段中,我们都删除了 InterProcess.mqh 头文件,并用 Defines.mqh 替换它。通过这两项修改,大部分代码将与回放/模拟器服务的预期结构保持一致。但是,存在一个问题。如果比较 InterProcess.mqh 和 Defines.mqh 的内容,您会注意到 Defines.mqh 没有引用终端全局变量。尽管如此,回放/模拟器系统仍然参考这些变量。
更具体地说,这些变量在 C_Replay.mqh 文件中使用。然而,这并不是我们担心的唯一问题。将来,我可能会决定进一步重构代码,以提高其组织性、稳定性和灵活性。不过,现在我将专注于调整现有结构,而不是为了稍微提高灵活性和稳定性而对整个系统进行重大改变 — 尽管两者都值得加强。
为了清楚起见,让我们把这个解释分成几个部分。我们要解决的第一个问题是一个缺陷,虽然它并不严重,但却违反了面向对象编程的核心原则之一:封装。
审查代码封装
任何代码库中最严重的问题之一是未能遵守确保安全性和可维护性的基本面向对象编程原则。长期以来,我忽视并误用了代码的特定部分,以方便直接访问回放/模拟器功能所需的某些数据。
然而,从现在开始,这种做法将不再使用。具体来说,我指的是 C_ConfigService 类中存在的封装漏洞。
如果您检查该类的头文件 (C_ConfigService.mqh),您会注意到一个包含多个变量的受保护子句。尽管本节中这些变量仅在 C_ConfigService 及其派生类 C_Replay 中使用,但它们的存在破坏了封装。这些变量在 C_ConfigService 外部以其当前形式访问是不合适的。如果您查看 C_Replay 类,您将看到它修改了这些变量,这正是这种方法存在问题的原因。在 C++ 中,有办法将类变量设为私有,同时仍允许在基类之外进行受控的访问和操作。然而,这些技术往往导致代码过于复杂,难以维护。此外,它们使未来的改进更具挑战性。
由于 MQL5 源自 C++,因此它避免采用 C++ 允许的某些风险做法。因此,严格遵守面向对象编程的三个基本原则(包括正确的封装)是更合适的。
通过修改 C_ConfigService.mqh 头文件,我们将在系统内恢复正确的封装。然而,这一变化将需要在更高级别的代码库中进行调整。具体来说,位于 C_Replay.mqh 文件中的 C_Replay 类将经历重大修改。同时,我们将借此机会改进代码结构,使得回放/模拟器服务更少嵌套。通过实施较小的增量更改,我们可以简化维护并改善对每一步发生的事情的控制。这对未来的更新尤其有益,因为我们很快将需要实现涉及多个互连组件的更复杂的功能。
让我们看看需要做些什么才能让事情变得更加合适。要开始改进封装,请打开 C_ConfigService.mqh 头文件并修改代码,如以下片段所示。其余代码将保持不变,但此片段中的更改将确保正确实现封装。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Support\C_FileBars.mqh" 05. #include "Support\C_FileTicks.mqh" 06. #include "Support\C_Array.mqh" 07. //+------------------------------------------------------------------+ 08. class C_ConfigService : protected C_FileTicks 09. { 10. protected: 11. datetime m_dtPrevLoading; 12. int m_ReplayCount, 13. m_ModelLoading; 14. //+------------------------------------------------------------------+ 15. inline void FirstBarNULL(void) 16. { 17. MqlRates rate[1]; 18. int c0 = 0; 19. 20. for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++); 21. rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid); 22. rate[0].open = rate[0].high = rate[0].low = rate[0].close; 23. rate[0].tick_volume = 0; 24. rate[0].real_volume = 0; 25. rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400; 26. CustomRatesUpdate(def_SymbolReplay, rate); 27. m_ReplayCount = 0; 28. } 29. //+------------------------------------------------------------------+ 30. private : 31. enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev}; 32. enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE}; 33. struct st001 34. { 35. C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev; 36. int Line; 37. }m_GlPrivate; 38. string m_szPath; 39. bool m_AccountHedging; 40. datetime m_dtPrevLoading; 41. int m_ReplayCount, 42. m_ModelLoading; 43. //+------------------------------------------------------------------+ 44. inline void FirstBarNULL(void) 45. { 46. MqlRates rate[1]; 47. int c0 = 0; 48. 49. for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == 0); c0++); 50. rate[0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid); 51. rate[0].open = rate[0].high = rate[0].low = rate[0].close; 52. rate[0].tick_volume = 0; 53. rate[0].real_volume = 0; 54. rate[0].time = macroRemoveSec(m_Ticks.Info[c0].time) - 86400; 55. CustomRatesUpdate(def_SymbolReplay, rate); 56. m_ReplayCount = 0; 57. } 58. //+------------------------------------------------------------------+ 59. inline eTranscriptionDefine GetDefinition(const string &In, string &Out)
C_ConfigService.mqh 文件源代码片段
请注意,第 11 至 13 行的内容已移至第 40 和 42 行。这意味着现在无法在 C_ConfigService 类主体之外访问这些变量。除此之外,还做出了一项修改。这个修改可以被忽略,但由于某些东西不会在类外使用,我决定将 FirstBarNULL 过程设为私有。因此,第 15 行到第 28 行之间的内容已被移至第 44 行到第 57 行。
很明显,当您对实际文件进行这些更改时,行号将会有所不同,因为删除的代码不再是类代码的一部分。然而,为了清晰起见,我决定保留片段中的所有内容。我认为这样会更清楚、更容易理解发生了什么变化。
好了,现在,做出这些更改后,我们必须彻底改变 C_Replay.mqh 文件中现有的代码。但是,让我们继续把事情分开,并在下一个主题中讨论这个问题。
重新开始 C_Replay 类的实现
尽管本节的标题可能看起来令人沮丧,暗示我们正在重新创造已经构建的东西,但事实并非如此。我想强调的是,虽然我们确实需要重新设计 C_Replay 类的很大一部分,但通过本系列文章获得的知识仍然很有价值。我们正在做的是适应新的结构和方法,因为某些事情不能再像以前那样实现了。
下面提供了 C_Replay 类的完整修订代码。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_ConfigService.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_IndicatorControl "Indicators\\Market Replay.ex5" 007. #resource "\\" + def_IndicatorControl 008. //+------------------------------------------------------------------+ 009. #define def_CheckLoopService ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) 010. //+------------------------------------------------------------------+ 011. #define def_ShortNameIndControl "Market Replay Control" 012. //+------------------------------------------------------------------+ 013. class C_Replay : public C_ConfigService 014. { 015. private : 016. long m_IdReplay; 017. struct st00 018. { 019. ushort Position; 020. short Mode; 021. }m_IndControl; 022. //+------------------------------------------------------------------+ 023. inline bool MsgError(string sz0) { Print(sz0); return false; } 024. //+------------------------------------------------------------------+ 025. inline void UpdateIndicatorControl(void) 026. { 027. uCast_Double info; 028. int handle; 029. double Buff[]; 030. 031. if ((handle = ChartIndicatorGet(m_IdReplay, 0, def_ShortNameIndControl)) == INVALID_HANDLE) return; 032. info.dValue = 0; 033. if (CopyBuffer(handle, 0, 0, 1, Buff) == 1) 034. info.dValue = Buff[0]; 035. IndicatorRelease(handle); 036. if ((short)(info._16b[0]) != SHORT_MIN) 037. m_IndControl.Mode = (short)info._16b[1]; 038. if (info._16b[0] != m_IndControl.Position) 039. { 040. if (((short)(info._16b[0]) != SHORT_MIN) && ((short)(info._16b[1]) == SHORT_MAX)) 041. m_IndControl.Position = info._16b[0]; 042. info._16b[0] = m_IndControl.Position; 043. info._16b[1] = (ushort)m_IndControl.Mode; 044. EventChartCustom(m_IdReplay, evCtrlReplayInit, 0, info.dValue, ""); 045. } 046. } 047. //+------------------------------------------------------------------+ 048. void SweepAndCloseChart(void) 049. { 050. long id; 051. 052. if ((id = ChartFirst()) > 0) do 053. { 054. if (ChartSymbol(id) == def_SymbolReplay) 055. ChartClose(id); 056. }while ((id = ChartNext(id)) > 0); 057. } 058. //+------------------------------------------------------------------+ 059. public : 060. //+------------------------------------------------------------------+ 061. C_Replay() 062. :C_ConfigService() 063. { 064. Print("************** Market Replay Service **************"); 065. srand(GetTickCount()); 066. SymbolSelect(def_SymbolReplay, false); 067. CustomSymbolDelete(def_SymbolReplay); 068. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 069. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 070. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 071. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 072. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 073. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 074. SymbolSelect(def_SymbolReplay, true); 075. } 076. //+------------------------------------------------------------------+ 077. bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate) 078. { 079. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 080. return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket."); 081. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 082. return MsgError("Asset configuration is not complete, need to declare the ticket value."); 083. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 084. return MsgError("Asset configuration not complete, need to declare the minimum volume."); 085. SweepAndCloseChart(); 086. m_IdReplay = ChartOpen(def_SymbolReplay, arg1); 087. if (!ChartApplyTemplate(m_IdReplay, szNameTemplate + ".tpl")) 088. Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl"); 089. else 090. Print("Apply template: ", szNameTemplate, ".tpl"); 091. 092. return true; 093. } 094. //+------------------------------------------------------------------+ 095. bool InitBaseControl(const ushort wait = 1000) 096. { 097. int handle; 098. 099. Print("Waiting for Mouse Indicator..."); 100. Sleep(wait); 101. while ((def_CheckLoopService) && (ChartIndicatorGet(m_IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 102. if (def_CheckLoopService) 103. { 104. Print("Waiting for Control Indicator..."); 105. if ((handle = iCustom(ChartSymbol(m_IdReplay), ChartPeriod(m_IdReplay), "::" + def_IndicatorControl, m_IdReplay)) == INVALID_HANDLE) return false; 106. ChartIndicatorAdd(m_IdReplay, 0, handle); 107. IndicatorRelease(handle); 108. m_IndControl.Position = 0; 109. m_IndControl.Mode = SHORT_MIN; 110. UpdateIndicatorControl(); 111. } 112. 113. return def_CheckLoopService; 114. } 115. //+------------------------------------------------------------------+ 116. bool LoopEventOnTime(void) 117. { 118. 119. while (def_CheckLoopService) 120. { 121. UpdateIndicatorControl(); 122. Sleep(250); 123. } 124. 125. return false; 126. } 127. //+------------------------------------------------------------------+ 128. ~C_Replay() 129. { 130. SweepAndCloseChart(); 131. SymbolSelect(def_SymbolReplay, false); 132. CustomSymbolDelete(def_SymbolReplay); 133. Print("Finished replay service..."); 134. } 135. //+------------------------------------------------------------------+
C_Replay.mqh 文件的源代码
尽管此代码还不能像以前那样执行回放/模拟,因为仍然缺少某些组件,但其目的是使回放/模拟服务能够利用上一篇文章中未涉及的元素。这些元素包括能够像以前一样加载以前的柱形,以及回放和模拟所需的柱形。然而,在本文中,我们还不能充分利用这些回放或模拟柱形。相反,当系统能够在自定义资产图表上正确显示它们时,它们将被加载并可用。
上述代码有几个方面需要进一步解释。即使对于那些拥有丰富 MQL5 经验的人来说,它的许多组件可能也不是那么清楚。然而,这里提供的解释是针对那些真正想了解为什么以这种特定方式构建代码的人。
在代码的开头,第 5 行到第 11 行,我们定义了某些参数并将编译后的指标文件包含在服务的可执行文件中。这背后的原因已在本系列有关回放/模拟的先前文章中进行了广泛的讨论。因此,我只是强调这一点,提醒您没有必要手动传输控制指标文件。
然后,在第 13 行,我们建立从 C_ConfigService 类的公共继承。这样做是为了确保工作负载不只集中在 C_Replay 类中;而是分布在 C_Replay 和 C_ConfigService 之间。这加强了前一节中所做更改的重要性,我们在前一节讨论了正确封装数据和变量所需的修改。
C_Replay 类的私有部分从第 15 行开始,一直延伸到第 58 行,即公共部分的开始。我们首先来研究一下私有部分是如何运作的。它包含一小组全局变量,在第 16 行到第 21 行之间声明。请特别注意第 21 行,其中变量被声明为结构,这意味着它包含额外的嵌套数据。
在第 23 行,我们定义了一个小函数,其唯一目的是向终端打印错误消息并返回 false。但为什么这里返回 false 呢?如果没有这个返回值,每次向终端打印错误消息时,我们都需要额外的一行代码。为了清楚起见,请看第 79 行,我们在这里检查了某个条件。如果检测到错误,我们通常需要两行:一行打印错误消息,另一行返回错误指示。这将造成不必要的冗余。但是,通过使用第 23 行中声明的函数,我们可以在一个步骤中打印消息并返回失败指示。这在第 80 行看到,它简化了实现。我们以某种方式结合起来以减少我们的编码工作。
也许代码中最重要的部分是第 25 行到第 46 行之间,这段代码为我们做了一些非常重要的工作。它管理和调整来自控制指标的数据。在尝试理解本节之前,请确保您完全理解所有相关组件如何交互。如果有疑问,请参阅以前的文章,其中解释了控制指标如何与外部组件通信。
第 31 行尝试获取用于访问控制指标的句柄。如果失败了,这也不是一个严重的错误。该函数只是返回,跳过其余的过程。如果成功获得句柄,我们将重置测试值,如第 32 行所示。这很关键,必须正确完成。第 33 行检查指标缓冲区是否可读。如果是的话,第 34 行将该值分配给测试和调整变量。本节可能会在以后的文章中进行细微的改进,但核心逻辑将保持不变。
一旦不再需要该句柄,第 35 行就会释放它,我们进入测试和调整获取到的信息的阶段。第 36 行检查控制指标是否包含有效数据。如果是,我们会保存系统是否处于暂停模式还是主动播放模式(回放/模拟)的信息。此保存工作在第 37 行执行。这必须在发生任何修改之前完成;否则,检索到的数据可能会过早更改,从而损害信息的完整性。这里的目标是确保服务提供最新形式的控制指标 — 以前使用全局终端变量完成的操作。
现在请注意第 38 行。它将指标缓冲区的内容与全局定位系统进行比较。如果发现差异,第 40 行将执行二次检查,以查看控制指标是否已初始化以及系统是否处于播放模式。如果两个条件都满足,第 41 行将保存缓冲区值。这很关键,因为在暂停模式下,我们不想自动更新数据。我们希望允许用户根据需要手动调整控制指标。
最后,在第 42 和 43 行,我们整理要传递给控制指标的信息。这是通过第 44 行触发的自定义事件传输的。一旦触发,MetaTrader 5 将接管并执行其任务,同时服务继续并行运行。
应该非常仔细地分析此过程中的代码,直到真正清楚发生了什么。与前一篇文章中的方法相比,这个版本更复杂,尽管执行的功能基本相同。一旦 MetaTrader 5 将控制指标放置在图表上,此代码就会对其进行初始化。从那时起,它就监视其状态。如果用户改变时间框架,该服务将保留指标的最后已知状态,确保在返回图表时使用以前的设置重新初始化它。
现在让我们看一下考虑可重用性而创建的代码,它位于第 48 行。该过程只是关闭所有包含要复制的交易品种的 MetaTrader 5 图表窗口。看到了吧,没什么复杂的。但由于我们至少需要执行两次,因此我决定创建此过程以避免重复代码。
因此,从这一点开始我们转到 C_Replay 类的公共过程。基本上,您可以看到代码与以前没有太大区别,至少就类构造函数和析构函数而言是这样。因此,我不会对它们做任何进一步的解释,因为我在之前的文章中已经对它们进行了适当的介绍,在这些文章中我解释了 C_Replay 类的功能。然而,这里有三个函数确实值得解释一下。让我们按照它们在代码中出现的顺序来看一下它们。
第一个函数称为 OpenChartReplay,从第 77 行开始,到第 93 行结束。它检查引导系统收集的信息的完整性。这是必要的,以便回放或模拟器能够真正执行。然而,正是在这个函数中我们发现了一些相当复杂的东西,它与我们稍后将讨论的 InitBaseControl 函数一起允许我们使用模板。
使用模板的问题对我们来说非常重要。必须正确使用并以适当的方式启动。但做到这一点并不像包括我在内的许多人最初想象的那么容易。在第 87 行中,我们尝试将模板添加到图表中,之前我们在第 86 行中打开了该模板。要使用的模板被指定为函数参数之一。无论如何,模板都会放置在图表上,无论是用户指定的模板还是标准的 MetaTrader 5 模板。但这里有一个很少被提及的细节:模板不会立即放置。ChartApplyTemplate 函数不会立即应用模板。此函数是异步的,这意味着它可以在调用后的几毫秒内执行。这对我们来说是一个问题。
为了了解问题的尺度,我们将暂时离开 C_Replay 类并查看下面的服务代码。
01. //+------------------------------------------------------------------+ 02. #property service 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property copyright "Daniel Jose" 05. #property version "1.58" 06. #property description "Replay-Simulator service for MetaTrade 5 platform." 07. #property description "This is dependent on the Market Replay indicator." 08. #property description "For more details on this version see the article." 09. #property link "https://www.mql5.com/pt/articles/" 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Service Graphics\C_Replay.mqh> 12. //+------------------------------------------------------------------+ 13. input string user00 = "Mini Dolar.txt"; //Replay Configuration File. 14. input ENUM_TIMEFRAMES user01 = PERIOD_M5; //Initial Graphic Time. 15. input string user02 = "Default"; //Template File Name 16. //+------------------------------------------------------------------+ 17. C_Replay *pReplay; 18. //+------------------------------------------------------------------+ 19. void OnStart() 20. { 21. pReplay = new C_Replay(); 22. 23. UsingReplay(); 24. 25. delete pReplay; 26. } 27. //+------------------------------------------------------------------+ 28. void UsingReplay(void) 29. { 30. if (!(*pReplay).SetSymbolReplay(user00)) return; 31. if (!(*pReplay).OpenChartReplay(user01, user02)) return; 32. if (!(*pReplay).InitBaseControl()) return; 33. Print("Permission granted. Replay service can now be used..."); 34. while ((*pReplay).LoopEventOnTime()); 35. } 36. //+------------------------------------------------------------------+
回放/模拟服务的源代码
请注意,我们按照特定顺序执行任务,如第 30 行和第 34 行之间所示。通过第 21 行的构造函数初始化后,我们继续进行第 30 行来验证加载过程是否一切正确。然后,在第 31 行,我们尝试打开图表,只有在那之后,在第 32 行,我们才加载控制服务所需的元素。如果一切顺利的话,在第 33 行,我们会向终端打印一条消息,在第 34 行,我们会进入执行循环。
乍一看,第 31 行打开图表和第 32 行添加控件之间似乎没有发生任何不寻常的事情。但是,由于使用 C_Replay 类中加载的模板,可能会出现一些无法预料的问题。为了更好地理解潜在的问题,让我们重新审视该类,以检查使用模板的真正复杂性。
在指示 MetaTrader 5 应用模板后,如 C_Replay 类的第 87 行所示,代码的执行速度比理想速度快得多。因此,在第 99 行,我们通知用户服务正在等待鼠标指标。如果模板中存在鼠标指标,它将自动加载;如果没有,则用户需要手动添加。
这会出现一个问题,因为负责应用模板的函数是异步运行的。为了减轻潜在的问题,我们使用第 100 行,在那里我们暂停服务一段时间,以使图表稳定,模板应用程序功能正常执行。只有等待之后,我们才会在第 101 行验证鼠标指标是否存在。这个循环将持续,直到鼠标指标出现在图表上或图表被用户关闭。
一旦检测到鼠标指标或关闭图表,代码就会继续。如果一切如预期,我们尝试在第 105 行将控制指标添加到图表的。虽然这工作得很好,但有一个重要的细节:如果控制指标已经是模板的一部分,则不会被接受。这是我稍后展示的修改之一,它可以防止控制指标出现在模板中。鼠标指标也需要稍微改变,但这将在稍后进行。如果没有第 100 行,图表会在打开后不久关闭,这正是我们要防止的情况。
结论
尽管感觉这不是结束,但有必要详细解释一下为什么应用模板后图表会立即关闭。这非常复杂,需要展示其他的东西才能让您真正理解这是如何实现的,以及为什么仅仅第 100 行就可以阻止它。因此,我将在下一篇文章中更详细地讨论模板和指标模块中的必要修改。这将帮助您充分了解这些变化如何确保回放/模拟服务按预期工作。
如您所见,该系统与上一篇文章讨论的测试服务不同。在我离开你们之前,我将分享一个视频,展示执行该系统的结果。由于它还没有处于视频中显示的状态,因此本文中没有附件。
演示视频
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12039


