
开发回放系统(第 62 部分):玩转服务(三)
概述
在上一篇文章,开发回放系统(第 61 部分):玩转服务(二)中 ,我解释了我们目前在系统中使用模拟模式时面临的一个问题。这个问题不一定源于我们正在开发的应用程序中的灾难性故障。相反,这是由于系统的整体响应速度。响应时间不足,应用程序无法正确处理所有传入数据。因此,我们必须做出一些调整。即使我们的服务与理想场景不完全一致,我们也认识到在实践中很少存在这样的理想场景。
我能想到的最佳解决方案是调整模拟中适用的最大限制。然而,在本文中,我将仔细探讨这些变化的影响,并解释我为什么选择这种特殊的方法。除此之外,还有另一个因素与正在开发的应用程序之外的真实或外部模拟数据直接相关。尽管看起来很不寻常,但在某些情况下,尤其是与期货合约相关的情况下,我们可能会在一分钟柱形内遇到异常多的报价或交易。当这种情况发生时,即使连接到交易服务器,我们也会遇到与 MetaTrader 5 平台处理和显示价格变动的速度相关的问题。如果您以前从未遇到过此问题,您可能会认为这是由于运行 MetaTrader 5 的硬件限制或操作系统故障造成的。然而,我遗憾地通知您,这些假设是完全没有根据的 — 那些对计算缺乏适当理解的人散布了误解。
考虑到即使连接到真实的交易服务器,平台也难以处理大量涌入的数据,我们也面临着这些挑战,在重放这些数据时,情况变得更加棘手。这将是一场彻底的灾难,因为计时精度将大幅下降。因此,我们还将为真实或外部模拟数据设定一个限制,以防止平台的数据处理限制变得明显或导致进一步的问题。现在,让我们研究一下新代码的结构。
一个新概念,一个新系统
也许这一节的标题并不完全不言自明 — 它没有充分传达我们将要实现的内容。然而,我们将首先分析对负责在我们的系统中生成和建模任何模拟的类所做的更改。在查看模拟类代码之前,我们必须首先检查定义文件,因为添加了一行新代码,该行将在代码中的各个点被引用。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_VERSION_DEBUG 05. //+------------------------------------------------------------------+ 06. #ifdef def_VERSION_DEBUG 07. #define macro_DEBUG_MODE(A) \ 08. Print(__FILE__, " ", __LINE__, " ", __FUNCTION__ + " " + #A + " = " + (string)(A)); 09. #else 10. #define macro_DEBUG_MODE(A) 11. #endif 12. //+------------------------------------------------------------------+ 13. #define def_SymbolReplay "RePlay" 14. #define def_MaxPosSlider 400 15. #define def_MaxTicksVolume 2000 16. //+------------------------------------------------------------------+ 17. union uCast_Double 18. { 19. double dValue; 20. long _long; // 1 Information 21. datetime _datetime; // 1 Information 22. uint _32b[sizeof(double) / sizeof(uint)]; // 2 Informations 23. ushort _16b[sizeof(double) / sizeof(ushort)]; // 4 Informations 24. uchar _8b [sizeof(double) / sizeof(uchar)]; // 8 Informations 25. }; 26. //+------------------------------------------------------------------+ 27. enum EnumEvents { 28. evHideMouse, //Hide mouse price line 29. evShowMouse, //Show mouse price line 30. evHideBarTime, //Hide bar time 31. evShowBarTime, //Show bar time 32. evHideDailyVar, //Hide daily variation 33. evShowDailyVar, //Show daily variation 34. evHidePriceVar, //Hide instantaneous variation 35. evShowPriceVar, //Show instantaneous variation 36. evSetServerTime, //Replay/simulation system timer 37. evCtrlReplayInit, //Initialize replay control 38. }; 39. //+------------------------------------------------------------------+
Defines.mqh 文件源代码
这是已添加到代码的第 15 行。我现在不解释这个值从哪里来,我们将在模拟代码中使用它时讨论它。这将是第一个使用它的地方。现在我们已经介绍了这一更改,我们可以继续讨论负责模拟分时报价的类的源代码。修改后的代码如下所示:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "..\..\Defines.mqh" 005. //+------------------------------------------------------------------+ 006. class C_Simulation 007. { 008. private : 009. //+------------------------------------------------------------------+ 010. int m_NDigits; 011. bool m_IsPriceBID; 012. double m_TickSize; 013. struct st00 014. { 015. bool bHigh, bLow; 016. int iMax; 017. }m_Marks; 018. //+------------------------------------------------------------------+ 019. template < typename T > 020. inline T RandomLimit(const T Limit01, const T Limit02) 021. { 022. T a = (Limit01 > Limit02 ? Limit01 - Limit02 : Limit02 - Limit01); 023. return (Limit01 >= Limit02 ? Limit02 : Limit01) + ((T)(((rand() & 32767) / 32737.0) * a)); 024. } 025. //+------------------------------------------------------------------+ 026. inline void Simulation_Time(const MqlRates &rate, MqlTick &tick[]) 027. { 028. for (int c0 = 0, iPos, v0 = (int)(60000 / m_Marks.iMax), v1 = 0, v2 = v0; c0 <= m_Marks.iMax; c0++, v1 = v2, v2 += v0) 029. { 030. iPos = RandomLimit(v1, v2); 031. tick[c0].time = rate.time + (iPos / 1000); 032. tick[c0].time_msc = iPos % 1000; 033. } 034. } 035. //+------------------------------------------------------------------+ 036. inline void CorretTime(MqlTick &tick[]) 037. { 038. for (int c0 = 0; c0 <= m_Marks.iMax; c0++) 039. tick[c0].time_msc += (tick[c0].time * 1000); 040. } 041. //+------------------------------------------------------------------+ 042. inline int Unique(const double price, const MqlTick &tick[]) 043. { 044. int iPos = 1; 045. 046. do 047. { 048. iPos = (m_Marks.iMax > 20 ? RandomLimit(1, m_Marks.iMax - 1) : iPos + 1); 049. }while ((m_IsPriceBID ? tick[iPos].bid : tick[iPos].last) == price); 050. 051. return iPos; 052. } 053. //+------------------------------------------------------------------+ 054. inline void MountPrice(const int iPos, const double price, const int spread, MqlTick &tick[]) 055. { 056. if (m_IsPriceBID) 057. { 058. tick[iPos].bid = NormalizeDouble(price, m_NDigits); 059. tick[iPos].ask = NormalizeDouble(price + (m_TickSize * spread), m_NDigits); 060. }else 061. tick[iPos].last = NormalizeDouble(price, m_NDigits); 062. } 063. //+------------------------------------------------------------------+ 064. inline void Random_Price(const MqlRates &rate, MqlTick &tick[]) 065. { 066. for (int c0 = 1; c0 < m_Marks.iMax; c0++) 067. { 068. MountPrice(c0, NormalizeDouble(RandomLimit(rate.high, rate.low), m_NDigits), (rate.spread + RandomLimit((int)(rate.spread | (m_Marks.iMax & 0xF)), 0)), tick); 069. m_Marks.bHigh = (rate.high == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bHigh; 070. m_Marks.bLow = (rate.low == (m_IsPriceBID ? tick[c0].bid : tick[c0].last)) || m_Marks.bLow; 071. } 072. } 073. //+------------------------------------------------------------------+ 074. inline void DistributeVolumeReal(const MqlRates &rate, MqlTick &tick[]) 075. { 076. for (int c0 = 0; c0 <= m_Marks.iMax; c0++) 077. { 078. tick[c0].volume_real = 1.0; 079. tick[c0].volume = 1; 080. } 081. if ((m_Marks.iMax + 1) < rate.tick_volume) for (int c0 = (int)(rate.tick_volume - m_Marks.iMax); c0 > 0; c0--) 082. tick[RandomLimit(0, m_Marks.iMax - 1)].volume += 1; 083. for (int c0 = (int)(rate.real_volume - m_Marks.iMax); c0 > 0; c0--) 084. tick[RandomLimit(0, m_Marks.iMax)].volume_real += 1.0; 085. } 086. //+------------------------------------------------------------------+ 087. inline int RandomWalk(int In, int Out, const double Open, const double Close, double High, double Low, const int Spread, MqlTick &tick[], int iMode, int iDesloc) 088. { 089. double vStep, vNext, price, vH = High, vL = Low; 090. char i0 = 0; 091. 092. vNext = vStep = (Out - In) / ((High - Low) / m_TickSize); 093. for (int c0 = In, c1 = 0, c2 = 0; c0 <= Out; c0++, c1++) 094. { 095. price = (m_IsPriceBID ? tick[c0 - 1].bid : tick[c0 - 1].last) + (m_TickSize * ((rand() & 1) == 1 ? -iDesloc : iDesloc)); 096. price = (price > vH ? vH : (price < vL ? vL : price)); 097. MountPrice(c0, price, (Spread + RandomLimit((int)(Spread | (m_Marks.iMax & 0xF)), 0)), tick); 098. switch (iMode) 099. { 100. case 1: 101. i0 |= (price == High ? 0x01 : 0); 102. i0 |= (price == Low ? 0x02 : 0); 103. vH = (i0 == 3 ? High : vH); 104. vL = (i0 ==3 ? Low : vL); 105. break; 106. case 0: 107. if (price == Close) return c0; 108. default: 109. break; 110. } 111. if (((int)floor(vNext)) >= c1) continue; else if ((++c2) <= 3) continue; 112. vNext += vStep; 113. vL = (iMode != 2 ? (Close > vL ? (i0 == 3 ? vL : vL + m_TickSize) : vL) : (((c2 & 1) == 1) ? (Close > vL ? vL + m_TickSize : vL) : (Close < vH ? vL : vL + m_TickSize))); 114. vH = (iMode != 2 ? (Close > vL ? vH : (i0 == 3 ? vH : vH - m_TickSize)) : (((c2 & 1) == 1) ? (Close > vL ? vH : vH - m_TickSize) : (Close < vH ? vH - m_TickSize : vH))); 115. } 116. 117. return Out; 118. } 119. //+------------------------------------------------------------------+ 120. public : 121. //+------------------------------------------------------------------+ 122. C_Simulation(const int nDigits) 123. { 124. m_NDigits = nDigits; 125. m_IsPriceBID = (SymbolInfoInteger(def_SymbolReplay, SYMBOL_CHART_MODE) == SYMBOL_CHART_MODE_BID); 126. m_TickSize = SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE); 127. } 128. //+------------------------------------------------------------------+ 129. inline int Simulation(const MqlRates &rate, MqlTick &tick[], const int MaxTickVolume = def_MaxTicksVolume) 130. { 131. int i0, i1, i2; 132. bool b0; 133. 134. m_Marks.iMax = (MaxTickVolume <= 0 ? 1 : (MaxTickVolume >= def_MaxTicksVolume ? def_MaxTicksVolume : MaxTickVolume)); 135. m_Marks.iMax = ((int)rate.tick_volume > m_Marks.iMax ? m_Marks.iMax : (int)rate.tick_volume - 1); 136. m_Marks.bHigh = (rate.open == rate.high) || (rate.close == rate.high); 137. m_Marks.bLow = (rate.open == rate.low) || (rate.close == rate.low); 138. Simulation_Time(rate, tick); 139. MountPrice(0, rate.open, rate.spread, tick); 140. if (m_Marks.iMax > 10) 141. { 142. i0 = (int)(MathMin(m_Marks.iMax / 3.0, m_Marks.iMax * 0.2)); 143. i1 = m_Marks.iMax - i0; 144. i2 = (int)(((rate.high - rate.low) / m_TickSize) / i0); 145. i2 = (i2 == 0 ? 1 : i2); 146. b0 = (m_Marks.iMax >= 1000 ? ((rand() & 1) == 1) : (rate.high - rate.open) < (rate.open - rate.low)); 147. i0 = RandomWalk(1, i0, rate.open, (b0 ? rate.high : rate.low), rate.high, rate.low, rate.spread, tick, 0, i2); 148. 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); 149. RandomWalk(i1, m_Marks.iMax, (m_IsPriceBID ? tick[i1].bid : tick[i1].last), rate.close, rate.high, rate.low, rate.spread, tick, 2, i2); 150. m_Marks.bHigh = m_Marks.bLow = true; 151. 152. }else Random_Price(rate, tick); 153. if (!m_IsPriceBID) DistributeVolumeReal(rate, tick); 154. if (!m_Marks.bLow) MountPrice(Unique(rate.high, tick), rate.low, rate.spread, tick); 155. if (!m_Marks.bHigh) MountPrice(Unique(rate.low, tick), rate.high, rate.spread, tick); 156. MountPrice(m_Marks.iMax, rate.close, rate.spread, tick); 157. CorretTime(tick); 158. 159. return m_Marks.iMax; 160. } 161. //+------------------------------------------------------------------+ 162. }; 163. //+------------------------------------------------------------------+
C_Simulation.mqh 源代码
此时,您可能不会立即注意到任何变化,特别是因为我们上次修改这个类已经有一段时间了。我们上次对此进行研究是在有关随机游走的文章中。然而,因为我们需要确保系统在时间方面保持一定程度的一致性,即使生成的分时报价数有所下降,也需要对这段代码进行轻微的修改。
为了帮助您了解发生了什么变化,我将强调一个关键方面。尽管整个代码中有一些不需要特别注意的细微调整,因为它们只是为了使流程与新方法保持一致。在这种情况下,最显著的变化是第 16 行的增加。该变量最初不存在于类代码中。然而,在研究它对整个类的更广泛影响之前,让我们先看看它是在哪里初始化的。您可能会认为它的初始化发生在类构造函数中。但事实并非如此。相反,它发生在第 134 行和第 135 行。请密切关注这两行中发生的事情。如果您计划修改系统,这一点至关重要。在第 129 行,我们声明负责生成分时报价模拟的函数。但是,我们现在引入了一个附加参数,用于指定要模拟的最大分时报价数。还记得 Defines.mqh 文件中添加的那行吗?运用这一定义的地方之一恰恰就在这里。让我们分析一下发生了什么。这样,如果您决定修改代码,您将了解您的调整将如何影响其行为。当调用函数执行分时报价模拟时,您还必须提供一个值,表示要模拟的报价的最大数量。此值不是强制性的,因为它已经有一个默认值。然而,如果提供的值小于或等于零,系统将假定最小值为一个分时报价。这不是一个武断的决定。相反,这是因为在外汇交易中,最小可能的报价交易量恰好是 1。如果您指定的值超过了预定义的限制,则类将忽略您的输入,并使用系统定义的最大值。此限制在 Defines.mqh 头文件中设置,并决定系统内部可模拟的最大分时报价数。这两个极值之间的任何值都将被用作模拟的最大分时报价数。因此,您可以在这个范围内进行调整。
现在有一个重要的细节:这个特定的最大值不是随机选择的。如果我们将一分钟的柱形除以这个限制(设置为 2000 个分时报价),则每个分时报价间隔大约 30 毫秒。这个间隔被认为是最佳的,可确保整个绘图过程的运动平稳且一致。
虽然您可以指定更高的值,但请注意,这不会增加模拟的实际分时报价数。相反,它只会提高可以模拟的上限。此解释适用于第 134 行,但实际的最大分时报价数是在第 135 行确定的。此时,当第 135 行确定最终的分时报价数时,它会根据柱形中存在的数据验证第 134 行中生成的值。如果第 134 行的值低于该柱中的分时报价数,则使用该值。如果柱形的分时报价数较低,则忽略所提供的输入,而使用柱形的分时报价数。
如前所述,这些修改需要对与最大分时报价数相关的所有测试进行全面审查。因此,此类内的所有函数和过程都经历了微小的改变。由于这些变化很简单,因此我不会深入探讨其详细描述。如果有疑问的话,可以参考随机游走的文章。检查链接:“开发回放系统 — 市场模拟(第 15 部分):模拟器的诞生(五)- 随机游走“。
非常好,那么,第一个问题就解决了。现在,每次我们运行模拟时,我们都可以调整创建一个柱形所需的最大分时报价数。然而,这对于我们的目的来说还不够。请记住:我们仅控制分时报价模拟过程,但不允许用户更改设置以设置较少数量的模拟分时报价。所以我们有两个问题需要解决。它们两者都需要对系统进行一些修改。
所以让我们采取行动吧。下一个要解决的问题是可以存在的最大真实分时报价数或外部模拟分时报价数。为了考虑这一点,让我们继续讨论下一个主题。
根据实际市场数据进行调整
尽管标题表明只会使用真实的市场数据,但这并不完全正确。您可以从外部模拟市场走势,将其保存在文件中,然后使用此文件提供分时报价数据而不是柱形。这是一个完全有效且可行的方法。然而,核心问题与上一节相同:即使在处理真实市场数据时,我们也需要对最大分时报价数设置限制。
与之前的情况不同,由于所处理数据的性质,这个问题显得更加复杂。如果回放/模拟服务是针对单一市场类型设计的,那么这个问题就相对容易解决。在这种情况下,我们可以在加载分时报价时进行分析,并检查时间窗口内的总和是否超过给定值。如果确实如此,我们将强制系统丢弃此窗口内的超出的分时报价,这相当于一分钟的柱形。然后我们将让模拟器根据随机游走产生运动。这样,就可以轻松限制窗口中的分时报价数。然而,由于我们不知道在加载报价时处理的是出价还是最新报价,所以我们有一个棘手的问题需要解决。困难恰恰在于我们不知道使用了哪种图表系统:出价或最新价。
首先要考虑的问题之一是,这个问题的最佳解决方案是什么?我们需要允许用户指定图表类型并冒出错的风险,还是修改代码来处理这种情况?虽然用户指定的图表类型解决方案更容易实现,但它有一个缺点:用户可能会错误地指定它。老实说:有多少用户真正理解有两种类型的图表表示,其中一种对应于特定的市场模型,另一种对应完全不同的模型?大多数用户不知道 MetaTrader 5 支持三种类型的服务器,更不用说他们需要为其特定资产配置正确的绘图方法了。
由于这个潜在的陷阱,我们的实现将需要额外的努力。但是,我们现在有一个明确的起点:C_FileTicks 类中的 LoadTicks 函数。现在让我们看看原始函数,并考虑我们应该实现什么。该函数如下所示:
01. datetime LoadTicks(const string szFileNameCSV, const bool ToReplay = true) 02. { 03. int MemNRates, 04. MemNTicks; 05. datetime dtRet = TimeCurrent(); 06. MqlRates RatesLocal[], 07. rate; 08. bool bNew; 09. 10. MemNRates = (m_Ticks.nRate < 0 ? 0 : m_Ticks.nRate); 11. MemNTicks = m_Ticks.nTicks; 12. if (!Open(szFileNameCSV)) return 0; 13. if (!ReadAllsTicks()) return 0; 14. rate.time = 0; 15. for (int c0 = MemNTicks; c0 < m_Ticks.nTicks; c0++) 16. { 17. if (!BuildBar1Min(c0, rate, bNew)) continue; 18. if (bNew) ArrayResize(m_Ticks.Rate, (m_Ticks.nRate > 0 ? m_Ticks.nRate + 2 : def_BarsDiary), def_BarsDiary); 19. m_Ticks.Rate[(m_Ticks.nRate += (bNew ? 1 : 0))] = rate; 20. } 21. if (!ToReplay) 22. { 23. ArrayResize(RatesLocal, (m_Ticks.nRate - MemNRates)); 24. ArrayCopy(RatesLocal, m_Ticks.Rate, 0, 0); 25. CustomRatesUpdate(def_SymbolReplay, RatesLocal, (m_Ticks.nRate - MemNRates)); 26. dtRet = m_Ticks.Rate[m_Ticks.nRate].time; 27. m_Ticks.nRate = (MemNRates == 0 ? -1 : MemNRates); 28. m_Ticks.nTicks = MemNTicks; 29. ArrayFree(RatesLocal); 30. }else SetSymbolInfos(); 31. m_Ticks.bTickReal = true; 32. 33. return dtRet; 34. };
C_FilesTicks.mqh 源代码片段
不必担心行号。在这里,它们仅作为解释的参考,并不代表文件 C_FileTicks.mqh 中的实际行,因为它已经经过了修改,稍后将进行审查。现在,让我们了解一下读取真实分时报价时发生的情况。
当函数开始时,在第 10 行和第 11 行,我们有两个点,我们在其中临时存储已加载的分时报价位置的当前值以及代表这些分时报价的柱形。如果调用者指定不以分时报价作为回放的基础而是作为预先存在的柱形,则这些值将分别在第 27 行和第 28 行被替换。这确保系统保持完整,等待回放。
在第 12 行中,我们尝试打开包含数据的文件,在第 13 行中,我们读取文件中存在的所有(绝对所有)分时报价。如果读取成功,我们将加载所有分时报价。然而,在某些情况下,单位时间内的分时报价数可能会超过系统定义的最大值。到目前为止,我们对此无能为力。原因是我们还不知道将使用哪种类型的图表。但一旦所有分时报价都被完全读取,我们将确定图表类型并开始寻找解决方案。
现在到了有趣的部分。我们将分析一分钟的时间间隔。这是我们所做的。在第 15 行,我们进入一个循环,旨在构建一分钟的柱形,以便系统在需要时访问它们。这就是我们进行干预的地方。在第 17 行,我们调用负责构建柱形的函数。此函数将解释用于在柱形内产生运动的分时报价量。现在,请密切注意:当第 18 行的条件计算为真时,柱形数据将以与从柱形文件中读取相同的方式显示。这正是我们需要传递给模拟类的数据。您可以通过重新访问上一个主题并查看第 129 行来检查这一点。
您是否看到了我们需要的实现方案?当确定分时报价数超过程序内部定义的值时,我们必须采取行动,至少在实现的初始阶段是这样。我们稍后会做出与此相关的修改。
这部分很简单:检查分时报价数,如果有必要,指示模拟类执行随机游走,以确保移动的分时报价数正确。现在到了复杂的部分。报价模拟类会生成分时报价,但是我们需要尽可能简单的方法来修改加载的分时报价。但是,我们只会删除时间窗口内分时报价数超过内部定义或应用程序指定的限制的分时报价。除了这个问题之外,我们还有另一个更简单的问题。仅当数据用于回放时才应进行模拟。幸运的是,这很容易解决,因为调用者会告诉我们数据是否会用于回放。我们需要做的就是检查 ToReplay 值。小菜一碟!现在,让我们尝试解决困难的部分:有效地覆盖不必要的分时报价。为了实现这一点,我们将修改前面片段中显示的函数,并用另一个函数替换它。
首次尝试完成这项工作(开发新功能始终是一个反复试验的过程)如下图所示:
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 类的源代码片段
您可能会注意到这里添加了一些内容。此外,对某些命令的执行顺序进行了一些修改。然而,即使这个片段看起来像是解决方案,它仍然包含一个小缺陷。但在讨论这一点之前,让我们先分析一下 C_FileTicks 类中的这个代码片段如何有效地防止实际分时报价数超出内部定义的系统限制。
为了实现这一点,添加了第 6、7 和 11 行。这些行引入了我们实际需要的新变量。在第 15 行中,我们初始化第一个新变量来跟踪当前已加载的分时报价数。还添加了第 18 行和第 19 行,尽管它们仅用于初始化一些值。需要添加第 20 行来分配内存以存储将要模拟的分时报价。这里分配的内存仅在第 41 行释放。然而,这里有一个小缺陷,与第 31 行有关。我们将在代码的最终版本中修正该问题。坚持住,我们会到达那里。
在第 21 行我们发现一些非常重要的东西。在此片段中,原因可能并不明显。然而,如果将第21行的内容放在代码中的原始位置,它将无法实现其预期的功能。您可以通过查看上面的原始代码片段,并确定该片段中第 21 行最初出现的位置来进行比较。然而,当第 24 行执行时,BuildBar1Min 函数将无法建立必要的标准。这就是为什么执行顺序必须遵循这个新片段中显示的顺序。
无论如何,所描述的必要内容的实现包含在第 27 行和第 35 行之间。这个部分专门处理当一分钟柱形中的分时报价数超过预定义值时使用的分时报价模拟。该值在应用程序内部设置。
现在请注意以下几点。第 27 行包含两个检查,第一个确定数据是否以回放模式使用或作为预先存在的柱形使用。如果数据将用作预先存在的柱形,则无需进行模拟。第二个检查确定分时报价数是否超出预定义的限制。然而,由于第二个检查可能涉及负索引,因此第一次检查旨在防止此类问题。因此,在执行第二次检查时,我们确保它指的是刚刚记录了报价量的当前索引。
但请等一等。第 25 行中的条件确保仅在检测到新柱时才进行这些检查。那么,当我们已处于一个新柱形中时,我们如何分析记录的柱形数据呢?如果您考虑过这一点,那么恭喜您!这意味着你真正理解了代码。然而,你可能忽略了一个很小但至关重要的细节。直到第 39 行执行时,我们仍在查看刚刚关闭的柱形。实际转换到新柱仅发生在第 39 行。现在你明白为什么执行顺序必须精心构造了吗?
现在,让我们回到模拟过程。除第 51 行外,其他所有部分继续按照本系列前面的文章中描述的方式运行。第 51 行取决于分时报价模拟阶段发生的情况。所以请密切注意,因为这里存在一个缺陷,尽管它对测试此函数没有太大的损害。
在第 29 行中,我们将位移指针设置为要替换的柱形中分时报价的起始位置。然后,在第 30 行,我们初始化模拟系统。在第 31 行,我们开始实际模拟。注意,如果模拟失败,第 41 行将不会执行,这意味着分配的内存将不会被释放。虽然这对于测试目的而言只是一个小问题,但它稍后会得到纠正。一旦报价模拟成功,我们就在第 32 行进行函数调用。现在,请注意:第 32 行可以用 for 循环替换,但由于 MQL5 库例程可能针对快速数据传输进行了优化,因此最好在此处使用它而不是在循环中。在第 33 行中,我们更新位移值,使其在数据传输后立即指向新的位置。最后,在第 34 行,我们销毁了模拟器。
为了确保位置内存得到更新,以便我们在第 29 行正确指向预期位置,我们使用第 36 行来执行此更新。然而,整个过程存在一些小缺陷,我们将在下一篇文章中讨论。此外,我们还需要纠正另一个问题,这个问题几乎还是隐藏在模拟阶段但却存在。
结论
尽管在限制每分钟柱形的分时报价数方面取得了重大进展,但这些改进也带来了新的问题并暴露了其他问题。鉴于这篇文章已经相当密集,需要仔细研究才能完全掌握正在发生的事情,我不想引入更多的复杂性。然而,如果你想通过识别在实现此代码过程中出现的剩余缺陷来挑战自己,这里有一个提示:其中一个缺陷涉及应该模拟的最小分时报价数。这是一个值得解决的有趣问题。这个缺陷之所以变得明显,是因为我们现在想在计数超过给定值时模拟分时报价。花点时间想想,你应该能够理解这是如何发生的。另一个缺陷发生在将模拟值复制到分时报价数组时。当进行此复制时,回放柱形生成系统会受到损害。有时它可能会产生不合逻辑或不稳定的模式。此外,在某些时候分时报价会消失,从而阻止回放系统准确和正确地工作。
如果您想在阅读下一篇文章之前尝试解决这些问题,那太好了!这将是一次很好的训练,可以帮助您了解如何排除故障、开发和改进自己的解决方案。无论如何,在下一篇文章中,我将展示如何修复这些缺陷。这将会非常有趣。下一篇文章见。
演示系统缺陷的视频
很快,我将展示如何解决这个问题,尽管不是立即解决,因为它并不重要。此问题仅与加载或卸载创建的对象有关。这是一个绝佳的机会。如果您真的想测试您的编程技能,在我向您展示之前,先尝试解决这个问题。您不需要向我展示您的解决方案,但在阅读我的解决方案之前请尝试一下。这将帮助你评估你目前的学习水平。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/12231



