English Русский Español Deutsch 日本語 Português
preview
开发回放系统(第 40 部分):启动第二阶段(一)

开发回放系统(第 40 部分):启动第二阶段(一)

MetaTrader 5示例 | 2 八月 2024, 10:19
113 0
Daniel Jose
Daniel Jose

概述

在上一篇文章中 开发回放系统(第 39 部分):铺平道路(三)中,我们研究了如何组织进程之间的通信以实现某些操作。目前我们正在使用 EA 和指标,但根据需要我们将能够扩展这些工具。

这种通信方式的主要优点是我们可以以模组形式构建我们的系统。您可能还不了解我们真正能做什么。最终,我们将能够通过比使用全局终端变量更“安全”的渠道在进程之间交换信息。

为了实现这一点并展示如何将我们的回放/模拟器系统集成到模块化系统中,让我们退后一步,然后向前一步。在本文中,我们将删除使用 EA 鼠标的研究系统。我们会将同一系统转换成指标,以便与我们将要实现的下一步兼容。

如果我们成功了,那么此后显然我们可以做得更多。指标很可能不会在这里结束,因为我们稍后需要做其他事情,但最终它会完成。最大的优点是我们能够更新或修改模块而不影响主系统的其余部分。


开始编码

我们的代码将根据我们目前已有的部分构建。我们将进行最小的更改,以便使 EA 代码成为一个指标。

首先要做的是检查 InterProcess.mqh 头文件中的代码。完整代码如下所示。

InterProcess.mqh 文件代码

01. #property copyright "Daniel Jose"
02. //+------------------------------------------------------------------+
03. #define def_SymbolReplay                    "RePlay"
04. #define def_GlobalVariableReplay            def_SymbolReplay + "_Infos"
05. #define def_GlobalVariableIdGraphics        def_SymbolReplay + "_ID"
06. #define def_GlobalVariableServerTime        def_SymbolReplay + "_Time"
07. #define def_MaxPosSlider                    400
08. //+------------------------------------------------------------------+
09. union u_Interprocess
10. {
11.     union u_0
12.     {
13.             double  df_Value;  // Value of the terminal global variable...
14.             ulong   IdGraphic; // Contains the Graph ID of the asset...
15.     }u_Value;
16.     struct st_0
17.     {
18.             bool    isPlay;     // Indicates whether we are in Play or Pause mode...
19.             bool    isWait;     // Tells the user to wait...
20.             bool    isHedging;  // If true we are in a Hedging account, if false the account is Netting...
21.             bool    isSync;     // If true indicates that the service is synchronized...
22.             ushort  iPosShift;  // Value between 0 and 400...
23.     }s_Infos;
24.     datetime        ServerTime;
25. };
26. //+------------------------------------------------------------------+
27. union uCast_Double
28. {
29.     double   dValue;
30.     long     _long;                    // 1 Information
31.     datetime _datetime;                // 1 Information
32.     int      _int[sizeof(double)];     // 2 Informations
33.     char     _char[sizeof(double)];    // 8 Informations
34. };
35. //+------------------------------------------------------------------+

在这段代码中我们真正感兴趣的是第 27 行到第 34 行之间。这里我们有一个可以在进程间传输信息的联合(union)。这个联合现在具有我们从一开始就需要并使用的某些类型的数据。我们的想法是使用我们在上一篇文章中已经讨论过的数据传输,但是以一种实用和有针对性的方式。

这些代码行已添加到 InterProcess.mqh 文件中。让我们继续,但是现在我们将对代码进行其他更改。它们必须在类文件 C_Study.mqh 中实现。

首先,我们将在类得开始时修改一些内容。它们如下所示:

class C_Study : public C_Mouse
{
        protected:
                enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
        private :
                enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};

划掉的行已从代码的私有部分中删除,现在位于受保护部分。私有部分表示其内容不能从类外部访问,但枚举并非如此:无论定义部分如何,它们始终被视为公有的。

以下是实际发生变化的部分:

//+------------------------------------------------------------------+
void Update(const eStatusMarket arg)
void Update(void)
{
   datetime dt;
                                
   switch (m_Info.Status = (m_Info.Status != arg ? arg : m_Info.Status))
   switch (m_Info.Status)
   {
      case eCloseMarket : m_Info.szInfo = "Closed Market";
         break;
      case eInReplay    :
      case eInTrading   :
         if ((dt = GetBarTime()) < ULONG_MAX)
         {
            m_Info.szInfo = TimeToString(dt, TIME_SECONDS);
            break;
         }
      case eAuction     : m_Info.szInfo = "Auction";
         break;
      default           : m_Info.szInfo = "ERROR";
   }
   Draw();
}
//+------------------------------------------------------------------+
void Update(const MqlBookInfo &book[])
{
   m_Info.Status = (ArraySize(book) == 0 ? eCloseMarket : (def_InfoTerminal.szSymbol == def_SymbolReplay ? eInReplay : eInTrading));
   for (int c0 = 0; (c0 < ArraySize(book)) && (m_Info.Status != eAuction); c0++)
      if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Info.Status = eAuction;
   this.Update();
}
//+------------------------------------------------------------------+

所有划掉的代码行都已从类中删除,并替换为新的突出显示的代码行

为什么会发生这种情况?要理解这一点,您需要认识到,将“EA 交易”代码转变为指标将导致运行方式的改变。

有问题的代码与使用鼠标作为研究工具有关。

当我们以这种方式编写代码以使 EA 执行所有操作时,我们是可以这样做的,但当我们用指标做同样的事情时,我们必须采用不同的方式。这是因为指标存在于一个线程中,如果它受到某种影响,它将会影响图表中存在的所有其他指标。

但我们不希望指标在某些时候被阻塞。正因为如此,我们必须以稍微不同的方式处理事情。

我们来看看下面的指标代码,这是完整的代码。

指标源代码

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property description "This is an indicator for graphical studies using the mouse."
004. #property description "This is an integral part of the Replay / Simulator system."
005. #property description "However it can be used in the real market."
006. #property version "1.40"
007. #property icon "/Images/Market Replay/Icons/Indicators.ico"
008. #property link "https://www.mql5.com/en/articles/11624"
009. #property indicator_chart_window
010. #property indicator_plots 0
011. #property indicator_buffers 1
012. //+------------------------------------------------------------------+
013. #include <Market Replay\Auxiliar\Study\C_Study.mqh>
014. #include <Market Replay\Auxiliar\InterProcess.mqh>
015. //+------------------------------------------------------------------+
016. C_Terminal *Terminal  = NULL;
017. C_Study    *Study     = NULL;
018. //+------------------------------------------------------------------+
019. input C_Study::eStatusMarket user00 = C_Study::eAuction;   //Market Status
020. input color user01 = clrBlack;                             //Price Line
021. input color user02 = clrPaleGreen;                         //Positive Study
022. input color user03 = clrLightCoral;                        //Negative Study
023. //+------------------------------------------------------------------+
024. C_Study::eStatusMarket m_Status;
025. int m_posBuff = 0;
026. double m_Buff[];
027. //+------------------------------------------------------------------+
028. int OnInit()
029. {
030.    if (!CheckPass("Indicator Mouse Study")) return INIT_FAILED;
031.            
032.    Terminal = new C_Terminal();
033.    Study = new C_Study(Terminal, user01, user02, user03);
034.    if ((*Terminal).GetInfoTerminal().szSymbol != def_SymbolReplay)
035.    {
036.            MarketBookAdd((*Terminal).GetInfoTerminal().szSymbol);
037.            OnBookEvent((*Terminal).GetInfoTerminal().szSymbol);
038.            m_Status = C_Study::eCloseMarket;
039.    }else
040.            m_Status = user00;
041.    SetIndexBuffer(0, m_Buff, INDICATOR_DATA);
042.    ArrayInitialize(m_Buff, EMPTY_VALUE);
043.    
044.    return INIT_SUCCEEDED;
045. }
046. //+------------------------------------------------------------------+
047. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
048. {
049.    m_posBuff = rates_total - 4;
050.    (*Study).Update(m_Status);      
051.    
052.    return rates_total;
053. }
054. //+------------------------------------------------------------------+
055. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
056. {
057.    (*Study).DispatchMessage(id, lparam, dparam, sparam);
058.    SetBuffer();
059.    
060.    ChartRedraw();
061. }
062. //+------------------------------------------------------------------+
063. void OnBookEvent(const string &symbol)
064. {
065.    MqlBookInfo book[];
066.    C_Study::eStatusMarket loc = m_Status;
067.    
068.    if (symbol != (*Terminal).GetInfoTerminal().szSymbol) return;
069.    MarketBookGet((*Terminal).GetInfoTerminal().szSymbol, book);
070.    m_Status = (ArraySize(book) == 0 ? C_Study::eCloseMarket : C_Study::eInTrading);
071.    for (int c0 = 0; (c0 < ArraySize(book)) && (m_Status != C_Study::eAuction); c0++)
072.            if ((book[c0].type == BOOK_TYPE_BUY_MARKET) || (book[c0].type == BOOK_TYPE_SELL_MARKET)) m_Status = C_Study::eAuction;
073.    if (loc != m_Status) (*Study).Update(m_Status);
074. }
075. //+------------------------------------------------------------------+
076. void OnDeinit(const int reason)
077. {
078.    if (reason != REASON_INITFAILED)
079.    {
080.            if ((*Terminal).GetInfoTerminal().szSymbol != def_SymbolReplay)
081.                    MarketBookRelease((*Terminal).GetInfoTerminal().szSymbol);
082.            delete Study;
083.            delete Terminal;
084.    }
085. }
086. //+------------------------------------------------------------------+
087. bool CheckPass(const string szShortName)
088. {
089.    IndicatorSetString(INDICATOR_SHORTNAME, szShortName + "_TMP");
090.    if (ChartWindowFind(ChartID(), szShortName) != -1)
091.    {
092.            ChartIndicatorDelete(ChartID(), 0, szShortName + "_TMP");
093.            Print("Only one instance is allowed...");
094.            
095.            return false;
096.    }
097.    IndicatorSetString(INDICATOR_SHORTNAME, szShortName);
098.    
099.    return true;
100. }
101. //+------------------------------------------------------------------+
102. inline void SetBuffer(void)
103. {
104.    uCast_Double Info;
105.    
106.    m_posBuff = (m_posBuff < 0 ? 0 : m_posBuff);
107.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
108.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
109.    m_Buff[m_posBuff + 1] = Info.dValue;
110.    Info._int[0] = (*Study).GetInfoMouse().Position.X;
111.    Info._int[1] = (*Study).GetInfoMouse().Position.Y;
112.    m_Buff[m_posBuff + 2] = Info.dValue;
113.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
114.    m_Buff[m_posBuff + 3] = Info.dValue;
115. }
116. //+------------------------------------------------------------------+

如果你查看指标源代码并将其与文章中提供的代码进行比较 开发回放系统(第 39 部分):铺平道路(三),可以看到很多类似的部分。

如果您一直在关注有关回放/模拟系统的这一系列文章,那么您应该可以毫无问题地理解所提到的大部分代码。然而,有些事情值得更详细地解释,因为许多人仅通过查看代码还无法弄清楚发生了什么。我希望这种方法的动机和思路尽可能清晰,因为正确理解这些和其他源代码对于真正想了解系统如何实现的人来说非常重要。

第 2 行和第 8 行之间包含随时间而改变的信息。此信息将代码链接到创建、修改或提交它的文章。此处提供的信息是为了指导经验不足的用户。

在第 9 行和第 11 行中,我们告诉编译器指标的具体运行方式。所有指标代码必须至少有一行类似第 9 行和一行类似于第 10 行。全部第 11 行告诉我们,我们将使用一个缓冲区,并且任何想要通过 CopyBuffer 函数读取某些内容的进程都可以通过访问指标的内部缓冲区来实现。

第 13 行和第 14 行包含告诉编译器要使用哪些头文件。虽然我们已经对这些文件做了一些修改,但我们还需要做其他修改。现在,我们可以使用提供的头文件而不会出现任何问题。

在第 16 和 17 行,我们声明两个内部全局指针变量,这些指针将帮助我们获得对类的控制访问。通常,全局变量默认不用初始化,因为编译器会自动执行此操作。但在这里我想澄清一下,虽然这些变量(指针)不指向任何特定的内存位置,但如果你明确声明它们,那么我们必须记住,编译器默认会隐式地这样做。

第 19 行和第 22 行之间是外部设置变量。从现在开始,我建议您不要再将这些变量视为用户访问或设置指标的一种方式。如果我们这样想,我们的视野就会变得非常有限,无法想象在它们的帮助下可以完成的一切。

任何程序都不应被视为纯粹而简单的。您应该始终将程序视为一种函数,因为它接收信息、以某种方式处理信息并产生一些输出。编程中哪个元素具有相同的行为?函数。因此,让我们从更开阔的视角来看待事物。让我们停止想象或梦想各种可能性,开始看清事物的本质:无论我们能做什么,程序都是函数,程序就是函数。

在第 19 行,我们有一个参数,当用户将指标放置在图表上时,该参数没有意义。此参数并非用于由用户配置;它存在是为了让其他程序可以使用我们创建的这个指标。这怎么可能?现在是不是很难理解🤔?现在不用担心这个,请注意,这是我们创建回放/模拟器系统第二阶段的第一篇文章。下面的文章将向您展示一些内容,以帮助您理解第 19 行存在的原因,所以不要惊慌。

另一方面,第 20-22 行是实际上有意义的参数,因为它们用于调整鼠标在图表上使用的颜色。

现在我们有一个问题:为什么我们需要第 24 行?为什么我们有一个与第 19 行相同类型的全局变量?原因是第 19 行的参数实际上不被视为变量,而是常量。因此,我们需要声明一个在指标运行期间使用和设置的变量。这就是第 24 行声明的原因。

第 25 行声明的变量是一种内存形式,但其目的比普通内存更复杂一些。我们很快会回到这一部分。在第 26 行,我们声明了缓冲区,它将作为指标的输出。注意:我们声明了一个缓冲区,但它还不起作用。如果您尝试在其中执行任何操作,就会出现运行时错误警告,如果您尝试读取它,也将无法执行此操作。

从现在开始,我们继续进行指标与 MetaTrader 5 的交互。请记住,无论我们的代码如何构建以及它应该执行什么工作,指标都会对 MetaTrader 5 报告的事件做出反应。它总是会对给定的事件做出反应。其中一些可能是交互事件,其他可能是活动事件。

其中包括 Init 事件,当该事件触发时,会生成对 OnInit 函数的调用。该函数从第 28 行开始,不接受任何参数,但我们可以使用第 19 行和第 22 行之间声明的交互参数作为 OnInit 函数中的参数。

让我们看看 OnInit 函数是如何工作的。我们要做的第一件事就是进行登记,这发生在我们调用该函数的第 30 行。然后执行线程转到到第 87 行。让我们进行分析,以了解此登记是什么,以及为什么如果 OnInit 函数不起作用,它会返回指标初始化错误。

第 87 行的函数接受一个参数 - 指标的短名称。这个名称必须始终定义,以确保在某些条件下运行。我们做的第一件事(发生在第 89 行)是给指标一个临时名称。一旦指标获得临时名称,我们就可以继续此登记阶段。我们接下来要做的是在图表上搜索我们的指标,这是在第 90 行完成的。如果指标已经存在于图表上,MetaTrader 5 将准确指示其位置。如果不是,我们最终得到的值是 -1。

因此,第 90 行的检查告诉我们指标在图表上是否存在。如果指标存在,则执行第 92 行,这将删除临时指标,即我们试图放置在图表上的指标。接下来,我们打印一条有关此情况的警告消息,并在第 95 行返回给调用者并通知它登记失败。

如果第 90 行的测试报告没有这个指标,我们将执行第 97 行,这将告诉 MetaTrader 5 该指标的真实名称。在第 99 行,我们返回给调用者并通知它指标可以成功放置在图表上。但这并不意味着它真的已经被放置在图表上。这只是 MetaTrader 5 在图表上未发现它的信息。现在它可以被启动了。

这将带我们回到第 30 行,我们实际上从这里开始启动并运行指标。如果成功,我们将转到第 32 行。一旦发生故障,MetaTrader 5 将触发事件来调用第 76 行的函数。但是因为我们在第 30 行告知了失败,所以一旦执行第 78 行的代码,它就会阻止第 80 行和第 83 行之间的代码执行。这对于避免指标输出故障非常重要。

但是让我们回到第 32 行,这是我们真正开始使用指标的地方。在第 32 行,我们启动一个指针来访问 C_Terminal 类。从此时起,我们将能够使用 C_Terminal 类的函数。紧接着,在第 33 行,我们运行一个指针来访问 C_Study 类。这个类将允许我们使用鼠标并生成图形分析。由于我们需要访问 C_Terminal 类,因此我们需要执行以下一系列操作。

在第 34 行,我们有一个新的测试,它将确定我们是否正在使用回放/模拟器资产或其他资产。如果这是一个回放/模拟器资产,则第 19 行声明的指标输入配置参数的内容将需要放在我们的全局变量中。这是在第 40 行完成的。

如果第 34 行表明我们正在处理不同类型的资产,那么第 36 至 38 行将被执行。这些行初始化订单簿的使用,告诉 MetaTrader 5 我们想要接收订单簿中发生的事件。此外,第 38 行我们指出市场已关闭。此情况是暂时的,因为我们将从订单簿中获得有关正确信息的指导。

现在来看第 41 行,这对于指标来说非常重要。请记住,在第 11 行和第 26 行声明缓冲区并不能保证它可以被访问,并且尝试访问它将产生运行时错误。如果没有第 41 行,该指标几乎完全无用。在本文中,我们讨论了索引、矩阵和它们的用处。通常,我们从索引部分中的零值开始。在数组部分,我们指出哪个变量用作缓冲区。至于第三个部分,它告诉我们缓冲区的用途,我们可以使用 ENUM_INDEXBUFFER_TYPE 枚举值之一。但是,由于我们要存储数据,因此我们使用 INDICATOR_DATA。但在此特定代码中可以使用任何其他类型。

完成后,我们转到第 42 行。这看起来似乎没有必要,但我想保持缓冲区清洁,这正是我们现在正在做的事情。我们还可以在这里添加其他内容,但我认为没有必要。我们想要为鼠标数据创建某种缓存,正如您将在下一篇文章中看到的那样。

这是对如何初始化指标的解释。现在我们需要知道如何对 MetaTrader 5 触发的事件做出反应。为了解释这一部分,我们首先来看一下 OnBookEvent 函数中的订单簿事件代码。该函数从第 63 行开始,每次订单簿中发生某些事情时,MetaTrader 5 都会调用该函数。

如果仔细观察,就会发现其内容位于第 70 行和第 72 行之间,与 C_Study 类的内容几乎没有区别。为什么我们不把这个过程留在 C_Study 类中?现在很难解释为什么,但是相信我,原因以后会变得更加清晰。当然,如果您关注并研究过这一系列文章,那么这一部分对您来说可能已经很熟悉了。该函数包含了额外的代码。

在第 66 行,我们声明了一个局部临时变量,它将临时存储市场状态,因为它可能会在第 70 行或第 72 行发生变化。如果发生这种情况,我们应该调用第 73 行来方便地更新图表信息。在我们查看图表更新部分之前,让我们快速浏览一下其他代码行。

在第 68 行,我们将订单簿事件过滤为仅包含图表上资产实际一部分的事件。MetaTrader 5 不会做出这样的区分,因此如果市场观察窗口中的任何资产触发订单簿事件,MetaTrader 5 就会触发订单簿事件。因此,我们必须立即作出区分。

此后,在第 69 行,我们记录订单簿中包含的数据,以便我们可以根据需要进行分析。然后,在第 70 行,我们通过参考实际市场来告诉指标市场是否关闭(或开放),即交易服务器是否在允许的交易窗口内。为了找出答案,我们检查订单簿数组是否为空。

现在,在第 71 行,我们进入一个循环,逐点检查数组,看是否存在表明有资产正在拍卖的位置。如果第 72 行的两个条件之一为真,则有资产处于拍卖状态。由于我们不知道订单簿中的出价和要价的具体位置,因此我们在第 71 行使用循环。但是,如果我们使用的资产具有固定的出价和要价位置,我们可以通过删除第 71 行并简单地指定数组中出价和 要价所在的点来跳过循环。如果其中任何一个表明该资产正在拍卖,则状态将会更新。

让我们回到更新部分。这些信息并不总是被传输。仅当第 66 行存储的值与新状态不同时,我们才会调用市场状态更新。

在第 73 行这样做的原因是,当资产在交易窗口期间进入拍卖或处于暂停状态时,只有订单簿事件才会包含此信息和对状态变化的正确视图。如果我们等待其他事情发生,我们可能会陷入不知道发生了什么的情况。

状态更新的第二个点正是我们要研究的事件:OnCalculate 函数。

每次更新价格或图表上出现新柱形时,MetaTrader 5 都会触发一个事件。该事件调用指标代码中的OnCalculate函数。当然,我们通常总是希望这段代码能够尽快执行,以免错过我们感兴趣的事件。与订单簿事件在发生状态变化时立即报告不同,OnCalculate 事件仅当有事件影响价格时才会生效。

然而,我们可以看到,在第 49 行,我们正在存储和调整一个未使用的值。如果我们有一个函数调用,我们需要使用给定的值,但是为什么会发生这种情况🤔?再次强调,我们需要尽快执行 OnCalculate 函数,并且每次价格变动或出现每个新柱形时更新缓冲区是没有意义的。不要忘记,我们不是在使用图表上的价格或条形图,而是使用鼠标。

因此,真正负责更新缓冲区的是 MetaTrader 5 触发的下一个事件:OnChartEvent 函数。

这个从第 55 行开始的 OnChartEvent 函数非常简单明了。第 57 行的函数包含一个调用,以确保 study 类正确处理事件。这反过来会完成完全处理鼠标事件所需的内部工作。您可以在文章中查看更多详细信息 “开发回放系统(第 31 部分):EA 项目 — C_Mouse 类 (五)”。不管怎样,我们都将调用该过程,并且执行线程将在第 102 行停止。

在继续讨论第 102 行之前,我们先来看一下前面在解释初始化过程时提到的第 76 行的事件。现在让我们看看当指标正确初始化时相同的代码将如何表现。当发生这种情况时,将执行第 80 至 83 行。在第 80 行,我们将检查该指标用于哪种资产。原因是,如果该资产是可以监控订单簿事件的资产之一,我们必须告诉 MetaTrader 5 我们不再希望接收此类事件;这在第 81 行完成。第 82 行和第 83 行将简单地销毁该类,确保指标正确地从图表中删除。

由于第 102 行以后的代码基本上就是我们在本文中想要和需要理解的内容,因此它值得在它自己的主题中得到特别关注。它是如下开始的:


SetBuffer:奇迹发生的地方

如果你一直在关注本系列的文章,你可能已经注意到,在文章 “开发回放系统(第 39 部分):铺平道路(三)"中,有一整节专门用于解释如何缓冲值以将指标信息发送到另一个进程。

在这一部分中,我解释了信息必须放在非常具体的点,但最重要的是,我强调信息必须受到双重控制。您可能没有在那篇文章中意识到我们能做什么,但关键是我们可以比迄今为止许多人所做的走得更远。

如果您查看第 102 行到第 115 行之间的代码,您会发现我一切都是按照自己的方式进行的。但我为什么要这么做呢?

为了避免需要向下滚动页面来理解解释,我会把代码行放在更靠近解释的地方。这将使我们更容易理解这个想法。

102. inline void SetBuffer(void)
103. {
104.    uCast_Double Info;
105.    
106.    m_posBuff = (m_posBuff < 0  ? 0 : m_posBuff);
107.    m_Buff[m_posBuff + 0] = (*Study).GetInfoMouse().Position.Price;
108.    Info._datetime = (*Study).GetInfoMouse().Position.dt;
109.    m_Buff[m_posBuff + 1] = Info.dValue;
110.    Info._int[0] = (*Study).GetInfoMouse().Position.X;
111.    Info._int[1] = (*Study).GetInfoMouse().Position.Y;
112.    m_Buff[m_posBuff + 2] = Info.dValue;
113.    Info._char[0] = ((*Study).GetInfoMouse().ExecStudy == C_Mouse::eStudyNull ? (char)(*Study).GetInfoMouse().ButtonStatus : 0);
114.    m_Buff[m_posBuff + 3] = Info.dValue;
115. }

在第 49 行,我们指出我们将把四个双精度值放入缓冲区。但是缓冲区具体在什么位置呢?上面提到的文章中已经讨论过这一点。我们将使用距离 rates_total 正好四个点的位置。如果我们处于回放/模拟器模式,我们可以从零位置开始,因为我们是启动该过程的人。初始化在第 25 行完成。

为了使所需的转换更容易,我们使用第 104 行来声明一个局部变量。现在请注意第 106 行所需的检查。它可以防止可能发生的异常情况,这主要是由于第 49 行属于 OnCalculate 函数。对于真实资产而言,我们不太可能在 m_posBuff 中看到负值。但在回放或模拟资产中,我们可能会得到负值。第 106 行通过使 m_posBuff 指向正确的位置解决了这个问题。

如果 m_posBuff 指向小于零的索引,指标将会中断。请注意,我们在第 107 行开始更新指标数据。这一点是最简单的。现在看最困难的部分,见第 109、112 和 114 行。在这些行中,我们始终从最初计算的位置开始缓冲其他值。

我们可以在其他地方发布这些相同的数值吗?。我们可以按不同的顺序排列它们吗?是的。但是如果您改变了顺序,那么您将必须解决,或者更正未来的代码才能获得正确的信息。如果顺序发生变化,值的计算方式将有所不同,并且必须在这里或基于该指标创建的任何其他代码中进行调整。这也涉及我们想要构建的任何其他代码。

因此,开始的时候就制定固定的方法将会很好。否则,一切都会变得非常混乱,我们将无法充分利用 MetaTrader 5。因此,这里使用的方法是:

  • 首先在位置零,我们把鼠标线所在的价格保存起来。
  • 在位置一,我们将保存鼠标指针所在的时间值。
  • 在位置二,我们将存储有关屏幕位置的值,即 X 和 Y 坐标。首先是 X 值,然后是 Y 值。
  • 在位置三,我们将存储额外的元素,鼠标按钮的当前状态,它必须放在最低有效字节 (LSB)。也就是说,在第零字节中。

除非以任何方式更改或更新,否则此处使用的方法将适用于该指标,直至其使用寿命结束。


结论

还有一些其他问题我们需要解决。所以,我希望你跟随文章,这样你就能理解代码中写的内容,因为附件中的代码可能与文章中解释的不同。原因很简单:附加的代码稳定并且运行完美,而文章中提供的代码可能会发生一些变化。您需要及时理解文章的内容,以便在代码稳定后理解它。

然而,由于涉及多个应用程序,有人可能会发现编译复杂的代码很困难。但请不要担心,不定期的我会附上编译后的代码,以便大家可以跟随系统的开发。此外,一些文章中介绍的已编译的代码将帮助您了解,一旦一切都完美编译后应用程序会是什么样子。但是,如果您已经有了更多的经验并且想要提高您的编程技能,您可以添加文章中显示的内容。这样,您将学会如何修改系统,以便将来可以使用任何您想要的东西。但如果您决定这样做,我建议您谨慎行事,并记住始终测试您添加到代码中的内容。

无论如何,事情还没有结束。在下一篇文章中,我们将继续这个主题,并解决一些尚未解决的问题。

本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/11624

附加的文件 |
Anexo.zip (420.65 KB)
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
开发回放系统(第 39 部分):铺平道路(三) 开发回放系统(第 39 部分):铺平道路(三)
在进入开发的第二阶段之前,我们需要修正一些想法。您知道如何让 MQL5 满足您的需求吗?您是否尝试过超出文档所包含的范围?如果没有,那就做好准备吧。因为我们将做一些大多数人通常不会做的事情。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
理解编程范式(第 1 部分):开发价格行为智能系统的过程化方式 理解编程范式(第 1 部分):开发价格行为智能系统的过程化方式
了解编程范式及利用 MQL5 代码的应用。本文探讨了过程化编程的细节,并通过一个实际示例提供了实经验。您将学习如何利用 EMA 指标和烛条价格数据开发价格行为智能系统。额外,本文还介绍了函数化编程范式。