
开发回放系统(第 49 部分):事情变得复杂 (一)
概述
本文将采用开发回放系统(第 48 部分):理解和思考的概念一文中的讨论内容。所以,如果您还没有读过这篇文章,请阅读一下,因为这篇文章的内容对于理解我们在这里要做的事情非常重要。
在撰写前几篇文章时,最让我困扰的一件事是,回放/模拟系统包含一个指标,MetaTrader 5 用户可以在指标列表区域看到该指标,并可将其放置在图表上。
尽管文章有一个锁定功能,可以防止用户试图在错误的图表(即与回放/模拟服务使用的交易品种不同的图表)上放置此类指标,但该指标出现在该列表中,以及其他指标中,还是让我感到非常不安。
在这几个月里,我一直在尝试和分析如何以最合适的方式组织一切。幸运的是,我最近找到了一个可以改善这种情况的解决方案。在这种情况下,控制指标将不再出现在其他指标中,而是成为回放/模拟器服务的一个组成部分。
通过这样做,我们将在某些方面有更大的自由度。不过,我将逐步进行修改,因为我还将改进指标,以减少 MetaTrader 5 的负载。换句话说,我们将停止使用某些资源,而开始使用其他平台功能。这将提高回放/模拟系统的稳定性、安全性和可靠性。
让我们拭目以待,因为这些修改的实现将非常有趣,并将为我们提供有关如何更专业地使用 MQL5 的丰富知识。无论如何,这并不意味着我们的回放/模拟系统的开发会被推迟。事实上,系统需要一些东西,包括我们将在本文中讨论的东西,以便在未来编制其他程序。
因此,让我们继续我们的史诗之旅,实现更先进的回放/模拟服务。
开始修改
为了清楚地解释将要开展的工作,我将循序渐进,以便亲爱的读者朋友们能够了解这些修改。很多人可能会认为我选择的演示方法过于夸张,认为可以立即进入最终代码就好。但如果这正是所需要的,那么为什么还需要所有以前写的文章呢?这不合理吧?但是,由于本材料的目的是激励和鼓励人们创建自己的程序和解决方案,我必须展示如何清理现有代码,使其适合在更复杂的模型中使用。此外,我们不应该让代码变得奇怪和低效。
所以,让我们不要着急,让我们继续下去,总是想着那些经验较少的人。
首先,我们将删除图表 ID 绑定系统。该系统可防止控制指标被放置在多个图表上。同时,它还不允许将指标放置在错误的图表上。虽然我们现在要删除这个系统,但稍后会重新投入使用。但是,当发生这种情况时,指标将保留在正确的图表上,回放/模拟器服务将执行操作,而不是指标本身。
为确保正确、安全地完成清除工作,我们将始终在服务和控制指标之间使用相同的交互点。该通用代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SymbolReplay "RePlay" 05. #define def_GlobalVariableReplay def_SymbolReplay + "_Infos" 06. #define def_GlobalVariableIdGraphics def_SymbolReplay + "_ID" 07. #define def_GlobalVariableServerTime def_SymbolReplay + "_Time" 08. #define def_MaxPosSlider 400 09. //+------------------------------------------------------------------+ 10. union u_Interprocess 11. { 12. union u_0 13. { 14. double df_Value; // Value of the terminal global variable... 15. ulong IdGraphic; // Contains the Graph ID of the asset... 16. }u_Value; 17. struct st_0 18. { 19. bool isPlay; // Indicates whether we are in Play or Pause mode... 20. bool isWait; // Tells the user to wait... 21. bool isHedging; // If true we are in a Hedging account, if false the account is Netting... 22. bool isSync; // If true indicates that the service is synchronized... 23. ushort iPosShift; // Value between 0 and 400... 24. }s_Infos; 25. datetime ServerTime; 26. }; 27. //+------------------------------------------------------------------+ 28. union uCast_Double 29. { 30. double dValue; 31. long _long; // 1 Information 32. datetime _datetime; // 1 Information 33. int _int[sizeof(double)]; // 2 Informations 34. char _char[sizeof(double)]; // 8 Informations 35. }; 36. //+------------------------------------------------------------------+
Interprocess.mqh 中的源代码
第 6 行包含一个定义,告诉服务和指标使用哪个全局终端变量名来传递图表 ID。如果删除这一行,在尝试编译指标和服务时就会出现一系列错误。这恰恰正是我们需要的。事实上,编译过程中产生的每一个错误都指向我们应该处理的地方,以消除服务与指标之间通过全局终端变量存在的这种联系。
为了避免过于乏味和重复,我将用代码片段来展示。我会在源代码中明确说明您需要在哪里以及如何操作,以便使系统继续运行。
那么,让我们从以下几点开始考虑:
01. //+------------------------------------------------------------------+ 02. #property service 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property copyright "Daniel Jose" 05. #property version "1.49" 06. #property description "Replay-Simulator service for MT5 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/ru/articles/11820" 10. //+------------------------------------------------------------------+ 11. #define def_Dependence_01 "Indicators\\Replay\\Market Replay.ex5" 12. #resource "\\" + def_Dependence_01 13. //+------------------------------------------------------------------+ 14. #include <Market Replay\Service Graphics\C_Replay.mqh> 15. //+------------------------------------------------------------------+ 16. input string user00 = "Forex - EURUSD.txt"; //Replay configuration file. 17. input ENUM_TIMEFRAMES user01 = PERIOD_M5; //Initial graphic time. 18. //+------------------------------------------------------------------+ 19. void OnStart() 20. { 21. C_Replay *pReplay; 22. 23. pReplay = new C_Replay(user00); 24. if ((*pReplay).ViewReplay(user01)) 25. { 26. Print("Permission granted. Replay service can now be used..."); 27. while ((*pReplay).LoopEventOnTime(false)); 28. } 29. delete pReplay; 30. } 31. //+------------------------------------------------------------------+
源代码:replay.mq5 服务
在上述代码(即服务代码)中,您可以看到第 11 行和第 12 行与之前的代码有一些不同之处。虽然差别不大,但却有助于我们的工作。我们稍后再讨论这个问题。这里的问题是,在我们通过删除全局终端变量名称的定义来更改头代码后,控制指标是否是最近编译的。
在撰写本文时,MetaEditor 中还没有 MAKE 编译系统。但谁知道呢,也许在未来,那些创建 MQL 平台和语言的人将实现这一机会。但在此之前,我们需要采取一定的预防措施。
在继续解释之前,请允许我暂停一下,解释一下什么是 MAKE 编译系统。对于许多人来说,这将是一个新事物,但对于那些多年从事专业编程工作的人来说,这个系统已经非常熟悉了。它的工作原理是这样的:你以通常的方式构建源代码,但有时你可能需要一次构建多个可执行文件。它也可以用于创建单个可执行文件,尽管这不一定必须通过 MAKE 完成。
所以,事实上,在编译之前,您需要创建另一个文件。这是一个 MakeFile 文件,其中包含允许编译器和 LinkEditor 一次生成所有可执行文件所需的步骤、定义、配置和设置。这个系统的最大优点是,如果你更改了一个文件,无论是头文件还是库文件,MAKE 都会检测到它,并只编译真正需要的文件。这样,您就可以根据需要更新所有可执行文件。
因此,在创建最终可执行文件时,开发人员不必担心需要更新什么内容,因为 MAKE 已经完成了这项工作。这样,您可以避免创建某些内容或修复错误的风险,将其应用于一个可执行文件并跳过另一个。所以,MAKE 可以为我们完成所有工作。
不过,在撰写本篇文章时,MetaEditor 中还没有这样的工具。可以从命令行使用批处理文件创建类似的东西,但这只是一个临时解决方案,并不理想。所以我就不详细介绍如何做到这一点了。但是,由于上述代码的第 12 行告诉 MQL5 编译器应将控制指标可执行文件添加到服务中,因此您只需要求它编译服务代码,指标代码就会随之一起编译。我之前已经提到过这一点,但在此再次强调:为了正确编译指标,每次编译服务代码时,都需要删除指标的可执行文件。如果不这样做,在编译服务代码时将不会重新编译指标代码。请注意这一点。
当您尝试编译服务代码时,会得到以下结果:
图 01.尝试编译新代码的结果
您可能会注意到,我们有两个错误和一个警告。警告不是,也不应该是你首先要考虑的问题。首先需要纠正错误。如果查看图 01,就能发现错误所在。点击它们,即可前往发生错误的确切位置。
图 01 中显示的错误出现在第 12 行,原因是编译器无法找到服务资源的可执行文件。
现在,第 156 行报告的错误是服务代码的一部分,我们需要修复它才能继续编译。
以下是与该错误有关的代码:
141. ~C_Replay() 142. { 143. ArrayFree(m_Ticks.Info); 144. ArrayFree(m_Ticks.Rate); 145. m_IdReplay = ChartFirst(); 146. do 147. { 148. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 149. ChartClose(m_IdReplay); 150. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 151. for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++); 152. CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); 153. CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); 154. CustomSymbolDelete(def_SymbolReplay); 155. GlobalVariableDel(def_GlobalVariableReplay); 156. GlobalVariableDel(def_GlobalVariableIdGraphics); 157. GlobalVariableDel(def_GlobalVariableServerTime); 158. Print("Finished replay service..."); 159. }
来自 C_Replay.mqh 的代码
您可以注释掉或干脆删除代码中的第 156 行。由于修改将是永久性的,我们现在就删除这一行。这样,服务就不会再看到这个全局终端变量。然而,在对服务进行修复后,当我们尝试重新编译代码时,得到的结果如图 02 所示。
图 02.新的编译尝试
现在我们只有一个错误的指示,没有更多的警告。此时,许多经验不足的读者都在想如何修复这个显示的错误。然而,他们忘记了阅读编译器信息。
请看图 02,在错误信息上方,我们还能看到一些其他信息。千万不要忽视编译器的提示,你应该关注一切。紧接着错误提示的信息包含编译器提供的以下信息:
compiling '\Indicators\Replay\Market Replay.mq5' failed
这条信息非常关键,因为它告诉我们编译器由于某些原因无法创建可执行的指标文件。由于服务代码已被部分编译,我们需要重点关注指标代码。我们在 MetaEditor 中打开给定的代码,并要求编译器尝试生成一个可执行文件。有人会想:"既然我们知道代码有错误,为什么还要这样做呢?" 是的,我们知道代码有错误,但我们希望编译器能告诉我们错误的具体位置。手动搜索错误是没有效率的,让编译器显示出来。
因此,当你打开指标代码并要求编译器生成可执行文件时,你会看到图 03。
图 03.尝试编译控制指标
这次我们看到了五个错误和五个警告。先处理错误,再处理警告。第一次点击第一条错误信息时,我们将被重定向到错误发生位置的代码。要点:很多人认为可以随意点击错误。但根据规则,您应该按照以下步骤操作:找到列表中的第一个错误。不要在意其他的,总是从第一个开始。进行必要的更正,然后尝试重新编译代码。重复此操作,直到代码编译时没有错误或警告。很多时候,特别是对于初学者来说,当他们看到 100 个或更多的编译错误时,会出现恐慌。
然而,在许多情况下,修复第一个错误后,整个代码编译时不会出现问题。因此,请遵循以下建议:查找编译器指向的第一个错误。查看如何正确修复并尝试重新编译代码。如果出现新的错误,请转到列出的第一个错误并修复它,以此类推,直到没有错误为止。警告也应如此,总是从列表上的第一个开始。
由于编译器报告的所有错误(如图 03 所示)都出现在相同的代码中,因此显示整个代码会更容易,这样你就能知道将在哪里进行修改。
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.49" 07. #property link "https://www.mql5.com/ru/articles/11820" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Service Graphics\C_Controls.mqh> 12. //+------------------------------------------------------------------+ 13. #define def_BitShift ((sizeof(ulong) * 8) - 1) 14. //+------------------------------------------------------------------+ 15. C_Terminal *terminal = NULL; 16. C_Controls *control = NULL; 17. //+------------------------------------------------------------------+ 18. #define def_InfoTerminal (*terminal).GetInfoTerminal() 19. #define def_ShortName "Market_" + def_SymbolReplay 20. //+------------------------------------------------------------------+ 21. int OnInit() 22. { 23. #define macro_INIT_FAILED { ChartIndicatorDelete(def_InfoTerminal.ID, 0, def_ShortName); return INIT_FAILED; } 24. u_Interprocess Info; 25. ulong ul = 1; 26. 27. ResetLastError(); 28. if (CheckPointer(control = new C_Controls(terminal = new C_Terminal())) == POINTER_INVALID) return INIT_FAILED; 29. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 30. ul <<= def_BitShift; 31. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 32. if ((def_InfoTerminal.szSymbol != def_SymbolReplay) || (!GlobalVariableCheck(def_GlobalVariableIdGraphics))) macro_INIT_FAILED; 33. Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics); 34. if (Info.u_Value.IdGraphic != def_InfoTerminal.ID) macro_INIT_FAILED; 35. if ((Info.u_Value.IdGraphic >> def_BitShift) == 1) macro_INIT_FAILED; 36. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName + "Device"); 37. Info.u_Value.IdGraphic |= ul; 38. GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); 39. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.u_Value.df_Value = 0; 40. EventChartCustom(def_InfoTerminal.ID, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, ""); 41. (*control).Init(Info.s_Infos.isPlay); 42. 43. return INIT_SUCCEEDED; 44. 45. #undef macro_INIT_FAILED 46. } 47. //+------------------------------------------------------------------+ 48. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 49. { 50. static bool bWait = false; 51. u_Interprocess Info; 52. 53. Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 54. if (!bWait) 55. { 56. if (Info.s_Infos.isWait) 57. { 58. EventChartCustom(def_InfoTerminal.ID, C_Controls::ev_WaitOn, 1, 0, ""); 59. bWait = true; 60. } 61. }else if (!Info.s_Infos.isWait) 62. { 63. EventChartCustom(def_InfoTerminal.ID, C_Controls::ev_WaitOff, 1, Info.u_Value.df_Value, ""); 64. bWait = false; 65. } 66. 67. return rates_total; 68. } 69. //+------------------------------------------------------------------+ 70. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 71. { 72. (*control).DispatchMessage(id, lparam, dparam, sparam); 73. } 74. //+------------------------------------------------------------------+ 75. void OnDeinit(const int reason) 76. { 77. u_Interprocess Info; 78. ulong ul = 1; 79. 80. switch (reason) 81. { 82. case REASON_CHARTCHANGE: 83. ul <<= def_BitShift; 84. Info.u_Value.df_Value = GlobalVariableGet(def_GlobalVariableIdGraphics); 85. Info.u_Value.IdGraphic ^= ul; 86. GlobalVariableSet(def_GlobalVariableIdGraphics, Info.u_Value.df_Value); 87. break; 88. case REASON_REMOVE: 89. case REASON_CHARTCLOSE: 90. if (def_InfoTerminal.szSymbol != def_SymbolReplay) break; 91. GlobalVariableDel(def_GlobalVariableReplay); 92. ChartClose(def_InfoTerminal.ID); 93. break; 94. } 95. delete control; 96. delete terminal; 97. } 98. //+------------------------------------------------------------------+
指标的源代码:Market replay.mq5
代码中所有划掉的行都不复存在。因此,当你再次尝试编译时,会得到如下信息,如图 04 所示。
图 04
请注意,该图 04 中有一个警告,它实际上不会干扰代码,但可能会令人讨厌。因此,请转到第 77 行并删除它,然后再试着编译代码。您将收到如图 05 所示的信息,这正是我们所需要的。请注意:只有当编译器告诉我们没有使用该变量时,才能删除第 77 行。
图 05.编译成功完成
非常好。我们的代码已被部分清理,不过,如果回顾一下本主题开头所示的 Interprocess.mqh 头文件代码,就会发现代码中仍有一些我们不再需要的内容。这是因为我们不再使用全局终端变量将图表 ID 传递给指标。因此,应删除 Interprocess.mqh 文件的第 15 行。但问题来了:"我为什么不早点删掉这一行?"原因是服务文件应特别小心处理。但还有另一个原因,为了理解它,让我们继续下一个话题。
将决策激进化
在设计回放/模拟器服务时,我试图实现的目标之一就是消除用户干预图表中应该或不应该出现的内容的可能性。
我找到了解决这个问题的方法,即添加一个全局终端变量,告诉指标应该出现在哪个图表上。用户将无法把它放到另一个图表上。事实上,这个解决方案已经足够,而且实现起来也相当有趣。如果想确保指标、脚本或 EA 不在图表上,也可以使用类似方法。但这不是我们现在感兴趣的。
一旦我们使用一种服务来控制什么应该出现在图表上,什么不应该出现在图表上,这样的系统就变得完全没有必要了。或者更好的说法是:由于指标将作为服务资源存在,用户无法访问,因此没有必要继续支持该代码。安全级别大幅提高,访问控制也变得完全不同。
因此,由于从系统中删除 ID 变量时需要小心谨慎,我在上一个主题中没有删除它。很快你就会明白为什么了。如果提前删除,就很难进行适当的修改,我们也会犯很多错误。
优秀程序员的最大财富恰恰在于此:不要着急。解决一个又一个问题,逐步修复和修改,以保留程序的所有现有功能,并在必要或有趣的情况下,扩展和倍增这些功能。因此,我们的座右铭是:
分而治之。
让我们看看代码将如何被清理。Interprocess.mqh 的完整新代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SymbolReplay "RePlay" 05. #define def_GlobalVariableReplay def_SymbolReplay + "_Infos" 06. #define def_GlobalVariableServerTime def_SymbolReplay + "_Time" 07. #define def_MaxPosSlider 400 08. //+------------------------------------------------------------------+ 09. union u_Interprocess 10. { 11. double df_Value; // Value of the terminal global variable... 12. struct st_0 13. { 14. bool isPlay; // Indicates whether we are in Play or Pause mode... 15. bool isWait; // Tells the user to wait... 16. bool isHedging; // If true we are in a Hedging account, if false the account is Netting... 17. bool isSync; // If true indicates that the service is synchronized... 18. ushort iPosShift; // Value between 0 and 400... 19. }s_Infos; 20. datetime ServerTime; 21. }; 22. //+------------------------------------------------------------------+ 23. union uCast_Double 24. { 25. double dValue; 26. long _long; // 1 Information 27. datetime _datetime; // 1 Information 28. int _int[sizeof(double)]; // 2 Informations 29. char _char[sizeof(double)]; // 8 Informations 30. }; 31. //+------------------------------------------------------------------+
源代码:Interprocess.mqh
请注意,虽然这段代码看起来没有太大不同,但它将对我们在上一节中刚刚修改的系统产生很大影响。因此,当您再次尝试编译系统时,不要对出现的错误数量感到惊讶或害怕。我们唯一需要做的就是配置所有内容,以实现与以前相同的性能。但现在,用户将无法再使用控制指标。服务将负责在正确的图表上对其进行放置和支持。
当您尝试编译服务代码时,编译器会输出以下信息,如图 06 所示。
图 06.多个错误。但真的有那么多吗?
错误和警告的数量都很多。如上文所述,我们将从清单上的第一个错误开始。因此,修改从 C_Replay.mqh 头文件的第 28 行开始。为了不显得太枯燥,让我们看看下面的代码,因为我们需要做的主要工作就是删除 u_Value。不包含此引用的代码在这里:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_ConfigService.mqh" 005. //+------------------------------------------------------------------+ 006. class C_Replay : private C_ConfigService 007. { 008. private : 009. long m_IdReplay; 010. struct st01 011. { 012. MqlRates Rate[1]; 013. datetime memDT; 014. }m_MountBar; 015. struct st02 016. { 017. bool bInit; 018. double PointsPerTick; 019. MqlTick tick[1]; 020. }m_Infos; 021. //+------------------------------------------------------------------+ 022. void AdjustPositionToReplay(const bool bViewBuider) 023. { 024. u_Interprocess Info; 025. MqlRates Rate[def_BarsDiary]; 026. int iPos, nCount; 027. 028. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 029. if (Info.s_Infos.iPosShift == (int)((m_ReplayCount * def_MaxPosSlider * 1.0) / m_Ticks.nTicks)) return; 030. iPos = (int)(m_Ticks.nTicks * ((Info.s_Infos.iPosShift * 1.0) / (def_MaxPosSlider + 1))); 031. Rate[0].time = macroRemoveSec(m_Ticks.Info[iPos].time); 032. CreateBarInReplay(true); 033. if (bViewBuider) 034. { 035. Info.s_Infos.isWait = true; 036. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 037. }else 038. { 039. for(; Rate[0].time > (m_Ticks.Info[m_ReplayCount].time); m_ReplayCount++); 040. for (nCount = 0; m_Ticks.Rate[nCount].time < macroRemoveSec(m_Ticks.Info[iPos].time); nCount++); 041. nCount = CustomRatesUpdate(def_SymbolReplay, m_Ticks.Rate, nCount); 042. } 043. for (iPos = (iPos > 0 ? iPos - 1 : 0); (m_ReplayCount < iPos) && (!_StopFlag);) CreateBarInReplay(false); 044. CustomTicksAdd(def_SymbolReplay, m_Ticks.Info, m_ReplayCount); 045. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 046. Info.s_Infos.isWait = false; 047. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 048. } 049. //+------------------------------------------------------------------+ 050. inline void CreateBarInReplay(const bool bViewTicks) 051. { 052. #define def_Rate m_MountBar.Rate[0] 053. 054. bool bNew; 055. double dSpread; 056. int iRand = rand(); 057. 058. if (BuildBar1Min(m_ReplayCount, def_Rate, bNew)) 059. { 060. m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount]; 061. if ((!m_Ticks.bTickReal) && (m_Ticks.ModePlot == PRICE_EXCHANGE)) 062. { 063. dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 ); 064. if (m_Infos.tick[0].last > m_Infos.tick[0].ask) 065. { 066. m_Infos.tick[0].ask = m_Infos.tick[0].last; 067. m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread; 068. }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid) 069. { 070. m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread; 071. m_Infos.tick[0].bid = m_Infos.tick[0].last; 072. } 073. } 074. if (bViewTicks) CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 075. CustomRatesUpdate(def_SymbolReplay, m_MountBar.Rate); 076. } 077. m_ReplayCount++; 078. #undef def_Rate 079. } 080. //+------------------------------------------------------------------+ 081. void ViewInfos(void) 082. { 083. MqlRates Rate[1]; 084. 085. ChartSetInteger(m_IdReplay, CHART_SHOW_ASK_LINE, m_Ticks.ModePlot == PRICE_FOREX); 086. ChartSetInteger(m_IdReplay, CHART_SHOW_BID_LINE, m_Ticks.ModePlot == PRICE_FOREX); 087. ChartSetInteger(m_IdReplay, CHART_SHOW_LAST_LINE, m_Ticks.ModePlot == PRICE_EXCHANGE); 088. m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 089. m_MountBar.Rate[0].time = 0; 090. m_Infos.bInit = true; 091. CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, Rate); 092. if ((m_ReplayCount == 0) && (m_Ticks.ModePlot == PRICE_EXCHANGE)) 093. for (; m_Ticks.Info[m_ReplayCount].volume_real == 0; m_ReplayCount++); 094. if (Rate[0].close > 0) 095. { 096. if (m_Ticks.ModePlot == PRICE_EXCHANGE) m_Infos.tick[0].last = Rate[0].close; else 097. { 098. m_Infos.tick[0].bid = Rate[0].close; 099. m_Infos.tick[0].ask = Rate[0].close + (Rate[0].spread * m_Infos.PointsPerTick); 100. } 101. m_Infos.tick[0].time = Rate[0].time; 102. m_Infos.tick[0].time_msc = Rate[0].time * 1000; 103. }else 104. m_Infos.tick[0] = m_Ticks.Info[m_ReplayCount]; 105. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 106. ChartRedraw(m_IdReplay); 107. } 108. //+------------------------------------------------------------------+ 109. void CreateGlobalVariable(const string szName, const double value) 110. { 111. GlobalVariableDel(szName); 112. GlobalVariableTemp(szName); 113. GlobalVariableSet(szName, value); 114. } 115. //+------------------------------------------------------------------+ 116. public : 117. //+------------------------------------------------------------------+ 118. C_Replay(const string szFileConfig) 119. { 120. m_ReplayCount = 0; 121. m_dtPrevLoading = 0; 122. m_Ticks.nTicks = 0; 123. m_Infos.bInit = false; 124. Print("************** Market Replay Service **************"); 125. srand(GetTickCount()); 126. GlobalVariableDel(def_GlobalVariableReplay); 127. SymbolSelect(def_SymbolReplay, false); 128. CustomSymbolDelete(def_SymbolReplay); 129. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol); 130. CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); 131. CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); 132. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 133. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 134. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 135. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 136. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 137. m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1); 138. SymbolSelect(def_SymbolReplay, true); 139. } 140. //+------------------------------------------------------------------+ 141. ~C_Replay() 142. { 143. ArrayFree(m_Ticks.Info); 144. ArrayFree(m_Ticks.Rate); 145. m_IdReplay = ChartFirst(); 146. do 147. { 148. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 149. ChartClose(m_IdReplay); 150. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 151. for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++); 152. CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); 153. CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); 154. CustomSymbolDelete(def_SymbolReplay); 155. GlobalVariableDel(def_GlobalVariableReplay); 156. GlobalVariableDel(def_GlobalVariableServerTime); 157. Print("Finished replay service..."); 158. } 159. //+------------------------------------------------------------------+ 160. bool ViewReplay(ENUM_TIMEFRAMES arg1) 161. { 162. #define macroError(A) { Print(A); return false; } 163. u_Interprocess info; 164. 165. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 166. macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); 167. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 168. macroError("Asset configuration is not complete, need to declare the ticket value."); 169. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 170. macroError("Asset configuration not complete, need to declare the minimum volume."); 171. if (m_IdReplay == -1) return false; 172. if ((m_IdReplay = ChartFirst()) > 0) do 173. { 174. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 175. { 176. ChartClose(m_IdReplay); 177. ChartRedraw(); 178. } 179. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 180. Print("Waiting for [Market Replay] indicator permission to start replay ..."); 181. info.ServerTime = ULONG_MAX; 182. CreateGlobalVariable(def_GlobalVariableServerTime, info.df_Value); 183. m_IdReplay = ChartOpen(def_SymbolReplay, arg1); 184. ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); 185. while ((!GlobalVariableGet(def_GlobalVariableReplay, info.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); 186. info.s_Infos.isHedging = TypeAccountIsHedging(); 187. info.s_Infos.isSync = true; 188. GlobalVariableSet(def_GlobalVariableReplay, info.df_Value); 189. 190. return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); 191. #undef macroError 192. } 193. //+------------------------------------------------------------------+ 194. bool LoopEventOnTime(const bool bViewBuider) 195. { 196. u_Interprocess Info; 197. int iPos, iTest, iCount; 198. 199. if (!m_Infos.bInit) ViewInfos(); 200. iTest = 0; 201. while ((iTest == 0) && (!_StopFlag)) 202. { 203. iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1); 204. iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value) ? iTest : -1); 205. iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest); 206. if (iTest == 0) Sleep(100); 207. } 208. if ((iTest < 0) || (_StopFlag)) return false; 209. AdjustPositionToReplay(bViewBuider); 210. Info.ServerTime = m_Ticks.Info[m_ReplayCount].time; 211. GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value); 212. iPos = iCount = 0; 213. while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag)) 214. { 215. iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0); 216. CreateBarInReplay(true); 217. while ((iPos > 200) && (!_StopFlag)) 218. { 219. if (ChartSymbol(m_IdReplay) == "") return false; 220. GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value); 221. if (!Info.s_Infos.isPlay) return true; 222. Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks); 223. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 224. Sleep(195); 225. iPos -= 200; 226. iCount++; 227. if (iCount > 4) 228. { 229. iCount = 0; 230. GlobalVariableGet(def_GlobalVariableServerTime, Info.df_Value); 231. if ((m_Ticks.Info[m_ReplayCount].time - m_Ticks.Info[m_ReplayCount - 1].time) > 60) Info.ServerTime = ULONG_MAX; else 232. { 233. Info.ServerTime += 1; 234. Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time); 235. }; 236. GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value); 237. } 238. } 239. } 240. return (m_ReplayCount == m_Ticks.nTicks); 241. } 242. //+------------------------------------------------------------------+ 243. }; 244. //+------------------------------------------------------------------+ 245. #undef macroRemoveSec 246. #undef def_SymbolReplay 247. //+------------------------------------------------------------------+
文件 C_Replay.mqh 的源代码
上面的代码已经过修改,以适应我们在本文中的操作。但指标代码仍然缺失,如果再次编译服务代码并修改 C_Replay.mqh 头文件,结果将如图 07 所示。
图 07.指标中仍然存在错误
因此,现在我们需要修复指标代码。尝试这样做时,结果如图 08 所示。
图 08.因相同原因出现的错误
同样,我们继续一步一步来,总是从第一个错误开始。
然后,如果正确修改了指标文件,就会得到以下代码:
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.49" 07. #property link "https://www.mql5.com/ru/articles/11820" 08. #property indicator_chart_window 09. #property indicator_plots 0 10. //+------------------------------------------------------------------+ 11. #include <Market Replay\Service Graphics\C_Controls.mqh> 12. //+------------------------------------------------------------------+ 13. C_Terminal *terminal = NULL; 14. C_Controls *control = NULL; 15. //+------------------------------------------------------------------+ 16. #define def_InfoTerminal (*terminal).GetInfoTerminal() 17. #define def_ShortName "Market_" + def_SymbolReplay 18. //+------------------------------------------------------------------+ 19. int OnInit() 20. { 21. u_Interprocess Info; 22. 23. ResetLastError(); 24. if (CheckPointer(control = new C_Controls(terminal = new C_Terminal())) == POINTER_INVALID) return INIT_FAILED; 25. if (_LastError != ERR_SUCCESS) return INIT_FAILED; 26. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 27. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName + "Device"); 28. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0; 29. EventChartCustom(def_InfoTerminal.ID, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 30. (*control).Init(Info.s_Infos.isPlay); 31. 32. return INIT_SUCCEEDED; 33. } 34. //+------------------------------------------------------------------+ 35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 36. { 37. static bool bWait = false; 38. u_Interprocess Info; 39. 40. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 41. if (!bWait) 42. { 43. if (Info.s_Infos.isWait) 44. { 45. EventChartCustom(def_InfoTerminal.ID, C_Controls::ev_WaitOn, 1, 0, ""); 46. bWait = true; 47. } 48. }else if (!Info.s_Infos.isWait) 49. { 50. EventChartCustom(def_InfoTerminal.ID, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 51. bWait = false; 52. } 53. 54. return rates_total; 55. } 56. //+------------------------------------------------------------------+ 57. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 58. { 59. (*control).DispatchMessage(id, lparam, dparam, sparam); 60. } 61. //+------------------------------------------------------------------+ 62. void OnDeinit(const int reason) 63. { 64. switch (reason) 65. { 66. case REASON_REMOVE: 67. case REASON_CHARTCLOSE: 68. if (def_InfoTerminal.szSymbol != def_SymbolReplay) break; 69. GlobalVariableDel(def_GlobalVariableReplay); 70. ChartClose(def_InfoTerminal.ID); 71. break; 72. } 73. delete control; 74. delete terminal; 75. } 76. //+------------------------------------------------------------------+
回指标源代码
现在这段代码已经完全修复,但当你尝试编译时,编译器仍会出现一些错误,如图 09 所示。
图 09.尝试编译控制指标
现在,我们需要转到 C_Controls.mqh 头文件并做一些修改。但这些修复都相当简单。您只需删除对 u_Value 的 引用即可。这样,您最终会得到以下代码:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\Auxiliar\Interprocess.mqh" 005. //+------------------------------------------------------------------+ 006. #define def_PathBMP "Images\\Market Replay\\Control\\" 007. #define def_ButtonPlay def_PathBMP + "Play.bmp" 008. #define def_ButtonPause def_PathBMP + "Pause.bmp" 009. #define def_ButtonLeft def_PathBMP + "Left.bmp" 010. #define def_ButtonLeftBlock def_PathBMP + "Left_Block.bmp" 011. #define def_ButtonRight def_PathBMP + "Right.bmp" 012. #define def_ButtonRightBlock def_PathBMP + "Right_Block.bmp" 013. #define def_ButtonPin def_PathBMP + "Pin.bmp" 014. #define def_ButtonWait def_PathBMP + "Wait.bmp" 015. #resource "\\" + def_ButtonPlay 016. #resource "\\" + def_ButtonPause 017. #resource "\\" + def_ButtonLeft 018. #resource "\\" + def_ButtonLeftBlock 019. #resource "\\" + def_ButtonRight 020. #resource "\\" + def_ButtonRightBlock 021. #resource "\\" + def_ButtonPin 022. #resource "\\" + def_ButtonWait 023. //+------------------------------------------------------------------+ 024. #define def_PrefixObjectName "Market Replay _ " 025. #define def_NameObjectsSlider def_PrefixObjectName + "Slider" 026. #define def_PosXObjects 120 027. //+------------------------------------------------------------------+ 028. #include "..\Auxiliar\C_Terminal.mqh" 029. #include "..\Auxiliar\C_Mouse.mqh" 030. //+------------------------------------------------------------------+ 031. #define def_AcessTerminal (*Terminal) 032. #define def_InfoTerminal def_AcessTerminal.GetInfoTerminal() 033. //+------------------------------------------------------------------+ 034. class C_Controls : protected C_Mouse 035. { 036. protected: 037. enum EventCustom {ev_WaitOn, ev_WaitOff}; 038. private : 039. //+------------------------------------------------------------------+ 040. string m_szBtnPlay; 041. bool m_bWait; 042. struct st_00 043. { 044. string szBtnLeft, 045. szBtnRight, 046. szBtnPin, 047. szBarSlider, 048. szBarSliderBlock; 049. int posPinSlider, 050. posY, 051. Minimal; 052. }m_Slider; 053. C_Terminal *Terminal; 054. //+------------------------------------------------------------------+ 055. inline void CreateObjectBitMap(int x, int y, string szName, string Resource1, string Resource2 = NULL) 056. { 057. ObjectCreate(def_InfoTerminal.ID, szName, OBJ_BITMAP_LABEL, 0, 0, 0); 058. ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XDISTANCE, def_PosXObjects + x); 059. ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_YDISTANCE, y); 060. ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_BMPFILE, 0, "::" + Resource1); 061. ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_BMPFILE, 1, "::" + (Resource2 == NULL ? Resource1 : Resource2)); 062. ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_ZORDER, 1); 063. } 064. //+------------------------------------------------------------------+ 065. inline void CreteBarSlider(int x, int size) 066. { 067. ObjectCreate(def_InfoTerminal.ID, m_Slider.szBarSlider, OBJ_RECTANGLE_LABEL, 0, 0, 0); 068. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSlider, OBJPROP_XDISTANCE, def_PosXObjects + x); 069. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSlider, OBJPROP_YDISTANCE, m_Slider.posY - 4); 070. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSlider, OBJPROP_XSIZE, size); 071. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSlider, OBJPROP_YSIZE, 9); 072. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSlider, OBJPROP_BGCOLOR, clrLightSkyBlue); 073. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSlider, OBJPROP_BORDER_COLOR, clrBlack); 074. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSlider, OBJPROP_WIDTH, 3); 075. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSlider, OBJPROP_BORDER_TYPE, BORDER_FLAT); 076. //--- 077. ObjectCreate(def_InfoTerminal.ID, m_Slider.szBarSliderBlock, OBJ_RECTANGLE_LABEL, 0, 0, 0); 078. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSliderBlock, OBJPROP_XDISTANCE, def_PosXObjects + x); 079. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSliderBlock, OBJPROP_YDISTANCE, m_Slider.posY - 9); 080. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSliderBlock, OBJPROP_YSIZE, 19); 081. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSliderBlock, OBJPROP_BGCOLOR, clrRosyBrown); 082. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSliderBlock, OBJPROP_BORDER_TYPE, BORDER_RAISED); 083. } 084. //+------------------------------------------------------------------+ 085. void CreateBtnPlayPause(bool state) 086. { 087. m_szBtnPlay = def_PrefixObjectName + "Play"; 088. CreateObjectBitMap(0, 25, m_szBtnPlay, (m_bWait ? def_ButtonWait : def_ButtonPause), (m_bWait ? def_ButtonWait : def_ButtonPlay)); 089. ObjectSetInteger(def_InfoTerminal.ID, m_szBtnPlay, OBJPROP_STATE, state); 090. } 091. //+------------------------------------------------------------------+ 092. void CreteCtrlSlider(void) 093. { 094. u_Interprocess Info; 095. 096. m_Slider.szBarSlider = def_NameObjectsSlider + " Bar"; 097. m_Slider.szBarSliderBlock = def_NameObjectsSlider + " Bar Block"; 098. m_Slider.szBtnLeft = def_NameObjectsSlider + " BtnL"; 099. m_Slider.szBtnRight = def_NameObjectsSlider + " BtnR"; 100. m_Slider.szBtnPin = def_NameObjectsSlider + " BtnP"; 101. m_Slider.posY = 40; 102. CreteBarSlider(77, 436); 103. CreateObjectBitMap(47, 25, m_Slider.szBtnLeft, def_ButtonLeft, def_ButtonLeftBlock); 104. CreateObjectBitMap(511, 25, m_Slider.szBtnRight, def_ButtonRight, def_ButtonRightBlock); 105. CreateObjectBitMap(0, m_Slider.posY, m_Slider.szBtnPin, def_ButtonPin); 106. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBtnPin, OBJPROP_ANCHOR, ANCHOR_CENTER); 107. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0; 108. m_Slider.Minimal = Info.s_Infos.iPosShift; 109. PositionPinSlider(Info.s_Infos.iPosShift); 110. } 111. //+------------------------------------------------------------------+ 112. inline void RemoveCtrlSlider(void) 113. { 114. ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, false); 115. ObjectsDeleteAll(def_InfoTerminal.ID, def_NameObjectsSlider); 116. ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, true); 117. } 118. //+------------------------------------------------------------------+ 119. inline void PositionPinSlider(int p, const int minimal = 0) 120. { 121. m_Slider.posPinSlider = (p < minimal ? minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 122. m_Slider.posPinSlider = (p < m_Slider.Minimal ? m_Slider.Minimal : (p > def_MaxPosSlider ? def_MaxPosSlider : p)); 123. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBtnPin, OBJPROP_XDISTANCE, m_Slider.posPinSlider + def_PosXObjects + 95); 124. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != minimal); 125. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBtnLeft, OBJPROP_STATE, m_Slider.posPinSlider != m_Slider.Minimal); 126. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBtnRight, OBJPROP_STATE, m_Slider.posPinSlider < def_MaxPosSlider); 127. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, minimal + 2); 128. ObjectSetInteger(def_InfoTerminal.ID, m_Slider.szBarSliderBlock, OBJPROP_XSIZE, m_Slider.Minimal + 2); 129. ChartRedraw(); 130. } 131. //+------------------------------------------------------------------+ 132. public : 133. //+------------------------------------------------------------------+ 134. C_Controls(C_Terminal *arg) 135. :C_Mouse(arg), 136. m_bWait(false) 137. { 138. if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid); 139. m_szBtnPlay = NULL; 140. m_Slider.szBarSlider = NULL; 141. m_Slider.szBtnPin = NULL; 142. m_Slider.szBtnLeft = NULL; 143. m_Slider.szBtnRight = NULL; 144. } 145. //+------------------------------------------------------------------+ 146. ~C_Controls() 147. { 148. if (CheckPointer(Terminal) == POINTER_INVALID) return; 149. ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, false); 150. ObjectsDeleteAll(def_InfoTerminal.ID, def_PrefixObjectName); 151. } 152. //+------------------------------------------------------------------+ 153. void Init(const bool state) 154. { 155. CreateBtnPlayPause(state); 156. GlobalVariableTemp(def_GlobalVariableReplay); 157. if (!state) CreteCtrlSlider(); 158. ChartRedraw(); 159. } 160. //+------------------------------------------------------------------+ 161. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 162. { 163. u_Interprocess Info; 164. static int six = -1, sps; 165. int x, y, px1, px2; 166. 167. C_Mouse::DispatchMessage(id, lparam, dparam, sparam); 168. switch (id) 169. { 170. case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOn): 171. if (lparam == 0) break; 172. m_bWait = true; 173. CreateBtnPlayPause(true); 174. break; 175. case (CHARTEVENT_CUSTOM + C_Controls::ev_WaitOff): 176. if (lparam == 0) break; 177. m_bWait = false; 178. Info.df_Value = dparam; 179. CreateBtnPlayPause(Info.s_Infos.isPlay); 180. break; 181. case CHARTEVENT_OBJECT_DELETE: 182. if (StringSubstr(sparam, 0, StringLen(def_PrefixObjectName)) == def_PrefixObjectName) 183. { 184. if (StringSubstr(sparam, 0, StringLen(def_NameObjectsSlider)) == def_NameObjectsSlider) 185. { 186. RemoveCtrlSlider(); 187. CreteCtrlSlider(); 188. }else 189. { 190. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 191. CreateBtnPlayPause(Info.s_Infos.isPlay); 192. } 193. ChartRedraw(); 194. } 195. break; 196. case CHARTEVENT_OBJECT_CLICK: 197. if (m_bWait) break; 198. if (sparam == m_szBtnPlay) 199. { 200. Info.s_Infos.isPlay = (bool) ObjectGetInteger(def_InfoTerminal.ID, m_szBtnPlay, OBJPROP_STATE); 201. if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else 202. { 203. RemoveCtrlSlider(); 204. m_Slider.szBtnPin = NULL; 205. } 206. Info.s_Infos.iPosShift = (ushort) m_Slider.posPinSlider; 207. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 208. ChartRedraw(); 209. }else if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - 1); 210. else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + 1); 211. break; 212. case CHARTEVENT_MOUSE_MOVE: 213. if (GetInfoMouse().ExecStudy) return; 214. if ((CheckClick(C_Mouse::eClickLeft)) && (m_Slider.szBtnPin != NULL)) 215. { 216. x = GetInfoMouse().Position.X; 217. y = GetInfoMouse().Position.Y; 218. px1 = m_Slider.posPinSlider + def_PosXObjects + 86; 219. px2 = m_Slider.posPinSlider + def_PosXObjects + 114; 220. if ((y >= (m_Slider.posY - 14)) && (y <= (m_Slider.posY + 14)) && (x >= px1) && (x <= px2) && (six == -1)) 221. { 222. six = x; 223. sps = m_Slider.posPinSlider; 224. ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false); 225. } 226. if (six > 0) PositionPinSlider(sps + x - six); 227. }else if (six > 0) 228. { 229. six = -1; 230. ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true); 231. } 232. break; 233. } 234. } 235. //+------------------------------------------------------------------+ 236. }; 237. //+------------------------------------------------------------------+ 238. #undef def_InfoTerminal 239. #undef def_AcessTerminal 240. #undef def_PosXObjects 241. #undef def_ButtonPlay 242. #undef def_ButtonPause 243. #undef def_ButtonLeft 244. #undef def_ButtonRight 245. #undef def_ButtonPin 246. #undef def_NameObjectsSlider 247. #undef def_PrefixObjectName 248. #undef def_PathBMP 249. //+------------------------------------------------------------------+
C_Controls.mqh 文件的源代码
完成这些修复、更改和调整后,我们就可以尝试再次编译回放/模拟器服务了。因此,我们得到了以下结果:
图 10.最终编译
这意味着服务已成功编译,而且请注意,指标也已编译。这反过来又意味着,您现在可以使用文件资源管理器直接删除指标可执行文件,因为它现在将成为回放/模拟器服务可执行文件的一部分。但是,如果删除该指标可执行文件,并尝试在 MetaTrader 5 中运行回放/模拟系统,就会发现图表打开了,但控制指标却没有出现。为什么呢?
原因是控制指标实际上是由模板而不是服务触发的。模板中没有任何内容表明控制指标是服务可执行文件的一部分。查看模板文件的内容就能发现这一点。但由于我们的想法不是使用模板,而是使用服务在图表上运行控制指标,因此我就不详细介绍如何解决这个问题了。让我们专注于我们真正想做的事情。
由于我们需要做的事情并不简单,而且我也不想在这篇文章中谈得太详细,所以我会把修改留到下一篇文章中。这是因为我们需要改变很多东西,以保持控制指标的功能,同时保持回放/模拟器服务的免费、轻量级和开放性,以便您可以使用自己的指标、策略或个人模型。做出这些改变的初衷正是为了鼓励这样做。让您可以用自己的概念和思路来使用该系统。
但是,我们将在下一篇文章中继续演示使控制指标出现在图表上所需的一切,即作为服务资源而不使用模板,主要原因是我们必须删除一些现在重复的东西。如果直接通过服务使用,这种重复会使控制指标极不稳定。
结论
虽然在本文中,我们对控制指标和服务进行了修改,以至于可以从用户可用的列表中删除指标可执行文件,但事实证明,由于问题尚未解决,系统本身并不稳定。这些问题涉及控制指标和用户的交互方式。造成这种不稳定性的原因是,图表上还需要有其他元素。这些元素是模板文件的一部分,我不想在使用回放/模拟器服务时使用它们。我希望并将展示我们如何做到这一点,使服务自给自足,以便用户可以使用自己的设置或模板。
同时,我们还创造了一种合适的方式来推广回放/模拟器服务,以便您可以练习分析策略或模型。
我们还有很多工作要做,才能使该系统获得期待已久的在回放和模拟模式下处理订单和仓位的功能。亲爱的读者,我希望你能理解我们所达到的复杂程度。而这一切只需借助 MQL5 即可完成,无需外部编程。事实上,这个系统相当复杂。但我喜欢挑战,这个挑战一直在激励着我。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11820
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.


非常感谢你们推出如此有趣的项目!然而,对于任何交易者来说,阅读全部 49 篇文章以获得每个组成部分的线索都是非常困难的工作。
我恳请您以 "用户指南 "为名撰写下一篇 #50 文章,并在其中介绍本项目 的每个组成部分 - 回放系统、EA 和指标。
最好能添加一些实际使用的例子,比如以下例子:
- "出于教育目的,重播并重新交易 2016 年 6 月英镑兑美元的英国脱欧案例";
- 以教学为目的,重现并重新交易 2020 年 3 月 XAUUSD 上的淘金热案例";
预计在这些实用示例中,应使用真实符号 "GBPUSD"/"XAUUSD"(来自任何连接的外汇账户)检索历史数据,并在重播模式下使用提取的数据输入自定义符号,如 "repGBPUSD"/"repXAUUSd",添加一些通用指标(RSI(14)、MA(50)等),并为用户提供重新交易这些历史事件的实时体验。
这样一份用户指南,并附有实时重新交易英国脱欧和淘金热的实际案例,将是本项目的最终成果!