English Русский Español Deutsch 日本語 Português
preview
开发回放系统(第 44 部分):Chart Trader 项目(三)

开发回放系统(第 44 部分):Chart Trader 项目(三)

MetaTrader 5示例 |
451 0
Daniel Jose
Daniel Jose

概述

在上一篇文章开发回放系统(第 43 部分):Chart Trader 项目 (二) 中,我解释了如何操作模板数据以便在 OBJ_CHART 中使用。在那篇文章中,我只是概述了这一主题,并没有深入探讨细节,因为在那个版本中,这项工作是以非常简单的方式完成的。这样做是为了更容易解释内容,因为尽管很多事情表面上很简单,但其中有些并不那么明显,如果不了解最简单、最基本的部分,就无法真正理解全局。

因此,尽管这段代码可以工作(正如我们已经看到的那样),但它仍然无法让我们做某些事情。换句话说,除非在数据建模方面有所改进,否则执行某些任务将更加困难。上述改进涉及到更复杂的编码,但使用的概念是相同的。只是代码会稍微更复杂一些。

除了这一点,我们还要解决一个问题。如果你注意到了,我在文章中也提到过,这段代码的效率并不高,因为在我看来,它包含了太多的设置调用。为了解决这个问题,我们将对代码做一些小改动,以大幅减少调用次数,同时进行更充分的数据建模。


新类的诞生:C_AdjustTemplate

为了实现必要的改进,我们必须创建一个新的类。其完整代码如下所示:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "../Auxiliar/C_Terminal.mqh"
05. //+------------------------------------------------------------------+
06. class C_AdjustTemplate
07. {
08.     private :
09.             string m_szName[];
10.             string m_szFind[];
11.             string m_szReplace[];
12.             string m_szFileName;
13.             int m_maxIndex;
14.             int m_FileIn;
15.             int m_FileOut;
16.             bool m_bSimple;
17.     public  :
18. //+------------------------------------------------------------------+
19.             C_AdjustTemplate(const string szFileNameIn, string szFileNameOut = NULL)
20.                     :m_maxIndex(0),
21.                      m_szFileName(szFileNameIn),
22.                      m_FileIn(INVALID_HANDLE),
23.                      m_FileOut(INVALID_HANDLE)
24.                     {
25.                             ResetLastError();
26.                             if ((m_FileIn = FileOpen(szFileNameIn, FILE_TXT | FILE_READ)) == INVALID_HANDLESetUserError(C_Terminal::ERR_FileAcess);
27.                             if (_LastError == ERR_SUCCESS)
28.                             {
29.                                     if (!(m_bSimple = (StringLen(szFileNameOut) > 0))) szFileNameOut = szFileNameIn + "_T";                                                 
30.                                     if ((m_FileOut = FileOpen(szFileNameOut, FILE_TXT | FILE_WRITE)) == INVALID_HANDLE) SetUserError(C_Terminal::ERR_FileAcess);
31.                             }
32.                     }
33. //+------------------------------------------------------------------+
34.             ~C_AdjustTemplate()
35.                     {
36.                             FileClose(m_FileIn);
37.                             if (m_FileOut != INVALID_HANDLE)
38.                             {
39.                                     FileClose(m_FileOut);
40.                                     if ((!m_bSimple) && (_LastError == ERR_SUCCESS)) FileMove(m_szFileName + "_T", 0, m_szFileName, FILE_REWRITE);
41.                                     if ((!m_bSimple) && (_LastError != ERR_SUCCESS)) FileDelete(m_szFileName + "_T");
42.                             }
43.                             ArrayResize(m_szName, 0);
44.                             ArrayResize(m_szFind, 0);
45.                             ArrayResize(m_szReplace, 0);
46.                     }
47. //+------------------------------------------------------------------+
48.             void Add(const string szName, const string szFind, const string szReplace)
49.                     {
50.                             m_maxIndex++;
51.                             ArrayResize(m_szName, m_maxIndex);
52.                             ArrayResize(m_szFind, m_maxIndex);
53.                             ArrayResize(m_szReplace, m_maxIndex);
54.                             m_szName[m_maxIndex - 1] = szName;
55.                             m_szFind[m_maxIndex - 1] = szFind;
56.                             m_szReplace[m_maxIndex - 1] = szReplace;
57.                     }
58. //+------------------------------------------------------------------+
59.             string Get(const string szName, const string szFind)
60.                     {
61.                             for (int c0 = 0; c0 < m_maxIndex; c0++) if ((m_szName[c0] == szName) && (m_szFind[c0] == szFind)) return m_szReplace[c0];
62.                             
63.                             return NULL;
64.                     }
65. //+------------------------------------------------------------------+
66.             void Execute(void)
67.                     {
68.                             string sz0, tmp, res[];
69.                             int count0 = 0, i0;
70.                             
71.                             if ((m_FileIn != INVALID_HANDLE) && (m_FileOut != INVALID_HANDLE)) while ((!FileIsEnding(m_FileIn)) && (_LastError == ERR_SUCCESS))
72.                             {
73.                                     sz0 = FileReadString(m_FileIn);
74.                                     if (sz0 == "<object>") count0 = 1;
75.                                     if (sz0 == "</object>") count0 = 0;
76.                                     if (count0 > 0) if (StringSplit(sz0, '=', res) > 1)
77.                                     {
78.                                             i0 = (count0 == 1 ? 0 : i0);
79.                                             for (int c0 = 0; (c0 < m_maxIndex) && (count0 == 1); i0 = c0, c0++) count0 = (res[1] == (tmp = m_szName[c0]) ? 2 : count0);
80.                                             for (int c0 = i0; (c0 < m_maxIndex) && (count0 == 2); c0++) if ((res[0] == m_szFind[c0]) && (tmp == m_szName[c0]))
81.                                             {
82.                                                     if (StringLen(m_szReplace[c0])) sz0 =  m_szFind[c0] + "=" + m_szReplace[c0];
83.                                                     else m_szReplace[c0] = res[1];
84.                                             }
85.                                     }
86.                                     FileWriteString(m_FileOut, sz0 + "\r\n");
87.                             };
88.                     }
89. //+------------------------------------------------------------------+
90. };
91. //+------------------------------------------------------------------+

源代码:C_AdjustTemplate

这段代码正包含我们所需要的。如果你看看这里的代码,再看看上一篇文章中的 C_ChartFloatingRAD 类代码,你会发现 C_ChartFloatingRAD 类第 38 行和第 90 行之间的内容也在这里,但看起来有所不同。这是因为该 C_AdjustTemplate 类的数据建模方式有助于提高执行效率。本文稍后将展示新的 C_ChartFloatingRAD 类代码,您将看到这一点。所以,还是留到以后再说吧。首先,让我们了解一下这个 C_AdjustTemplate 类是怎么回事。

虽然 C_AdjustTemplate 类的代码看似复杂难懂,但实际上非常简单。不过,尽管它具有多种功能,却被设计为作为单一函数执行。要真正理解整个理念,请忘掉您正在使用 MetaTrader 或 MQL5 的代码。如果把自己想象成是在处理一台机器的部件,就会更容易理解一切。C_AdjustTemplate 类应被视为一个模板。因此,可以将其视为我们在前一篇文章中讨论过的模板文件。

这样一想,我们就会明白到底发生了什么,也会明白为什么我们今后要以这样的方式来处理这一类问题。因此,当你使用类构造函数时,基本上就是打开了模板,以便对其中的内容进行操作。使用析构函数时,就好像在说:"好了,MetaTrader,现在可以使用模板了"。其他函数则是调整模板的工具。

有鉴于此,让我们来看看这个类的每个部分是如何工作的。让我们从构造函数开始。从第 19 行开始,我们可以看到必须提供一个字符串,而第二个字符串参数是可选的。为什么要这样做?原因很简单:重载。如果不能重载,我们就得写两个构造函数,但既然可以重载,我们就使用这种方法。但事实上,这种重载并不常见,它就是要这样使用的。

完成后,我们将在第 20 行和第 23 行之间预初始化一些变量。这一点对我们很重要,虽然编译器会进行隐式初始化,但最好还是显式初始化。这样,我们就能清楚地知道每个变量的值是多少。

现在请注意以下情况:在第 25 行,我们 "重置" 了 _LastError 常量。因此,如果在调用构造函数之前出现任何错误,就应该进行检查;否则,就会丢失错误常量中指定的值。我在之前的文章中已经解释过为什么要这样做,请阅读这些文章了解更多详细信息。

在第 26 行,我们尝试打开必须指定的源文件。如果尝试失败,我们将在 _LastError 常量中报告。如果我们成功打开了文件,_LastError 常量将包含 ERR_SUCCESS 值,第 27 行中执行的检查也将成功,从而允许我们进入下一步。

此时,我们会在第 29 行检查是否为目标文件指定了名称。如果没有指定,我们将使用一个临时文件,其名称将基于原始文件的名称。有了目标文件的名称,我们就可以执行第 30 行,尝试创建目标文件。如果尝试失败,我们将在 _LastError 常量中报告。成功执行所有这些步骤后,_LastError 常量将包含 ERR_SUCCESS 值,我们将获得文件标识符。这些句柄将用于操作文件,因此在句柄关闭之前,不应尝试对文件执行任何外部操作。这就像机器是开放式的,如果您打开它,可能会出现问题。

因此,让我们按照代码编辑的顺序继续。这就到了第 34 行,类的析构代码就从这里开始。在第 36 行,我们要做的第一件事就是关闭输入文件。请注意:只有当文件句柄有效时,该文件才会被关闭。也就是说,文件必须是打开的。在第 37 行,我们检查输出文件是否已打开。这样做是为了避免不必要地执行下面的代码行。

因此,如果目标文件已打开,我们将在第 40 行检查是否为其指定了名称,以及在设置过程中是否有任何错误。如果一切正常,我们将重命名文件,使它与其余指标所期望的文件一致。总之,在第 41 行,我们删除了出错时使用的临时文件。

在第 43 行和第 45 行之间,我们释放已分配的内存。这样的事情相当重要,但很多人却忘记了去做。根据良好的编程实践,如果分配内存,就应该始终释放内存。这样,MetaTrader 5 就不会过度和不必要地消耗资源。

接下来,我们有一个非常简单的基本过程,从第 50 行开始,我们将计数器递增,紧接着分配内存来存储稍后要用到的数据。这一分配工作在第 51 和 53 行之间进行。还要注意第 54 到 56 行,这是我们存储信息的方式。由于这个过程很简单,我就不细说了。接下来,第 59 行有一个有趣的函数。

从第 59 行开始的这个函数虽然也很简单,但却相当有趣。不是因为它的功能,而是因为它的工作方式。请看第 60 行,这实际上是该函数中的唯一一行。在这个循环中,我们会遍历在第 50 行的函数中添加的所有仓位。问题是,程序员为什么要读取使用第 50 行函数所写入类的信息?这似乎毫无意义。的确,如果只看类代码,这是不合理的,但也有细微的差别。从第 66 行开始的一个小细节。

从第 66 行开始的 Execute 函数非常微妙。原因是很多事情都可能出错。基本上,我们可能会遇到以下错误:

  • 无法读取输入文件;
  • 输出文件可能不可用;
  • StringSplit 函数可能会失败。

任何这些问题都会导致错误常量发生变化。如果出现这种情况,第 71 行的 while 循环将提前终止,导致整个指标在图表上无法正常工作。请记住:如果 _LastError 常量包含 ERR_SUCCESS 以外的值,则在执行析构函数时模板将无法更新。如果是第一次调用,则不会创建。如果没有,指标就不会被放置在图表上。这就是为什么 "Execute" 如此重要。

然而,假设一切运行正常。让我们看看 while 循环内部发生了什么。

在第 73 行,我们从源文件中读取了一个字符串。该字符串将由一个完整的行组成。这样阅读会更容易检查其余部分。接下来,在第 74 行,我们检查是否正在输入某个对象的定义,而在第 75 行,我们检查定义是否已完成。

这些检查对于加快读取和定制模板的过程非常重要。如果不处理任何对象,我们可以直接将信息写入输出文件。该信息是在第 86 行写入的,请注意。因为现在我们将看到如何调整和定制原始模板,以创建我们所需的内容。

进入对象内部后,第 76 行的检查允许我们 "打断" 字符串。这种 "打断" 发生在等号(=)上,这意味着我们正在为对象的某些属性定义参数。如果我们确实在定义一个属性,这些检查就会通过,从而允许我们执行第 78 行。这一行将简单地调整临时内存。但下面的几行代码却提出了一个问题。

在第 79 行中,我们循环查看在 Add 调用(第 48 行的方法)过程中添加的所有数据。如果我们偶然在模板中找到了参数值,这就表明我们正在处理所需的对象。我们暂时保存对象的名称,并表示将进行第二级分析。因为我们执行的是第二级,所以第 79 行不会在同一对象中再次执行。因此,我们必须确保模板的结构与上一篇文章开发回放系统(第 43 部分):Chart Trade 项目(二)中的结构相同。文件必须完全相同。如果要更改,请确保尽可能与您之前提供的版本保持一致。

好吧,既然我们已经进入了第二层,在第 80 行,我们将有另一个循环。重要:第 79 行和第 80 行中的循环永远不会一起执行。总会执行其中一个,但绝不会同时执行两个。唯一的例外 是第一次调用。看似奇怪的是,第 79 行和第 80 行都没有执行,但实际情况就是这样。但如果执行了第 80 行,在循环内部我们就会知道所需的对象属性是否存在。请注意,对象名称很重要,因此我们在第 79 行的循环中暂时写入它。

如果检查结果显示找到了对象属性,我们将执行第 82 行所示的第二次检查。此时,我们将得到第 59 行中函数存在的原因。如果在稍后讨论的编程过程中,您告诉 C_AdjustTemplate 类您不知道在对象的属性中使用什么参数,则第 82 行的检查将导致执行第 83 行,从而固定对象属性中存在的值。如果您指定要使用的值,它将被写入模板。

正是这种功能使 C_AdjustTemplate 类如此有趣,因为您可以要求它报告属性的值,或指定使用哪个值。

关于 C_AdjustTemplate 类的说明到此为止。现在让我们来看看 C_ChartFloatingRAD 类是怎样的。可以想象,它也经过了修改,变得更加有趣。


C_ChartFloatingRAD 类的新外观

虽然我不会在本文中展示该类的最终代码(原因是我想慢慢解释每一个细节),但你会注意到,它现在要比上一篇文章中介绍的代码复杂得多。不过,大部分代码仍保持不变。因此,建议您按顺序阅读文章,否则可能会错过一些细节,影响您的整体理解。

下面是 C_ChartFloatingRAD 类到当前为止的完整代码。

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "../Auxiliar/C_Mouse.mqh"
005. #include "C_AdjustTemplate.mqh"
006. //+------------------------------------------------------------------+
007. class C_ChartFloatingRAD : private C_Terminal
008. {
009.    private :
010.            enum eObjectsIDE {MSG_MAX_MIN, MSG_TITLE_IDE, MSG_DAY_TRADE, MSG_LEVERAGE_VALUE, MSG_TAKE_VALUE, MSG_STOP_VALUE, MSG_BUY_MARKET, MSG_SELL_MARKET, MSG_CLOSE_POSITION, MSG_NULL};
011.            struct st00
012.            {
013.                    int     x, y;
014.                    string  szObj_Chart,
015.                            szFileNameTemplate;
016.                    long    WinHandle;
017.                    double  FinanceTake,
018.                            FinanceStop;
019.                    int     Leverage;
020.                    bool    IsDayTrade,
021.                            IsMaximized;
022.                    struct st01
023.                    {
024.                            int x, y, w, h;
025.                    }Regions[MSG_NULL];
026.            }m_Info;
027. //+------------------------------------------------------------------+
028.            C_Mouse  *m_Mouse;
029. //+------------------------------------------------------------------+
030.            void CreateWindowRAD(int x, int y, int w, int h)
031.                    {
032.                            m_Info.szObj_Chart = (string)ObjectsTotal(GetInfoTerminal().ID);
033.                            ObjectCreate(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJ_CHART, 0, 0, 0);
034.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = x);
035.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = y);
036.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XSIZE, w);
037.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, h);
038.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_DATE_SCALE, false);
039.                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_PRICE_SCALE, false);
040.                            m_Info.WinHandle = ObjectGetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_CHART_ID);
041.                    };
042. //+------------------------------------------------------------------+
043. inline void UpdateChartTemplate(void)
044.                    {
045.                            ChartApplyTemplate(m_Info.WinHandle, "/Files/" + m_Info.szFileNameTemplate);
046.                            ChartRedraw(m_Info.WinHandle);
047.                    }
048. //+------------------------------------------------------------------+
049. inline double PointsToFinance(const double Points)
050.                    {                               
051.                            return Points * (GetInfoTerminal().VolumeMinimal + (GetInfoTerminal().VolumeMinimal * (m_Info.Leverage - 1))) * GetInfoTerminal().AdjustToTrade;
052.                    };
053. //+------------------------------------------------------------------+
054. inline void AdjustTemplate(const bool bFirst = false)
055.                    {
056. #define macro_AddAdjust(A) {                         \
057.              (*Template).Add(A, "size_x", NULL);     \
058.              (*Template).Add(A, "size_y", NULL);     \
059.              (*Template).Add(A, "pos_x", NULL);      \
060.              (*Template).Add(A, "pos_y", NULL);      \
061.                            }
062. #define macro_GetAdjust(A) {                                                                          \
063.              m_Info.Regions[A].x = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_x"));  \
064.              m_Info.Regions[A].y = (int) StringToInteger((*Template).Get(EnumToString(A), "pos_y"));  \
065.              m_Info.Regions[A].w = (int) StringToInteger((*Template).Get(EnumToString(A), "size_x")); \
066.              m_Info.Regions[A].h = (int) StringToInteger((*Template).Get(EnumToString(A), "size_y")); \                                               
067.                            }
068.                            
069.                            C_AdjustTemplate *Template;
070.                            
071.                            if (bFirst)
072.                            {
073.                                    Template = new C_AdjustTemplate("Chart Trade/IDE_RAD.tpl", m_Info.szFileNameTemplate = StringFormat("Chart Trade/%u.tpl", GetInfoTerminal().ID));
074.                                    for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_AddAdjust(EnumToString(c0));
075.                            }else Template = new C_AdjustTemplate(m_Info.szFileNameTemplate);
076.                            Template.Add("MSG_NAME_SYMBOL", "descr", GetInfoTerminal().szSymbol);
077.                            Template.Add("MSG_LEVERAGE_VALUE", "descr", (string)m_Info.Leverage);
078.                            Template.Add("MSG_TAKE_VALUE", "descr", (string)m_Info.FinanceTake);
079.                            Template.Add("MSG_STOP_VALUE", "descr", (string)m_Info.FinanceStop);
080.                            Template.Add("MSG_DAY_TRADE", "state", (m_Info.IsDayTrade ? "1" : "0"));
081.                            Template.Add("MSG_MAX_MIN", "state", (m_Info.IsMaximized ? "1" : "0"));
082.                            Template.Execute();
083.                            if (bFirst) for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++) macro_GetAdjust(c0);
084.                            
085.                            delete Template;
086.                            
087.                            UpdateChartTemplate();
088.                                                            
089. #undef macro_AddAdjust
090. #undef macro_GetAdjust
091.                    }
092. //+------------------------------------------------------------------+
093.            eObjectsIDE CheckMousePosition(const int x, const int y)
094.                    {
095.                            int xi, yi, xf, yf;
096.                            
097.                            for (eObjectsIDE c0 = 0; c0 <= MSG_CLOSE_POSITION; c0++)
098.                            {
099.                                    xi = m_Info.x + m_Info.Regions[c0].x;
100.                                    yi = m_Info.y + m_Info.Regions[c0].y;
101.                                    xf = xi + m_Info.Regions[c0].w;
102.                                    yf = yi + m_Info.Regions[c0].h;
103.                                    if ((x > xi) && (y > yi) && (x < xf) && (y < yf) && ((c0 == MSG_MAX_MIN) || (c0 == MSG_TITLE_IDE) || (m_Info.IsMaximized))) return c0;
104.                            }
105.                            return MSG_NULL;
106.                    }
107. //+------------------------------------------------------------------+
108.    public  :
109. //+------------------------------------------------------------------+
110.            C_ChartFloatingRAD(string szShortName, C_Mouse *MousePtr, const int Leverage, const double FinanceTake, const double FinanceStop)
111.                    :C_Terminal()
112.                    {
113.                            m_Mouse = MousePtr;
114.                            m_Info.Leverage = (Leverage < 0 ? 1 : Leverage);
115.                            m_Info.FinanceTake = PointsToFinance(FinanceToPoints(MathAbs(FinanceTake), m_Info.Leverage));
116.                            m_Info.FinanceStop = PointsToFinance(FinanceToPoints(MathAbs(FinanceStop), m_Info.Leverage));
117.                            m_Info.IsDayTrade = true;
118.                            m_Info.IsMaximized = true;
119.                            if (!IndicatorCheckPass(szShortName)) SetUserError(C_Terminal::ERR_Unknown);
120.                            CreateWindowRAD(115, 64, 170, 210);
121.                            AdjustTemplate(true);
122.                    }
123. //+------------------------------------------------------------------+
124.            ~C_ChartFloatingRAD()
125.                    {
126.                            ObjectDelete(GetInfoTerminal().ID, m_Info.szObj_Chart);
127.                            FileDelete(m_Info.szFileNameTemplate);
128.                            
129.                            delete m_Mouse;
130.                    }
131. //+------------------------------------------------------------------+
132.            void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
133.                    {
134.                            static int sx = -1, sy = -1;
135.                            int x, y, mx, my;
136.                            static eObjectsIDE it = MSG_NULL;
137.    
138.                            switch (id)
139.                            {
140.                                    case CHARTEVENT_MOUSE_MOVE:
141.                                            if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft))
142.                                            {
143.                                                    switch (it = CheckMousePosition(x = (int)lparam, y = (int)dparam))
144.                                                    {
145.                                                            case MSG_TITLE_IDE:
146.                                                                    if (sx < 0)
147.                                                                    {
148.                                                                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, false);
149.                                                                            sx = x - m_Info.x;
150.                                                                            sy = y - m_Info.y;
151.                                                                    }
152.                                                                    if ((mx = x - sx) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = mx);
153.                                                                    if ((my = y - sy) > 0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = my);
154.                                                                    break;
155.                                                    }
156.                                            }else
157.                                            {
158.                                                    if (it != MSG_NULL)
159.                                                    {
160.                                                            switch (it)
161.                                                            {
162.                                                                    case MSG_MAX_MIN:
163.                                                                            m_Info.IsMaximized = (m_Info.IsMaximized ? false : true);
164.                                                                            ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YSIZE, (m_Info.IsMaximized ? 210 : m_Info.Regions[MSG_TITLE_IDE].h + 6));
165.                                                                            break;
166.                                                                    case MSG_DAY_TRADE:
167.                                                                            m_Info.IsDayTrade = (m_Info.IsDayTrade ? false : true);
168.                                                                            break;
169.                                                            }
170.                                                            it = MSG_NULL;
171.                                                            AdjustTemplate();
172.                                                    }
173.                                                    if (sx > 0)
174.                                                    {
175.                                                            ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);                                                
176.                                                            sx = sy = -1;
177.                                                    }
178.                                            }
179.                                            break;
180.                            }
181.                    }
182. //+------------------------------------------------------------------+
183. };
184. //+------------------------------------------------------------------+

C_ChartFloatingRAD 类源代码

我知道这些代码看起来非常混乱和复杂,尤其是对初学者来说。但是,如果您从一开始就一直关注本系列,那么您现在应该已经学到了很多有关 MQL5 编程的知识。总之,这段代码比许多人通常创建的代码要复杂得多。

如果将这段代码与前一篇文章中的代码进行比较,你也会发现它变得更加复杂了。不过,大部分的复杂性都转移到了 C_AdjustTemplate 类中,该类已在上一节中进行了介绍。让我们弄清楚这段代码的作用,因为这正是 Chart Trade 指标的神奇之处。这是因为显示的指标代码在上一篇文章中保持不变。但是,显示的代码在这里已经发生了变化,而且这种变化足以为指标添加新的功能。

首先,我们来看第 10 行,这里有一个枚举,可以让我们更方便地访问模板中的对象。但同一枚举的值 MSG_NULL,是用于控制的。在进一步的解释过程中,这一点将变得很清楚。

查看代码,我们会在第 22 行和第 25 行之间看到一个结构,它将被一个数组变量使用。但是,如果你查看数组中的元素数量,你可能会想知道它是什么。我们没有看到数值,它是别的东西。你可能会想,"我到处都找不到它是什么意思"。冷静下来,不必惊慌。该数据表示数组中的元素个数,是第 10 行中枚举的最后一个数据。但这里有一个细节。最后一个值实际上并不代表一个元素或对象。如果是的话,声明就应该是不同的。

下一行值得解释的是第 54 行。这就是我们实际访问模板的地方。但在解释之前,让我们再看另一件事。该函数可在两个位置访问。第一个在第 121 行,是构造函数,第二个在第 171 行,是消息处理部分。为什么要知道这些?原因在于我们在模板中做了什么和想做什么。

第一种情况发生在构造函数中,我们希望自定义模板,使其按某种方式运行。在第二种情况下,我们将利用我们已有的东西;我们不会改变模板,但我们会使它完全符合我们的要求。

这种解释可能看起来有点混乱,但让我们看看第 54 行的方法是如何工作的。也许这能帮助你更好地了解情况。在第 56 行和第 67 行之间,我们定义了两个宏,用于帮助我们更轻松地编程。就像第 89 行和第 90 行用于消除此类宏定义一样。我通常喜欢在多次重复相同代码或某些参数时使用宏。在这种特殊情况下,一个参数会重复出现。宏代码非常简单。

第一行位于第 56 行和第 61 行之间,将添加 C_AdjustTemplate 类返回给我们的元素。在第 62 行和第 67 行之间的第二个宏中,我们使用 C_AdjustTemplate 类告诉我们的值,并将其转换为另一个值,存储在第 25 行声明的数组中。注意这一点。我们不是简单地猜测对象在哪里,而是询问模板它们在哪里。

在第 71 行,我们检查是否开始调整模板。如果不是这种情况,我们就执行第 75 行的调用。但如果这是第一次调用,我们还要告诉 C_AdjustTemplate 类将使用哪些名称。这是在第 73 行完成的。现在请特别注意第 74 行。可以看到,在这一行中,我们使用了一个枚举来告诉 C_AdjustTemplate 类我们需要从哪些对象中获取数据。为此,我们使用了一个循环。这样,C_AdjustTemplate 类就能知道哪些属性需要修正。

在任何情况下,在第 76 行和第 81 行中,我们都向模板传递了对象属性中应使用的值。每行指定一个对象、一个要更改的属性和一个要使用的值。

最后,在第 82 行中,我们告诉 C_AdjustTemplate 类,它可以根据所提供的信息定制模板。具体做法如上一主题所示。所有工作完成后,我们在第 83 行检查这是否是第一次调用。如果是,我们就调整第 25 行声明的数组中的值。这需要使用一个循环,告诉 C_AdjustTemplate 类我们想要了解哪些对象和属性。

这项工作完成后,我们使用第 85 行来关闭 C_Template 类。最后,在第 87 行,我们要求 OBJ_CHART 对象更新。这样,我们就能看到文章末尾演示视频中展示的神奇效果。

请注意:我们不会在此检查任何错误,而是假定一切正常并按预期运行。但我们会在指标代码中检查是否有错误。因此,如果出现任何故障,不是在这里处理,而是将在指标代码中处理。

现在让我们来看看别的事情:第 93 行运行着一个相当有趣的函数,可以把它放在需要用到的地方。这样做是为了使代码更易读。该函数有一个从第 97 行开始的循环,遍历 OBJ_CHART 中的每个对象。请记住:OBJ_CHART 对象包含一个模板,该模板包含我们要检查的对象。在检查过程中,我们将创建一个矩形,作为每个对象的点击区域。这是在第 99 和 102 行完成的。

获得点击区域后,我们就可以将其与作为调用参数所指定的区域进行比较。这个比较是在第 103 行进行的。除了区域比较,还有一些附加条件。如果一切正常,将返回对象的索引;否则将返回 MSG_NULL。这就是为什么我们需要在开头定义的枚举中包含这个值。如果没有这个值,就无法报告点击的是 Chart Trade 指标中的无效对象。

接下来要解释的是第 132 行。它是事件处理程序。现在,它包含了一些新的部分。但正是这些新部分,才使您在演示视频中看到的一切成为可能。让我们来仔细看看到底发生了什么。请注意,到此为止,除了 OBJ_CHART,我们还没有创建任何其他对象。然而,我们有预想的功能。

大部分代码看起来与前一篇文章中显示的非常相似。不过,有些细微的差别还是值得一说的,以便让那些经验不足的人了解情况。在第 134 到 136 行,我们定义了一些变量。第 136 行定义的变量在本文的框架内特别重要,因为其他变量已经解释过了。

我们将在第 136 行使用该变量作为存储。这是因为我们无法依靠 MetaTrader 5 的额外协助来解决与点击相关的问题。通常,当图表上有对象时,MetaTrader 5 会告诉我们被点击对象的名称。这是通过 CHARTEVENT_OBJECT_CLICK 事件实现的。但在这种情况下,除了 OBJ_CHART,我们没有其他真正的对象。因此,在 Chart Trade 指标区域的任何点击都会被 MetaTrader 5 理解为对 OBJ_CHART 的点击。

我们处理的唯一事件是 CHARTEVENT_MOUSE_MOVE,这对我们来说已经足够了。不过,只有当鼠标指示器不在研究状态时,才会处理点击。第 141 行对此进行了检查。如果鼠标指标处于研究状态,或者发生了其他情况,我们将转到第 156 行。这里有一个问题。如果第 136 行中声明的变量有不同的值,那么一定会发生什么。但首先,让我们来看看这个变量是在何时何地获取其值的。

当鼠标指标处于空闲状态并发生点击时,第 141 行执行的检查将确定单击的位置和内容。这在第 143 行完成。此时,我们要告诉分析函数鼠标点击时的位置。不过,这里有一个小瑕疵,我现在就不多说了,因为它将在下一篇文章中得到纠正,还有其他一些我们还需要做的小事情。在进行检查的同时,函数会返回接收到点击的对象名称。该名称将被写入一个静态变量中。

由于实际原因,我们现在只检查标题对象。如果它被点击了,第 145 行将允许执行拖动代码。我们可以在这里放置其他对象,但这样会使检查逻辑复杂化,至少在现阶段是这样。由于鼠标按钮被按下,对象将继续接收点击消息。

如前所述,我们可以改进这一点。但现在我想让代码尽可能简单,逐步进行修改,因为这里展示的概念与许多人通常的编程大相径庭。

但让我们回到第 156 行。当执行这一行时,我们将在该条件中执行两次检查。第一个选项将确定是否点击了 OBJ_CHART 中的任何对象。如果发生了这种情况,我们就会遇到与对象实际存在于图表上时相同的条件,MetaTrader 5 就会生成 CHARTEVENT_OBJECT_CLICK 事件。也就是说,我们 "模拟" 现有系统的运行。我们这样做是为了确保整个指标的充分运行。

如果第 158 行的检查成功通过,我们将首先执行相应的事件处理。这将在第 160 和 169 行完成,然后在第 170 行移除对象的引用,在第 171 行更新模板的当前状态。这样,整个指标都会被更新,造成图表上存在对象的假象,尽管真正的对象只有 OBJ_CHART

C_ChartFloatingRAD 类中的所有其他代码都已在上一篇文章中解释过,因此我认为没有必要在此再次进行注释。

演示视频

结论

正如您所看到的,在本文中,我介绍了一种在 OBJ_CHART 中使用模板的方法,通过这种方法,您可以获得与图表上存在真实对象时非常相似的行为。我所展示的功能的最大优势可能是能够利用 MetaTrader 5 本身的元素快速创建界面,而无需使用复杂的 MQL5 编程。

虽然我所演示的内容看起来相当混乱和复杂,但这只是因为它对亲爱的读者你来说是全新的东西。随着时间的推移,当你开始练习时,你会发现这些知识可以广泛应用于各种场景和情况。然而,该系统尚未完成。此外,它还有一个小缺陷。但在下一篇文章中,我们将修复这一问题,最终允许用户直接在Chart Trade 中输入数值。这部分的实现将非常有趣。请不要错过本系列的下一篇文章。


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

附加的文件 |
Anexo.zip (420.65 KB)
如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV) 如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV)
在本文中,我们将介绍另一个基于波动率的指标——蔡金波动率(Chaikin Volatility)。在了解到蔡金波动率的使用方法和构建方式之后,我们将学习如何构建自定义指标。我们将分享一些可用的简单策略,并对其进行测试,以了解哪个策略更优。
构建K线图趋势约束模型(第一部分):针对EA和技术指标 构建K线图趋势约束模型(第一部分):针对EA和技术指标
本文面向初学者和专业的MQL5开发者。它提供了一段代码,用于定义并限制信号生成指标仅在较长的时间框架的趋势中运行。通过这种方式,交易者可以通过融入更广泛的市场视角来增强他们的策略,从而可能产生更稳健和可靠的交易信号。
如何使用抛物线转向(Parabolic SAR)指标设置跟踪止损(Trailing Stop) 如何使用抛物线转向(Parabolic SAR)指标设置跟踪止损(Trailing Stop)
在创建交易策略时,我们需要测试多种多样的保护性止损。这时,一个随着价格变动而动态调整止损位的想法浮现在我的脑海中。抛物线转向(Parabolic SAR)指标无疑是最佳选择。很难想到有比这更简单且视觉上更清晰的指标了。
练习开发交易策略 练习开发交易策略
在本文中,我们将尝试开发自己的交易策略。任何交易策略都必须以某种统计优势为基础。而且,这种优势应该长期存在。