
Developing a Replay System (Part 60): Playing the Service (I)
Introduction
In the previous article Developing a Replay System (Part 59): A New Future, I presented and explained some of the changes made to the control indicators and mouse indicator modules. Although those updates provide us with several future use possibilities, particularly for the mouse indicator module, it still contains a minor flaw. However, this flaw does not affect us at this stage. This is because, for now, our focus will be on addressing some remaining issues related to encapsulation within the replay/simulator service class code.
Although we have already made several adjustments, as discussed in previous articles, they were not sufficient to fully resolve the encapsulation issue. There are still elements leaking from within the classes, and we need to fix this immediately. Otherwise, it will lead to serious problems soon. In addition to addressing the encapsulation problem, which causes internal elements that should remain hidden the be accessible, we also need to refactor certain aspects of the code to ensure more appropriate access to some variables and information. All of this will be discussed in this article.
Starting to Fix Things
Very well. The first issue we will tackle is the encapsulation problem in the C_FilesTicks class. This class is located in the file C_FilesTicks.mqh, and currently, it exposes information that should not be directly accessible, at least, not in the way it currently is.
To fix this leakage issue, we will apply a relatively simple solution. Below, you can see the updated and complete version of the C_FilesTicks class.
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. //+------------------------------------------------------------------+
Source code of the C_FilesTicks.mqh file
You are very likely not noticing any major difference, except, of course, for something that is crossed out in line 21.
That crossed-out element represents the very data leak we were addressing. In other words, even though everything might have appeared safe and fully functional, when the time came to handle ticks that had been loaded or simulated, we would have faced a serious security issue. The reason for this is that any class could modify the data in that structure. Note that, outside the scope of classes, such a change could not be made, since the structure is within a protected clause. In a previous article, I explained how these clauses determine access levels to class members.
However, by removing that variable and allowing only the use of the structure itself, we achieve a much more acceptable level of security and encapsulation. Yet, by making this change, we have to make another decision. The decision is as follows: either declare a new variable, this time as private, to maintain the structure internally and add a function to access this variable. Or declare that same variable elsewhere and pass it as an argument to any functions that need to access its contents.
It may sound strange, but every case has its own context, consequences, and possibilities. Given the nature of what the project, I decided to follow the first approach, that is, we will create a private variable, which you can see on line 58. Apart from this, no other significant changes to the code were necessary. However, by removing this variable from public access (yes, we used it in other places, not only reading but also modifying its data, as will soon be demonstrated) we now need to ensure proper initialization of the structure. For this reason, you will notice a constructor appearing in line 118. Note that it is quite simple, performing a task that was previously done elsewhere. There was the leakage caused by the encapsulation flaw we have just fixed within the C_FilesTicks class.
Additionally, as mentioned earlier, we also need to provide a way to access this variable in order to read its data. This is accomplished by the function shown in line 199. Please pay close attention to this: we will be able to read the data inside the structure, but not write to it. This is due to the way the function is declared - the returned value must be treated as constant. The use of this approach has been explained in some of my previous articles in this same series. If you have any doubts, feel free to look back at those for clarification.
Now that we have solved one of the issues, we need to address another one, which is somewhat related to what we have just fixed. This second issue is found in the C_ConfigService class, located in the C_ConfigService.mqh file. This header file required some adjustments. Primarily because the data leak from C_FilesTicks no longer exists. Also, some elements declared in C_ConfigService will be removed since they are no longer necessary within this class and will instead be used elsewhere.
So, the updated code for the C_ConfigService.mqh file will be as follows:
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. //+------------------------------------------------------------------+
Source code of the C_ConfigService.mqh file
Several changes have been made, some more subtle, others more evident, such as the crossed-out sections. But you should now notice that we no longer access tick data directly. The new way to handle this can be seen on line 34. Notice that we can now access this data in a much more refined and, most importantly, secure way, since it is no longer possible to alter data configured in the script file or contained in the tick or bar data files. So, we can definitely say that we now have a much safer and more stable system, one that can be programmed with considerably greater ease and far fewer chances of critical errors.
In addition to that, I would like to draw your attention, dear reader, to another point. Pay attention to lines 23 to 27: the private global variables have been removed. Some of them have been moved inside the structure that begins in line 15. But there is a reason behind this change regarding global and private variables. The reason is that I do not want (because we do not need) certain elements to be part of the general configuration for the replay/simulation service.
Due to this change seen between lines 23 and 27, several parts of the code had to be adjusted. However, these do not deserve special attention since they are simple modifications, easy to understand. Still, please pay close attention to the class constructor, which can be found in line 180. Note that this constructor's code has been slightly modified. However, this is not the main point that makes this header file worth discussing. The key point lies between lines 207 and 209, which are crossed out.
Before fixing the encapsulation issue in the C_FileTicks class, the m_Ticks variable, which was in a protected part, was actually instantiated, or better said, initialized here, within those lines.
Notice how risky this situation was. Although the code was running quite stably, it was not truly secure, especially if we intended to introduce major modifications later on. This was a mistake on my part that persisted until now. But since I need things to work in a very specific way, it is not acceptable to allow information to leak and be modified from anywhere in the code.
With these corrections, our classes responsible for maintaining tick data, bar data, and configuring the asset to be used in replay/simulation are now fully secured, without any data leakage. In other words, we have now definitively closed this part of the system related to data loading, simulation, and asset configuration, which will be used by the replay/simulator service. From here, we can focus on getting the service to operate properly: starting, pausing, and also allowing the user to set the replay/simulation to start from a specific point.
These functionalities were already present before all these modifications. However, from now on, we will implement them in a much safer, simpler, and more stable way. We will also ensure better interaction between the components we will be working on in the near future.
Allowing the User to Play and Pause the Replay/Simulation
In this section, I will show how to finally make the service responsive to the user's click on the play and pause buttons. This will enable both replay and simulation functionalities.
But before that, let me show you, dear reader, two small updates that were made to the control indicator module. These updates aim to prevent an unpleasant issue that sometimes occurs when the system is running. Since this is a random issue, it can be difficult to reproduce, as you might run the system multiple times without anything strange happening. Yet, every now and then, something odd occurs. To avoid all the frustration caused by such random crashes, I made a few adjustments to the control module's code.
So, in the header file C_Replay.mqh, we update the functions and procedures, which are shown in the fragments below. Let's start with the following point:
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. //+------------------------------------------------------------------+
Code from C_Controls.mqh file
We need to add line 77 to the original code. This way, the control indicator module will always know what to do and will show the slider bar if the play button is visible. That is, when we are in pause mode, the slider bar will be displayed on the chart. Sometimes this did not happen, but with this update the bar will always be displayed.
One more change needs to be made. This should be done in the code part shown below:
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:
Code from C_Controls.mqh file
In this section, you will need to modify the original code and insert what is found between lines 180 and 185. The purpose of this change is to make the control indicator module more resilient to a particularly annoying issue that sometimes occurs. This issue involves a custom event that triggers the execution on line 179, which occasionally leads to wrong behaviors. One such behavior is the arrival of erroneous values in index 1 of the array. Originally, when this happened, an error would be reported, and the control module would be removed from the chart, causing the entire replay/simulation service to crash. In other words, the service would terminate due to a random, runtime failure that caused invalid data to appear.
In the new implementation, when a fault occurs, the control indicator module immediately goes into pause mode. This is much better than a complete failure of the replay/simulation system. Besides, there is one more important detail. If you haven't noticed, there's a little check in line 181. This ensures that even if invalid values appear, we will only use them if the first two bytes of the eight present in the double value contain something specific. If these values are not there, the event is simply ignored by the control indicator module. This type of validation ensures the integrity of the data, or at least confirms that the data came from a specific source: the replay/simulation service.
Once you finish these two modifications, you'll need to make one more in the indicator's source code. You can see this final required update in the fragment that follows.
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. //+------------------------------------------------------------------+
Fragment of the control indicator source code
The crossed-out lines should be removed from the source code. Therefore, delete the compiled executable of the control module so that the service is forced to recompile it when you attempt to compile the service again. Alternatively, if you prefer, manually recompile the control module to ensure that the latest updated version is properly included as a resource in the replay/simulation service.
Now, we can move on to the replay/simulation service code itself. We will proceed with some changes to the code that existed up until now, following the last update made to the C_Replay.mqh header file. These changes were implemented in a previous article. At that time, the only functional aspect was the display of data loaded by the system, alongside the control and mouse modules. However, no interaction was possible beyond this, meaning we could not play or pause the replay/simulation to display new data dynamically.
But here, we will change that reality. So let's take a look at the updated C_Replay.mqh header file. The file is presented in full below.
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. //+------------------------------------------------------------------+
Source code of the C_Replay.mqh file
Since this code underwent some important changes, I will also explain what was done and what these changes represent. This is needed to ensure that the system's operation is clearly understood. This is especially important because this version contains some issues that I will address at the end of this article.
To start, I decided to organize the information into structures, as this makes it simpler and more logical to separate them. Notice line 20, where I declare the handle to access the control indicator module. This avoids a large number of calls just to fetch a value that will be used continuously throughout the service lifetime. Thus, it makes no sense to access this value any other way, especially since we will perform many calls to the control module. Any time savings between these calls will be quite beneficial. You'll soon understand why this is important.
The procedure between lines 33 and 54 has not undergone major changes, except for the removal of code that used to obtain and release the handle to access the command module's buffer. Aside from that, only one modification was made, directly related to the fragment updated in C_Replay.mqh.
Notice lines 50 and 51, where I assign the same information expected by the custom event handler within the C_Replay class. This ensures that the data passed by the service is properly received by the control module.
The next procedure of interest lies between lines 67 and 93. This procedure is responsible for generating and submitting bar and tick data to the custom symbol. In other words, this is where we update information for MetaTrader 5 to display on the chart. This procedure is almost identical to what existed in the last version of the replay/simulation service. At that time, we were still using terminal global variables to exchange messages between applications. However, due to fixing the data leakage issue in the C_FileTicks class, we had to adjust several points here. Notice how data from C_FileTicks is accessed. An example of this is in line 75. Although this works, there is one important thing to consider: each call introduces slight delays. We will discuss this point later.
It was also necessary to add another procedure, which existed in older versions of the service. This can be found between lines 95 and 120, and its purpose is to initialize the price or quote lines display. If you recall from the last article where C_Replay.mqh appeared, this procedure was absent. This is why when the replay/simulator instructed MetaTrader 5 to open the custom symbol chart, no price lines were visible. This procedure fixes that issue. However, unlike what you may think, this procedure is called only once. During the "play" phase, the procedure CreateBarInReplay, found on line 67, will be responsible for keeping this display updated. Specifically, this task will be performed in line 89 within that procedure.
Moving forward, we reach the class constructor. Here, only the initialization lines were added. These lines are located between lines 138 and 141. Nothing else was changed. However, in the class destructor, a new line appears at 146, where we release the handle. Although perhaps not critical (since closing the chart would terminate the service anyway), I prefer to ensure that MetaTrader 5 is explicitly informed that the handle is no longer needed. For this reason this line was added.
Since we are releasing the handle, we must have acquired it somewhere. Indeed, this happens in the procedure between lines 171 and 188. Note that in line 178, we now call the procedure to initialize the price/quote lines, which was previously absent. Then in line 180, we capture the handle needed to access the control indicator's buffer. The rest of the procedure remains unchanged.
Finally, we arrive at the last function of this class, located on lines 190 to 215. This may be the most troublesome of all. This is because, unlike other procedures or functions that can be performed at quieter times - a procedure that takes one or two seconds may be acceptable within reason - this function does not allow for such flexibility. Real-time execution is critical here, and any delays or inaccuracies can significantly impact the service.
In the past, when we used terminal global variables, this same function caused significant headaches to work properly. We had to take drastic measures to make it functional. Now that we are using a different approach, our old "headache", known as LoopEventOnTime, has returned to haunt us once again.
But before delving into this challenge, let's analyze what we are doing now.
From lines 194 to 198, there is a loop. This does not pose any issues, as its purpose is to wait for user interaction with the control module (via the mouse module) to trigger play mode. If the user closes the chart or stops the service, the loop will exit, thanks to the def_CheckLoopService check. We declared this definition in line 9, where you can see that we always check if the chart has been closed or the service has terminated. In general, this loop does not cause us any problems.
However, the same cannot be said about the next loop between lines 200 and 212. You might think this is new and that's why we're having problems. But it is based on the old loop that we used to work with terminal global variables. However, this one is simplified compared to that earlier version. Yet, it still lacks some critical elements to fully replicate prior functionality.
This loop contains an internal loop, and as I explained before, this internal structure is crucial. In specific situations, the system would freeze without this, waiting indefinitely for a tick to "free" the service. You can refer back to the early articles in this series related to the construction of the replay/simulator system for a deeper explanation.
At a glance, this code might seem perfect, except for the missing logic to adjust the starting point of the replay/simulation. Optimism is good, but before evaluating results, I want to show the service code. Although it is unchanged since the last article, it's essential for understanding where the real issues are.
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. //+------------------------------------------------------------------+
Source code of the service
As you can see, nothing particularly significant has changed in the code.
Conclusion
To ensure you do not need to compile and test the replay/simulation service yourself, I am including a video demonstration. Please watch it carefully, as you will notice a drastic drop in the service's performance. Why does this happen? The reason lies primarily in the fact that there are now many more function calls and fewer variable usages within the code. However, that is not the only factor. There are other aspects that also affect performance. Since explaining, analyzing, and testing these factors requires time and patience, I will leave those details for a future article.
Nevertheless, the main focus will be on the loop inside the LoopEventOnTime function. One way or another, it is necessary to optimize this loop, which is responsible for generating and launching the replay or simulation, so that it works more efficiently. As mentioned earlier in this article, several aspects are undermining the service performance precisely at the moment when high performance is most critical. During the phase when global terminal variables were used, the performance was acceptable. What you will see in the video, however, makes any practical use of the new model unacceptable.
This suggests that the current system is not reliable. But as programmers, we often have to solve problems that go far beyond simple computation logic as we must also address performance issues. Overall, the new class-based system has made the code much more stable and secure. Our real challenge now is to restore the previous level of performance (or at least something close to it) while using event and buffer-based communication, instead of global variables. So, get ready for the next article because it will be quite interesting.
Demo video
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/12086






- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use