
开发回放系统(第 63 部分):玩转服务(四)
概述
在上一篇文章,开发回放系统(第 62 部分):玩转服务(三)中,我们讨论了如何将分时报价视为真实存在的东西。这种超出量并不是我们主要关心的问题;然而,它可能会使应用程序实现正确计时的能力变得复杂,从而确保一分钟的柱形在指定的一分钟窗口内完全构建。
尽管在上一篇文章中取得了进展,但我在最后提到,由于在真实数据中实现模拟,出现了某些错误。我想再次强调,这些错误在纯模拟中并不存在。但当模拟结果与真实数据相结合时,就难免会出现这样的误差。这不一定是个问题,因为它允许我们进行测试,并确定正在开发的系统是否适合维护和改进。通常,我们开发一些东西只是为了测试它的可行性。如果基本概念被证明不可行,我们可以放弃它,尽量减少修复和其他调整所花费的时间。
在同一篇文章中,我指出了这些错误及其原因。然而,鉴于已经提供的大量信息(亲爱的读者,在介绍任何进一步的进展之前,你必须彻底理解这些信息),我在某个时刻结束了这篇文章。然而,在这里,我们将解决和纠正这些错误,不是因为代码更改,而是因为我们正在实现一些如果系统的定时机制没有过载就不必要的东西。这种超负荷还不明显,因为我们仍处于实现所需一切的早期阶段。
由于这些问题是相互关联的,我们可以从解决其中任何一个问题开始。然而,由于对某些细节一丝不苟,我将首先解决使用真实数据时需要模拟的最小分时报价数的问题。
必须生成的最小分时报价数是多少?
回答这个问题并不像看上去那么简单。这是因为,在修改启用回放或模拟的应用程序时,必须始终记住一个关键点。在回放模式下,可以通过强制模拟类使用最少数量的分时报价来解决此问题。请记住,使用回放功能时,我们正在处理真实数据。然而,当模拟分时报价来控制一分钟窗口内的时间时,方法是不同的。当模拟基于获得的报价数据时,这适用。出现此问题的原因是,目前用户无法调整可利用的最大分时报价数。
我计划在配置文件中引入此选项,允许用户控制此值。原因很简单:如果用户的工作站可以生成比应用程序允许的更多的分时报价,他们可以调整配置文件中的值,以实现更准确地反映现实的回放体验。相反,如果用户的工作站无法处理特定数量的分时报价,他们可以将数量减少到较低的值,确保操作更平稳、压力更小。
使这个问题复杂化的另一个因素是,这不仅仅是一个指定数字的问题。这是因为可以在外部模拟数据,保存数据,并像使用真实的数据库一样使用它。对于回放系统来说,这是最糟糕的情况。不过,目前我们不会讨论这个方面。我们当前的重点是纠正应用程序用户的错误配置。
回到重点:所需的最小分时报价数是多少?答案取决于不同情况。为了理解这一点,请考虑以下内容。如果开盘价、收盘价、最高价和最低价都相同,则单个分时报价就足够了。然而,这是最简单的情况。为了考虑其他情况,我们必须定义一些条件。首先,开盘和收盘必须在最大和最小范围内进行。因此,我们得到以下场景:
- 如果开盘价等于其中一个限价,而收盘价等于另一个限价,则需要两个分时报价。
- 如果开盘价或收盘价等于其中一个限价,而相反的限价超出这些值,则需要三个分时报价。
- 如果所有四个 OHLC(开盘价、最高价、最低价、收盘价)值都不同,或者开盘价和收盘价相同但与条形图的最高价或最低价不一致,则需要四个分时报价。
这为确定模拟所需的最小分时报价数提供了基础。到目前为止,模拟运行顺利。但是,我想允许用户配置一个限制,以确保他们的工作站能够以最佳方式运行回放/模拟器应用程序。
也许这种解释在文字形式下看起来很复杂,但在代码中会变得清晰得多。因此,我不会复制整个 C_Simulation.mqh 文件,而只会包含下面的相关代码片段。这是为解决现有问题而修改的部分。
128. //+------------------------------------------------------------------+ 129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume) 130. { 131. int i0, i1, i2, dm = 0; 132. bool b0; 133. 134. m_Marks.iMax = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume)); 135. dm = (dm == 0 ? ((rate.open == rate.high == rate.low == rate.close) ? 1 : dm) : dm); 136. dm = (dm == 0 ? (((rate.open == rate.high) && (rate.low == rate.close)) || ((rate.open == rate.low) && (rate.close == rate.high)) ? 2 : dm) : dm); 137. if ((dm == 0 ? ((rate.open == rate.close == rate.high) || (rate.open == rate.close == rate.low) ? 3 : 4) : dm) == 0) return -1; 138. m_Marks.iMax = (MaxTickVolume <= dm ? dm : MaxTickVolume); 139. m_Marks.iMax = (((int)rate.tick_volume > m_Marks.iMax) || ((int)rate.tick_volume < dm) ? m_Marks.iMax : (int)rate.tick_volume - 1); 140. m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); 141. m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); 142. Simulation_Time(rate, tick); 143. MountPrice(0, rate.open, rate.spread, tick); 144. if (m_Marks.iMax > 10) 145. { 146. i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2)); 147. i1 = m_Marks.iMax - i0; 148. i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0); 149. i2 = (i2 == 0 ? 1 : i2); 150. b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low)); 151. i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2); 152. RandomWalk(i0, i1, (m_IsPriceBID ? tick[i0].bid : tick[i0].last), (b0 ? rate.low : rate.high), rate.high, rate.low, rate.spread, tick, 1, i2); 153. RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2); 154. m_Marks.bHigh = m_Marks.bLow = true; 155. 156. }else Random_Price(rate, tick); 157. if (!m_IsPriceBID) DistributeVolumeReal(rate, tick); 158. if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); 159. if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); 160. MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); 161. CorretTime(tick); 162. 163. return m_Marks.iMax; 164. } 165. //+------------------------------------------------------------------+
C_Simulation.mqh 文件片段
此片段中的行号精确地指示了您需要修改上一篇文章中完整代码的位置。请注意,在第 131 行,我添加了一个新变量,并在声明后立即对其进行了初始化。此外,请注意,必须删除原来的第 134 行,并用三行新代码来替换。这三行代码实现了我最近提到的要点,确保创建了所需的最小分时报价数。
然而,您必须密切注意一个关键细节。在第 138 行,在三元运算符测试期间,将 MaxTickVolume 的值与最近调整的值进行比较,以确定最小分时报价数。如果 MaxTickVolume 小于此调整值,则将使用调整值,而不考虑任何其他数据。此外,在第 139 行,我们再次验证了相同的条件。如果 rate.tick_volume 中的值也低于调整后的值,则调整后的值将优先。
现在,请记住测试此模拟函数的返回值,特别是如果您打算将其用于其他目的。这很重要,因为如果在调整最小分时报价数时发生错误,函数将在第 137 行立即返回。因此,在未首先验证函数的返回值之前,不要尝试使用返回数组中的值,因为数组值可能无效。
与上一篇文章中介绍的代码相比,这里进行了细微的修改。然而,在本文的最后,我将重新审视这个片段,以确保对这一变化的解释完全有意义。
这样,我们就解决了第一个问题。现在,我们进入下一个主题,解决第二个问题。
修复分时报价调整问题
第二个问题需要付出更多努力来解决。然而,它更劳动密集的事实并不一定意味着它更难 — 只是它需要更多的工作。所以我们要做的是:让我们回顾一下上一篇文章中负责处理移动和调整分时报价的代码片段,以确保它们可用于图表绘制。片段如下所示。
01. //+------------------------------------------------------------------+ 02. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume) 03. { 04. int MemNRates, 05. MemNTicks, 06. nDigits, 07. nShift; 08. datetime dtRet = TimeCurrent(); 09. MqlRates RatesLocal[], 10. rate; 11. MqlTick TicksLocal[]; 12. bool bNew; 13. 14. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 15. nShift = MemNTicks = m_Ticks.nTicks; 16. if (!Open(szFileNameCSV)) return 0; 17. if (!ReadAllsTicks()) return 0; 18. rate.time = 0; 19. nDigits = SetSymbolInfos(); 20. ArrayResize(TicksLocal, def_MaxSizeArray); 21. m_Ticks.bTickReal = true; 22. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 23. { 24. if (!BuildBar1Min(c0, rate, bNew)) continue; 25. if (bNew) 26. { 27. if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume) 28. { 29. nShift = MemShift; 30. C_Simulation *pSimulator = new C_Simulation(nDigits); 31. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) < 0) return 0; 32. ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 33. nShift += c1; 34. delete pSimulator; 35. } 36. MemShift = nShift; 37. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 38. }; 39. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 40. } 41. ArrayFree(TicksLocal); 42. if (!ToReplay) 43. { 44. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 45. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 46. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 47. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 48. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 49. m_Ticks.nTicks = MemNTicks; 50. ArrayFree(RatesLocal); 51. }else m_Ticks.nTicks = nShift; 52. 53. return dtRet; 54. }; 55. //+------------------------------------------------------------------+
C_FileTicks.mqh 文件片段
请注意,此代码包含我们需要在本文中修复的错误。现在,请密切关注下一个片段,并将其与上一个片段进行比较。
01. //+------------------------------------------------------------------+ 02. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume) 03. { 04. int MemNRates, 05. MemNTicks, 06. nDigits, 07. nShift; 08. datetime dtRet = TimeCurrent(); 09. MqlRates RatesLocal[], 10. rate; 11. MqlTick TicksLocal[]; 12. bool bNew; 13. 14. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 15. nShift = MemNTicks = m_Ticks.nTicks; 16. if (!Open(szFileNameCSV)) return 0; 17. if (!ReadAllsTicks()) return 0; 18. rate.time = 0; 19. nDigits = SetSymbolInfos(); 20. m_Ticks.bTickReal = true; 21. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 22. { 23. if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0]; 24. if (!BuildBar1Min(c0, rate, bNew)) continue; 25. if (bNew) 26. { 27. if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume) 28. { 29. nShift = MemShift; 30. ArrayResize(TicksLocal, def_MaxSizeArray); 31. C_Simulation *pSimulator = new C_Simulation(nDigits); 32. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0) 33. nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 34. delete pSimulator; 35. ArrayFree(TicksLocal); 36. if (c1 < 0) return 0; 37. } 38. MemShift = nShift; 39. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 40. }; 41. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 42. } 43. if (!ToReplay) 44. { 45. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 46. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 47. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 48. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 49. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 50. m_Ticks.nTicks = MemNTicks; 51. ArrayFree(RatesLocal); 52. }else m_Ticks.nTicks = nShift; 53. 54. return dtRet; 55. }; 56. //+------------------------------------------------------------------+
C_FileTicks.mqh 文件片段(最终版)
你看出区别了吗?它们并不特别明显,但它们确实存在。您可以注意到,我对某些操作的执行顺序进行了一些更改。最明显的变化是模拟过程中创建的分时报价的内存分配和释放。由于 LoadTicks 函数是一个主要函数,这意味着它在系统完全初始化并开始有性能要求之前执行,因此我们可以承受在内存分配和释放调用期间浪费一些时间。
如果您觉得这样的时间损失是不可接受的,请根据需要随意调整执行顺序。然而,在下面显示的更正片段中,我们不能忽视在发生故障时调用模拟类析构函数的重要性。在比较代码片段时,您会注意到,在更正后的版本中,该函数仅在第 36 行出现故障时返回。但在此之前,在第 34 行和第 35 行,我们显式调用析构函数并释放分配的内存。按照特定的顺序。
如果模拟成功并且数据已准备好移动,我们在第 33 行执行此步骤,其中我们还使用库函数的返回值来更新新的偏移值。
通过这种方式,我们解决了之前模拟失败时出现的问题。然而,这里还有一个问题需要解释。如果你看一下修正后的片段,你会注意到一些奇怪的东西。乍一看可能没有多大意义,这可以在第 23 行看到。为什么第 23 行要将分时报价计数器与偏移值进行比较?这次检查的目的是什么?
说实话,这似乎没有多大意义。事实确实如此。然而,如果执行模拟器,偏移值将与分时报价计数器不同。当这种情况发生时,一些真实的分时报价最终会被错误地索引。如果不纠正这个问题,那么当执行第 52 行时,许多真实分时报价可能会消失。更不用说模拟柱和非模拟柱之间出现的问题,由于索引不正确,它们之间可能会出现一些奇怪的分时报价。
现在我相信你真正了解了问题的根源。因此,通过在第 23 行添加一个检查来验证和重新定位真实的分时报价,一切都会正常执行,我们不会在图表上看到任何异常。这是一个简单的修复,但它彻底解决了这个问题。代码的最终版本位于 C_FileTicks.mqh 文件内,如下所示:
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. #define def_MaxSizeArray 16777216 // 16 Mbytes 009. //+------------------------------------------------------------------+ 010. class C_FileTicks 011. { 012. protected: 013. enum ePlotType {PRICE_EXCHANGE, PRICE_FOREX}; 014. struct stInfoTicks 015. { 016. MqlTick Info[]; 017. MqlRates Rate[]; 018. int nTicks, 019. nRate; 020. bool bTickReal; 021. ePlotType ModePlot; 022. }; 023. //+------------------------------------------------------------------+ 024. inline bool BuildBar1Min(const int iArg, MqlRates &rate, bool &bNew) 025. { 026. double dClose = 0; 027. 028. switch (m_Ticks.ModePlot) 029. { 030. case PRICE_EXCHANGE: 031. if (m_Ticks.Info[iArg].last == 0.0) return false; 032. dClose = m_Ticks.Info[iArg].last; 033. break; 034. case PRICE_FOREX: 035. dClose = (m_Ticks.Info[iArg].bid > 0.0 ? m_Ticks.Info[iArg].bid : dClose); 036. if ((dClose == 0.0) || (m_Ticks.Info[iArg].bid == 0.0)) return false; 037. break; 038. } 039. if (bNew = (rate.time != macroRemoveSec(m_Ticks.Info[iArg].time))) 040. { 041. rate.time = macroRemoveSec(m_Ticks.Info[iArg].time); 042. rate.real_volume = 0; 043. rate.tick_volume = (m_Ticks.ModePlot == PRICE_FOREX ? 1 : 0); 044. rate.open = rate.low = rate.high = rate.close = dClose; 045. }else 046. { 047. rate.close = dClose; 048. rate.high = (rate.close > rate.high ? rate.close : rate.high); 049. rate.low = (rate.close < rate.low ? rate.close : rate.low); 050. rate.real_volume += (long) m_Ticks.Info[iArg].volume_real; 051. rate.tick_volume += (m_Ticks.bTickReal ? 1 : (int)m_Ticks.Info[iArg].volume); 052. } 053. 054. return true; 055. } 056. //+------------------------------------------------------------------+ 057. private : 058. int m_File; 059. stInfoTicks m_Ticks; 060. //+------------------------------------------------------------------+ 061. inline bool Open(const string szFileNameCSV) 062. { 063. string szInfo = ""; 064. 065. if ((m_File = FileOpen("Market Replay\\Ticks\\" + szFileNameCSV + ".csv", FILE_CSV | FILE_READ | FILE_ANSI)) != INVALID_HANDLE) 066. { 067. for (int c0 = 0; c0 < 7; c0++) szInfo += FileReadString(m_File); 068. if (szInfo == "<DATE><TIME><BID><ASK><LAST><VOLUME><FLAGS>") return true; 069. Print("File ", szFileNameCSV, ".csv not a traded tick file."); 070. }else 071. Print("Tick file ", szFileNameCSV,".csv not found..."); 072. 073. return false; 074. } 075. //+------------------------------------------------------------------+ 076. inline bool ReadAllsTicks(void) 077. { 078. string szInfo; 079. 080. Print("Loading replay ticks. Please wait..."); 081. ArrayResize(m_Ticks.Info, def_MaxSizeArray, def_MaxSizeArray); 082. m_Ticks.ModePlot = PRICE_FOREX; 083. while ((!FileIsEnding(m_File)) && (m_Ticks.nTicks < (INT_MAX - 2)) && (!_StopFlag)) 084. { 085. ArrayResize(m_Ticks.Info, m_Ticks.nTicks + 1, def_MaxSizeArray); 086. szInfo = FileReadString(m_File) + " " + FileReadString(m_File); 087. m_Ticks.Info[m_Ticks.nTicks].time = StringToTime(StringSubstr(szInfo, 0, 19)); 088. m_Ticks.Info[m_Ticks.nTicks].time_msc = (m_Ticks.Info[m_Ticks.nTicks].time * 1000) + (int)StringToInteger(StringSubstr(szInfo, 20, 3)); 089. m_Ticks.Info[m_Ticks.nTicks].bid = StringToDouble(FileReadString(m_File)); 090. m_Ticks.Info[m_Ticks.nTicks].ask = StringToDouble(FileReadString(m_File)); 091. m_Ticks.Info[m_Ticks.nTicks].last = StringToDouble(FileReadString(m_File)); 092. m_Ticks.Info[m_Ticks.nTicks].volume_real = StringToDouble(FileReadString(m_File)); 093. m_Ticks.Info[m_Ticks.nTicks].flags = (uchar)StringToInteger(FileReadString(m_File)); 094. m_Ticks.ModePlot = (m_Ticks.Info[m_Ticks.nTicks].volume_real > 0.0 ? PRICE_EXCHANGE : m_Ticks.ModePlot); 095. m_Ticks.nTicks++; 096. } 097. FileClose(m_File); 098. if (m_Ticks.nTicks == (INT_MAX - 2)) 099. { 100. Print("Too much data in tick file.\nIt is not possible to continue..."); 101. return false; 102. } 103. return (!_StopFlag); 104. } 105. //+------------------------------------------------------------------+ 106. int SetSymbolInfos(void) 107. { 108. int iRet; 109. 110. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_DIGITS, iRet = (m_Ticks.ModePlot == PRICE_EXCHANGE ? 4 : 5)); 111. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_TRADE_CALC_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CALC_MODE_EXCH_STOCKS : SYMBOL_CALC_MODE_FOREX); 112. CustomSymbolSetInteger(def_SymbolReplay, SYMBOL_CHART_MODE, m_Ticks.ModePlot == PRICE_EXCHANGE ? SYMBOL_CHART_MODE_LAST : SYMBOL_CHART_MODE_BID); 113. 114. return iRet; 115. } 116. //+------------------------------------------------------------------+ 117. public : 118. //+------------------------------------------------------------------+ 119. C_FileTicks() 120. { 121. ArrayResize(m_Ticks.Rate, def_BarsDiary); 122. m_Ticks.nRate = -1; 123. m_Ticks.nTicks = 0; 124. m_Ticks.Rate[0].time = 0; 125. } 126. //+------------------------------------------------------------------+ 127. bool BarsToTicks(const string szFileNameCSV, int MaxTickVolume) 128. { 129. C_FileBars *pFileBars; 130. C_Simulation *pSimulator = NULL; 131. int iMem = m_Ticks.nTicks, 132. iRet = -1; 133. MqlRates rate[1]; 134. MqlTick local[]; 135. bool bInit = false; 136. 137. pFileBars = new C_FileBars(szFileNameCSV); 138. ArrayResize(local, def_MaxSizeArray); 139. Print("Converting bars to ticks. Please wait..."); 140. while ((*pFileBars).ReadBar(rate) && (!_StopFlag)) 141. { 142. if (!bInit) 143. { 144. m_Ticks.ModePlot = (rate[0].real_volume > 0 ? PRICE_EXCHANGE : PRICE_FOREX); 145. pSimulator = new C_Simulation(SetSymbolInfos()); 146. bInit = true; 147. } 148. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 3 : def_BarsDiary), def_BarsDiary); 149. m_Ticks.Rate[++m_Ticks.nRate] = rate[0]; 150. if (pSimulator == NULL) iRet = -1; else iRet = (*pSimulator).Simulation(rate[0], local, MaxTickVolume); 151. if (iRet < 0) break; 152. for (int c0 = 0; c0 <= iRet; c0++) 153. { 154. ArrayResize(m_Ticks.Info, (m_Ticks.nTicks + 1), def_MaxSizeArray); 155. m_Ticks.Info[m_Ticks.nTicks++] = local[c0]; 156. } 157. } 158. ArrayFree(local); 159. delete pFileBars; 160. delete pSimulator; 161. m_Ticks.bTickReal = false; 162. 163. return ((!_StopFlag) && (iMem != m_Ticks.nTicks) && (iRet > 0)); 164. } 165. //+------------------------------------------------------------------+ 166. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay, const int MaxTickVolume) 167. { 168. int MemNRates, 169. MemNTicks, 170. nDigits, 171. nShift; 172. datetime dtRet = TimeCurrent(); 173. MqlRates RatesLocal[], 174. rate; 175. MqlTick TicksLocal[]; 176. bool bNew; 177. 178. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 179. nShift = MemNTicks = m_Ticks.nTicks; 180. if (!Open(szFileNameCSV)) return 0; 181. if (!ReadAllsTicks()) return 0; 182. rate.time = 0; 183. nDigits = SetSymbolInfos(); 184. m_Ticks.bTickReal = true; 185. for (int c0 = MemNTicks, c1, MemShift = nShift; c0 < m_Ticks.nTicks; c0++, nShift++) 186. { 187. if (nShift != c0) m_Ticks.Info[nShift] = m_Ticks.Info[c0]; 188. if (!BuildBar1Min(c0, rate, bNew)) continue; 189. if (bNew) 190. { 191. if ((m_Ticks.nRate >= 0) && (ToReplay)) if (m_Ticks.Rate[m_Ticks.nRate].tick_volume > MaxTickVolume) 192. { 193. nShift = MemShift; 194. ArrayResize(TicksLocal, def_MaxSizeArray); 195. C_Simulation *pSimulator = new C_Simulation(nDigits); 196. if ((c1 = (*pSimulator).Simulation(m_Ticks.Rate[m_Ticks.nRate], TicksLocal, MaxTickVolume)) > 0) 197. nShift += ArrayCopy(m_Ticks.Info, TicksLocal, nShift, 0, c1); 198. delete pSimulator; 199. ArrayFree(TicksLocal); 200. if (c1 < 0) return 0; 201. } 202. MemShift = nShift; 203. ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 204. }; 205. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 206. } 207. if (!ToReplay) 208. { 209. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 210. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 211. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 212. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 213. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 214. m_Ticks.nTicks = MemNTicks; 215. ArrayFree(RatesLocal); 216. }else m_Ticks.nTicks = nShift; 217. 218. return dtRet; 219. }; 220. //+------------------------------------------------------------------+ 221. inline stInfoTicks GetInfoTicks(void) const 222. { 223. return m_Ticks; 224. } 225. //+------------------------------------------------------------------+ 226. }; 227. //+------------------------------------------------------------------+ 228. #undef def_MaxSizeArray 229. //+------------------------------------------------------------------+
头文件 C_FileTicks.mqh
既然我们已经解决了这些问题,我们就可以继续下一步了。这涉及允许用户定义一个值,该值将用作一分钟内允许的最大分时报价数。为了使事情井然有序,我们将在一个新的部分中解决这个问题。
允许用户进行调整
这部分无疑是最容易、最简单、最令人愉快的实现。这是因为作为开发人员,您唯一真正的任务是定义用于设置我们需要调整的值的键的名称。
本质上,此键是用户必须在资产的配置文件中输入的值。我在本系列的前几篇文章中已经展示了如何做到这一点。然而,由于这是一个非常简单的实现,我不会将代码分解为片段来解释每次添加。相反,在查看最终代码之前,让我们先看看如何使用这个新功能的示例。让我们看一下回放/模拟器应用程序的示例配置文件。示例如下。
01. [Config] 02. Path = WDO 03. PointsPerTick = 0.5 04. ValuePerPoints = 5.0 05. VolumeMinimal = 1.0 06. Account = NETTING 07. MaxTicksPerBar = 2800 08. 09. [Bars] 10. WDON22_M1_202206140900_202206141759 11. 12. [ Ticks -> Bars] 13. 14. [ Bars -> Ticks ] 15. 16. [Ticks] 17. WDON22_202206150900_202206151759
配置文件示例
请注意第七行,其中引入了新的配置设置。如果您尝试将此配置文件与 MetaTrader 5 的回放/模拟应用程序的早期版本一起使用,您将收到一条错误消息,指出第七行包含无法识别的参数。但是,从我在此处展示的版本来看,应用程序能够解释第七行的含义。
重要的一点是:您和最终用户都不需要指定第七行的这个新设置。如果提供,配置将覆盖编译的应用程序中嵌入的默认值。如果省略,回放/模拟器服务将在编译期间恢复使用内部定义的默认设置。
在显示代码之前,我指出了这一点,因为我想强调的是,这种配置完全是可选的。但是,使用时,它将优先于预编译值。最后一点提醒:每个配置文件都是唯一的。这意味着您可以为每个标记定义不同的最分时报价计数。因此,请随意尝试不同的设置,直到找到能够在 MetaTrader 5 中保持良好性能并确保图表流畅呈现的配置。
现在,让我们看一下负责解释和应用配置文件中的值的更新的头文件。我说的是 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. MaxTickVolume; 020. bool AccountHedging; 021. char ModelLoading; 022. string szPath; 023. }m_GlPrivate; 024. //+------------------------------------------------------------------+ 025. inline void FirstBarNULL(void) 026. { 027. MqlRates rate[1]; 028. int c0 = 0; 029. 030. for(; (GetInfoTicks().ModePlot == PRICE_EXCHANGE) && (GetInfoTicks().Info[c0].volume_real == 0); c0++); 031. rate[0].close = (GetInfoTicks().ModePlot == PRICE_EXCHANGE ? GetInfoTicks().Info[c0].last : GetInfoTicks().Info[c0].bid); 032. rate[0].open = rate[0].high = rate[0].low = rate[0].close; 033. rate[0].tick_volume = 0; 034. rate[0].real_volume = 0; 035. rate[0].time = macroRemoveSec(GetInfoTicks().Info[c0].time) - 86400; 036. CustomRatesUpdate(def_SymbolReplay, rate); 037. } 038. //+------------------------------------------------------------------+ 039. inline eTranscriptionDefine GetDefinition(const string &In, string &Out) 040. { 041. string szInfo; 042. 043. szInfo = In; 044. Out = ""; 045. StringToUpper(szInfo); 046. StringTrimLeft(szInfo); 047. StringTrimRight(szInfo); 048. if (StringSubstr(szInfo, 0, 1) == "#") return Transcription_INFO; 049. if (StringSubstr(szInfo, 0, 1) != "[") 050. { 051. Out = szInfo; 052. return Transcription_INFO; 053. } 054. for (int c0 = 0; c0 < StringLen(szInfo); c0++) 055. if (StringGetCharacter(szInfo, c0) > ' ') 056. StringAdd(Out, StringSubstr(szInfo, c0, 1)); 057. 058. return Transcription_DEFINE; 059. } 060. //+------------------------------------------------------------------+ 061. inline bool Configs(const string szInfo) 062. { 063. const string szList[] = { 064. "PATH", 065. "POINTSPERTICK", 066. "VALUEPERPOINTS", 067. "VOLUMEMINIMAL", 068. "LOADMODEL", 069. "ACCOUNT", 070. "MAXTICKSPERBAR" 071. }; 072. string szRet[]; 073. char cWho; 074. 075. if (StringSplit(szInfo, '=', szRet) == 2) 076. { 077. StringTrimRight(szRet[0]); 078. StringTrimLeft(szRet[1]); 079. for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break; 080. switch (cWho) 081. { 082. case 0: 083. m_GlPrivate.szPath = szRet[1]; 084. return true; 085. case 1: 086. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE, StringToDouble(szRet[1])); 087. return true; 088. case 2: 089. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE, StringToDouble(szRet[1])); 090. return true; 091. case 3: 092. CustomSymbolSetDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP, StringToDouble(szRet[1])); 093. return true; 094. case 4: 095. m_GlPrivate.ModelLoading = StringInit(szRet[1]); 096. m_GlPrivate.ModelLoading = ((m_GlPrivate.ModelLoading < 1) && (m_GlPrivate.ModelLoading > 4) ? 1 : m_GlPrivate.ModelLoading); 097. return true; 098. case 5: 099. if (szRet[1] == "HEDGING") m_GlPrivate.AccountHedging = true; 100. else if (szRet[1] == "NETTING") m_GlPrivate.AccountHedging = false; 101. else 102. { 103. Print("Entered account type is not invalid."); 104. return false; 105. } 106. return true; 107. case 6: 108. m_GlPrivate.MaxTickVolume = (int) MathAbs(StringToInteger(szRet[1])); 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, true, m_GlPrivate.MaxTickVolume) == 0) cError = 4; 158. break; 159. case eTickToBar : 160. if (LoadTicks(szInfo, false, m_GlPrivate.MaxTickVolume) == 0) cError = 5; else bBarsPrev = true; 161. break; 162. case eBarToTick : 163. if (!BarsToTicks(szInfo, m_GlPrivate.MaxTickVolume)) 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. m_GlPrivate.MaxTickVolume = 2000; 186. } 187. //+------------------------------------------------------------------+ 188. inline const bool TypeAccountIsHedging(void) const 189. { 190. return m_GlPrivate.AccountHedging; 191. } 192. //+------------------------------------------------------------------+ 193. bool SetSymbolReplay(const string szFileConfig) 194. { 195. #define macroFileName ((m_GlPrivate.szPath != NULL ? m_GlPrivate.szPath + "\\" : "") + szInfo) 196. int file; 197. char cError, 198. cStage; 199. string szInfo; 200. bool bBarsPrev; 201. 202. if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE) 203. { 204. Print("Failed to open configuration file [", szFileConfig, "]. Service being terminated..."); 205. return false; 206. } 207. Print("Loading data for playback. Please wait...."); 208. cError = cStage = 0; 209. bBarsPrev = false; 210. m_GlPrivate.Line = 1; 211. m_GlPrivate.pTicksToReplay = m_GlPrivate.pTicksToBars = m_GlPrivate.pBarsToTicks = m_GlPrivate.pBarsToPrev = NULL; 212. while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0)) 213. { 214. switch (GetDefinition(FileReadString(file), szInfo)) 215. { 216. case Transcription_DEFINE: 217. cError = (WhatDefine(szInfo, cStage) ? 0 : 1); 218. break; 219. case Transcription_INFO: 220. if (szInfo != "") switch (cStage) 221. { 222. case 0: 223. cError = 2; 224. break; 225. case 1: 226. if (m_GlPrivate.pBarsToPrev == NULL) m_GlPrivate.pBarsToPrev = new C_Array(); 227. (*m_GlPrivate.pBarsToPrev).Add(macroFileName, m_GlPrivate.Line); 228. break; 229. case 2: 230. if (m_GlPrivate.pTicksToReplay == NULL) m_GlPrivate.pTicksToReplay = new C_Array(); 231. (*m_GlPrivate.pTicksToReplay).Add(macroFileName, m_GlPrivate.Line); 232. break; 233. case 3: 234. if (m_GlPrivate.pTicksToBars == NULL) m_GlPrivate.pTicksToBars = new C_Array(); 235. (*m_GlPrivate.pTicksToBars).Add(macroFileName, m_GlPrivate.Line); 236. break; 237. case 4: 238. if (m_GlPrivate.pBarsToTicks == NULL) m_GlPrivate.pBarsToTicks = new C_Array(); 239. (*m_GlPrivate.pBarsToTicks).Add(macroFileName, m_GlPrivate.Line); 240. break; 241. case 5: 242. if (!Configs(szInfo)) cError = 7; 243. break; 244. } 245. break; 246. }; 247. m_GlPrivate.Line += (cError > 0 ? 0 : 1); 248. } 249. FileClose(file); 250. CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eTickReplay : eBarToTick)); 251. CMD_Array(cError, (m_GlPrivate.ModelLoading <= 2 ? eBarToTick : eTickReplay)); 252. bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eTickToBar : eBarPrev)) ? true : bBarsPrev); 253. bBarsPrev = (CMD_Array(cError, ((m_GlPrivate.ModelLoading & 1) == 1 ? eBarPrev : eTickToBar)) ? true : bBarsPrev); 254. switch(cError) 255. { 256. case 0: 257. if (GetInfoTicks().nTicks <= 0) 258. { 259. Print("There are no ticks to use. Service is being terminated..."); 260. cError = -1; 261. }else if (!bBarsPrev) FirstBarNULL(); 262. break; 263. case 1 : Print("The command on the line ", m_GlPrivate.Line, " not recognized by the system..."); break; 264. case 2 : Print("The system did not expect the contents of the line: ", m_GlPrivate.Line); break; 265. default : Print("Error accessing the file indicated in the line: ", m_GlPrivate.Line); 266. } 267. 268. return (cError == 0 ? !_StopFlag : false); 269. #undef macroFileName 270. } 271. //+------------------------------------------------------------------+ 272. }; 273. //+------------------------------------------------------------------+
头文件 C_ConfigService.mqh 的源代码
C_ConfigService 类的代码确实令人愉快。这是因为这是一种需要最小更改的代码,让我们用很少的努力完成很多事情。这种情况很少见。但让我们直奔主题吧。所有底层都在这里做繁重的工作,以便我们加载回放或模拟所需的数据。一切都是根据配置文件的内容编排的,就像我们之前展示的那样。
所以,我们在这段代码中做的第一件事是定义一个新变量。这可在第 19 行看到。它的名字非常直观,清楚地表明了我们在以下步骤中打算做什么。这个变量在类构造函数中的第 185 行初始化。由于此初始化,如果配置文件没有指定其他值,则将使用此默认值。
现在,您可能想知道:“等一下,为什么您不使用 Defines.mqh 文件中的 def_MaxTicksVolume 定义?”原因是该定义不再存在。它被删除了,因为它不再需要了。自从我们开始使用配置文件以来,我们不再依赖于某些硬编码的值。这就是 def_MaxTicksVolume 从 Defines.mqh 中删除的原因。如果你想保留它,那完全没问题。但是,如果在未来的文章中我们重新审视 Defines.mqh 时,这个定义不再存在,请不要感到惊讶。
现在让我们了解一下为什么这个定义变得没有必要。在开发允许回放系统在一分钟内出现溢出时模拟分时报价的功能期间,我最初没有定义这些信息的来源。为了避免代码中有太多的值,我创建了一个通用的定义。因此 def_MaxTicksVolume 诞生了,并放置在 Defines.mqh 中。当你构建的东西预计会随着时间的推移而发展时,这种设计决策很常见。然而,一旦我们到达 C_ConfigService 类,我就有了允许用户自定义这个值的想法,而不需要重新编译整个项目。
不管怎样,负责辛苦工作的类已经实施了。所以,我们要做的就是将这个最大分时报价计数值传递给他们。只需对代码进行少量编辑,我们就能够确保适当的类可以接收和使用为整个应用程序定义的值。相应的方法调用出现在第 157、160 和 163 行。
请注意,我们并没有真正添加任何主要的新逻辑,只是做了一些小的调整,为这种配置提供了必要的支持。然而,这使我们能够控制在一分钟的单个柱形中可以模拟或存在的最大分时报价数。
现在,这就是所有东西都被引入这个类的真正原因。这是为了允许用户定义一分钟柱形内允许的最大分时报价数。如果您脱离上下文来研究此代码,您可能会认为这项任务很复杂且广泛。但事实并非如此。实现用户指定最大分时报价数的功能实际上很简单、容易且快速。
首先,我们添加配置键。这是通过向用于配置参数的键列表添加另一个字符串来完成的。该数组在第 63 行定义。现在要注意了,我之前已经解释过这一点,但值得重复一遍。添加新键时,始终以大写形式定义它。键值是什么并不重要。只要确保它是大写即可。我们的新密钥在第 70 行定义。现在,还有另一个技巧:在第 75 行,我们使用 MQL5 库函数来拆分键值对。使用等号作为分隔符。因此,等号之前的任何内容都被视为键,等号之后的任何内容就是分配给该键的值。
下一个关键点是第 79 行,我们在数组中搜索键以找到其索引。因此,如果更改数组中键的顺序,还必须更新稍后使用的相应索引。这并不难,只是需要细心注意。
在我们的例子中,新设置的索引是 6。因此,在第 107 行,我们定义了如何应用此设置。由于我们期望得到一个整数值,我们使用另一个 MQL5 库函数来执行转换。这就是用户如何设置一个值作为一分钟条形图的最大刻度计数。
重要事项:在第108行,我们将配置文件中的值转换为可用整数,我们不执行任何验证检查以确保该值在可接受的参数范围内。唯一的保障措施是确保值为正。如果该值不一致或无效,则模拟过程可能会失败。
最终结论
在结束这篇文章之前,我想提醒你上一篇文章中提到的一些事情。我说的是其中包含的视频。虽然本文的主要重点是实现回放功能的分时报价限制系统,但我想强调的是,视频中显示的错误并没有被遗忘。
虽然这些错误并不是特别严重,因为它们似乎不会损害应用程序的稳定性或导致平台崩溃,但它们确实存在。特别是,关闭图表时,一些图形对象没有正确删除。虽然这只发生在非常特殊的情况下,但我们已经在努力解决这个问题。一旦这个问题得到解决,我将发表一篇后续文章,解释如何解决这个问题。
最后,我邀请您观看下面嵌入的短视频。它演示了系统当前的运行方式,特别是根据配置文件中是否定义了分时报价限制,它的行为方式。
视频很简短,但有效地突出了使用模拟数据和真实数据之间的明显差异。您会发现,当我们使用随机游走模拟来填充分时报价时,产生的运动看起来与真实交易产生的实际刻度数据有很大不同。
我要提出的最后一点涉及另一个问题。这是次要的,比有害的更烦人。您可以在视频中注意到它。有时,回放/模拟服务会随机暂停,需要您再次点击播放。
虽然这个问题相对无害,但确实需要迅速解决。因此,在下一篇文章中,我们将解决这个错误并确保回放/模拟器不会意外进入暂停模式。我们还将开始重新启用服务中仍暂时禁用的一些功能。
演示视频
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12240


