
开发回放系统(第 50 部分):事情变得复杂 (二)
概述
在上一篇文章开发回放系统(第 49 部分):事情变得复杂(一)之后,我们开始在回放/模拟器系统中让事情变得更加复杂。这种复杂性虽然是无意的,但旨在使系统更加稳定和安全:这涉及在完全模块化的模型中使用它的可能性。
虽然这些变化乍一看似乎完全没有必要,但它们通过保护仅回放/模拟器系统需要的特定部分,防止用户滥用我们正在开发的一些东西。这样,我们可以防止没有经验的用户意外删除或错误配置回放/模拟器服务所需的图表部分。这在服务运行时发生。
在上一篇文章的末尾,我报告了一个导致控制指示器不稳定的问题。因此,可能会发生一些不应该发生的事情。这些问题并不严重,不会导致平台崩溃,但有时会导致意外的服务故障。因此,我没有在上一篇文章中附加任何文件,否则您将在当前开发阶段访问该系统。
在本文中,我们将修复或尝试修复现有问题,至少其中之一。
解决第一个问题
第一个问题是,回放/模拟器服务是使用模板实现的。该模板预计包含打开图表和启动服务所需的所有数据。
虽然这种方法很实用并且很有吸引力,但它对用户来说却有限制。更简单地说:服务使用某个模板意味着用户不能再使用他们自己的方法或所需的图表配置。使用用户创建的模板配置图表要方便得多,该模板包含您所需的一切,而不是每次交易特定资产时都必须手动设置。
如果您是市场新手,这可能没有多大意义,但更有经验的交易者有预设的模板,可以在特定时间点对特定资产进行标准分析。他们花费数月甚至数年的时间来开发这些模板,提前做好一切准备。这样,交易者只需保存模板并在需要时将其应用于图表。
文章开发回放系统(第 48 部分):理解服务的概念中也展示了类似的东西,这就是修改回放/模拟器系统的所有工作的开始。在那篇文章中,我展示了如何使用服务或标准图表而不是模板来设置图表,这样无论使用什么模板,图表上都会存在某些元素。MetaTrader 5 上运行的服务始终支持这种设置,以规范工作。
然而,为了将所需的图形对象放置在图表上,以便控制指标可以管理服务,我们需要重要的信息,包括将接收这些对象的图表 ID。
您可能认为只需使用 ChartID() 函数就可以轻松让指标知道这一点。有人说无知是福。别误会,每次我试图弄清楚为什么某件事在某个时间不能正常工作时,我都会头疼。所以我不认为我错了。
事实上,使用 ChartID() 函数肯定会返回图表 ID,以便我们可以在其上放置对象。记住:我们需要这个 ID 来通知 MetaTrader 5 该对象将附加到哪个图表上。
然而,当通过服务打开图表时,ChartID 函数将不起作用。也就是说,当服务使用 C_Replay.mqh 类,并执行 183 行的代码时,会创建另一个 ID。我们在上一篇文章中见过这段代码。在同一行 183,我们调用 ChartOpen 来创建回放/模拟将运行的交易品种图表。
如果将 ChartOpen 服务返回的值与控制指标中的 ChartID 值进行比较,您会发现它们是不同的。这意味着 MetaTrader 5 平台将不知道使用哪个 ID。如果使用 ChartID 返回的 ID,则会将对象放置在错误甚至不存在的窗口中,但是如果使用服务内部生成的 ID,则只要 ChartOpen 创建ID,我们就能够使用对象。
现在问题出现了:解决图表 ID 问题的最佳方法是什么?您可能会想:为什么不使用从调用 ChartOpen 获取的 ID 值?但问题就在这里。在上一篇文章中,我们删除了负责将 ChartOpen 中生成的图表 ID 传递给控制指标的全局终端变量。
此后,获取图表 ID 的任务就转移到了 C_Terminal 类代码。这是使用 ChartID 函数完成的。如果您一直关注本系列并相应地更新代码,您的 C_Terminal 类代码应该如下所示:
59. //+------------------------------------------------------------------+ 60. C_Terminal(const long id = 0) 61. { 62. m_Infos.ID = (id == 0 ? ChartID() : id); 63. m_Mem.AccountLock = false; 64. CurrentSymbol(); 65. m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR); 66. m_Mem.Show_Date = ChartGetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE); 67. ChartSetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR, false); 68. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_DELETE, 0, true); 69. ChartSetInteger(m_Infos.ID, CHART_EVENT_OBJECT_CREATE, 0, true); 70. ChartSetInteger(m_Infos.ID, CHART_SHOW_DATE_SCALE, false); 71. m_Infos.nDigits = (int) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_DIGITS); 72. m_Infos.Width = (int)ChartGetInteger(m_Infos.ID, CHART_WIDTH_IN_PIXELS); 73. m_Infos.Height = (int)ChartGetInteger(m_Infos.ID, CHART_HEIGHT_IN_PIXELS); 74. m_Infos.PointPerTick = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_SIZE); 75. m_Infos.ValuePerPoint = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_TRADE_TICK_VALUE); 76. m_Infos.VolumeMinimal = SymbolInfoDouble(m_Infos.szSymbol, SYMBOL_VOLUME_STEP); 77. m_Infos.AdjustToTrade = m_Infos.ValuePerPoint / m_Infos.PointPerTick; 78. m_Infos.ChartMode = (ENUM_SYMBOL_CHART_MODE) SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_CHART_MODE); 79. if(m_Infos.szSymbol != def_SymbolReplay) SetTypeAccount((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)); 80. ResetLastError(); 81. } 82. //+------------------------------------------------------------------+ 83.
C_Terminal.mqh 源代码片段
在此代码中,您可以看到第 60 行包含 C_Terminal 类的构造函数。它获得一个默认值 NULL,因此该构造函数的行为就像一个普通构造函数。实际问题发生在第 62 行,在检查传递给构造函数的值时,我们确定使用哪个图表 ID。如果默认使用该值,C_Terminal 类将要求 MetaTrader 5 使用 ChartID 返回的值提供图表 ID。如果进行了调用,该值将不正确,因为服务创建了图表并启动了指标,而指标又调用 C_Terminal 来找出 ID 值。
因此,我们可以将 ID 值传递给 C_Terminal 类构造函数,如果这样做,ChartID 调用将被忽略,并且传递给构造函数的 ID 将是指标使用的 ID。
再次提醒,请记住,我们将不再使用全局终端变量将此值传递给指标。我们可以暂时这样做,但解决方案会有所不同。我们将通过指标调用参数传递 ID 值。
实现解决方案
您可能会对我们将要做的事情感到非常惊讶,但这是因为在上一篇文章中,我没有在应用解决方案之前解释如何实现它以及它是如何工作的。请观看视频 01,该视频显示了系统的行为。这是在 ID 作为参数传递给控制指标之前。
视频 01
您可能会注意到出现了一条错误消息。出现此消息是因为指标无法知道图表 ID,因此 ChartID 调用返回错误,指标代码会检查此错误(见上面指标代码中的第25行)。但是当你看到这段代码时,你会注意到代码和视频之间存在差异:事实上,有些东西是不同的。但别担心,你很快就能访问视频中显示的代码,所以一切都会更加可靠。视频中显示的内容与上面给出的代码之间的区别在于,当时我不明白为什么该指标列在图表上,但没有显示在图表上。
我必须修改代码才能弄清楚它为什么不能正常工作。这就是为什么我请求你,如果我说无知是福,不要生气。我也不明白为什么代码会这样。
然而,我不会声称一切都是完全修复,因为这对我来说是不诚实的。将图表中的 ID 传递到指标会导致指标被显示。但是...在我们研究这个“但是”之前,让我们先看看如何修改代码,让一切重新正常工作。至少现在控制指标已显示在图表上。
这不需要修改服务的源代码,但需要修改 C_Replay.mqh 头文件的代码。修改后的文件全文如下:
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. void AddIndicatorControl(void) 117. { 118. int handle; 119. 120. handle = iCustom(ChartSymbol(m_IdReplay), ChartPeriod(m_IdReplay), "::" + def_IndicatorControl, m_IdReplay); 121. ChartIndicatorAdd(m_IdReplay, 0, handle); 122. IndicatorRelease(handle); 123. } 124. //+------------------------------------------------------------------+ 125. public : 126. //+------------------------------------------------------------------+ 127. C_Replay(const string szFileConfig) 128. { 129. m_ReplayCount = 0; 130. m_dtPrevLoading = 0; 131. m_Ticks.nTicks = 0; 132. m_Infos.bInit = false; 133. Print("************** Market Replay Service **************"); 134. srand(GetTickCount()); 135. GlobalVariableDel(def_GlobalVariableReplay); 136. SymbolSelect(def_SymbolReplay, false); 137. CustomSymbolDelete(def_SymbolReplay); 138. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay), _Symbol); 139. CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); 140. CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); 141. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, 0); 142. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, 0); 143. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, 0); 144. CustomSymbolSetString(def_SymbolReplay, SYMBOL_DESCRIPTION, "Symbol for replay / simulation"); 145. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, 8); 146. m_IdReplay = (SetSymbolReplay(szFileConfig) ? 0 : -1); 147. SymbolSelect(def_SymbolReplay, true); 148. } 149. //+------------------------------------------------------------------+ 150. ~C_Replay() 151. { 152. ArrayFree(m_Ticks.Info); 153. ArrayFree(m_Ticks.Rate); 154. m_IdReplay = ChartFirst(); 155. do 156. { 157. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 158. ChartClose(m_IdReplay); 159. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 160. for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++); 161. CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); 162. CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); 163. CustomSymbolDelete(def_SymbolReplay); 164. GlobalVariableDel(def_GlobalVariableReplay); 165. GlobalVariableDel(def_GlobalVariableServerTime); 166. Print("Finished replay service..."); 167. } 168. //+------------------------------------------------------------------+ 169. bool ViewReplay(ENUM_TIMEFRAMES arg1) 170. { 171. #define macroError(A) { Print(A); return false; } 172. u_Interprocess info; 173. 174. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 175. macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); 176. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 177. macroError("Asset configuration is not complete, need to declare the ticket value."); 178. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 179. macroError("Asset configuration not complete, need to declare the minimum volume."); 180. if (m_IdReplay == -1) return false; 181. if ((m_IdReplay = ChartFirst()) > 0) do 182. { 183. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 184. { 185. ChartClose(m_IdReplay); 186. ChartRedraw(); 187. } 188. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 189. Print("Waiting for [Market Replay] indicator permission to start replay ..."); 190. info.ServerTime = ULONG_MAX; 191. CreateGlobalVariable(def_GlobalVariableServerTime, info.df_Value); 192. m_IdReplay = ChartOpen(def_SymbolReplay, arg1); 193. AddIndicatorControl(); 194. while ((!GlobalVariableGet(def_GlobalVariableReplay, info.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); 195. info.s_Infos.isHedging = TypeAccountIsHedging(); 196. info.s_Infos.isSync = true; 197. GlobalVariableSet(def_GlobalVariableReplay, info.df_Value); 198. 199. return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); 200. #undef macroError 201. } 202. //+------------------------------------------------------------------+ 203. bool LoopEventOnTime(const bool bViewBuider) 204. { 205. u_Interprocess Info; 206. int iPos, iTest, iCount; 207. 208. if (!m_Infos.bInit) ViewInfos(); 209. iTest = 0; 210. while ((iTest == 0) && (!_StopFlag)) 211. { 212. iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1); 213. iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value) ? iTest : -1); 214. iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest); 215. if (iTest == 0) Sleep(100); 216. } 217. if ((iTest < 0) || (_StopFlag)) return false; 218. AdjustPositionToReplay(bViewBuider); 219. Info.ServerTime = m_Ticks.Info[m_ReplayCount].time; 220. GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value); 221. iPos = iCount = 0; 222. while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag)) 223. { 224. iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0); 225. CreateBarInReplay(true); 226. while ((iPos > 200) && (!_StopFlag)) 227. { 228. if (ChartSymbol(m_IdReplay) == "") return false; 229. GlobalVariableGet(def_GlobalVariableReplay, Info.df_Value); 230. if (!Info.s_Infos.isPlay) return true; 231. Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks); 232. GlobalVariableSet(def_GlobalVariableReplay, Info.df_Value); 233. Sleep(195); 234. iPos -= 200; 235. iCount++; 236. if (iCount > 4) 237. { 238. iCount = 0; 239. GlobalVariableGet(def_GlobalVariableServerTime, Info.df_Value); 240. if ((m_Ticks.Info[m_ReplayCount].time - m_Ticks.Info[m_ReplayCount - 1].time) > 60) Info.ServerTime = ULONG_MAX; else 241. { 242. Info.ServerTime += 1; 243. Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time); 244. }; 245. GlobalVariableSet(def_GlobalVariableServerTime, Info.df_Value); 246. } 247. } 248. } 249. return (m_ReplayCount == m_Ticks.nTicks); 250. } 251. //+------------------------------------------------------------------+ 252. }; 253. //+------------------------------------------------------------------+ 254. #undef macroRemoveSec 255. #undef def_SymbolReplay 256. //+------------------------------------------------------------------+
C_Replay.mqh 类源代码
虽然我们已经修改了此代码来支持我们的需求,但它将允许我们在不久的将来做更多的事情。让我们先看看添加了什么。您可以看到,大部分代码与上一篇文章中的代码保持不变。由于我希望您能够很好地理解这是如何实现的,因此我包含了完整的代码,以便您确定将使用的函数放置在哪里。
在第 116 行,我添加了一个新过程,用于将控制指标添加到图表中。此过程在第 193 行中调用,即在图表被服务打开并由 MetaTrader 5 显示后立即调用。但是让我们回到第 116 行。我们要做的第一件事是在第 120 行创建一个句柄,它将引用服务代码中存在的指标。请记住,指标作为资源嵌入到服务可执行文件中。在我们告知 MetaTrader 5 指标的位置后,我们需要向其提供一些信息。它代表 m_IdReplay,这是由 ChartOpen 调用创建的图表 ID。
这样,指标就会知道哪个图表 ID 是正确的。请注意这一点。即使您打开与回放交易品种相关联的另一个图表,指标也不会出现,而只会显示在服务创建的图表上。这是在第 121 行强制执行的。然后,在第 122 行,我们释放了创建的句柄,因为我们不再需要它。
但我们刚刚看到的只是解决方案的一部分,另一部分位于控制指标的源代码中。您可以在下面看到:
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.50" 07. #property link "https://www.mql5.com/zh/articles/11871" 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. input long user00 = 0; //ID 17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. u_Interprocess Info; 21. 22. ResetLastError(); 23. if (CheckPointer(control = new C_Controls(terminal = new C_Terminal(user00))) == POINTER_INVALID) 24. SetUserError(C_Terminal::ERR_PointerInvalid); 25. if ((!(*terminal).IndicatorCheckPass("Market Replay Control")) || (_LastError != ERR_SUCCESS)) 26. { 27. Print("Control indicator failed on initialization."); 28. return INIT_FAILED; 29. } 30. if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.df_Value = 0; 31. EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 32. (*control).Init(Info.s_Infos.isPlay); 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. static bool bWait = false; 40. u_Interprocess Info; 41. 42. Info.df_Value = GlobalVariableGet(def_GlobalVariableReplay); 43. if (!bWait) 44. { 45. if (Info.s_Infos.isWait) 46. { 47. EventChartCustom(user00, C_Controls::ev_WaitOn, 1, 0, ""); 48. bWait = true; 49. } 50. }else if (!Info.s_Infos.isWait) 51. { 52. EventChartCustom(user00, C_Controls::ev_WaitOff, 1, Info.df_Value, ""); 53. bWait = false; 54. } 55. 56. return rates_total; 57. } 58. //+------------------------------------------------------------------+ 59. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 60. { 61. (*control).DispatchMessage(id, lparam, dparam, sparam); 62. } 63. //+------------------------------------------------------------------+ 64. void OnDeinit(const int reason) 65. { 66. switch (reason) 67. { 68. case REASON_REMOVE: 69. case REASON_CHARTCLOSE: 70. if (ChartSymbol(user00) != def_SymbolReplay) break; 71. GlobalVariableDel(def_GlobalVariableReplay); 72. ChartClose(user00); 73. break; 74. } 75. delete control; 76. delete terminal; 77. } 78. //+------------------------------------------------------------------+
控制指标源代码
请注意,我们现在在第 16 行有一个输入参数,这意味着指标将接收一个参数。这就是为什么用户不能直接访问该指标并且不能手动将其放置在图表上的原因之一。指标将在第 16 行接收此参数,它指示将放置该对象的图表的 ID。该值必须正确填写。默认情况下它将为 NULL,即如果服务尝试放置指标但不提供图表,则会产生错误。错误消息出现在第 27 行。这解释了预期内容和视频 01 中呈现的内容之间的差异。
现在注意第 16 行输入参数值的使用位置。它在几个地方使用,但主要的地方是第 23 行,我们在那里告诉 C_Terminal 类,在查找图表 ID 时不应该使用生成的值。C_Terminal 类应该使用创建图表的服务报告的值。可以看到,其他部分也使用了第 16 行指定的值。然而,重新编译服务文件并在MetaTrader 5中运行后,我们得到了视频 02 中显示的结果。
视频 02
仔细观看视频02,正如您所看到的,当我们尝试与系统交互时,系统的行为非常奇怪。问题是为什么会发生这种情况。我们没有做出任何可能导致这种行为的修改。
这是我们要解决的第二个问题。然而,这个问题的解决要复杂得多。这里的原因不在于服务,不在于控制指标,也不在于 MetaTrader 5 平台。这是鼠标指针与图表上的对象的交互或缺乏交互。
此时您可能会想,“在我们开始修改看似完美运行的代码之前,我们如何修复不存在的错误?”那是在做出重大决定之前,允许用户使用自定义模板,而不是回放/模拟器系统长期以来使用的模板。好吧,这就是编程:它解决了引入新元素时出现的问题,也解决了未来的问题。
但在我们解决这个问题并解决鼠标指针与控制指标创建的图表对象交互的问题之前,让我们实现一个允许用户使用自定义模板的函数。这将允许用户应用预设模板,该模板旨在与模拟或真实帐户中的特定交易品种一起使用。但现在我们将允许用户在回放/模拟器系统中使用相同的模板。
做到这一点并不难。但是,用户可能倾向于以不合逻辑的方式做事,所以我们需要确保控制指标保留在图表上。即使用户坚持以完全非预期的方式使用回放/模拟器系统,我们也应该保护它。
在这种情况下,首先要做的就是让用户指示服务按照某个模板打开图表。只需进行一些小小的修改就可以轻松实现这一点。为了避免重复整个代码,我将仅发布需要修改的函数部分。首先我们来看一下服务代码。它就位于下面:
01. //+------------------------------------------------------------------+ 02. #property service 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property copyright "Daniel Jose" 05. #property version "1.50" 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/zh/articles/11871" 10. //+------------------------------------------------------------------+ 11. #define def_IndicatorControl "Indicators\\Replay\\Market Replay.ex5" 12. #resource "\\" + def_IndicatorControl 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. input string user02 = "Default"; //Template File Name 19. //+------------------------------------------------------------------+ 20. void OnStart() 21. { 22. C_Replay *pReplay; 23. 24. pReplay = new C_Replay(user00); 25. if ((*pReplay).ViewReplay(user01, user02)) 26. { 27. Print("Permission granted. Replay service can now be used..."); 28. while ((*pReplay).LoopEventOnTime(false)); 29. } 30. delete pReplay; 31. } 32. //+------------------------------------------------------------------+
服务源代码
我们添加了新的第 18 行,让用户可以选择指定当服务打开交易品种图表以用作回放或模拟器时应使用哪个模板。该值在第 25 行传递给类,我们在那里进行必要的配置。要了解发生了什么,请查看此过程的一部分。我只提供这个片段,因为重复整个代码是没用的。
175. //+------------------------------------------------------------------+ 176. bool ViewReplay(ENUM_TIMEFRAMES arg1, const string szNameTemplate) 177. { 178. #define macroError(A) { Print(A); return false; } 179. u_Interprocess info; 180. 181. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 182. macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); 183. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 184. macroError("Asset configuration is not complete, need to declare the ticket value."); 185. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 186. macroError("Asset configuration not complete, need to declare the minimum volume."); 187. if (m_IdReplay == -1) return false; 188. if ((m_IdReplay = ChartFirst()) > 0) do 189. { 190. if (ChartSymbol(m_IdReplay) == def_SymbolReplay) 191. { 192. ChartClose(m_IdReplay); 193. ChartRedraw(); 194. } 195. }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); 196. Print("Waiting for [Market Replay] indicator permission to start replay ..."); 197. info.ServerTime = ULONG_MAX; 198. CreateGlobalVariable(def_GlobalVariableServerTime, info.df_Value); 199. m_IdReplay = ChartOpen(def_SymbolReplay, arg1); 200. if (!ChartApplyTemplate(m_IdReplay, szNameTemplate + ".tpl")) 201. Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl"); 202. AddIndicatorControl(); 203. while ((!GlobalVariableGet(def_GlobalVariableReplay, info.df_Value)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); 204. info.s_Infos.isHedging = TypeAccountIsHedging(); 205. info.s_Infos.isSync = true; 206. GlobalVariableSet(def_GlobalVariableReplay, info.df_Value); 207. 208. return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); 209. #undef macroError 210. } 211. //+------------------------------------------------------------------+
C_Replay.mqh 源代码片段(更新)
请注意,在第 176 行,我们添加了一个额外的参数,它通知类将加载哪个模板。然后在第 200 行我们尝试应用用户提供的模板。如果尝试失败,我们会在第 201 行通知用户,并切换到默认模板。现在让我们停下来解释一下。默认情况下,MetaTrader 5 将始终使用预定义的模板,因此不需要新的调用来加载并将其应用于图表。
在这一点上,有一些有趣的事情需要澄清:如果您使用多个交易品种,并希望使用特定模板针对每个交易品种运行有针对性的研究,那么将所需模板的名称添加到建模配置文件中可能会很有意义。它将被预先配置,并且无需通知有关服务启动的信息。但这只是那些想做这件事的人的一个想法。在这个系统中,我不会这样做。此外,这个带有模板的故事还有另一个方面。这正是程序员头疼的方面。该方面是用户,
为什么用户对我们来说是一个问题?这可能不大合理。但这是一个很令人头疼的问题,因为我们可以告诉 MetaTrader 5 启动服务,并在适当的时候更改回放/模拟器的图表模板。无需重新启动服务即可应用更改。可能你还没有明白问题的本质。在这种情况下,模板设置将覆盖图表设置,问题是控制指标将从图表中删除。
而用户将无法手动将控制指标放置在图表上。但还是有办法做到这一点的。但是,让我们考虑到这样一个事实,即如果用户不知道如何在 MetaTrader 5 和 MQL5中工作,那么他们将无法替换图表上的控制指标。这种情况正是用户要负责的那种故障。但负责纠正错误的是程序员。
当模板发生更改时,MetaTrader 5 将通知图表上的程序发生了模板更改,以便他们采取行动。MetaTrader 5 的实现方式如下:
void OnDeinit(const int reason) { switch (reason) { case REASON_TEMPLATE: Print("Template change ..."); break;
当模板发生变化时,MetaTrader 5 中会发生 DeInit 事件,它将调用上面显示的过程。我们可以检查导致 MetaTrader 5 调用 DeInit 事件的条件,如果是模板更改,则 相关消息将显示在终端中。
换句话说,我们可以知道模板是否已更改,但这种认识不会立即导致指标重置。这里我们必须做出一个决定:强制服务终止或者强制服务在图表上重新放置控制指标。依我之见,我们应该强行终止这项服务。原因很简单,如果允许用户自定义回放/模拟器图表上使用的模板,为什么我们要允许用户手动更改模板?这毫无意义。因此,在我看来,最好的办法就是强制终止服务,并在服务启动时要求用户提供模板。否则,为什么需要允许用户向服务提供模板?
因此,我们需要在指标代码中进行另一个简单的更新,如下所示:
64. //+------------------------------------------------------------------+ 65. void OnDeinit(const int reason) 66. { 67. switch (reason) 68. { 69. case REASON_TEMPLATE: 70. Print("Modified template. Replay/simulation system shutting down."); 71. case REASON_PARAMETERS: 72. case REASON_REMOVE: 73. case REASON_CHARTCLOSE: 74. if (ChartSymbol(user00) != def_SymbolReplay) break; 75. GlobalVariableDel(def_GlobalVariableReplay); 76. ChartClose(user00); 77. break; 78. } 79. delete control; 80. delete terminal; 81. } 82. //+------------------------------------------------------------------+
控制指标源代码片段(更新)
请注意,我们在第 69 行添加了一个测试来检查指标从图表中删除的原因。如果原因是模板更改,第 70 行将向终端打印一条消息,我们将得到与用户关闭图表或用户删除指标相同的结果。也就是说,服务将终止。这个决定可能看起来太激进了,但正如我已经解释的那样,否则就没有意义了。
我们修复了一个错误,现在我们遇到了另一个问题:用户可以简单地更改控制指标使用的任何参数,并由服务传递给它。我们仍在配置系统,因此可能会出现新的参数。应对这种情况要困难得多,但我们将采取激进的方法来解决这个问题。该解决方案已在上述片段中实现。
注意第 71 行,之前没有这样一行代码。添加它是为了防止用户更改服务传递给控制指标的任何参数。如果发生这种情况,MetaTrader 5 将生成一个 DeInit 事件,其参数为参数改变。我们目前不会报告任何故障,因为故障将在 MetaTrader 5 重新启动指标时发生。但由于一些更精明的用户可以提供实际图表的 ID,我们在第 76 行关闭回放/模拟器图表。因此,当服务检查图表是否打开时,它将收到一个错误,这意味着服务应该终止。因此,我们也纠正了这个问题。
结论
在本文中,我们修复了由于控制指标对用户不可用而导致的几个主要错误。虽然它仍然可以通过 CTRL+I 快捷方式在指标窗口中看到,但它不再包含在可在任何图表上使用的指标列表中。这种类型的更改涉及对代码进行大量修改,使其更加稳定和一致,并防止用户做我们不想要或不期望的事情。
但是,我们仍然存在一个问题,即用户很难与指标交互,很难使用鼠标指针调整和操作控制指标。但这个问题与我们即将修复的问题有关,这将使系统更加自由和完全模块化。
在下面的视频 03 中,您可以看到系统现在如何处理本文中描述的更新。但是,由于代码仍然不稳定,我不会在本文中附加任何文件。
视频 03
在下一篇文章中,我们将继续解决与用户和回放/模拟器服务之间的交互相关的问题。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11871



