Разработка системы репликации (Часть 60): Нажатие кнопки воспроизведения в сервисе (I)
Введение
В предыдущей статье "Разработка системы ретрансляторов (часть 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.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, которая находилась в части protected.
Вы понимаете, насколько это было опасно? Несмотря на то, что код работал достаточно стабильно, он не был действительно безопасен для внесения серьезных изменений. Это была моя ошибка, которая сохранилась по сей день. Однако, поскольку нам нужно, чтобы всё работало очень конкретным образом, не стоит допускать утечки информации и ее изменения в любой точке кода.
Благодаря данным изменениям наши классы, отвечающие за поддержание тиков, баров и настройку символа для использования в репликации/моделировании, теперь полностью безопасны, без утечек информации. Другими словами, мы окончательно закрыли эту часть, связанную с загрузкой и моделированием данных, а также с конфигурацией символа, который будет использоваться сервисом репликации/моделирования. Теперь мы можем сосредоточиться на том, чтобы снова запустить сервис и позволить пользователю установить репликацию/моделирование для запуска в определенный момент, а также запускать и приостанавливать моделирование.
Данные функции уже существовали до многочисленных модификаций. Однако теперь мы будем реализовывать их гораздо более безопасно, просто и стабильно. Сюда же относится взаимодействие между элементами, с которыми нам предстоит работать в ближайшее время.
Мы даем возможность пользователю запускать и приостанавливать репликацию/моделирование
В этой теме мы рассмотрим, как сделать сервис активным, когда пользователь нажимает на кнопки воспроизведения и паузы. Таким образом, можно проводить как репликацию, так и моделирование.
Однако прежде, чем я продолжу, позвольте показать вам два небольших обновления, которые были внесены в модуль индикатора управления. Данные обновления призваны предотвратить довольно неприятную проблему, которая иногда возникает во время работы системы. Поскольку это случайное явление, его трудно доказать, так как мы можем запускать систему много раз, и ничего странного не произойдет. Однако время от времени случается нечто необычное. Чтобы избежать неудобств, вызванных этими случайными сбоями, мы внесли некоторые изменения в код модуля управления.
Поэтому оно поддерживает в заголовочном файле 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, что приводит к нестандартному поведению. Одним из таких поведений является появление неустойчивых значений в первом индексе массива. Первоначально, когда это происходило, зафиксировалась ошибка, и управляющий модуль удалялся с графика, вызывая тем самым сбой в сервисе репликации/моделирования. Другими словами, сервис был остановлен из-за чего-то случайного, произошедшего во время выполнения, что привело к появлению неправильного значения.
В новой реализации при возникновении неисправности модуль индикатора управления немедленно переходит в режим паузы. Это гораздо лучше, чем полный отказ системы репликации/моделирования. Кроме того, есть еще одна важная деталь. Если вы не заметили, в строке 181 есть небольшая проверка. Это гарантирует, что даже если появятся неустойчивые значения, мы будем использовать их только в том случае, если первые два байта из восьми, присутствующих в значении double, содержат что-то определенное. Если данные значения отсутствуют, то событие просто игнорируется модулем индикатора управления. Этот вид проверки гарантирует целостность данных или, по крайней мере, подтверждает, что данные получены из определенного источника: сервиса репликации/моделирования.
После этих двух модификаций потребуется еще одна, но на этот раз в исходном коде индикатора. Данную модификацию можно увидеть в приведенном ниже отрывке.
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 открыть график пользовательского символа, линии котировок не были видны. Данная процедура исправляет именно этот недостаток. Однако, в отличие от того, что можно себе представить, данная процедура выполняется только один раз. На этапе, когда сервис находится в режиме воспроизведения, процедура CreateBarInReplay, расположенная в строке 67, будет отвечать за обновление этого представления. В частности, именно в строке 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
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования