
开发回放系统(第 60 部分):玩转服务(一)
概述
在上一篇文章开发回放系统(第 59 部分):新的未来中,我介绍并解释了对控制指标和鼠标指标模块所做的部分更改。虽然这些更新为我们提供了一些未来的使用可能性,特别是鼠标指标模块,但它仍然存在一个小缺陷。然而,这个缺陷在现阶段并不影响我们。这是因为,目前,我们的重点是解决回放/模拟器服务类代码中与封装相关的一些剩余问题。
尽管我们已经进行了一些调整,如前几篇文章所述,但这些调整不足以完全解决封装问题。类中仍有元素暴露,我们需要立即修复。否则,很快就会引发严重问题。除了解决封装问题(使应该保持隐藏的内部元素不可访问)外,我们还需要重构代码的某些方面,以确保更适当地访问某些变量和信息。所有这些都将在本文中讨论。
开始修复问题
好的,我们要解决的第一个问题是 C_FilesTicks 类中的封装问题。该类位于文件 C_FilesTicks.mqh 中,目前,它公有的信息不应直接访问,至少不是以当前的方式。
为了解决此暴露问题,我们将采用一个相对简单的解决方案。下面,您可以看到 C_FilesTicks 类的更新和完整版本。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_FileBars.mqh" 005. #include "C_Simulation.mqh" 006. //+------------------------------------------------------------------+ 007. #define macroRemoveSec(A) (A - (A % 60)) 008. //+------------------------------------------------------------------+ 009. class C_FileTicks 010. { 011. protected: 012. enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX}; 013. struct stInfoTicks 014. { 015. MqlTick Info[]; 016. MqlRates Rate[]; 017. int nTicks, 018. nRate; 019. bool bTickReal; 020. ePlotType ModePlot; 021. };m_Ticks; 022. //+------------------------------------------------------------------+ 023. inline bool BuildBar1Min(const int iArg, MqlRates &rate, bool &bNew) 024. { 025. double dClose = 0; 026. 027. switch (m_Ticks.ModePlot) 028. { 029. case PRICE_EXCHANGE: 030. if (m_Ticks.Info[iArg].last == 0.0) return false; 031. dClose = m_Ticks.Info[iArg].last; 032. break; 033. case PRICE_FOREX: 034. dClose = (m_Ticks.Info[iArg].bid > 0.0 ? m_Ticks.Info[iArg].bid : dClose); 035. if ((dClose == 0.0) || (m_Ticks.Info[iArg].bid == 0.0)) return false; 036. break; 037. } 038. if (bNew = (rate.time != macroRemoveSec(m_Ticks.Info[iArg].time))) 039. { 040. rate.time = macroRemoveSec(m_Ticks.Info[iArg].time); 041. rate.real_volume = 0; 042. rate.tick_volume = (m_Ticks.ModePlot == PRICE_FOREX ? 1 : 0); 043. rate.open = rate.low = rate.high = rate.close = dClose; 044. }else 045. { 046. rate.close = dClose; 047. rate.high = (rate.close > rate.high ? rate.close : rate.high); 048. rate.low = (rate.close < rate.low ? rate.close : rate.low); 049. rate.real_volume += (long) m_Ticks.Info[iArg].volume_real; 050. rate.tick_volume++; 051. } 052. 053. return true; 054. } 055. //+------------------------------------------------------------------+ 056. private : 057. int m_File; 058. stInfoTicks m_Ticks; 059. //+------------------------------------------------------------------+ 060. inline bool Open(const string szFileNameCSV) 061. { 062. string szInfo = ""; 063. 064. if ((m_File = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE) 065. { 066. for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(m_File); 067. if (szInfo == "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>") return true; 068. Print("File ", szFileNameCSV, ".csv not a traded tick file."); 069. }else 070. Print("Tick file ", szFileNameCSV,".csv not found..."); 071. 072. return false; 073. } 074. //+------------------------------------------------------------------+ 075. inline bool ReadAllsTicks(const bool ToReplay) 076. { 077. string szInfo; 078. 079. Print("Loading replay ticks. Please wait..."); 080. ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray); 081. m_Ticks.ModePlot = PRICE_FOREX; 082. while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag)) 083. { 084. ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray); 085. szInfo = FileReadString(m_File) + " " + FileReadString(m_File); 086. m_Ticks.Info[m_Ticks.nTicks].time = StringToTime(StringSubstr(szInfo, 0, 19)); 087. m_Ticks.Info[m_Ticks.nTicks].time_msc = (m_Ticks.Info[m_Ticks.nTicks].time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3)); 088. m_Ticks.Info[m_Ticks.nTicks].bid = StringToDouble(FileReadString(m_File)); 089. m_Ticks.Info[m_Ticks.nTicks].ask = StringToDouble(FileReadString(m_File)); 090. m_Ticks.Info[m_Ticks.nTicks].last = StringToDouble(FileReadString(m_File)); 091. m_Ticks.Info[m_Ticks.nTicks].volume_real = StringToDouble(FileReadString(m_File)); 092. m_Ticks.Info[m_Ticks.nTicks].flags = (uchar)StringToInteger(FileReadString(m_File)); 093. m_Ticks.ModePlot = (m_Ticks.Info[m_Ticks.nTicks].volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot); 094. m_Ticks.nTicks++; 095. } 096. FileClose(m_File); 097. if (m_Ticks.nTicks == (INT_MAX - 2)) 098. { 099. Print("Too much data in tick file.\nIt is not possible to continue..."); 100. return false; 101. } 102. return (!_StopFlag); 103. } 104. //+------------------------------------------------------------------+ 105. int SetSymbolInfos(void) 106. { 107. int iRet; 108. 109. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, iRet = (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5)); 110. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX); 111. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID); 112. 113. return iRet; 114. } 115. //+------------------------------------------------------------------+ 116. public : 117. //+------------------------------------------------------------------+ 118. C_FileTicks() 119. { 120. ArrayResize(m_Ticks.Rate, def_BarsDiary); 121. m_Ticks.nRate = -1; 122. m_Ticks.Rate[0].time = 0; 123. } 124. //+------------------------------------------------------------------+ 125. bool BarsToTicks(const string szFileNameCSV) 126. { 127. C_FileBars *pFileBars; 128. C_Simulation *pSimulator = NULL; 129. int iMem = m_Ticks.nTicks, 130. iRet = -1; 131. MqlRates rate[1]; 132. MqlTick local[]; 133. bool bInit = false; 134. 135. pFileBars = new C_FileBars(szFileNameCSV); 136. ArrayResize(local, def_MaxSizeArray); 137. Print("Converting bars to ticks. Please wait..."); 138. while ((*pFileBars).ReadBar(rate) && (!_StopFlag)) 139. { 140. if (!bInit) 141. { 142. m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX); 143. pSimulator = new C_Simulation(SetSymbolInfos()); 144. bInit = true; 145. } 146. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary); 147. m_Ticks.Rate[++m_Ticks.nRate] = rate[0]; 148. if (pSimulator == NULL) iRet = -1; else iRet = (*pSimulator).Simulation(rate[0], local); 149. if (iRet < 0) break; 150. for (int c0 = 0; c0 <= iRet; c0++) 151. { 152. ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray); 153. m_Ticks.Info[m_Ticks.nTicks++] = local[c0]; 154. } 155. } 156. ArrayFree(local); 157. delete pFileBars; 158. delete pSimulator; 159. m_Ticks.bTickReal = false; 160. 161. return ((!_StopFlag) && (iMem != m_Ticks.nTicks) && (iRet > 0)); 162. } 163. //+------------------------------------------------------------------+ 164. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true) 165. { 166. int MemNRates, 167. MemNTicks; 168. datetime dtRet = TimeCurrent(); 169. MqlRates RatesLocal[], 170. rate; 171. bool bNew; 172. 173. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 174. MemNTicks = m_Ticks.nTicks; 175. if (!Open(szFileNameCSV)) return 0; 176. if (!ReadAllsTicks(ToReplay)) return 0; 177. rate.time = 0; 178. for (int c0 = MemNTicks; c0 < m_Ticks.nTicks; c0++) 179. { 180. if (!BuildBar1Min(c0, rate, bNew)) continue; 181. if (bNew) ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 182. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 183. } 184. if (!ToReplay) 185. { 186. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 187. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 188. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 189. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 190. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 191. m_Ticks.nTicks = MemNTicks; 192. ArrayFree(RatesLocal); 193. }else SetSymbolInfos(); 194. m_Ticks.bTickReal = true; 195. 196. return dtRet; 197. }; 198. //+------------------------------------------------------------------+ 199. inline stInfoTicks GetInfoTicks(void) const 200. { 201. return m_Ticks; 202. } 203. //+------------------------------------------------------------------+ 204. }; 205. //+------------------------------------------------------------------+ 206. #undef def_MaxSizeArray 207. //+------------------------------------------------------------------+
C_FilesTicks.mqh 文件的源代码
您很可能没有注意到任何重大差异,当然,除了第21行中划掉的部分。
这个被划掉的元素代表了我们正在解决的数据泄漏问题。换句话说,尽管一切看起来都是安全的并且功能齐全,但当需要处理已加载或模拟的分时报价时,我们就会面临严重的安全问题。其中原因是任何类都可以修改该结构中的数据。请注意,在类的范围之外,无法进行这样的修改,因为结构位于受保护的子句内。在之前的文章中,我解释了这些子句如何确定类成员的访问级别。
但是,通过删除该变量并仅允许使用结构本身,我们可以实现更可接受的安全性和封装级别。然而,通过做出这一修改,我们必须做出另一个决定。决定如下:要么声明一个新变量,这次声明为私有变量,以在内部维护结构,并添加一个函数来访问该变量。或者在其他地方声明相同的变量,并将其作为参数传递给需要访问其内容的任何函数。
这听起来可能很奇怪,但每个案例都有自己的背景、后果和可能性。鉴于项目的性质,我决定采用第一种方法,即我们将创建一个私有变量,您可以在第 58 行看到。除此之外,不需要对代码进行其他重大更改。然而,通过从公共访问中删除此变量(是的,我们在其他地方使用了它,不仅读取而且修改了它的数据,很快就会演示),我们现在需要确保结构的正确初始化。因此,您会注意到第 118 行出现了一个构造函数。请注意,它非常简单,执行的是以前在其他地方完成的任务。我们刚刚修复了 C_FilesTicks 类中的导致泄露的封装缺陷。
此外,如前所述,我们还需要提供一种访问此变量的方法,以便读取其数据。这是通过第 199 行所示的函数实现的。请密切注意:我们将能够读取结构内的数据,但不能写入。这是由于函数的声明方式 — 返回的值必须被视为常量。我在本系列之前的一些文章中已经解释了这种方法的使用。如果你有任何疑问,请随时回顾以澄清。
现在我们已经解决了其中一个问题,我们需要解决另一个问题。第二个问题出现在 C_ConfigService 类中,位于 C_ConfigService.mqh 文件中。这个头文件需要进行一些调整,主要是因为 C_FilesTicks 的数据泄漏不再存在。此外,C_ConfigService 中声明的一些元素将被删除,因为它们在此类中不再需要,而是在其它地方使用。
因此,C_ConfigService.mqh 文件的更新代码将如下所示:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Support\C_FileBars.mqh" 005. #include "Support\C_FileTicks.mqh" 006. #include "Support\C_Array.mqh" 007. //+------------------------------------------------------------------+ 008. class C_ConfigService : protected C_FileTicks 009. { 010. protected: 011. //+------------------------------------------------------------------+ 012. private : 013. enum eWhatExec {eTickReplay, eBarToTick, eTickToBar, eBarPrev}; 014. enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE}; 015. struct st001 016. { 017. C_Array *pTicksToReplay, *pBarsToTicks, *pTicksToBars, *pBarsToPrev; 018. int Line; 019. bool AccountHedging; 020. char ModelLoading; 021. string szPath; 022. }m_GlPrivate; 023. string m_szPath; 024. bool m_AccountHedging; 025. datetime m_dtPrevLoading; 026. int m_ReplayCount, 027. m_ModelLoading; 028. //+------------------------------------------------------------------+ 029. inline void FirstBarNULL(void) 030. { 031. MqlRates rate[1]; 032. int c0 = 0; 033. 034. for(; (GetInfoTicks().ModePlot == PRICE_EXCHANGE) && (GetInfoTicks().Info[c0].volume_real == 0); c0++); 035. rate[0].close = (GetInfoTicks().ModePlot == PRICE_EXCHANGE ? GetInfoTicks().Info[c0].last : GetInfoTicks().Info[c0].bid); 036. rate[0].open = rate[0].high = rate[0].low = rate[0].close; 037. rate[0].tick_volume = 0; 038. rate[0].real_volume = 0; 039. rate[0].time = macroRemoveSec(GetInfoTicks().Info[c0].time) - 86400; 040. CustomRatesUpdate(def_SymbolReplay, rate); 041. } 042. //+------------------------------------------------------------------+ 043. inline eTranscriptionDefine GetDefinition(const string &In, string &Out) 044. { 045. string szInfo; 046. 047. szInfo = In; 048. Out = ""; 049. StringToUpper(szInfo); 050. StringTrimLeft(szInfo); 051. StringTrimRight(szInfo); 052. if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO; 053. if (StringSubstr(szInfo, 0, 1) != "[") 054. { 055. Out = szInfo; 056. return Transcription_INFO; 057. } 058. for (int c0 = 0; c0 < StringLen(szInfo); c0++) 059. if (StringGetCharacter(szInfo, c0) > ' ') 060. StringAdd(Out, StringSubstr(szInfo, c0, 1)); 061. 062. return Transcription_DEFINE; 063. } 064. //+------------------------------------------------------------------+ 065. inline bool Configs(const string szInfo) 066. { 067. const string szList[] = { 068. "PATH", 069. "POINTSPERTICK", 070. "VALUEPERPOINTS", 071. "VOLUMEMINIMAL", 072. "LOADMODEL", 073. "ACCOUNT" 074. }; 075. string szRet[]; 076. char cWho; 077. 078. if (StringSplit(szInfo, '=', szRet) == 2) 079. { 080. StringTrimRight(szRet[0]); 081. StringTrimLeft(szRet[1]); 082. for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break; 083. switch (cWho) 084. { 085. case 0: 086. m_GlPrivate.szPath = szRet[1]; 087. return true; 088. case 1: 089. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1])); 090. return true; 091. case 2: 092. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1])); 093. return true; 094. case 3: 095. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1])); 096. return true; 097. case 4: 098. m_GlPrivate.ModelLoading = StringInit(szRet[1]); 099. m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading); 100. return true; 101. case 5: 102. if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true; 103. else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false; 104. else 105. { 106. Print("Entered account type is not invalid."); 107. return false; 108. } 109. return true; 110. } 111. Print("Variable >>", szRet[0], "<< not defined."); 112. }else 113. Print("Configuration definition >>", szInfo, "<< invalidates."); 114. 115. return false; 116. } 117. //+------------------------------------------------------------------+ 118. inline bool WhatDefine(const string szArg, char &cStage) 119. { 120. const string szList[] = { 121. "[BARS]", 122. "[TICKS]", 123. "[TICKS->BARS]", 124. "[BARS->TICKS]", 125. "[CONFIG]" 126. }; 127. 128. cStage = 1; 129. for (char c0 = 0; c0 < ArraySize(szList); c0++, cStage++) 130. if (szList[c0] == szArg) return true; 131. 132. return false; 133. } 134. //+------------------------------------------------------------------+ 135. inline bool CMD_Array(char &cError, eWhatExec e1) 136. { 137. bool bBarsPrev = false; 138. string szInfo; 139. C_FileBars *pFileBars; 140. C_Array *ptr = NULL; 141. 142. switch (e1) 143. { 144. case eTickReplay : ptr = m_GlPrivate.pTicksToReplay; break; 145. case eTickToBar : ptr = m_GlPrivate.pTicksToBars; break; 146. case eBarToTick : ptr = m_GlPrivate.pBarsToTicks; break; 147. case eBarPrev : ptr = m_GlPrivate.pBarsToPrev; break; 148. } 149. if (ptr != NULL) 150. { 151. for (int c0 = 0; (c0 < INT_MAX) && (cError == 0); c0++) 152. { 153. if ((szInfo = ptr.At(c0, m_GlPrivate.Line)) == "") break; 154. switch (e1) 155. { 156. case eTickReplay : 157. if (LoadTicks(szInfo) == 0) cError = 4; 158. break; 159. case eTickToBar : 160. if (LoadTicks(szInfo, false) == 0) cError = 5; else bBarsPrev = true; 161. break; 162. case eBarToTick : 163. if (!BarsToTicks(szInfo)) cError = 6; 164. break; 165. case eBarPrev : 166. pFileBars = new C_FileBars(szInfo); 167. if ((*pFileBars).LoadPreView() == 0) cError = 3; else bBarsPrev = true; 168. delete pFileBars; 169. break; 170. } 171. } 172. delete ptr; 173. } 174. 175. return bBarsPrev; 176. } 177. //+------------------------------------------------------------------+ 178. public : 179. //+------------------------------------------------------------------+ 180. C_ConfigService() 181. :C_FileTicks() 182. { 183. m_GlPrivate.AccountHedging = false; 184. m_GlPrivate.ModelLoading = 1; 185. } 186. //+------------------------------------------------------------------+ 187. inline const bool TypeAccountIsHedging(void) const 188. { 189. return m_GlPrivate.AccountHedging; 190. } 191. //+------------------------------------------------------------------+ 192. bool SetSymbolReplay(const string szFileConfig) 193. { 194. #define macroFileName ((m_GlPrivate.szPath != NULL ? m_GlPrivate.szPath + "\\" : "") + szInfo) 195. int file; 196. char cError, 197. cStage; 198. string szInfo; 199. bool bBarsPrev; 200. 201. if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) 202. { 203. Print("Failed to open configuration file [", szFileConfig, "]. Service being terminated..."); 204. return false; 205. } 206. Print("Loading data for playback. Please wait...."); 207. ArrayResize(Ticks.Rate, def_BarsDiary); 208. Ticks.nRate = -1; 209. Ticks.Rate[0].time = 0; 210. cError = cStage = 0; 211. bBarsPrev = false; 212. m_GlPrivate.Line = 1; 213. m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL; 214. while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0)) 215. { 216. switch (GetDefinition(FileReadString(file), szInfo)) 217. { 218. case Transcription_DEFINE: 219. cError = (WhatDefine(szInfo, cStage) ? 0 : 1); 220. break; 221. case Transcription_INFO: 222. if (szInfo != "") switch (cStage) 223. { 224. case 0: 225. cError = 2; 226. break; 227. case 1: 228. if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array(); 229. (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line); 230. break; 231. case 2: 232. if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array(); 233. (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line); 234. break; 235. case 3: 236. if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array(); 237. (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line); 238. break; 239. case 4: 240. if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array(); 241. (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line); 242. break; 243. case 5: 244. if (!Configs(szInfo)) cError = 7; 245. break; 246. } 247. break; 248. }; 249. m_GlPrivate.Line += (cError > 0 ? 0 : 1); 250. } 251. FileClose(file); 252. CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eTickReplay : eBarToTick)); 253. CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eBarToTick : eTickReplay)); 254. bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev); 255. bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev); 256. switch(cError) 257. { 258. case 0: 259. if (GetInfoTicks().nTicks <= 0) //Atualizado ... 260. { 261. Print("There are no ticks to use. Service is being terminated..."); 262. cError = -1; 263. }else if (!bBarsPrev) FirstBarNULL(); 264. break; 265. case 1 : Print("The command on the line ", m_GlPrivate.Line, " not recognized by the system..."); break; 266. case 2 : Print("The system did not expect the contents of the line: ", m_GlPrivate.Line); break; 267. default : Print("Error accessing the file indicated in the line: ", m_GlPrivate.Line); 268. } 269. 270. return (cError == 0 ? !_StopFlag : false); 271. #undef macroFileName 272. } 273. //+------------------------------------------------------------------+ 274. }; 275. //+------------------------------------------------------------------+
C_ConfigService.mqh 文件的源代码
已做出一些修改,有些修改比较细微,有些修改比较明显,例如删除掉的部分。但您现在应该注意到,我们不再直接访问分时报价数据。处理这个问题的新方法可以在第 34 行看到。请注意,我们现在可以以更精细、最重要的是更安全的方式访问这些数据,因为不再可能更改脚本文件中配置的数据或分时或柱形数据文件中包含的数据。因此,我们可以肯定地说,我们现在有了一个更安全、更稳定的系统,一个可以更容易地编程、发生严重错误的机会更少的系统。
除此之外,亲爱的读者,我想提请您注意另一点。注意第 23 行至第 27 行:私有全局变量已被删除。其中一些已被移至从第 15 行开始的结构内部。但是关于全局和私有变量的这种变化背后是有原因的,原因是我不希望(因为我们不需要)某些元素成为回放/模拟服务的一般配置的一部分。
由于第 23 行和第 27 行之间的变化,代码的几个部分必须进行调整。然而,这些并不值得特别关注,因为它们是简单的修改,易于理解。不过,请密切关注类构造函数,它位于第 180 行。请注意,此构造函数的代码已稍作修改。然而,这并不是使这个头文件值得讨论的要点。关键点在第 207 行和第 209 行之间,这两行被划掉了。
在修复 C_FileTicks 类中的封装问题之前,位于受保护部分中的 m_Ticks 变量实际上是在这些行内实例化的,或者更确切地说,在这里初始化的。
注意这种情况有多危险。虽然代码运行得相当稳定,但它并不是真正安全的,特别是如果我们打算稍后进行重大修改。这是我的一个错误,一直持续到现在。但是,由于我需要以一种非常具体的方式工作,因此允许信息从代码中的任何地方泄露和修改是不可接受的。
通过这些修正,我们负责维护分时报价数据、柱形数据和配置用于回放/模拟的资产的类现在是完全安全的,没有任何数据泄漏。换句话说,我们现在已经明确关闭了与数据加载、模拟和资产配置相关的系统部分,这些部分将由回放/模拟器服务使用。从这里开始,我们可以专注于让服务正常运行:启动、暂停,还允许用户设置回放/模拟从特定点开始。
这些功能在所有这些修改之前就已经存在了,但是从现在开始,我们将以更安全、更简单、更稳定的方式实现它们。我们还将确保在不久的将来,我们将研究的组件之间有更好的交互。
允许用户播放和暂停回放/模拟
在本节中,我将展示如何最终使服务响应用户点击播放和暂停按钮。这将启用回放和模拟功能。
但在此之前,亲爱的读者,让我向您展示对控制指标模块所做的两个小更新。这些更新旨在防止系统运行时有时会出现令人不快的问题。由于这是一个随机问题,因此很难重现,因为您可能会多次运行系统而不会发生任何奇怪的事情。然而,偶尔会发生一些奇怪的事情。为了避免这种随机崩溃造成的所有挫折,我对控制模块的代码进行了一些调整。
因此,在头文件 C_Replay.mqh 中,我们更新了函数和过程,如下面的片段所示。让我们从以下这一点开始:
71. //+------------------------------------------------------------------+ 72. void SetPlay(bool state) 73. { 74. if (m_Section[ePlay].Btn == NULL) 75. m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_PrefixCtrlName + EnumToString(ePlay), def_ColorFilter, "::" + def_ButtonPlay, "::" + def_ButtonPause); 76. m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, ((m_Section[ePlay].state = state) ? 0 : 1)); 77. if (!state) CreateCtrlSlider(); 78. } 79. //+------------------------------------------------------------------+
来自 C_Controls.mqh 文件的代码
我们需要在原始代码中添加第 77 行。这样,控制指标模块将始终知道该做什么,并且如果播放按钮可见则会显示滑块。也就是说,当我们处于暂停模式时,滑块条将显示在图表上。有时这种情况不会发生,但通过此更新,该条将始终显示。
还需要进行一项更改,这应该在下面显示的代码部分中完成:
170. //+------------------------------------------------------------------+ 171. void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam) 172. { 173. short x, y; 174. static short iPinPosX = -1, six = -1, sps; 175. uCast_Double info; 176. 177. switch (id) 178. { 179. case (CHARTEVENT_CUSTOM + evCtrlReplayInit): 180. info.dValue = dparam; 181. if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break; 182. x = (short) info._16b[0]; 183. iPinPosX = m_Slider.Minimal = (x > def_MaxPosSlider ? def_MaxPosSlider : (x < iPinPosX ? iPinPosX : x)); 184. SetPlay((short)(info._16b[1]) == SHORT_MAX); 185. break; 186. case CHARTEVENT_OBJECT_DELETE:
来自 C_Controls.mqh 文件的代码
在本节中,您需要修改原始代码并插入第 180 行和第 185 行之间的内容。这一修改的目的是使控制指标模块对有时发生的特别烦人的问题更具弹性。该问题涉及触发第 179 行执行的自定义事件,偶尔会导致错误的行为。其中一种行为是数组索引 1 中出现错误的值。本来,发生这种情况时会报告错误,并且控制模块会从图表中删除,导致整个回放/模拟服务崩溃。换句话说,服务将由于随机运行时故障导致无效数据出现而终止。
在新的实现中,当发生故障时,控制指标模块立即进入暂停模式。这比回放/模拟系统彻底失败要好得多。此外,还有一个重要的细节。如果您还没有注意到,第 181 行有一个小检查。这确保了即使出现无效值,我们也只会在双精度值中八个字节的前两个字节包含特定内容时才会使用它们。如果不存在这些值,则控制指标模块将忽略该事件。这种类型的验证可确保数据的完整性,或者至少确认数据来自特定来源:回放/模拟服务。
完成这两项修改后,您将需要在指标的源代码中再进行一项修改。您可以在下面的片段中看到最终所需的更新。
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. //+------------------------------------------------------------------+
控制指标源代码片段
应将划掉的行从源代码中删除。因此,请删除控制模块的已编译可执行文件,以便在您尝试再次编译服务时强制服务重新编译它。或者,如果您愿意,可以手动重新编译控制模块,以确保最新更新版本正确作为资源包含在回放/模拟服务中。
现在,我们可以继续讨论回放/模拟服务代码本身。在对 C_Replay.mqh 头文件进行最后一次更新之后,我们将继续对迄今为止存在的代码进行一些修改。这些修改已在之前的文章中实现。当时,唯一的功能是显示系统加载的数据,以及控制和鼠标模块。但是,除此之外不可能进行任何交互,这意味着我们无法播放或暂停回放/模拟来动态显示新数据。
但在这里,我们将改变这一现实。因此让我们看一下更新后的 C_Replay.mqh 头文件。该文件的完整内容如下。
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_Infos.IdReplay) != "")) 010. //+------------------------------------------------------------------+ 011. #define def_ShortNameIndControl "Market Replay Control" 012. //+------------------------------------------------------------------+ 013. class C_Replay : public C_ConfigService 014. { 015. private : 016. struct st00 017. { 018. ushort Position; 019. short Mode; 020. int Handle; 021. }m_IndControl; 022. struct st01 023. { 024. long IdReplay; 025. int CountReplay; 026. double PointsPerTick; 027. MqlTick tick[1]; 028. MqlRates Rate[1]; 029. }m_Infos; 030. //+------------------------------------------------------------------+ 031. inline bool MsgError(string sz0) { Print(sz0); return false; } 032. //+------------------------------------------------------------------+ 033. inline void UpdateIndicatorControl(void) 034. { 035. uCast_Double info; 036. double Buff[]; 037. 038. info.dValue = 0; 039. if (m_IndControl.Handle == INVALID_HANDLE) return; 040. if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1) 041. info.dValue = Buff[0]; 042. if ((short)(info._16b[0]) != SHORT_MIN) 043. m_IndControl.Mode = (short)info._16b[1]; 044. if (info._16b[0] != m_IndControl.Position) 045. { 046. if (((short)(info._16b[0]) != SHORT_MIN) && ((short)(info._16b[1]) == SHORT_MAX)) 047. m_IndControl.Position = info._16b[0]; 048. info._16b[0] = m_IndControl.Position; 049. info._16b[1] = (ushort)m_IndControl.Mode; 050. info._8b[7] = 'D'; 051. info._8b[6] = 'M'; 052. EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, info.dValue, ""); 053. } 054. } 055. //+------------------------------------------------------------------+ 056. void SweepAndCloseChart(void) 057. { 058. long id; 059. 060. if ((id = ChartFirst()) > 0) do 061. { 062. if (ChartSymbol(id) == def_SymbolReplay) 063. ChartClose(id); 064. }while ((id = ChartNext(id)) > 0); 065. } 066. //+------------------------------------------------------------------+ 067. inline void CreateBarInReplay(bool bViewTick) 068. { 069. bool bNew; 070. double dSpread; 071. int iRand = rand(); 072. 073. if (BuildBar1Min(m_Infos.CountReplay, m_Infos.Rate[0], bNew)) 074. { 075. m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay]; 076. if (GetInfoTicks().ModePlot == PRICE_EXCHANGE) 077. { 078. dSpread = m_Infos.PointsPerTick + ((iRand > 29080) && (iRand < 32767) ? ((iRand & 1) == 1 ? m_Infos.PointsPerTick : 0 ) : 0 ); 079. if (m_Infos.tick[0].last > m_Infos.tick[0].ask) 080. { 081. m_Infos.tick[0].ask = m_Infos.tick[0].last; 082. m_Infos.tick[0].bid = m_Infos.tick[0].last - dSpread; 083. }else if (m_Infos.tick[0].last < m_Infos.tick[0].bid) 084. { 085. m_Infos.tick[0].ask = m_Infos.tick[0].last + dSpread; 086. m_Infos.tick[0].bid = m_Infos.tick[0].last; 087. } 088. } 089. if (bViewTick) CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 090. CustomRatesUpdate(def_SymbolReplay, m_Infos.Rate); 091. } 092. m_Infos.CountReplay++; 093. } 094. //+------------------------------------------------------------------+ 095. void AdjustViewDetails(void) 096. { 097. MqlRates rate[1]; 098. 099. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_ASK_LINE, GetInfoTicks().ModePlot == PRICE_FOREX); 100. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_BID_LINE, GetInfoTicks().ModePlot == PRICE_FOREX); 101. ChartSetInteger(m_Infos.IdReplay, CHART_SHOW_LAST_LINE, GetInfoTicks().ModePlot == PRICE_EXCHANGE); 102. m_Infos.PointsPerTick = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 103. CopyRates(def_SymbolReplay, PERIOD_M1, 0, 1, rate); 104. if ((m_Infos.CountReplay == 0) && (GetInfoTicks().ModePlot == PRICE_EXCHANGE)) 105. for (; GetInfoTicks().Info[m_Infos.CountReplay].volume_real == 0; m_Infos.CountReplay++); 106. if (rate[0].close > 0) 107. { 108. if (GetInfoTicks().ModePlot == PRICE_EXCHANGE) 109. m_Infos.tick[0].last = rate[0].close; 110. else 111. { 112. m_Infos.tick[0].bid = rate[0].close; 113. m_Infos.tick[0].ask = rate[0].close + (rate[0].spread * m_Infos.PointsPerTick); 114. } 115. m_Infos.tick[0].time = rate[0].time; 116. m_Infos.tick[0].time_msc = rate[0].time * 1000; 117. }else 118. m_Infos.tick[0] = GetInfoTicks().Info[m_Infos.CountReplay]; 119. CustomTicksAdd(def_SymbolReplay, m_Infos.tick); 120. } 121. //+------------------------------------------------------------------+ 122. public : 123. //+------------------------------------------------------------------+ 124. C_Replay() 125. :C_ConfigService() 126. { 127. Print("************** Market Replay Service **************"); 128. srand(GetTickCount()); 129. SymbolSelect(def_SymbolReplay, false); 130. CustomSymbolDelete(def_SymbolReplay); 131. CustomSymbolCreate(def_SymbolReplay, StringFormat("Custom\\%s", def_SymbolReplay)); 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. SymbolSelect(def_SymbolReplay, true); 138. m_Infos.CountReplay = 0; 139. m_IndControl.Handle = INVALID_HANDLE; 140. m_IndControl.Mode = 0; 141. m_IndControl.Position = 0; 142. } 143. //+------------------------------------------------------------------+ 144. ~C_Replay() 145. { 146. IndicatorRelease(m_IndControl.Handle); 147. SweepAndCloseChart(); 148. SymbolSelect(def_SymbolReplay, false); 149. CustomSymbolDelete(def_SymbolReplay); 150. Print("Finished replay service..."); 151. } 152. //+------------------------------------------------------------------+ 153. bool OpenChartReplay(const ENUM_TIMEFRAMES arg1, const string szNameTemplate) 154. { 155. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) 156. return MsgError("Asset configuration is not complete, it remains to declare the size of the ticket."); 157. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) 158. return MsgError("Asset configuration is not complete, need to declare the ticket value."); 159. if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) 160. return MsgError("Asset configuration not complete, need to declare the minimum volume."); 161. SweepAndCloseChart(); 162. m_Infos.IdReplay = ChartOpen(def_SymbolReplay, arg1); 163. if (!ChartApplyTemplate(m_Infos.IdReplay, szNameTemplate + ".tpl")) 164. Print("Failed apply template: ", szNameTemplate, ".tpl Using template default.tpl"); 165. else 166. Print("Apply template: ", szNameTemplate, ".tpl"); 167. 168. return true; 169. } 170. //+------------------------------------------------------------------+ 171. bool InitBaseControl(const ushort wait = 1000) 172. { 173. Print("Waiting for Mouse Indicator..."); 174. Sleep(wait); 175. while ((def_CheckLoopService) && (ChartIndicatorGet(m_Infos.IdReplay, 0, "Indicator Mouse Study") == INVALID_HANDLE)) Sleep(200); 176. if (def_CheckLoopService) 177. { 178. AdjustViewDetails(); 179. Print("Waiting for Control Indicator..."); 180. if ((m_IndControl.Handle = iCustom(ChartSymbol(m_Infos.IdReplay), ChartPeriod(m_Infos.IdReplay), "::" + def_IndicatorControl, m_Infos.IdReplay)) == INVALID_HANDLE) return false; 181. ChartIndicatorAdd(m_Infos.IdReplay, 0, m_IndControl.Handle); 182. m_IndControl.Position = 0; 183. m_IndControl.Mode = SHORT_MIN; 184. UpdateIndicatorControl(); 185. } 186. 187. return def_CheckLoopService; 188. } 189. //+------------------------------------------------------------------+ 190. bool LoopEventOnTime(void) 191. { 192. int iPos; 193. 194. while ((def_CheckLoopService) && (m_IndControl.Mode != SHORT_MAX)) 195. { 196. UpdateIndicatorControl(); 197. Sleep(200); 198. } 199. iPos = 0; 200. while ((m_Infos.CountReplay < GetInfoTicks().nTicks) && (def_CheckLoopService)) 201. { 202. if (m_IndControl.Mode == SHORT_MIN) return true; 203. iPos += (int)(m_Infos.CountReplay < (GetInfoTicks().nTicks - 1) ? GetInfoTicks().Info[m_Infos.CountReplay + 1].time_msc - GetInfoTicks().Info[m_Infos.CountReplay].time_msc : 0); 204. CreateBarInReplay(true); 205. while ((iPos > 200) && (def_CheckLoopService)) 206. { 207. Sleep(195); 208. iPos -= 200; 209. m_IndControl.Position = (ushort)((m_Infos.CountReplay * def_MaxPosSlider) / GetInfoTicks().nTicks); 210. UpdateIndicatorControl(); 211. } 212. } 213. 214. return (m_Infos.CountReplay == GetInfoTicks().nTicks); 215. } 216. //+------------------------------------------------------------------+ 217. }; 218. //+------------------------------------------------------------------+ 219. #undef macroRemoveSec 220. #undef def_SymbolReplay 221. #undef def_CheckLoopService 222. //+------------------------------------------------------------------+
C_Replay.mqh 文件的源代码
由于此代码经历了一些重要的修改,我还将解释所做的工作以及这些修改代表什么。这是为了确保清楚地理解系统的运行。这一点尤其重要,因为这个版本包含一些我将在本文末尾解决的问题。
首先,我决定将信息组织成结构,因为这使得将它们分开更简单、更合乎逻辑。注意第 20 行,我在此声明了用于访问控制指标模块的句柄。这避免了仅仅为了获取将在整个服务生命周期内持续使用的值而进行的大量调用。因此,以任何其他方式访问此值都是没有意义的,特别是因为我们将对控制模块执行许多调用。在这些调用之间节省的任何时间都将非常有益。您很快就会明白为什么这很重要。
第 33 行和第 54 行之间的过程没有发生重大变化,除了删除了用于获取和释放访问命令模块缓冲区的句柄的代码。除此之外,仅进行了一项修改,与 C_Replay.mqh 中更新的片段直接相关。
请注意第 50 行和第 51 行,我在其中分配了 C_Replay 类中的自定义事件处理函数所需的相同信息。这可以确保服务传递的数据被控制模块正确接收。
下一个感兴趣的过程位于第 67 行和第 93 行之间。此过程负责生成并向自定义交易品种提交柱形和分时报价数据。换句话说,这就是我们更新 MetaTrader 5 的信息以显示在图表上的地方。此过程与回放/模拟服务的上一版本中的过程几乎相同。当时,我们仍在使用终端全局变量在应用程序之间交换消息。但是,由于修复 C_FileTicks 类中的数据泄漏问题,我们不得不在这里调整几点。请注意如何访问 C_FileTicks 的数据,第 75 行就是一个例子。尽管这种方法有效,但有一个重要的事情需要考虑:每次调用都会带来轻微的延迟。我们稍后会讨论这一点。
还需要添加另一个过程,该过程存在于服务的旧版本中。这位于第 95 行和第 120 行之间,其目的是初始化价格或报价线的显示。如果您还记得上一篇文章中出现的 C_Replay.mqh,则没有这个过程。这就是为什么当回放/模拟器指示 MetaTrader 5 打开自定义交易品种图表时,看不到价格线的原因。此过程修复了该问题。然而,与您想象的不同,这个过程只被调用一次。在“播放”阶段,第 67 行的 CreateBarInReplay 过程将负责保持此显示更新。具体来说,此任务将在该程序的第 89 行中执行。
继续前进,我们到达类构造函数,这里仅添加了初始化行。这些行位于第 138 行和第 141 行之间。其他都没有什么修改。然而,在类析构函数中,在 146 处出现了新的一行,我们在这里释放了句柄。尽管可能并不重要(因为关闭图表无论如何都会终止服务),但我还是希望确保明确告知 MetaTrader 5 不再需要该句柄。因此添加了此行代码。
由于我们正在释放句柄,所以我们必须在某处获取它。事实上,这发生在第 171 行和第 188 行之间的过程中。请注意,在第 178 行,我们现在调用该过程来初始化价格/报价线,而这以前是不存在的。然后在第 180 行,我们捕获访问控制指标缓冲区所需的句柄。过程的其余部分保持不变。
最后,我们到达该类的最后一个函数,位于第 190 行至第 215 行。这可能是最麻烦的。这是因为,与可以在较安静的时间执行的其他过程或函数不同(在合理范围内,花费一两秒钟的过程可能是可以接受的),但此函数不允许这种灵活性。实时执行在这里至关重要,任何延迟或不准确都可能对服务产生严重影响。
过去,当我们使用终端全局变量时,同样的函数会给正常工作带来很大的麻烦。我们必须采取严格措施才能使其发挥作用。现在我们采用了不同的方法,我们以前的“头痛问题”,即 LoopEventOnTime,又再次困扰着我们。
但在深入探讨这一挑战之前,让我们先分析一下我们现在正在做的事情。
从 194 行到 198 行,有一个循环。这不会造成任何问题,因为其目的是等待用户与控制模块(通过鼠标模块)的交互来触发播放模式。如果用户关闭图表或停止服务,循环将退出,这要归功于 def_CheckLoopService 检查。我们在第 9 行声明了这个定义,您可以看到我们始终检查图表是否已关闭或服务是否已终止。一般来说,这个循环不会给我们带来任何问题。
然而,第 200 行和第 212 行之间的下一个循环却并非如此。您可能认为这是新增的内容,这就是我们遇到问题的原因。但它基于我们用来处理终端全局变量的旧循环。然而,与早期版本相比,这个版本已经简化了。然而,它仍然缺少一些关键元素来完全复制以前的功能。
这个循环包含一个内部循环,正如我之前所解释的,这个内部结构至关重要。在特定情况下,如果没有此功能,系统就会冻结,无限期地等待“释放”服务。您可以参考本系列早期与回放/模拟器系统构建相关的文章,以获得更深入的解释。
乍一看,该代码似乎很完美,除了缺少调整回放/模拟起点的逻辑。乐观是好的,但在评估结果之前,我想展示服务代码。尽管它自上一篇文章以来没有变化,但对于了解真正的问题所在至关重要。
01. //+------------------------------------------------------------------+ 02. #property service 03. #property icon "/Images/Market Replay/Icons/Replay - Device.ico" 04. #property copyright "Daniel Jose" 05. #property version "1.60" 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/12086" 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. //+------------------------------------------------------------------+
服务源代码
如您所见,代码中没有什么特别重大的变化。
结论
为了确保您不需要自己编译和测试回放/模拟服务,我提供了视频演示。请仔细观察,因为您会注意到服务的性能急剧下降。为什么会发生这种情况?原因主要在于现在代码中的函数调用更多,而变量使用更少。但这并不是唯一因素,还有其他方面也会影响性能。由于解释、分析和测试这些因素需要时间和耐心,我将在以后的文章中介绍这些细节。
尽管如此,主要焦点还是放在 LoopEventOnTime 函数内部的循环上。不管怎样,有必要优化这个负责生成和启动回放或模拟的循环,以使其更有效地工作。正如本文前面所提到的,在高性能最为关键的时刻,有几个方面正在破坏服务性能。在使用全局终端变量的阶段,性能是可以接受的。然而,您在视频中看到的内容使得新模型的任何实际用途都不可接受。
这表明当前系统不可靠。但作为程序员,我们经常需要解决远远超出简单计算逻辑的问题,因为我们还必须解决性能问题。总体而言,新的基于类的系统使代码更加稳定和安全。我们现在真正的挑战是在使用基于事件和缓冲区的通信而不是全局变量的同时,恢复之前的性能水平(或至少接近它)。所以,请为下一篇文章做好准备,因为它将会非常有趣。
演示视频
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12086
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。



