English Русский Español Deutsch 日本語 Português
preview
开发回放系统(第29部分):EA 交易项目——C_Mouse类(三)

开发回放系统(第29部分):EA 交易项目——C_Mouse类(三)

MetaTrader 5测试者 | 7 五月 2024, 14:24
373 0
Daniel Jose
Daniel Jose

概述

在上一篇文章开发回放系统(第28部分):EA 交易项目——C_Mouse类(二)中,我们研究了如何创建可读性更强的代码。虽然这个模型对于使程序更易于理解非常有趣,但我想您会注意到使用这种方法进行编程可能需要更长的时间。这并不是因为编程变得令人困惑,恰恰相反,困难在于,这种提高程序可读性的方法有其局限性。一个这样的限制是任何编程语言的语法。尽管语法被设计为具有特定的格式和结构,但定义的使用虽然有用,但在其他方面限制了我们。然而,从我的角度来看,我认为在实际代码中显示这一点是合适的。

我们将保留原始语法,但如果您想以我们显示的方式探索代码,请随意创建任何您认为必要的定义,并调整代码以使其更易于理解。这将帮助你学习一些非常有趣的技巧。这就是我如何学会用其他语言编程的。这是一个劳动密集但有用的过程,因为有些方法甚至算法在一种语言中比在另一种语言更容易实现。然而,如果你能用原始语言阅读代码,那么你就可以做别人做不到的事情。你可以把它想象成一部翻译作品,你是两个不同世界之间的口译员。要想取得成功,我们的理解必须比那些总是使用相同的符号、符号和术语进行交流的人要广泛得多。

开阔你的思维,跳出框框,整个宇宙就会为你打开。

但让我们来看看是什么让我们看到了这篇文章。在这里,我们将研究如何在不更改类和不使用继承系统的情况下,以可控、安全和可靠的方式扩展系统的功能,而不考虑能力。这项任务一开始可能看起来很简单,但它将提供对事物如何工作的更深入理解,远远超出我们每次构建相同方法时所得到的结果。

在今天的文章中,我们将探讨一种扩展金融工具分析系统的方法。我们将使用C_Mouse类及其从C_Terminal类继承的内容来创建另一个分析工具。然而,我们将以一种非常有趣的方式来做这件事:我们将创建一个新的类,该类将使用C_Mouse类的内容,但不直接从中继承。根据我们的目标,这个新类可能会被添加到最终代码中,也可能不会。但不管怎样,我们将学习如何在不违反先前创建和测试的代码的完整性的情况下创建自己的训练模型。这就是本文的真正目的。


为扩展搭建舞台

在我们开始编程不会继承 C_Mouse 类但会扩展或修改其功能的类之前,我们需要调整原始 C_Mouse 类的一些细节。不是因为有任何问题,而是因为我们需要进行一些添加和小的更改,使任何形式的扩展都更容易。这样,类功能的任何更改都将非常实用,因为如果出现问题,我们可以简单地恢复使用原始类,而不会出现任何问题。这些变化将是少而简单但重要的。首先,我们将向C_Mouse类代码中添加一个新变量。

class C_Mouse : public C_Terminal
{
   protected:
      enum eEventsMouse {ev_HideMouse, ev_ShowMouse};
      enum eBtnMouse {eKeyNull = 0x00, eClickLeft = 0x01, eClickRight = 0x02, eSHIFT_Press = 0x04, eCTRL_Press = 0x08, eClickMiddle = 0x10};
      struct st_Mouse
      {
         struct st00
         {
            int     X,
                    Y;
            double  Price;
            datetime dt;
         }Position;
         uint    ButtonStatus;
         bool    ExecStudy;
      };

这个变量将允许我们在不需要继承或多态性的情况下扩展或更改C_Mouse类的行为。尽管这些是最常见的方法,但我们将采取不同的方法。事实上,我们将演示的方法允许我们在任何类中应用此策略。这里的重要部分是,所有操作都不需要更改类的一行源代码。在实现上述旨在扩展功能的更改之前,我们需要向C_Mouse类添加一行简单的代码,一些简单的东西。

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj, const color cor)
   {
      ObjectCreate(GetInfoTerminal().ID, szName, obj, 0, 0, 0);
      ObjectSetString(GetInfoTerminal().ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_COLOR, cor);
      ObjectSetInteger(GetInfoTerminal().ID, szName, OBJPROP_ZORDER, -1);
   }

这条特定的线确保即使鼠标价格线原来是前景线,它也不会收到任何事件,以防它与我们试图点击的任何对象重叠。如果鼠标光标的焦点中没有对象,则价格线将仅捕获单击事件。需要注意的是,即使单击了对象,添加此行也不会阻止生成分析,因为此行代码不允许对象直接接收单击,但不会阻止CHARTEVENT_MOUSE_MOVE事件被 C_Mouse 类激发、激活和捕获。

重要提示:以前,我遇到过某些问题,正是因为缺少这行代码。在让图表更有趣:添加背景的文章中,有一个缺陷,当时我无法解决。不管我怎么努力,问题依然存在。我们可以通过简单地将突出显示的线添加到用于将背景插入图表的对象中来解决阻止访问图表上存在的对象的错误。我本可以早点分享这个建议,但我想以某种方式奖励那些真正阅读过这些文章的人。现在你知道如何解决文章中提到的问题了。

接下来,我们将对以下功能进行一些小的更改:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
      {
         int w = 0;
         static double memPrice = 0;
                                
         C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
         switch (id)
         {
            case (CHARTEVENT_CUSTOM + ev_HideMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
               break;
            case (CHARTEVENT_CUSTOM + ev_ShowMouse):
               ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
               break;
            case CHARTEVENT_MOUSE_MOVE:
               ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X = (int)lparam, m_Info.Data.Position.Y = (int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
               ObjectMove(GetInfoTerminal().ID, def_NameObjectLineH, 0, 0, m_Info.Data.Position.Price = AdjustPrice(m_Info.Data.Position.Price));
               m_Info.Data.Position.dt = AdjustTime(m_Info.Data.Position.dt);
               ChartTimePriceToXY(GetInfoTerminal().ID, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price, m_Info.Data.Position.X, m_Info.Data.Position.Y);
               if (m_Info.Study != eStudyNull) ObjectMove(GetInfoTerminal().ID, def_NameObjectLineV, 0, m_Info.Data.Position.dt, 0);
               m_Info.Data.ButtonStatus = (uint) sparam;
               if (CheckClick(eClickMiddle) && ((color)ObjectGetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR) != clrNONE)) CreateStudy();
               if (CheckClick(eClickLeft) && (m_Info.Study == eStudyCreate))
               {
                  ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
                  ObjectMove(GetInfoTerminal().ID, def_NameObjectLineT, 0, m_Info.Data.Position.dt, memPrice = m_Info.Data.Position.Price);
                  m_Info.Study = eStudyExecute;
               }
               if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
               m_Info.Data.ExecStudy = m_Info.Study == eStudyExecute;
               break;
            case CHARTEVENT_OBJECT_DELETE:
               if (sparam == def_NameObjectLineH) CreateLineH();
               break;
         }
      }

这些更改并不特别有利于C_Mouse类,而是将基于C_Mouse类别构建的整个程序。我们可能没有注意到之前和当前文章之间的代码有任何差异,这是由于更改的微妙性和特殊性。实际上,代码中没有任何更改。这些变化在可用性和自定义选项方面带来了各种好处。可能不容易注意到差异,但我们添加到代码中的三行新代码帮助很大。让我们看看它们每一行都做了什么:

  1. 这一行将调整时间值,以便我们不仅可以使用屏幕坐标(X和Y),还可以使用金融工具坐标(价格和时间)来实际使用图表上的任何对象。我一直在寻找这个问题的答案很长时间了。再考虑到使用资产坐标比有时使用屏幕坐标有趣得多,这给我们的自由程度是显而易见的。您还可以看到我们正在进行调用,出于实际原因,它位于C_Terminal类中。
  2. 添加的调用是ChartTimePriceToXY,用于将价格坐标转换为屏幕坐标。
  3. 最后一点就是这样。我们指示C_Mouse类是否处于学习模式。为了避免混淆,请注意语法

这些都是对C_Mouse类所做的更改。但是,如前所述,C_Terminal类现在包含一个新函数。让我们看看这个函数来了解它是什么。添加的功能如下所示:

inline datetime AdjustTime(const datetime arg) const
   {
      int nSeconds= PeriodSeconds();
      datetime dt = iTime(m_Infos.szSymbol, PERIOD_CURRENT, 0);
                                
      return (dt < arg ? ((datetime)(arg / nSeconds) * nSeconds) : iTime(m_Infos.szSymbol, PERIOD_CURRENT, Bars(m_Infos.szSymbol, PERIOD_CURRENT, arg, dt)));
   }

如果这个功能对你来说非常奇怪和困惑,不要担心。尽管一开始它可能看起来令人困惑和毫无意义,但它实际上提供了一些美妙而有趣的东西。为了理解这个函数的魔力,我们需要了解关于类型转换的一件事,以及一些额外的细节。首先,我们来看一下类型转换。

当调用此函数时,它将接收日期和时间值作为参数重要的是,不仅要将此值视为日期和时间,还要将其视为ulong值,因为这才是真正的值:一个8字节的值。这是第一件事。第二个如下:由于datetime是一个ulong值,因此变量中的时间和日期信息以一种相当特定的方式压缩。我们感兴趣的是最低有效位(LSB),其中秒、分钟、小时、天、月和年的值从最低有效位到最高有效位排序。

请注意,nSeconds包含图表中使用的周期的值(以秒为单位)。这是由提供此信息的PeriodSeconds函数确定的。现在,dt变量包含图表上最后一个柱的创建时间值。这一事实非常重要,因为如果dt的值小于调用的值,这将表明我们在什么时间点,在这种情况下,我们将在未来。因此,相对于屏幕坐标(X和Y)的时间位置指示未来柱的创建位置。目前,使用iTime函数无法找出它的位置,因为到目前为止的图表尚未构建。然而,即便如此,我们也需要知道它的实际位置,特别是如果我们正在进行与未来相关的图表分析。

要找出屏幕坐标(X和Y),我们将使用datetimeulong值这一事实。通过将该值除以秒数nSeconds,我们将得到double型的数值。接下来是一个重要的问题:如果我们将这个分数值乘以nSeconds,我们将得到原始值。例如,如果我们把10除以3,再把除法的结果乘以3,我们又得到10。然而,当我们进行类型转换时(这是关键),我们将double值转换为ulong,或者更好的是,转换为datetime类型的值。这里没有分数部分。因此,将该值乘以nSeconds,我们将得到已经校正的未来值。这是最有趣的部分。然而,存在一个问题。

当我们回顾过去时,这个问题就会出现,尤其是对于连续序列,也就是说,当没有间隙时。这种方法不适合分析过去,主要是针对有这种缺口的资产,这是在特定时间或天数内交易的资产的典型情况。这是指股票市场工具,其中柱形只在特定的时间窗口内形成,而市场在该时间窗口之外收盘。为了处理这种情况,我们使用稍微不同的设置来尝试确定指定值在哪一列中。我们通过计算当前柱形和指定时间柱之间的柱形数量来实现这一点,这为我们提供了一个值,我们可以将其用作偏移量来捕捉确切的时间点。这样,无论发生了什么,我们也能够在过去做出调整。

你可能会认为这个功能完全没有必要。为什么要这么麻烦开发它呢?但是,使用此功能,您可以将交易品种坐标(价格和时间)转换为屏幕坐标(X和Y)。因此,我们可以使用任何类型的图形对象,而不限于屏幕坐标或资源对象。我们将能够将一种类型的坐标转换为另一种类型,为此,我们将使用调用:ChartXYToTimePrice(将屏幕坐标转换为资产坐标)和ChartTimePriceToXY(将资产坐标转换为屏幕坐标)。 然而,在某些类型的分析中,我们需要尽可能准确的信息。当我们希望某个特定的柱用作某个事物的指示点时,这种转换就变得必要了。此外,这给了我们非常有趣的信息,我们稍后将对此进行研究。


创建 C_Studys 类

在改进了C_Mouse类之后,我们可以专注于创建一个类,该类旨在为我们的分析创建一个全新的框架。我们不会使用继承或多态性来创建这个新类。相反,我们将改变,或者更好地说,在价格线中添加新的对象。这就是我们在这篇文章中要做的。在下一节中,我们将研究如何更改分析。但我们将在不更改C_Mouse类的代码的情况下完成所有这些操作。实际上,使用继承或多态性会更容易实现这一点。还有其他方法可以实现相同的结果,因为如果新代码中存在缺陷,它们提供了灵活性,而不会造成重大中断。

这种方法允许我们删除有问题的代码,修复错误,并再次引入它,而不需要更改以前测试过的代码。请记住:错误修复通常会使代码与现有的类结构不兼容,从而使使用继承或多态性来启用新添加变得困难。因此,掌握这些替代技术是很重要的,尤其是如果我们想在已经完成和测试的程序中实现新的功能,而不需要对现有代码进行重大重组。

第一步是创建一个新文件,尽管也可以在所涉及的类之间创建几个具有自己层次结构的不同文件。这不会是一个问题,因为此文件和文件不会集成父类层次结构。通过这种方式,我们可以建立一个独立的层次结构,可以根据需要以各种方式进行更改或改进。这种灵活性来源于这样一个事实,即它与主要的开发层次结构没有直接关系,而且几乎就像一个单独的项目。

我们将从一个更简单的系统开始,并在未来探索更复杂的方法。文件运行方式如下:

#include "..\C_Mouse.mqh"
#include "..\..\..\Service Graphics\Support\Interprocess.mqh"
//+------------------------------------------------------------------+
#define def_ExpansionPrefix "Expansion1_"
#define def_ExpansionBtn1 def_ExpansionPrefix + "B1"
#define def_ExpansionBtn2 def_ExpansionPrefix + "B2"
#define def_ExpansionBtn3 def_ExpansionPrefix + "B3"
//+------------------------------------------------------------------+
#define def_InfoTerminal (*mouse).GetInfoTerminal()
#define def_InfoMousePos (*mouse).GetInfoMouse().Position
//+------------------------------------------------------------------+

通过对代码的介绍,我们了解到这个过程将是非常细致的。请注意,此文件中包含两个位于不同位置的头文件。这一部分前面已经讨论过了。稍后我们将提到一些我们将要使用的对象。这对于在进一步访问所需对象时避免混淆非常重要。我们还定义了一种别名以便于编码,因为我们将使用与指针非常相似的东西,指针是可用的最强大、同时也是风险最高的资源之一。但由于我们计划的编程方式,与此资源相关的风险将非常可控,这将使我们能够以非常有趣的方式使用它。

当我们想访问某些内容,但又不想在编写代码时冒着键入错误的风险时,这种类型的定义(别名)非常常见。这总是一个非常有趣的资源。

接下来是类的代码:

class C_Studys
{
   protected:
   private :
//+------------------------------------------------------------------+
      enum eStatusMarket {eCloseMarket, eAuction, eInTrading, eInReplay};
//+------------------------------------------------------------------+
      C_Mouse *mouse;
//+------------------------------------------------------------------+
      struct st00
      {
         eStatusMarket   Status;
         MqlRates        Rate;
         string          szInfo;
         color           corP,
                         corN;
         int             HeightText;
      }m_Info;

在这个阶段,我们只关注类的私有变量。看看其中一个,它充当了指向C_Mouse类的某种指针。指针是编程中最强大的资源之一,但由于使用它们时可能会出现问题,因此需要特别注意。因此,谨慎使用指针是非常重要的,即使在MQL5中它们与C/C++中的特性不同。不管怎样,你在使用它们时应该小心。永远不要低估指针。代码的其余部分还没有什么特别之处。

类函数中的第一个如下所示:

const datetime GetBarTime(void)
   {
      datetime dt = TimeCurrent();
                                
      if (m_Info.Rate.time <= dt)
         m_Info.Rate.time = iTime(def_InfoTerminal.szSymbol, PERIOD_CURRENT, 0) + PeriodSeconds();

      return m_Info.Rate.time - dt;
   }

这是一个相当简单的函数,用于计算新柱出现之前的剩余时间。计算本身只发生在特定的点上,但每次测试检测到一个柱的时间限制已经达到时,我们都会进行必要的读取和调整,将计算移动到下一个柱。因此,对于每个创建的柱形,这个iTime函数调用只发生一次,而不是每次交互或方法调用。下一个函数以标准方式创建对象。

inline void CreateObjectBase(const string szName, const ENUM_OBJECT obj)
   {
      ObjectCreate(def_InfoTerminal.ID, szName, obj, 0, 0, 0);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_TOOLTIP, "\n");
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BACK, false);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_ZORDER, -1);
   }

我们基本上正在经历与C_Mouse中的函数相同的过程,这表明我们可能很快会考虑将两者组合成一个更通用的函数。在这之后,我们立即提出一个可能对一些人感兴趣的程序进行测试。

int CreateBTNInfo(const string szExample, int x, string szName, color backColor, string szFontName, int FontSize)
   {
      int w;
                                
      CreateObjectBase(szName, OBJ_BUTTON);
      TextGetSize(szExample, w, m_Info.HeightText);
      m_Info.HeightText += 5;
      w += 5;
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_STATE, true);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BORDER_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_BGCOLOR, backColor);
      ObjectSetString(def_InfoTerminal.ID, szName, OBJPROP_FONT, szFontName);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_FONTSIZE, FontSize);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_CORNER, CORNER_LEFT_UPPER); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XSIZE, w); 
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_YSIZE, m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, szName, OBJPROP_XDISTANCE, x);
                                
      return w;
   }

此方法允许您根据所用字体的大小和类型以及将在对象内部打印的最长文本来调整对象的大小。这是通过使用TextGetSize函数实现的,该函数根据提供的信息为我们提供文本大小的估计。但是,为了使文本在对象内部看起来更好,我们将略微增加其大小。这将在文本和对象的边界之间创建一个小空间。

接下来我们有一个在图表上显示信息的函数。

void Draw(void)
   {
      double v1;
                                
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - m_Info.HeightText);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_YDISTANCE, def_InfoMousePos.Y <= 0 ? UINT_MAX : def_InfoMousePos.Y - 1);
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn1, OBJPROP_TEXT, m_Info.szInfo);
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / def_InfoMousePos.Price) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn2, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
      v1 = NormalizeDouble(100.0 - ((m_Info.Rate.close / iClose(def_InfoTerminal.szSymbol, PERIOD_D1, 0)) * 100.0), 2);
      ObjectSetInteger(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_BGCOLOR, (v1 < 0 ? m_Info.corN : m_Info.corP));
      ObjectSetString(def_InfoTerminal.ID, def_ExpansionBtn3, OBJPROP_TEXT, (string)MathAbs(v1) + "%");
   }

这恰恰产生了一种研究类型,这种研究将永远存在,并伴随着价格线。这样,我们就能很容易地理解某些事情。将显示的信息类型和内容取决于您需要什么。为了便于演示,我们将提供三种类型的信息。所以,让我们看看将提供什么。

我们可以根据程序与平台和交易服务器的交互方式接收不同的信息。但是,我们收到以下信息:

  • 图表上出现下一个柱形之前的剩余时间;
  • 市场关闭的信息;
  • 我们正在处理的信息回放;
  • 资产正在拍卖的信息;
  • 以及(在极少数情况下)错误消息。

请注意,我们正在做的事情并不罕见。这只是为了展示我们将要使用的工作技术。接下来是析构函数。

~C_Studys()
   {
      ObjectsDeleteAll(def_InfoTerminal.ID, def_ExpansionPrefix);
   }

上面代码的目的是向平台指示我们要删除由类创建的元素。请注意,我们不在乎这是否会触发平台中的事件,因为如果这些对象被删除,我们不打算重新创建它们。

然后是下一个函数:

void Update(void)
   {
      switch (m_Info.Status)
      {
         case eCloseMarket: m_Info.szInfo = "Closed Market";                         break;
         case eAuction   : m_Info.szInfo = "Auction";                                break;
         case eInTrading : m_Info.szInfo = TimeToString(GetBarTime(), TIME_SECONDS); break;
         case eInReplay  : m_Info.szInfo = "In Replay";                              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();
   }

为什么我们有两个名为Update的函数?有可能吗?是的,我们可以声明具有相同名称的函数。这种现象被称为重载。尽管名称相同,但对于编译器来说,两个函数的名称不同。这是因为参数。通过这种方式,您可以重载函数和过程,但唯一的规则是参数必须不同。我们不能忘记,在第二个函数中,我们使用不需要参数的方法调用第一个函数。当编程方法重载并希望创建一些能使调试更容易的东西时,这种做法非常常见。

现在我们来讨论一个有趣的问题:如何确定资产是否在拍卖中?定价信息通常在订单簿中提供。然而,资产的订单簿可能会显示其中一个特定值,当这种情况发生时,就意味着该资产正在拍卖中。请注意这一点,因为它可能是一个有趣的资源,可以添加到您的自动化EA中。我已经在另一系列文章中提到了这一点:创建一个自动交易的EA(第14部分):自动化(六)。但我没有详细说明如何了解资产是否要拍卖。我们的目标不是使用自动化EA提供交易过程所有方面的详细覆盖。

以下函数用于响应某些平台事件:

virtual void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
   {               
      switch (id)
      {
         case CHARTEVENT_MOUSE_MOVE:
            Draw();
            break;
      }
   }

简单但有效,因为对象必须跟随鼠标,并且鼠标分析由C_Mouse类处理。再次说明,我们在不使用继承或多态性的情况下修改了C_Mouse类的工作。因此,调整和校正鼠标位置的工作是C_Mouse类的责任。我们将只使用数据。

我们几乎看到了整个代码。但我们需要看看魔法到底发生在哪里。让我们从EA代码开始,它完整地显示在下面:

//+------------------------------------------------------------------+
#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
#include <Market Replay\System EA\Auxiliar\Study\C_Studys.mqh>
//+------------------------------------------------------------------+
input group "Mouse";
input color     user00 = clrBlack;      //Price Line
input color     user01 = clrPaleGreen;  //Positive Study
input color     user02 = clrLightCoral; //Negative Study
//+------------------------------------------------------------------+
C_Mouse *mouse = NULL;
C_Studys *extra = NULL;
//+------------------------------------------------------------------+
int OnInit()
{
   mouse = new C_Mouse(user00, user01, user02);
   extra = new C_Studys(mouse, user01, user02);
                
   OnBookEvent(_Symbol);
   EventSetMillisecondTimer(500);

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   MarketBookRelease(_Symbol);
   EventKillTimer();
        
   delete extra;
   delete mouse;
}
//+------------------------------------------------------------------+
void OnTick() {}
//+------------------------------------------------------------------+
void OnTimer()
{
   (*extra).Update();
}
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{

       MqlBookInfo book[];
   

       if (mouse.GetInfoTerminal().szSymbol == def_SymbolReplay) ArrayResize(book, 1, 0); else
   {
      if (symbol != (*mouse).GetInfoTerminal().szSymbol) return;
      MarketBookGet((*mouse).GetInfoTerminal().szSymbol, book);
   }
   (*extra).Update(book);
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
    (*mouse).DispatchMessage(id, lparam, dparam, sparam);
    (*extra).DispatchMessage(id, lparam, dparam, sparam);
        
    ChartRedraw();
}
//+------------------------------------------------------------------+

请注意,我们为该EA提供了使用相同工具交易真实资产和模拟资产的能力。由于这种类型的验证,这是可能的。目前,在这个开发阶段,我们将根据EA在图表上识别的内容获得不同的信息。

我们将在初始化C_Mouse类时创建的指针传递给类构造函数。这样,C_Studys类就不需要直接继承C_Mouse类来使用其内容,从而无需在C_Studys中初始化C_Mouse。当我们希望在不使用继承或多态性的情况下交换信息或与元素交互时,这种建模技术非常有用。如果我们需要删除、调整或创建与C_Mouse类有某种关系的C_Studys类的新功能,这可以很容易地完成,而无需更改C_Mouse类。

这种模型的最大优点是可以并行开发组件。如果C_Studys类不是最终项目的一部分,您只需从中删除代码即可。这个C_Studys成为一个平行类,独立于最终代码的主类系统。

现在,让我们分析构造函数代码,以了解这种信息传输是如何实现的。

C_Studys(C_Mouse *arg, color corP, color corN)
   {
#define def_FontName "Lucida Console"
#define def_FontSize 10
      int x;
                                        
      mouse = arg;
      ZeroMemory(m_Info);
      m_Info.Status = eCloseMarket;
      m_Info.Rate.close = iClose(def_InfoTerminal.szSymbol, PERIOD_D1, ((def_InfoTerminal.szSymbol == def_SymbolReplay) || (macroGetDate(TimeCurrent()) != macroGetDate(iTime(def_InfoTerminal.szSymbol, PERIOD_D1, 0))) ? 0 : 1));
      m_Info.corP = corP;
      m_Info.corN = corN;
      TextSetFont(def_FontName, -10 * def_FontSize, FW_NORMAL);
      CreateBTNInfo("Closed Market", 2, def_ExpansionBtn1, clrPaleTurquoise, def_FontName, def_FontSize);
      x = CreateBTNInfo("99.99%", 2, def_ExpansionBtn2, clrNONE, def_FontName, def_FontSize);
      CreateBTNInfo("99.99%", x + 5, def_ExpansionBtn3, clrNONE, def_FontName, def_FontSize);
      Draw();
#undef def_FontSize
#undef def_FontName
   }

正如您所看到的,这段代码有一些奇怪,包括一个有些不寻常的参数:指向类的指针。这展示了这些元素是如何结合在一起形成我们需要设计的系统的。然而,还有一个有趣的功能乍一看可能很奇怪:TextSetFont函数。根据我们要显示的信息类型调整对象的大小是非常重要的。注意,我们在这里做因子分解。为什么使用负数?为了清楚起见,让我们看看文档中给出的解释:

使用正值或负值来确定字体大小。这一事实决定了文本大小对操作系统设置(大小比例)的依赖性。

  • 如果大小为正,则在将逻辑字体显示为物理字体时,将其转换为设备物理单位(像素)。大小与可用字体中符号单元格的高度相对应。在共享使用TextOut()函数显示的文本和使用OBJ_LABEL(“文本标签”)图形对象显示的文本的情况下,不建议使用此选项。
  • 如果大小为负,则假定其设置为逻辑点的十分之一(值-350等于35个逻辑点),并除以10。结果值被转换为设备的物理单位(像素),并对应于可用字体的字符高度的绝对值。将对象属性中定义的字体大小乘以-10,使屏幕文本大小类似于OBJ_LABEL对象大小。

 出于这个原因,因子分解是以这种方式执行的。如果我们不在这里设置这个参数,我们将在使用对象创建函数中使用的TextGetSize函数时遇到问题。发生这种情况是因为所使用的字体或其尺寸可能与我们想要使用的不完全匹配。


结论

请确保测试附加的应用程序。建议在重放/模拟模式下和在市场上运行的账户(DEMO或REAL)上进行实验,

以获得广泛的理解。但是我不会修改主类中的任何一行代码,我保证。

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

附加的文件 |
Files_-_FUTUROS.zip (11397.51 KB)
Files_-_FOREX.zip (3743.96 KB)
Files_-_BOLSA.zip (1358.24 KB)
MQL5中的范畴论(第23部分):对双重指数移动平均的不同看法 MQL5中的范畴论(第23部分):对双重指数移动平均的不同看法
在这篇文章中,我们继续我们的主题,最后是从“新”的角度处理日常交易指标。我们正在为这篇文章处理自然变换的水平组合,而这方面的最佳指标是双重指数移动平均(DEMA),它扩展了我们刚刚涵盖的内容。
MQL5中的范畴论(第22部分):对移动平均的不同看法 MQL5中的范畴论(第22部分):对移动平均的不同看法
在本文中,我们尝试通过只关注一个指标来简化对这些系列中所涵盖概念的说明,这是最常见的,可能也是最容易理解的。它就是移动平均。在这样做的时候,我们会探讨垂直自然变换的意义和可能的应用。
神经网络变得简单(第 56 部分):利用核范数推动研究 神经网络变得简单(第 56 部分):利用核范数推动研究
强化学习中的环境研究是一个紧迫的问题。我们之前已视察过一些方式。在本文中,我们将讲述另一种基于最大化核范数的方法。它允许智能体识别拥有高度新颖性和多样性的环境状态。
为 MetaTrader 5 开发MQTT客户端:TDD方法——第3部分 为 MetaTrader 5 开发MQTT客户端:TDD方法——第3部分
本文是一系列文章的第三部分,介绍了我们为MQTT协议开发本机MQL5客户端的步骤。在这一部分中,我们详细描述了如何使用测试驱动开发来实现CONNECT/CONNACK数据包交换的操作行为部分。在这一步骤结束时,我们的客户端必须能够在处理连接尝试可能产生的任何服务器结果时表现得正常。