开发回放系统(第31部分):EA交易项目——C_Mouse类(五)
概述
在上一篇文章开发回放系统(第28部分):EA交易项目——C_Mouse类(IV)中,我们研究了如何更改、添加或调整类系统以测试新的资源或模型。通过这种方式,我们避免了主代码中的依赖性,使其始终可靠、稳定和健壮,因为只有在您创建的模型完全合适之后,任何新资源才会被推送到主系统中。开发回放/模拟系统的主要挑战,也许也是这项工作如此困难的原因,是创建尽可能接近(如果不完全相同的话)我们在真实账户上交易时使用的系统的机制。如果在使用真实帐户时无法使用相同的资源,那么创建一个系统来创建或运行重播/模拟是没有意义的。
查看前几篇文章中显示的C_Mouse类系统和分析类,您可以注意到,当在实时市场中使用时,无论是演示帐户还是真实帐户,计时器都会告诉您下一个柱何时开始。但当使用复制/模拟系统时,我们并不指望这一点。我们在这里收到一条消息。乍一看,这种违反对称性的行为似乎并不特别严重。但是,如果我们允许无用的东西堆积起来而不加以纠正或消除,我们最终会得到一堆完全无用的垃圾,这只会阻碍我们真正需要解决的问题的解决。我们需要一个计时器,它可以显示距离回放/模拟运行结束还有多少时间。乍一看,这可能是一个简单快捷的解决方案。许多人只是尝试适应并使用交易服务器使用的相同系统。但有一件事是很多人在考虑这个解决方案时没有考虑的:对于回放,甚至更多的是模拟,时钟的工作方式不同。发生这种情况有几个原因:
- 回放总是指向过去,因此,平台或计算机上的时钟决不是足够的时间指示。
- 在回放/模拟过程中,我们可以快进、暂停或倒退时间。后一种情况已经不可能了,而且这种情况在很久以前就已经发生了,原因多种多样,我们在以前的文章中也谈到过。我们仍然可以快进和暂停。因此,使用交易服务器上的时间不再合适。
为了让您了解我们实际处理的内容,以及在回放/模拟系统中设置计时器的复杂性,让我们看看图01。
图01-真实市场上的计时器
此图01显示了计时器是如何工作的,以指示新柱何时会出现在图表上。这是一个很短的图表。我们会不时收到OnTimer事件,它触发Update事件,该事件将更新计时器值。通过这种方式,我们可以显示在新柱出现之前还有多少时间。但是,为了让Update函数知道将显示什么值,它会向GetBarTime函数请求柱形显示所需的时间。GetBarTime使用TimeCurrent,它不在服务器上执行,但在服务器上修复本地时间。因此,我们可以找出自服务器在最后一个柱被触发以来已经过去了多少时间,并根据该值计算出在触发新柱之前还有多少时间。这是整个故事中最简单的部分,因为我们不必担心系统是否已经暂停或是否已经过了一定的时间。如果我们使用数据直接来自交易服务器的资产,就会发生这种情况。当我们使用回放/模拟系统时,事情会变得更加复杂。最大的问题不在于我们使用的是回放/模拟模式。问题是想出一种方法来绕过回放/模拟中的TimeCurrent调用,因为整个问题就是在这个时刻出现的。但这必须以最小的修改来完成,我们只想绕过TimeCurrent调用系统。但是,当使用服务器时,我们希望系统如图01所示工作。
幸运的是,有一种方法,即使用MetaTrader 5,可以让您以最小的麻烦和显著减少对已实现代码的修改或添加。这正是我们将在本文中讨论的内容。
计划
计划如何做到这一点可能是故事中最简单的部分。我们将简单地向系统发送由回放/模拟服务计算的时间值。这是最容易的部分,我们将只使用一个全局终端变量。在本系列文章的前面,我们已经了解了如何使用这些变量。在其中一篇文章中,我们引入了一个控件来通知服务用户想要做什么。许多人认为使用这些变量只能传输 double 型数据,但他们忘记了一个事实:二进制文件只是二进制文件,也就是说,它们在任何情况下都不代表除0和1之外的任何信息。所以我们可以通过这些全局终端变量传递任何信息。当然,假设我们可以以逻辑的方式排列比特,以便稍后重建信息。幸运的是,DateTime类型实际上是一个ulong值。我们使用64位,这意味着DateTime值需要8字节的信息,其中我们将有完整日期和时间的表示,包括年、月、日、小时、分钟和秒。这就是我们绕过TimeCurrent调用所需要的全部内容,因为它使用并返回8字节中的相同值。由于Double类型正好使用64位在平台内传输信息,因此它将是我们的解决方案。
但事情并没有那么简单,我们有一些问题,当我们解释这个系统是如何实现的时,这些问题会变得更加清楚。尽管该服务非常简单且易于构建,但它确实有一点复杂,这与所需的修改有关,该修改旨在为类提供可以在计时器中表示的信息。在构建结束时,我们将得到如图02所示的结果:
图02-通用定时器。
这个通用计时器将能够做出相应的反应,并且将是完全透明的,这样用户将拥有非常接近他们在演示/实时帐户上的回放/模拟体验。这就是创建这样一个系统的目的:这样体验是一样的,用户确切地知道如何行动,而不必从头开始重新学习如何使用系统。
实现
这里是这个系统中最有趣的部分,此时,我们将研究如何绕过TimeCurrent调用。用户不应该知道他是在使用重播还是在与服务器交互。要开始实现该系统,我们需要添加一个新的全局终端变量(从前面的主题中应该很清楚)。同时,我们需要通过Double变量传递DateTime信息的正确方法。为此,我们将使用Interprocess.mqh头文件。我们还添加了以下内容:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_SymbolReplay "RePlay" #define def_GlobalVariableReplay def_SymbolReplay + " Infos" #define def_GlobalVariableIdGraphics def_SymbolReplay + " ID" #define def_GlobalVariableServerTime def_SymbolReplay + " Time" #define def_MaxPosSlider 400 #define def_ShortName "Market " + def_SymbolReplay //+------------------------------------------------------------------+ union u_Interprocess { union u_0 { double df_Value; // Value of the terminal global variable... ulong IdGraphic; // Contains the Graph ID of the asset... }u_Value; struct st_0 { bool isPlay; // Indicates whether we are in Play or Pause mode... bool isWait; // Tells the user to wait... ushort iPosShift; // Value between 0 and 400... }s_Infos; datetime ServerTime; }; //+------------------------------------------------------------------+
在这里,我们设置将用于通信的全局终端变量的名称。接下来,我们定义一个变量,该变量将用于访问日期时间格式的数据。之后,我们转到C_Replay类,并将以下内容添加到类析构函数中:
~C_Replay() { ArrayFree(m_Ticks.Info); ArrayFree(m_Ticks.Rate); m_IdReplay = ChartFirst(); do { if (ChartSymbol(m_IdReplay) == def_SymbolReplay) ChartClose(m_IdReplay); }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); for (int c0 = 0; (c0 < 2) && (!SymbolSelect(def_SymbolReplay, false)); c0++); CustomRatesDelete(def_SymbolReplay, 0, LONG_MAX); CustomTicksDelete(def_SymbolReplay, 0, LONG_MAX); CustomSymbolDelete(def_SymbolReplay); GlobalVariableDel(def_GlobalVariableReplay); GlobalVariableDel(def_GlobalVariableIdGraphics); GlobalVariableDel(def_GlobalVariableServerTime); Print("Finished replay service..."); }
通过添加这一行,我们确保在关闭回放/模拟服务时,删除负责将时间值传递给计时器的全局终端变量。现在我们需要确保在正确的时间创建这个全局终端变量。当循环运行时,您不应该创建相同的全局变量,因为它可能在不同的时间启动。一旦系统被启用,它就应该已经可以访问全局终端变量的内容。在某些方面,我考虑使用类似于控制指标的创建系统。但是,由于目前EA可能出现在图表上,也可能不出现在图表中,那么至少目前,让我们让终端变量负责服务创建的计时器。通过这种方式,我们将至少对我们所做的事情有最低限度的充分控制。也许这种情况将来会改变。下面的代码显示了它的创建位置:
bool ViewReplay(ENUM_TIMEFRAMES arg1) { #define macroError(A) { Print(A); return false; } u_Interprocess info; if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) macroError("Asset configuration is not complete, need to declare the ticket value."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) macroError("Asset configuration not complete, need to declare the minimum volume."); if (m_IdReplay == -1) return false; if ((m_IdReplay = ChartFirst()) > 0) do { if (ChartSymbol(m_IdReplay) == def_SymbolReplay) { ChartClose(m_IdReplay); ChartRedraw(); } }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); Print("Waiting for [Market Replay] indicator permission to start replay ..."); info.ServerTime = m_Ticks.Info[m_ReplayCount].time; CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value); info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value); while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); #undef macroError }
在所有这些杂乱的代码行的中间是一个初始化变量值的点。这确保了系统已经有了可供使用的值,并允许我们检查回放/模拟是否真的同步。紧接着,我们有一个创建并初始化全局终端变量的调用。此初始化代码如下所示:
void CreateGlobalVariable(const string szName, const double value) { GlobalVariableDel(szName); GlobalVariableTemp(szName); GlobalVariableSet(szName, value); }
创建和初始化代码非常简单。当平台关闭并请求保存全局终端变量时,不会保存回放/模拟系统中使用的那些变量。我们不希望以后存储和读取这样的值。现在我们可以开始看计时器的读数了。但是,如果我们现在这样做,其中所包含的值将永远是一样的,不一定会给我们带来任何实际利益。它将显示为重放/模拟服务已暂停。但我们希望计时器在活动时移动,即在播放模式下。因此,我们将对TimeCurrent函数的要求进行建模,在该函数中我们可以获得在服务器上找到的相应时间和日期数据。为此,我们需要全局变量的值大约每秒变化一次。为这个过程设置一个计时器是正确的。但由于我们没有能力做到这一点,我们需要另一种方法来实现全局变量值的这种变化。
为了确定时间,我们还需要实现对C_Replay类的一些添加。它们可以在下面的代码中看到:
bool LoopEventOnTime(const bool bViewBuider) { u_Interprocess Info; int iPos, iTest, iCount; if (!m_Infos.bInit) ViewInfos(); iTest = 0; while ((iTest == 0) && (!_StopFlag)) { iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1); iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1); iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest); if (iTest == 0) Sleep(100); } if ((iTest < 0) || (_StopFlag)) return false; AdjustPositionToReplay(bViewBuider); Info.ServerTime = m_Ticks.Info[m_ReplayCount].time; GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); iPos = iCount = 0; while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag)) { iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0); CreateBarInReplay(true); while ((iPos > 200) && (!_StopFlag)) { if (ChartSymbol(m_IdReplay) == "") return false; GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value); if (!Info.s_Infos.isPlay) return true; Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks); GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value); Sleep(195); iPos -= 200; iCount++; if (iCount > 4) { iCount = 0; GlobalVariableGet(def_GlobalVariableServerTime, Info.u_Value.df_Value); Info.ServerTime += 1; GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); } } } return (m_ReplayCount == m_Ticks.nTicks); }
新添加的变量将帮助我们尝试近似新柱开始的时间。但是,由于我们可以将时间向前移动,因此需要重置“服务器”指定的值。这将在函数指向新的观测点时完成。即使我们在暂停模式下停留一段时间,我们也必须确保该值是适当的。真正的问题出现在我们即将设置计时器的时候。我们不能只获取任何值,该系统可以在计算机时钟上显示的实时时间之前或之后。因此,在这个早期阶段,我们正努力把握好时机。我们在柱形生成器中有一个内置计时器,我们将以它为指南。它大约每195毫秒产生一次分时,这使我们更接近5个单位的计数。由于我们从零开始,我们将检查计数器值是否大于4。当这种情况发生时,我们将把时间增加一个单位,即1秒,这个值将被放在一个全局终端变量中,以便系统的其他部分可以使用它。然后整个循环将重复。
这使我们能够知道新柱何时出现。是的,正是个想法,但在不同的时间可能会有细微的变化。随着它们的积累,时间变得不同步。我们需要的是让时间同步到可以接受的程度,而不是真正做到完美。在绝大多数情况下,我们可以非常接近。为此,我们将略微更改上述功能,以确保柱形和计时器之间的同步性增强。这些变化如下所示:
//... Sleep(195); iPos -= 200; iCount++; if (iCount > 4) { iCount = 0; GlobalVariableGet(def_GlobalVariableServerTime, Info.u_Value.df_Value); Info.ServerTime += 1; Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time); GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); } //...
你可能会觉得这太疯狂了。在某种程度上,我同意这对我来说是一个有点疯狂的举动。但通过添加这条特定的线路,我们将能够将同步保持在可接受的水平。如果该工具具有良好的流动性,交易是在相对较短的时间内创建的,最好不到1秒,那么我们将拥有一个相当同步的系统。它可能非常接近一个近乎完美的系统。但只有当该工具实际上具有足够的流动性时,才能实现这一点。这是怎么回事?让我们思考一下:每5个循环大约195毫秒,我们就会执行代码来更新计时器。因此,更新速率约为975毫秒,这意味着每个循环丢失25毫秒。但事实上,这个值并不是恒定的。有时可以大一点,有时可以小一点。您不应该试图使用新的sleep命令来设置同步,以迫使系统放慢速度来克服这种差异。乍一看,这应该有效。但随着时间的推移,这些微小的差异变得如此之大,以至于整个系统将不同步。为了解决这个问题,我们做了一些不同的事情。我们没有试图获得时间,而是使用柱形本身来确保同步性。执行CreateBarInReplay函数时,它始终指向当前分时。通过将该分时的时间值与全局变量中的时间值进行比较,在某些情况下,我们可以获得大于1的值,在这种情况下为1秒。如果此值较低,即Info.ServerTime变量的时间延迟了25毫秒,当前的分时时间值将用于校正差异,使计时器非常接近完美值。但是,正如我在解释之初已经报告的那样,如果我们使用的工具有足够的流动性,这种机制会自动调整系统。如果交易长时间停止,一次交易和另一次交易之间经过5-10分钟,那么计时系统的准确性将受到影响。这是因为每秒钟它将平均滞后25毫秒(这是一个平均值,而不是一个确切的值)。
现在我们可以进入下一部分。它位于C_Study.mqh头文件中,我们将在其中强制系统报告数据,以便正确估计新柱形何时会出现在图表上。
调整C_Study类
要开始修改,我们首先需要进行如下所示的更改:
void Update(void) { switch (m_Info.Status) { case eCloseMarket: m_Info.szInfo = "Closed Market"; break; case eAuction : m_Info.szInfo = "Auction"; break; case eInReplay : case eInTrading : m_Info.szInfo = TimeToString(GetBarTime(), TIME_SECONDS); break; case eInReplay : m_Info.szInfo = "In Replay"; break; default : m_Info.szInfo = "ERROR"; } Draw(); }
我们删除了划掉的代码行,并将其移动到更高的级别,以具有与在实体市场中使用系统时相同的状态和功能。通过这种方式,我们将事情分级,即,无论是在回放/模拟期间还是在模拟/真实帐户上,我们在这两种情况下都会有相同的行为。既然我们已经完成了,我们可以看看对GetBarTime函数代码所做的第一次修改:
const datetime GetBarTime(void) { datetime dt; u_Interprocess info; if (m_Info.Status == eInReplay) { if (!GlobalVariableGet(def_GlobalVariableServerTime, info.u_Value.df_Value)) return ULONG_MAX; dt = info.ServerTime; }else dt = TimeCurrent(); if (m_Info.Rate.time <= dt) m_Info.Rate.time = iTime(GetInfoTerminal().szSymbol, PERIOD_CURRENT, 0) + PeriodSeconds(); return m_Info.Rate.time - dt; }
这就是分析系统的神奇之处。在这个函数的旧版本中,这个调用设置了一个计时器。但是它不适合在重放/模拟系统中运行。因此,我们创建了一个绕过此调用的方法,以便无论在何处使用,系统都具有相同的概念和信息。因此,我们添加了额外的内容,这些内容将在代码位于其资产用于重播的图表上时使用。这并不是什么特别的事情。我们只需将报告的值放入全局变量中,并将其用作来自交易服务器的值。这就是我们真正绕过系统的地方。但当我们处于低时间框架图表时,我们仍然有一个问题,交易数量不足以正确构建柱形图。当使用的数据包含日内交易时尤其如此(对于某些资产来说,这是一种非常常见的情况)。我们将以失败告终。此错误表现为所呈现的信息和所显示的信息之间的间隙。这并不是因为服务停止更新。但指标没有显示任何信息,让我们蒙在鼓里,不知道真正发生了什么。即使没有交易,对于一些外汇货币对来说,当柱形打开时只发生一次交易时,这可能是很常见的。这可以在所附的文件中看到,在一天开始的时候,柱形开启的点和交易实际发生的点之间有一个缺口。在这个阶段,我们不会看到任何关于正在发生的事情的信息。这需要以某种方式加以纠正,要么是因为交易发生在系统预期的点之外,要么是由于所使用的资产正在参与拍卖。我们真的需要让事情尽可能接近现实。
为了解决这些问题,我们需要对类代码进行两次更改。我们现在就动手,第一个修改如下所示:
void Update(void) { datetime dt; switch (m_Info.Status) { case eCloseMarket: m_Info.szInfo = "Closed Market"; break; case eInReplay : case eInTrading : dt = GetBarTime(); if (dt < ULONG_MAX) { m_Info.szInfo = TimeToString(dt, TIME_SECONDS); break; } case eAuction : m_Info.szInfo = "Auction"; break; default : m_Info.szInfo = "ERROR"; } Draw(); }
上面显示的Update代码,尽管其外观奇怪而复杂,但比看起来更简单、更方便。我们有以下场景,如果我们在回放系统中,甚至在真实市场中,并且从GetBarTime函数接收到ULONG_MAX值,则我们将显示有关竞拍的消息。如果该值小于ULONG_MAX,并且在正常情况下始终如此,则将显示计时器值。
根据这些信息,我们可以返回到GetBarTime函数,并生成Update函数所需的数据来绘制正确的数据,以便用户了解情况。因此,可以在下面的代码中看到新的GetBarTime函数。
const datetime GetBarTime(void) { datetime dt; u_Interprocess info; int i0 = PeriodSeconds(); if (m_Info.Status == eInReplay) { if (!GlobalVariableGet(def_GlobalVariableServerTime, info.u_Value.df_Value)) return ULONG_MAX; dt = info.ServerTime; if (dt == ULONG_MAX) return ULONG_MAX; }else dt = TimeCurrent(); if (m_Info.Rate.time <= dt) m_Info.Rate.time = (datetime)(((ulong) dt / i0) * i0)) + i0; return m_Info.Rate.time - dt; }
这个漂亮的代码完全解决了我们的问题,至少目前是这样,因为我们将不得不对服务代码进行添加。让我们看看这里发生了什么。在实体市场的情况下,我们使用TimeCurrent函数,没有任何变化,这才刚刚开始。但当我们在回放系统中时,事情会以一种非常特殊的方式发生变化。因此,要注意理解系统是如何管理表示正在发生的事情的,无论回放或模拟数据发生了什么。如果服务将ULONG_MAX值放置到全局终端变量,或者如果找不到此变量,则GetBarTime函数应返回ULONG-MAX值。之后,Update方法将告诉我们我们处于竞拍模式,无法使计时器继续向前。现在是有趣的部分,它解决了我们的第二个问题。与在连接到交易服务器的仪器上使用该系统不同,在交易服务器上,我们将始终保持同步,当使用回放/模拟模式时,事情可能会失控,我们可能会遇到一些非常不寻常的情况。为了解决这个问题,我们使用这种计算方法,它适用于实体市场和我们正在开发的系统。在这个计算中,当需要知道当前柱的打开时间时,我们替换了旧的方法。因此,我们能够使用回放/模拟来解决这两个问题。
但是,我们需要返回C_Replay类,以便系统能够指示资产何时进入竞拍。这部分相对容易,因为我们只需要将ULONG_MAX值设置为全局终端变量。请看,这一解释相对简单,因为我们面前还有其他问题。但让我们看看这在实践中会是什么样子。
使C_Replay类适应通信系统
我们在C_Replay类中要做的第一件事是更改以下代码:
bool ViewReplay(ENUM_TIMEFRAMES arg1) { #define macroError(A) { Print(A); return false; } u_Interprocess info; if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_SIZE) == 0) macroError("Asset configuration is not complete, it remains to declare the size of the ticket."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_TRADE_TICK_VALUE) == 0) macroError("Asset configuration is not complete, need to declare the ticket value."); if (SymbolInfoDouble(def_SymbolReplay, SYMBOL_VOLUME_STEP) == 0) macroError("Asset configuration not complete, need to declare the minimum volume."); if (m_IdReplay == -1) return false; if ((m_IdReplay = ChartFirst()) > 0) do { if (ChartSymbol(m_IdReplay) == def_SymbolReplay) { ChartClose(m_IdReplay); ChartRedraw(); } }while ((m_IdReplay = ChartNext(m_IdReplay)) > 0); Print("Waiting for [Market Replay] indicator permission to start replay ..."); info.ServerTime = ULONG_MAX; info.ServerTime = m_Ticks.Info[m_ReplayCount].time; CreateGlobalVariable(def_GlobalVariableServerTime, info.u_Value.df_Value); info.u_Value.IdGraphic = m_IdReplay = ChartOpen(def_SymbolReplay, arg1); ChartApplyTemplate(m_IdReplay, "Market Replay.tpl"); CreateGlobalVariable(def_GlobalVariableIdGraphics, info.u_Value.df_Value); while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(m_IdReplay) != "")) Sleep(750); return ((!_StopFlag) && (ChartSymbol(m_IdReplay) != "")); #undef macroError }
请注意,我们已经删除了一行,并将其替换为另一行。有了这一更改,当回放/模拟服务启动并且资产在图表上运行时,将显示有关竞拍的消息。它将指示系统正在正常通信。这是容易的部分,现在让我们进入困难的部分。当服务已经推出时,我们可能需要了解资产是否在竞拍模式。一项资产可以被竞拍,原因有很多,所有这些都可能是完全出乎意料的。可能会有更大的变化,或者订单簿可能已经崩溃并被完全清除,等等。该资产被竞拍的原因并不重要,重要的是当它被拍卖时会发生什么。有一些具体的规则决定了拍卖的进行方式,但对我们来说最重要的规则是:资产拍卖的最短时间是多少?这正是重点所在。如果这个时间小于1分钟,那么真实市场中的资产肯定可以进入和退出交易,而回放/模拟系统无法检测到它。或者更确切地说,不可能注意到这种变化,因为它总是发生在可用于定义酒吧时间的最短时间内。
在回放/模拟系统中,可以使用最简单的机制来确定资产已被拍卖,即检查一个柱和另一个柱之间的时间差。如果这一差异超过1分钟,我们必须通知用户该资产刚刚进入拍卖流程,从而在整个期间暂停该资产。这种类型的机制在未来将是有用的。目前,我们将只处理其制定和实施问题,而将其他问题留待以后处理。让我们看看如何解决这个问题。这可以在以下代码中看到:
bool LoopEventOnTime(const bool bViewBuider) { u_Interprocess Info; int iPos, iTest, iCount; if (!m_Infos.bInit) ViewInfos(); iTest = 0; while ((iTest == 0) && (!_StopFlag)) { iTest = (ChartSymbol(m_IdReplay) != "" ? iTest : -1); iTest = (GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value) ? iTest : -1); iTest = (iTest == 0 ? (Info.s_Infos.isPlay ? 1 : iTest) : iTest); if (iTest == 0) Sleep(100); } if ((iTest < 0) || (_StopFlag)) return false; AdjustPositionToReplay(bViewBuider); Info.ServerTime = m_Ticks.Info[m_ReplayCount].time; GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); iPos = iCount = 0; while ((m_ReplayCount < m_Ticks.nTicks) && (!_StopFlag)) { iPos += (int)(m_ReplayCount < (m_Ticks.nTicks - 1) ? m_Ticks.Info[m_ReplayCount + 1].time_msc - m_Ticks.Info[m_ReplayCount].time_msc : 0); CreateBarInReplay(true); while ((iPos > 200) && (!_StopFlag)) { if (ChartSymbol(m_IdReplay) == "") return false; GlobalVariableGet(def_GlobalVariableReplay, Info.u_Value.df_Value); if (!Info.s_Infos.isPlay) return true; Info.s_Infos.iPosShift = (ushort)((m_ReplayCount * def_MaxPosSlider) / m_Ticks.nTicks); GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_Value); Sleep(195); iPos -= 200; iCount++; if (iCount > 4) { iCount = 0; GlobalVariableGet(def_GlobalVariableServerTime, Info.u_Value.df_Value); if ((m_Ticks.Info[m_ReplayCount].time - m_Ticks.Info[m_ReplayCount - 1].time) > 60) Info.ServerTime = ULONG_MAX; else { Info.ServerTime += 1; Info.ServerTime = ((Info.ServerTime + 1) < m_Ticks.Info[m_ReplayCount].time ? Info.ServerTime : m_Ticks.Info[m_ReplayCount].time); }; GlobalVariableSet(def_GlobalVariableServerTime, Info.u_Value.df_Value); } } } return (m_ReplayCount == m_Ticks.nTicks); }
请注意,我们检查了一个分时和下一个分时之间的时间差。如果这一差异大于60秒,即大于最短的柱形创建时间,我们将报告这是一个“拍卖呼叫”,整个回放/模拟系统将指示拍卖。如果时间差小于或等于60秒,则意味着资产仍然处于活动状态,并且计时器应该如本文所述启动。至此,我们完成了当前阶段的工作。
结论
今天,我们研究了如何以一种完全实用、稳健、可靠和有效的方式添加一个计时器来指示新柱形的外观。我们经历过一些看似不可能的时刻,但事实上没有什么是不可能的。也许克服起来会有点困难,但我们总能找到合适的方法来解决问题。这里的要点是表明,您应该始终尝试创建一个适用于所有需要的情况的解决方案。如果这种模型的应用也需要使用完全不同的方法,那么为用户甚至我们自己编程(学习可能性)是没有意义的;这太令人沮丧了。因此,永远记住:如果你需要做某事,那么它需要正确地完成,而不是让一切在理论上可行,而是在实践中表现出完全不同的行为。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11378