English Русский Español Deutsch 日本語 Português
preview
市场模拟:仓位视图(四)

市场模拟:仓位视图(四)

MetaTrader 5 — 测试者 |
26 0
Daniel Jose
Daniel Jose

引言

大家好,欢迎阅读本系列关于如何创建复制/模拟系统的又一篇文章。

在上一篇文章“市场模拟:仓位视图(三)”中,我们展示了了解何时以及为何应为对象设置 ZOrder 属性的重要性。到目前为止,我一直保持了相当的耐心,并没有确切展示我们将要做什么。阅读了之前的文章后,你可能会认为这一切都很好,但完全不切实际。好吧,简单部分已经结束了。现在,我们要做一些事情,否则我们无法继续实现仓位指标。为了继续实现仓位指标,我们需要整合四个现有的应用程序。然而,亲爱的读者,您暂时还不必担心挂单。我们如何处理它们将在未来变得清晰。因此,我们需要一步步地向前推进。

现阶段,最重要的不是处理挂单,而是确保妥善的仓位管理。请记住以下几点:我们已经拥有一个可以以市场价格开仓和平仓的 EA。我们还有 Mouse Study 指标,它已经可以让我们创建简单的图表分析工具和图形对象,尽管您可以对其进行修改以创建更复杂的研究。我们还有 Chart Trade,允许向 EA 发送指令,指示买入或卖出,以及交易量。但这三个应用程序仍然需要一个额外的组件,才能构成一个可行的系统。这个缺失的组件正是我们现在正在实现的指标。

在当前开发阶段,仓位指标不过是一个有趣的应用程序。但如果我们将它与已经创建的其他三个应用程序结合起来,整个系统就会变得有趣得多。然而,你可能会想:我们该怎么做呢?我们要在 EA 中使用 C_IndicatorPosition 类吗?不,我们不会那样做。我们将把仓位指标与 EA 分开。这样,你就可以将它添加到你自己的 EA 中。

当然,您需要进行一些调整,以便您的 EA 可以使用仓位指标。但相信我,这比创建多个具有相同操作功能的 EA 要容易得多。对仓位指标的任何更改都会立即反映在使用它的所有 EA 中。这与每个 EA 都有其自己的仓位指标的情况不同。

我所提出的想法,以及如何实现它的演示,并非什么新鲜事物。事实上,这正是不同程序能够使用共享组件的原因。这与使用 DirectX 的电脑游戏有些相似。当 DirectX 更新时,所有使用 DirectX 库的游戏和程序也会随之更新。现在想象一下,如果每次 DirectX 库改进时,每个应用程序都必须手动更新,那将完全不切实际。因此,这种模式不应被视为潜在问题。相反,它可以被视为一种解决方案,随着时间的推移,将使我们能够创建越来越有用且安全的应用程序。一个应用程序中的任何修复或改进都会影响 MetaTrader 5 中使用的整个应用程序链。那么,让我们开始吧。


让我们回顾一下 EA 的当前状态。

在对代码进行任何更改之前,我们需要先查看一下主 EA 代码是什么样子。这是因为距离上一次更改已经过去了相当长的时间。因此,在下面的代码中,你可以看到 EA 实现的当前状态:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - EA.ico"
04. #property description "Demo version between interaction"
05. #property description "of Chart Trade and Expert Advisor"
06. #property version   "1.84"
07. #property link "https://www.mql5.com/pt/articles/12598"
08. //+------------------------------------------------------------------+
09. #include <Market Replay\Order System\C_Orders.mqh>
10. //+------------------------------------------------------------------+
11. enum eTypeContract {MINI, FULL};
12. //+------------------------------------------------------------------+
13. input eTypeContract user00 = MINI;         //Cross order in contract
14. //+------------------------------------------------------------------+
15. C_Orders    *Orders;
16. long        GL_ID;
17. //+------------------------------------------------------------------+
18. int OnInit()
19. {
20.     GL_ID = 0;
21.     Orders = new C_Orders(0xC0DEDAFE78514269);
22.     
23.     return INIT_SUCCEEDED;
24. }
25. //+------------------------------------------------------------------+
26. void OnTick() {}
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
31.     switch (id)
32.     {
33.         case CHARTEVENT_CHART_CHANGE:
34.             if (GL_ID > 0)    break; else GL_ID = ChartID();
35.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
36.             EventChartCustom(GL_ID, evEA_At_ChartTrade, user00, 0, "");
37.             break;
38.     }
39. }
40. //+------------------------------------------------------------------+
41. void OnDeinit(const int reason)
42. {
43.     switch (reason)
44.     {
45.         case REASON_REMOVE:
46.         case REASON_INITFAILED:
47.             EventChartCustom(GL_ID, evEA_At_ChartTrade, -1, 0, "");
48.             break;
49.     }
50.     
51.     delete Orders;
52. }
53. //+------------------------------------------------------------------+

原始 EA 代码

哇!这段代码最后一次修改真的是在文章 84 吗?是的,自那以后就再也没有做过任何修改。但现在该是时候重新开始处理它了。在对这段代码进行任何修改之前,我们需要先了解以下几点。请注意,第 13 行包含了与用户或交易者交互的唯一形式。这种交互旨在告知所有将以某种方式与 EA 交互的其他应用程序,它是适用于完整合约还是迷你合约。

对于那些不在 B3(巴西证券交易所)进行交易的人来说,这个设置可能不太有意义。但无论你交易什么,你都需要在图表上使用 Chart Trade 指标,以及这个 EA。将 EA 添加到图表后,它就会向 MetaTrader 5 中运行的所有应用程序发送消息。我之前在同一系列文章中已经解释过这一点。如果您不明白这是怎么回事,请尝试阅读之前的文章,以更深入地理解这个主题。理解这个消息传递问题在后续内容中将至关重要。如果不理解,你将无法理解后续文章中的内容。

好的,此消息发送后,所有应用程序都会知道 EA 对它们的期望。此 EA 不会直接从用户或交易者那里接收命令。它只会通过消息接收和发送命令。因此,不要期望看到线性且完全有序的代码。这里存在相当多的事件驱动交互。

好的,但是,在这一切中,仓位指标又在哪里出现呢?如果 EA 不知道图表上存在该指标,我们该如何使用它呢?事实上,和其他应用程序一样,EA 并不知道其他应用程序的存在。它们都只是期望交易者将它们添加到图表上,以便它们能够正确执行任务。但这里有一个重要的细节。由于每个仓位指标只对应一个特定仓位,因此我们不能在为该交易品种出现仓位之前将其附加到图表上。与其他指标不同,仓位指标将由 EA 自动添加和移除。其理念是,交易者不应该干预这个过程。EA 会自动处理。

但是,我不会将仓位指标嵌入到 EA 中。我的想法是,该指标可以被修改或改进,以便所有 EA 都能使用,而无需重新编译。但为了让一切无缝衔接,需要进行一些更改。同时,亲爱的读者,你应该密切关注将要解释的内容。否则,整个系统将无法正常工作,无法提供图表上应显示的数据。另一点是,目前你不需要担心价格线的细节。我们现在需要做的第一件事是,为 EA 和仓位指标创建一种交互方式。之后,我们将处理仓位指标与鼠标指标之间的交互,后者将负责管理价格线。


开始实现交互

好的,我们先来对仓位指标的主代码做一些修改。原因是它不再 — 或者更确切地说,不应该再由交易者或用户添加到图表中。这将由 EA 来处理。但是,由于我们希望 EA 能够识别每个仓位所对应的指标,我们需要在指标代码和 EA 代码中都进行一些修改。

所以,既然我们已经明确了需要做什么,那就让我们从新的仓位指标代码开始吧。您可以在下面看到:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Indicator for tracking an open position on the server."
04. #property description "This should preferably be used together with an Expert Advisor."
05. #property description "For more details see the same article."
06. #property link "https://www.mql5.com/pt/articles/13168"
07. #property version   "1.116"
08. #property indicator_chart_window
09. #property indicator_plots 0
10. //+------------------------------------------------------------------+
11. #define def_ShortName "Position View"
12. //+------------------------------------------------------------------+
13. #include <Market Replay\Order System\C_IndicatorPosition.mqh>
14. //+------------------------------------------------------------------+
15. input ulong user00 = 0;                 //For Expert Advisor use
16. input color user01 = clrRoyalBlue;      //Color Line Price
17. input color user02 = clrForestGreen;    //Color Line Take Profit
18. input color user03 = clrFireBrick;      //Color Line Stop Loss
19. //+------------------------------------------------------------------+
20. C_IndicatorPosition *Positions = NULL;
21. //+------------------------------------------------------------------+
22. int OnInit()
23. {
24.     IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
25.     Positions = new C_IndicatorPosition(user01, user02, user03);
26.     if (!Positions.CheckCatch(user00))
27.     {
28.         ChartIndicatorDelete(ChartID(), 0, def_ShortName);
29.         return INIT_FAILED;
30.     }
31. 
32.     return INIT_SUCCEEDED;
33. }
34. //+------------------------------------------------------------------+
35. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
36. {
37.     return rates_total;
38. }
39. //+------------------------------------------------------------------+
40. void OnDeinit(const int reason)
41. {
42.     delete Positions;
43. }
44. //+------------------------------------------------------------------+

仓位视图指标代码

请注意,代码已经进行了小幅修改。这些修改都很直观,无需过多解释。但你应特别注意,现在第 15 行需要从 EA 获取数据。作为交易者,你甚至可以自己输入这些数据。但我建议你不要更改这些信息。如果你谨慎操作且不将它们设置为相同,你可以更改颜色,因为目前我们使用它们来检查每种颜色代表哪条水平线。总之,只要可能,就避免更改应由 EA 提供的数据。具体来说,当这两个组件都出现在图表上时,EA 将使用这些数据与指标交换信息。

请注意,在第 26 行,我们将一个参数传递给了检查函数。此前,该参数并不存在。让我们来看一下 C_IndicatorPosition 类的代码,完整代码如下所示:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define def_SufixTake    "Take"
05. #define def_SufixStop    "Stop"
06. //+------------------------------------------------------------------+
07. #include "..\Auxiliar\C_Terminal.mqh"
08. //+------------------------------------------------------------------+
09. class C_IndicatorPosition : private C_Terminal
10. {
11.     private    :
12.         struct st00
13.         {
14.             ulong        ticket;
15.             color        corPrice, corTake, corStop;
16.         }m_Infos;
17. //+------------------------------------------------------------------+
18.         void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n")
19.         {
20.             if (price <= 0) return;
21.             CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop))));
22.             ObjectSetDouble(GetInfoTerminal().ID, szObjName, OBJPROP_PRICE, price);
23.             ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TEXT, szDescription);
24.             ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TOOLTIP, szDescription);
25.             ObjectSetInteger(GetInfoTerminal().ID, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice);
26.         }
27. //+------------------------------------------------------------------+
28.     public    :
29. //+------------------------------------------------------------------+
30.         C_IndicatorPosition(color corPrice, color corTake, color corStop)
31.             :C_Terminal()
32.         {
33.             ZeroMemory(m_Infos);
34.             m_Infos.corPrice = corPrice;
35.             m_Infos.corTake  = corTake;
36.             m_Infos.corStop  = corStop;
37.         }
38. //+------------------------------------------------------------------+
39.         ~C_IndicatorPosition()
40.         {
41.             if (m_Infos.ticket != 0)
42.                 ObjectsDeleteAll(GetInfoTerminal().ID, IntegerToString(m_Infos.ticket));
43.         }
44. //+------------------------------------------------------------------+
45.         bool CheckCatch(ulong ticket)
46.         {
47.             string szName = IntegerToString(m_Infos.ticket = ticket);
48.             
49.             if (!PositionSelectByTicket(m_Infos.ticket)) return false;
50.             if (ObjectFind(GetInfoTerminal().ID, szName) >= 0)
51.             {
52.                 m_Infos.ticket = 0;
53.                 return false;
54.             }
55.             IndicatorSetString(INDICATOR_SHORTNAME, szName);
56.             CreateLineInfos(szName, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice, "Position opening price.");
57.             CreateLineInfos(szName + def_SufixTake, PositionGetDouble(POSITION_TP), m_Infos.corTake, "Take Profit point.");
58.             CreateLineInfos(szName + def_SufixStop, PositionGetDouble(POSITION_SL), m_Infos.corStop, "Stop Loss point.");
59.             
60.             return true;
61.         }
62. //+------------------------------------------------------------------+
63. };
64. //+------------------------------------------------------------------+
65. #undef def_SufixTake
66. #undef def_SufixStop
67. //+------------------------------------------------------------------+

C_IndicatorPosition.mqh

虽然代码有所变化,但这些变化都很直观,不需要过多解释。不过,如前所述,检查函数现在接受一个参数。让我们来看看这个函数有哪些变化。为此,请转到第 45 行,其中定义了 CheckCatch 函数。

请注意,函数一开始,我们就会看到第 47 行。在这个阶段,我们会转换 EA 传递给我们的数据,以便在创建对象时使用。然后,在第 49 行,我们会检查仓位是否存在。值得解释一下我们为何要再次执行此检查,因为 EA 会告诉我们并保证仓位存在。好的,关键是,我无意将此仓位指标作为 EA 的组成部分。因此,如果交易者甚至用户试图将此指标添加到图表中,我们需要确认仓位存在。请注意,在这种情况下,我们并非像上一篇文章那样从指标中检索此参数。

一旦确认该仓位存在,就会进行新的检查。这是在第 50 行完成的。但在这种情况下,该检查用于确保图表上的每个仓位只有一个指标实例。还有其他方法可以执行相同的检查。但是,在这里,我们检查是否存在具有特定名称的线。如果一切顺利,该指标将按照上一篇文章中所示的方式运行。因此,我们可以继续研究 EA 的代码,以了解当 EA 添加到图表中时,系统将如何运行。新的 EA 代码如下所示:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - EA.ico"
04. #property description "Demo version between interaction"
05. #property description "of Chart Trade and Expert Advisor"
06. #property version   "1.116"
07. #property link "https://www.mql5.com/pt/articles/13168"
08. //+------------------------------------------------------------------+
09. #include <Market Replay\Order System\C_Orders.mqh>
10. //+------------------------------------------------------------------+
11. enum eTypeContract {MINI, FULL};
12. //+------------------------------------------------------------------+
13. input eTypeContract user00 = MINI;         //Cross order in contract
14. //+------------------------------------------------------------------+
15. C_Orders    *Orders;
16. long        GL_ID;
17. //+------------------------------------------------------------------+
18. int OnInit()
19. {
20.     GL_ID = 0;
21.     Orders = new C_Orders(0xC0DEDAFE78514269);
22.     
23.     return INIT_SUCCEEDED;
24. }
25. //+------------------------------------------------------------------+
26. void OnTick() {}
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
31.     switch (id)
32.     {
33.         case CHARTEVENT_CHART_CHANGE:
34.             if (GL_ID > 0)    break;
35.             else
36.             {
37.                 GL_ID = ChartID();
38.                 for (int handle, count = PositionsTotal() - 1; count >= 0; count--)
39.                 {
40.                     handle = iCustom(_Symbol, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", PositionGetTicket(count));
41.                     ChartIndicatorAdd(GL_ID, 0, handle);
42.                     IndicatorRelease(handle);
43.                 }
44.             }
45.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
46.             EventChartCustom(GL_ID, evEA_At_ChartTrade, user00, 0, "");
47.             break;
48.     }
49. }
50. //+------------------------------------------------------------------+
51. void OnDeinit(const int reason)
52. {
53.     switch (reason)
54.     {
55.         case REASON_REMOVE:
56.         case REASON_INITFAILED:
57.             EventChartCustom(GL_ID, evEA_At_ChartTrade, -1, 0, "");
58.             break;
59.     }
60.     
61.     delete Orders;
62. }
63. //+------------------------------------------------------------------+

Expert Advisor.mq5

请注意,与本文开头所示的代码相比,这段代码的改动非常小。改动如此之少的真正原因是为了向读者展示,系统正常运行并不需要付出太多精力。请注意,所有改动,或者说代码的增加部分,都位于第 38 行和第 43 行之间。但这已经足够让 EA 至少与仓位指标建立连接。事实上,我们在这里所做的操作,过去是在指标代码内部完成的。在这段代码中,每个未平仓位都会在图表上附加一个仓位指标。

然而,请注意一件重要的事情。我们追踪所有未平仓位,但 — 这一点很重要 — 我们不对它们进行筛选。我们只是简单地在运行 EA 的图表上添加指标。但一个仓位可能对应于与该图表上预期显示的交易品种不同的交易品种。好吧,你可能会认为这是我的错误,但并非如此,我故意这样做是为了表明这种错误相当普遍。而当人们注意到这一点时,一个不可靠的机制已经被使用了。

请注意,问题不在于我们使用 MQL5 中的 _Symbol 变量来访问图表交易品种的名称。错误在于,我们没有像在指标代码中那样,检查 PositionGetTicket 函数返回的仓位是否属于图表交易品种,或与图表交易品种存在某种关联。这是一个你应该时刻警惕的错误。在测试期间,我们可能会觉得模拟账户上一切运行正常。然而,当转移到真实账户时,由于编程粗心或在实施过程中测试不足,可能会造成资金损失。因此,亲爱的读者,如果你只是在学习或掌握新概念,那么在更改任何代码时都应该小心谨慎。始终彻底检查你所做的更改,并仔细阅读文档以消除任何疑虑。

好的,现在你应该开始明白,编程的某些方面比乍一看要复杂一些。然而,这并不是气馁和放弃编程的理由。最重要的是适应并找到正确的路径。但你一定在想,在这种情况下,只需检查单号上对应的仓位交易品种是否与我们预期使用的交易品种相匹配就足够了。不是吗?在某种程度上,是的。

我之所以这么说,正是因为我不知道你交易的是哪个市场。这一点在文章开头已经提到过。在一些情况下,主要是在B3(巴西交易所)上,我们既有完整的合约,也有迷你合约。我们还掌握当前合约的历史数据。一些交易者更喜欢使用历史数据进行交易。而另一些交易者则使用他们想要交易的品种的图表。

请注意,尽管这看起来可能是一个复杂的情况,你可能会疑惑,为何有人会试图根据与特定合约或交易品种图表不同的图表进行交易,从而让自己的生活变得复杂,但这始终是交易者的选择。我们不能限制交易者;我们作为程序员的任务是满足交易者的需求。

这就引出了另一个问题:如果我们无法知道哪个交易品种是正确的,我们该如何处理这种情况?然而,亲爱的读者,情况并没有那么复杂。是的,我们知道对应交易品种的数据在哪里。它显示在图表交易指标中。而这正是事情变得真正有趣的地方。程序员有两种选择。第一种方法是询问 “Chart Trade” 面板哪个交易品种是正确的,以及应该在图表上显示哪些仓位。好吧,你可能会想:“但我为什么要问 Chart Trade 这个问题呢?如果数据显示在 “Chart Trade” 对象中,我只需要读取该对象的内容即可。”

亲爱的读者,你可能会认为你应该这么做。数据就在 Chart Trade 对象中,但访问它并不那么简单。要从 Chart Trade 获取此数据,我们需要创建一个单独的消息,但这会大大增加 EA 代码的复杂性。然而,还有另一种更简单的解决方法,但我们需要正确地进行操作。否则,我们将破坏很久以前创建和测试过的封装。如果你冷静地思考并分析代码,你会发现 Chart Trade 面板是从 C_Terminal 类中获取交易品种数据的。更准确地说,是从 C_Terminal 类的第 47 行的 CurrentSymbol 过程中获取的。

这正是危险所在,尤其是对于那些刚开始学习编程的人来说。许多新手开发人员会试图直接从 EA 访问这个过程。这是不可能的,因为这个过程位于受保护的部分。所以你可能会想:“我会把这个过程移到公有区域。”这是一个错误。而第二个错误是:如果你查看这个过程,你会发现它既不返回值也不接受任何参数。然后你决定修改它,让它返回正确交易品种的数据,但这样做会完全破坏这个过程,因为将来它可能会发生变化,使我们能够添加更多功能。因此,正确的解决方案是不要修改 C_Terminal 类第 47 行的过程。

所以,作为一个初学者,你可能会想:“好吧,这个问题无法解决,因为我们不可能知道会使用哪个交易品种。”但这是错误的问题表述方式。是的,有一个解决方案,它简单、优雅且高效。它不会给已经创建并测试过的代码引入副作用。我们只需转到 C_Terminal 类的构造函数并在那里进行更改。源代码片段如下:

106. //+------------------------------------------------------------------+        
107.         C_Terminal(const long id = 0, const uchar sub = 0)
108.             {
109.                 m_Infos.ID = (id == 0 ? ChartID() : id);
110.                 m_Mem.AccountLock = false;
111.                 m_Infos.SubWin = (int) sub;
112.                 CurrentSymbol(false);
113.                 ZeroMemory(m_Mouse);
114.                 m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);

C_Terminal.mqh 文件的原始片段

需要进行的更改如下所示:

106. //+------------------------------------------------------------------+        
107.         C_Terminal(const long id = 0, const uchar sub = 0, const bool bFull = false)
108.             {
109.                 m_Infos.ID = (id == 0 ? ChartID() : id);
110.                 m_Mem.AccountLock = false;
111.                 m_Infos.SubWin = (int) sub;
112.                 CurrentSymbol(bFull);
113.                 ZeroMemory(m_Mouse);
114.                 m_Mem.Show_Descr = ChartGetInteger(m_Infos.ID, CHART_SHOW_OBJECT_DESCR);

C_Terminal.mqh 文件的修改片段

你注意到这两段代码的区别了吗?这是一个非常小巧、简单且优雅的改动。现在,我们可以在 EA 中直接使用 C_Terminal 类来获取品种名称。或者,我们可以将数据传递给仓位指标,让它为我们执行这项任务。无论哪种情况,EA 现在都能告诉仓位指标,它是要显示完整合约还是迷你合约。这是通过直接使用合约历史来实现的。你可能会认为这会产生问题,因为它可能与 Chart Trade 中指定的内容冲突,但实际上并不会。数据将与 Chart Trade 中指定的数据完全匹配。

那么,让我们来实现这个方案。由于指标的用途完全不同,我不想在指标中进行测试,所以我们将直接在 EA 中测试数据。因此,修正后的 EA 代码如下所示:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property icon "/Images/Market Replay/Icons/Replay - EA.ico"
04. #property description "Demo version between interaction"
05. #property description "of Chart Trade and Expert Advisor"
06. #property version   "1.116"
07. #property link "https://www.mql5.com/pt/articles/13168"
08. //+------------------------------------------------------------------+
09. #include <Market Replay\Order System\C_Orders.mqh>
10. #include <Market Replay\Auxiliar\C_Terminal.mqh>
11. //+------------------------------------------------------------------+
12. enum eTypeContract {MINI, FULL};
13. //+------------------------------------------------------------------+
14. input eTypeContract user00 = MINI;         //Cross order in contract
15. //+------------------------------------------------------------------+
16. C_Orders       *Orders = NULL;
17. C_Terminal     *Terminal = NULL;
18. //+------------------------------------------------------------------+
19. int OnInit()
20. {
21.     Orders = new C_Orders(0xC0DEDAFE78514269);
22.     
23.     return INIT_SUCCEEDED;
24. }
25. //+------------------------------------------------------------------+
26. void OnTick() {}
27. //+------------------------------------------------------------------+
28. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
29. {
30.     (*Orders).DispatchMessage(id, lparam, dparam, sparam);
31.     switch (id)
32.     {
33.         case CHARTEVENT_CHART_CHANGE:
34.             if (Terminal != NULL) break;
35.             else
36.             {
37.                 ulong ul;
38.                 Terminal = new C_Terminal(0, 0, user00);
39.                 for (int handle, count = PositionsTotal() - 1; count >= 0; count--)
40.                 {
41.                     ul = PositionGetTicket(count);
42.                     if (PositionGetString(POSITION_SYMBOL) != (*Terminal).GetInfoTerminal().szSymbol) continue;
43.                     handle = iCustom(_Symbol, PERIOD_CURRENT, "\\Indicators\\Position View.ex5", ul);
44.                     ChartIndicatorAdd((*Terminal).GetInfoTerminal().ID, 0, handle);
45.                     IndicatorRelease(handle);
46.                 }
47.             }
48.         case CHARTEVENT_CUSTOM + evChartTrade_At_EA:
49.             if (Terminal != NULL)
50.                 EventChartCustom((*Terminal).GetInfoTerminal().ID, evEA_At_ChartTrade, user00, 0, "");
51.             break;
52.     }
53. }
54. //+------------------------------------------------------------------+
55. void OnDeinit(const int reason)
56. {
57.     switch (reason)
58.     {
59.         case REASON_REMOVE:
60.         case REASON_INITFAILED:
61.             if (Terminal != NULL)
62.                 EventChartCustom((*Terminal).GetInfoTerminal().ID, evEA_At_ChartTrade, -1, 0, "");
63.             break;
64.     }
65.     
66.     delete Orders;
67.     delete Terminal;
68. }
69. //+------------------------------------------------------------------+

Expert Advisor.mq5

这就是编程的魅力所在。将前面的代码与本文中展示的其他 EA 代码进行比较,你会发现编程是多么优美迷人。通过在 C/C++ 中使用一些指针知识,我们在 MQL5 中能够移除全局变量,只保留类声明。实际上,这些是指向类对象的指针。但这没关系:MQL5 使用指针的方式与 C/C++ 不同。不过,我们可以使用 C/C++ 中现有的一些功能。如果您不理解这个 EA 的运行机制,请不要担心,我会帮助您理解这段代码。

请注意,我们在第 10 行包含了 C_Terminal.mqh 头文件,我们刚刚根据我们的需要对其进行了修改。第 17 行我们声明了一个全局变量,以便我们可以访问该类。但是,虽然这不是强制性的,但我还是要明确地表明指针变量已初始化为 NULL 值。如果您不指定此参数,编译器将隐式执行此操作。但同样重要的是要明确我们究竟在执行什么初始化操作。

好的,一旦 OnInit 函数执行完毕,MetaTrader 5 将触发 CHART_CHANGE 事件。它将被第 28 行的 OnChartEvent 函数拦截,而 EA 正是从这里开始处理事件的。首次执行此函数时,Terminal 指针的值为 NULL。因此,第 34 行的条件将评估为 false,从而允许执行第 35 行。奇迹就此开始。请注意,这里的代码与我们之前在 EA 中看到的代码非常相似。但是,在第 38 行,我们调用了 C_Terminal 类的构造函数,指定我们将使用一种合约类型还是另一种合约类型。

因此,构造函数将生成正确的交易品种名称。因此,在第 41 行,我们得到了 ticket 的值。我们需要在调用其他函数获取仓位数据之前执行此操作。MQL5 文档中提到了这一点;请查阅文档以获取更多详细信息。之后,在第 42 行,我们可以检查该仓位所属的交易品种名称是否与 EA 预期的名称相符。如果不相符,我们就返回第 39 行。否则,也就是说,如果交易品种具有预期的名称,我们就调用仓位指标,就像之前所做的那样。

现在请注意第 49 行和第 61 行。请回答这个问题:我为什么要对这些行进行检查?请先思考片刻,再阅读我下面提供的答案。好吧,我希望你在阅读答案之前至少尝试过自己解答一下。这些代码行的存在正是为了提高 EA 代码的可靠性。如果在 C_Terminal 类尚未正确初始化之前尝试调用该类的任何函数或过程,则检查指针值是否为 NULL 将防止 EA 在 MetaTrader 5 终端中简单地生成错误。通过执行此检查,您可以确保执行流程保持预期状态。

许多 C/C++ 程序员,尤其是 C 程序员,在使用指针时通常不会执行这些检查。这就是为什么许多人认为用 C 语言编写的程序包含许多启动错误的原因。然而,这些错误与语言本身无关,而是与程序的编写方式或编写原因有关。在某些情况下,甚至会故意创建用于在系统中搜索漏洞的程序,以执行系统所有者不希望的操作。


最后的想法

在本文中,我们开始将之前完全相互隔离的各种应用程序整合在一起。虽然 Chart Trade、鼠标指标和 EA 之间已经存在一定的联系,但仍然没有办法直接在图表上显示交易服务器上的持仓情况。通常使用对冲订单系统。从这一点开始,这成为可能,为新的想法和实施开辟了许多机会。尽管我们才刚刚开始将这些组件投入运行,但我们已经有了前进的方向。

是的,乍一看这个系统非常原始,本可以以更先进的状态呈现,但我认为那样并不合适。这会让很多初级程序员无法真正理解代码中发生的事情。记住,变化是循序渐进的,这样每个人都能跟上。本材料具有教学目的;它并非旨在展示在 MetaTrader 5 中执行某项操作的唯一方法。我希望这些材料能帮助那些真正在寻求知识的人。我们将在下一篇文章中继续推进本文提出的思路。期待很快与您见面!

文件 描述
Experts\Expert Advisor.mq5
演示 Chart Trade 与 EA 之间的交互(交互需要 Mouse Study)。
Indicators\Chart Trade.mq5 创建一个窗口,用于配置待发送的委托(需要 Mouse Study 才能进行交互)
Indicators\Market Replay.mq5 创建用于与复制/模拟服务交互的控件(交互需要 Mouse Study)。
Indicators\Mouse Study.mq5 提供图形控件与用户之间的交互(这对于使用复制/模拟系统以及在真实市场中都是必需的)。
Indicators\Order Indicator.mq5 负责指示市场订单,并允许与市场订单进行交互和控制。
Indicators\Position View.mq5 负责标示市场持仓,并支持与持仓交互及对其进行管理。
Services\Market Replay.mq5 创建并维护市场复制/模拟服务(整个系统的主文件)。

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

附加的文件 |
Anexo.zip (779.24 KB)
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
交易中的神经网络:以分解替代规模化 —— 模块构建 交易中的神经网络:以分解替代规模化 —— 模块构建
本文我们继续实操讲解SSCNN —— 一款能够处理碎片化时间序列的新一代架构方案。SSCNN不再盲目扩大模型规模,而是依靠精巧的模块化设计、精细化处理和针对性归一化。我们将在MQL5环境中逐步搭建计算单元,为可靠的预测分析打下基础。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
从基础到中级:FileSave 和 FileLoad 从基础到中级:FileSave 和 FileLoad
在今天的文章中,我们将探讨几种使用 FileSave 和 FileLoad 库函数的方法。尽管由于它们在特定场景中带来的某些限制或困难,许多人认为它们的使用范围有限,但正确理解这两个函数的工作原理在某些情况下可以为我们节省大量精力。它们也是处理日志文件的绝佳方式。