市场模拟:仓位视图(三)
引言
大家好,欢迎阅读本系列关于如何创建复制/模拟系统的又一篇文章。
在上一篇文章“市场模拟:仓位视图(二)”中,我们创建了一个非常简单的小工具,其目的是在图表上展示与未平仓位相关的价格线。尽管该指标能为我们提供必要的信息,但目前尚不能投入实际使用,因为还有几个小问题需要解决。但这其实并不复杂。亲爱的读者,这只是你在我们继续推进代码实现之前需要先理解的一点内容。
为了让事情变得更简单、更易于理解,我们将专门针对这一任务,在单独的章节中进行探讨。那么,让我们不再耽搁,直接进入正题。
了解 ZOrder 属性
亲爱的读者,我不知道你是否注意到,MQL5 对象有一个属性,该属性在代码中很少使用或明确定义。我本人研究过大量代码,但从未发现有人在实践中真正定义过这个对象属性。我指的是 ZOrder 属性。如果你一直在关注这一系列文章,你可能已经注意到,在相当长的一段时间里,当通过 CreateObjectGraphics 调用(这是 C_Terminal 类的一个过程)创建对象时,该属性已在 C_Terminal 类中定义。
在最近的这些文章中,我提到在某些特定时刻,我们需要为这个属性设定一个值。但是为什么呢?原因在于,许多向图表添加对象的代码根本不使用(或者更准确地说,不定义)此属性的值。关键在于,我并不是来规定每个程序员应该或不应该做什么,或者他们应该或不应该如何编写代码的。亲爱的读者,我在此向您以及所有真正想了解这些过程内部运作原理的人展示,幕后究竟发生了什么。
如果你不了解我将要解释的内容,或者没有充分理解使用 MetaTrader 5 或使用 MQL5 编程的某些概念,那么你几乎肯定会在使用这个平台时获得非常糟糕的体验。其中一个概念与 ZOrder 属性直接相关。
为了尽可能清晰地展示我们所讨论的内容,我们将使用以下代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property version "1.00" 04. #property indicator_chart_window 05. #property indicator_plots 0 06. //+------------------------------------------------------------------+ 07. input char user00 = 0; //ZOrder to Chart #01 08. input char user01 = 0; //ZOrder to Chart #02 09. //+------------------------------------------------------------------+ 10. void ChartOfTest(string sz1, int x, int y, int zOrder) 11. { 12. long id = ChartID(); 13. 14. ObjectCreate(id, sz1, OBJ_CHART, 0, 0, 0); 15. ObjectSetInteger(id, sz1, OBJPROP_XDISTANCE, x); 16. ObjectSetInteger(id, sz1, OBJPROP_YDISTANCE, y); 17. ObjectSetInteger(id, sz1, OBJPROP_ZORDER, zOrder); 18. ObjectSetInteger(id, sz1, OBJPROP_SELECTABLE, true); 19. } 20. //+------------------------------------------------------------------+ 21. int OnInit() 22. { 23. ChartOfTest("Chart #01", 160, 140, user00); 24. ChartOfTest("Chart #02", 200, 200, user01); 25. 26. return INIT_SUCCEEDED; 27. } 28. //+------------------------------------------------------------------+ 29. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 30. { 31. return rates_total; 32. } 33. //+------------------------------------------------------------------+ 34. void OnDeinit(const int reason) 35. { 36. ObjectsDeleteAll(ChartID(), "Chart #"); 37. } 38. //+------------------------------------------------------------------+
指标源代码
这段简单的代码恰好能满足我们的需求,有助于我们理解 ZOrder 属性如何以及将会如何影响 MetaTrader 5 的用户体验。请注意,在第 23 行和第 24 行,我们调用了负责创建两个对象并将其放置在当前打开图表上的过程。如第 14 行所示,创建的对象是 OBJ_CHART 类型。为了方便起见,亲爱的读者,我们在第 07 行和第 08 行提供了两个输入参数。这样我们就不用反复去调整对象的 ZOrder 值了。
默认情况下,这些值始终为零。所以,目前我们会保持现状不变。结果如下图所示:

如预期的那样,简单明了。“但是如果我们改变图表的时间周期,会发生什么呢?这个窗口中,我们有一个指标,其目的是为图表创建背景,那么在这个窗口中会发生什么呢?我们最终会不会陷入无法再与 OBJ_CHART 对象交互的境地?”嗯,这可以从以下动画中看到:

请注意,尽管背景图像(即 OBJ_BITMAPLABEL 类型的对象)位于 OBJ_CHART 对象之上,这通常会引起问题。OBJ_CHART 对象的背景不再是黑色,而是显示其所在区域的内容,覆盖在 OBJ_BITMAPLABEL 对象之上。然而,由于我们在第 17 行将 ZOrder 的值设置为零,而带有背景图像的对象的 ZOrder 值较低(在本例中为 -1),正如我们在之前的文章中所看到的,OBJ_CHART 对象的点击响应和对象选择行为不会受到影响,如上面的动画所示。
然而,我们会清楚地产生一种错觉,即前景中的物体具有优先性,如下面的动画所示:

正如我刚才所说,这是一种错觉,因为实际上位于前景的对象是包含填充整个图表的图像的 OBJ_BITMAPLABEL,从而创建了背景图像。但如果我们改变对象的优先级,这种错觉会变得更加有趣。要使用上述代码实现这一点,我建议观看以下动画:

请注意,我们更改了下方对象的优先级,即图表 #01。因此,由于其优先级更高,我们得到了动画中显示的结果。如果你对此不理解,可以在你自己的 MetaTrader 5 终端中测试一下。你会发现,即使你点击的区域同时属于图表 #02 和图表 #01,图表 #01 也会被选中。只有当你点击的区域在图表 #01 之外但属于图表 #02 时,你才能选择图表 #02。如果两者位于完全相同的区域,即使图表 #01 在显示上被图表 #02 覆盖,只要两者区域重叠,图表 #01 仍然会优先被选中。
如果你尝试为 OBJ_CHART 设置的值小于或等于作为背景图像的 OBJ_BITMAPLABEL 对象的值,或者如果你使用下图所示的快捷/快速命令放置另一个对象,那么这无疑会变得更加有趣。

尝试执行以下操作:添加另一个 OBJ_CHART,但这次使用之前展示的键盘快捷键,并像之前的动画演示那样调整指标值。毫无疑问,你将开始理解在 ZOrder 属性中使用适当值的重要性。但是,在我们当前开发的项目中,这具体应用在哪里呢?为了更恰当地回答这个问题,并把相关内容分开说明,我们放到下一个主题中讨论。
运用所学知识
了解并理解所解释的内容至关重要,这样你才能明白我们在实施过程中会做什么。稍后,这些内容将失去其意义,因为我们会采用不同的方法。但就目前而言,这很重要,尤其是当你为了个人使用而修改或调整代码时。
在上一篇文章中,我们画了三条线:一条表示持仓开仓价,一条代表止损位,一条代表止盈位。非常好,到目前为止,所做的事情没有任何问题。标示开盘价的水平线绝不能移动。我们会尽快纠正这个问题。相比之下,止损线和止盈线可以自由移动。当止损线和止盈线都处于同一价格时,就会出现问题。
在这种情况下,当试图选择其中一条线时,哪条线应具有优先权?这是必要的,以便系统接收到特定事件,例如,线被移动甚至删除。你可能会说应该是止损线,而另一位交易者可能会说应该是止盈线。在这种情况下,谁才是正确的呢?
你看出问题所在了吗?从某种意义上说,如果两条线的 ZOrder 值相同,那么创建线的顺序将影响这一决定。因此,只需改变创建顺序就能解决许多问题。然而,让我们将这种情况推向一个更极端的层面。一些交易者,尤其是在锁仓账户上,持有多个未平仓头寸,这并不罕见。在这种情况下,可能需要在止损线和止盈线之间设置不同的 ZOrder。但即使这样做了,虽然消除了止损线和止盈线重合的问题,另一个问题也会出现:当两条线(例如两条止损线)意外重叠时。
你是否注意到,ZOrder 中的任何变化都变得毫无意义?即使你使用不同的值来完成一项任务,也总会有另一项任务需要完成。然而,根据一些专业交易员在复制/模拟系统开发和实施相关方面的建议,在服务器上保留超过两个、三个或最多四个这样的实体/记录是没有实际意义的。换言之,在他们看来,如果使用锁仓账户,同时保留两三个、最多四个以上的未平仓位,并没有实际意义。因为在单边账户中,这个问题并不存在。在这个初始阶段,我将只考虑锁仓账户。但这并不意味着该指标在单边账户上不起作用。
那么,让我们回到上一篇文章中我们研究的代码。在那里,我们使用同一个 ZOrder 实现了止损线和止盈线。要改变这一点,我们需要对代码进行小幅修改。这些改动非常细微,我们就不赘述了。只需看看修改后的代码是什么样子:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #define def_SufixLinePrice "Price" 09. #define def_SufixLineTake "Take" 10. #define def_SufixLineStop "Stop" 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Auxiliar\C_Terminal.mqh> 13. //+------------------------------------------------------------------+ 14. input color user00 = clrRoyalBlue; //Color Line Price 15. input color user01 = clrForestGreen; //Color Line Take Profit 16. input color user02 = clrFireBrick; //Color Line Stop Loss 17. //+------------------------------------------------------------------+ 18. C_Terminal *Terminal; 19. struct st 20. { 21. long id; 22. string szPrefixName; 23. }glVariables; 24. //+------------------------------------------------------------------+ 25. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 26. { 27. if (price <= 0) return; 28. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02)))); 29. ObjectSetDouble(glVariables.id, szObjName, OBJPROP_PRICE, price); 30. ObjectSetString(glVariables.id, szObjName, OBJPROP_TEXT, szDescription); 31. ObjectSetString(glVariables.id, szObjName, OBJPROP_TOOLTIP, szDescription); 32. ObjectSetInteger(glVariables.id, szObjName, OBJPROP_SELECTABLE, cor != user00); 33. } 34. //+------------------------------------------------------------------+ 35. int OnInit() 36. { 37. ZeroMemory(glVariables); 38. Terminal = new C_Terminal(); 39. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 40. glVariables.id = (*Terminal).GetInfoTerminal().ID; 41. glVariables.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 42. CreateLineInfos(glVariables.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price."); 43. CreateLineInfos(glVariables.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point."); 44. CreateLineInfos(glVariables.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point."); 45. 46. return INIT_SUCCEEDED; 47. } 48. //+------------------------------------------------------------------+ 49. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 50. { 51. return rates_total; 52. } 53. //+------------------------------------------------------------------+ 54. void OnDeinit(const int reason) 55. { 56. delete Terminal; 57. if (glVariables.id > 0) 58. ObjectsDeleteAll(glVariables.id, glVariables.szPrefixName); 59. } 60. //+------------------------------------------------------------------+
仓位视图指标
请注意,现在止损线的优先级高于止盈线。这是由于 ZOrder 值较高造成的。然后,如果我们有两个未平仓位,且止损线与止盈线价格相同,那么任何尝试与这些线进行交互的操作都将优先考虑止损线。请注意,导致这种情况的唯一更改发生在第 28 行。将此行与之前代码中的相同行进行比较,以理解这一更改。
然而,虽然该指标有效,但并不适合用于锁仓账户。原因是该系统总是会寻找第一个或最后一个所开仓位。即使你以某种方式去改变这一点,最终也不会带来实质性的改进。我们需要实施一种能带来更有效结果且更具普遍性的解决方案。此外,您可以看到我们使用了不同的颜色,以便指标能够理解每条线代表的含义。此类解决方案对测试很有用。然而,它们完全不适合更一般的用途。因此,我们仍然有很多问题需要解决和纠正。
在这个阶段,许多刚开始编程的人往往会放弃,或者做出太多修改,以至于最终失去了对自己所创建代码的控制。因此,如果你已经懂得编程并且具备正确的基本知识,我恳请你对那些经验不足的人保持耐心,因为我将展示初学者应该如何开始思考,以便他们能够真正爱上编程的艺术,并学会创造自己的解决方案。
开始着手解决问题
亲爱的读者,如果你遇到像上面所示的、需要改进的代码,请不要在没有明确思路或原则的情况下随意改动代码。首先将任务分解成若干部分,但始终要确保代码能够正常运行。不,我再说一遍:在将各个部件分开之前,请勿进行任何更改。最简单且最恰当的方法之一是将部分代码放入头文件中,这样你就可以立即开始对其进行修改。但是,由于将所有部分合并为一个类要容易得多,因此我们将这样做。但是,为什么将代码放在一个类中比将其拆分成头文件更容易呢?
原因很简单:如果你将代码放在一个类中,以后就可以将它移至头文件中。然而,仅仅将其移至头文件中并不能使管理变得更容易。这是因为将来可能会出现名称冲突。在包含大量文件的代码库中,这种冲突并不少见。相反,当我们把元素移入一个类中时,发生冲突的可能性会大大降低。这并不意味着它们被完全消除了,随着你在编程中的深入,你会亲眼看到这一点。然而,解决此类冲突会变得更加简单快捷。了解了这一点之后,让我们首先将这些元素移到复制/模拟系统中的一个新类中。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #define def_SufixLinePrice "Price" 09. #define def_SufixLineTake "Take" 10. #define def_SufixLineStop "Stop" 11. //+------------------------------------------------------------------+ 12. #include <Market Replay\Auxiliar\C_Terminal.mqh> 13. //+------------------------------------------------------------------+ 14. input color user00 = clrRoyalBlue; //Color Line Price 15. input color user01 = clrForestGreen; //Color Line Take Profit 16. input color user02 = clrFireBrick; //Color Line Stop Loss 17. //+------------------------------------------------------------------+ 18. class C_IndicatorPosition 19. { 20. private : 21. struct st00 22. { 23. long id; 24. string szPrefixName; 25. }m_Infos; 26. //+------------------------------------------------------------------+ 27. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 28. { 29. if (price <= 0) return; 30. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02)))); 31. ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price); 32. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription); 33. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription); 34. ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00); 35. } 36. //+------------------------------------------------------------------+ 37. public : 38. //+------------------------------------------------------------------+ 39. C_IndicatorPosition(const long Id) 40. { 41. ZeroMemory(m_Infos); 42. m_Infos.id = Id; 43. m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 44. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price."); 45. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point."); 46. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point."); 47. } 48. //+------------------------------------------------------------------+ 49. ~C_IndicatorPosition() 50. { 51. if (m_Infos.id > 0) 52. ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName); 53. } 54. //+------------------------------------------------------------------+ 55. }; 56. //+------------------------------------------------------------------+ 57. C_Terminal *Terminal; 58. C_IndicatorPosition *Positions; 59. //+------------------------------------------------------------------+*/ 60. int OnInit() 61. { 62. Terminal = new C_Terminal(); 63. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 64. Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID); 65. 66. return INIT_SUCCEEDED; 67. } 68. //+------------------------------------------------------------------+ 69. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 70. { 71. return rates_total; 72. } 73. //+------------------------------------------------------------------+ 74. void OnDeinit(const int reason) 75. { 76. delete Terminal; 77. delete Positions; 78. } 79. //+------------------------------------------------------------------+
仓位视图源代码
在下面的代码中,你可能会觉得我们增加了代码的复杂性。但实际上,我们只是将之前位于类外部的代码移到了类内部。因此,与表面看起来的情况相反,我们并没有增加复杂性,而是在改进元素的实现方式,因为之前存在的相同代码现在有了更好的保护,并且使我们能够更有效地分离任务。我所说的更高程度的保护是因为 CreateLineInfos 函数现在是私有的。也就是说,调用代码或用户不需要确切地知道指标是如何创建的。它只是请求创建该指标。这种实现方式可能会随着时间的推移而发生显著变化,但对用户而言,其效果将始终保持不变。
然而,做我刚刚展示的事情并不能解决我们遇到的任何问题。这只会使代码更易于以更高效的方式实现。请注意,所有代码与之前完全相同,其操作也丝毫未变。这是我们必须始终确保的第一件事。在代码完全恢复到你将其放入类之前的运行状态之前,请不要对其进行任何更改。之后,进一步的行动将取决于你首先需要或想要做什么。但是,由于类已经创建,我们可以立即将其放入头文件中。这样可以避免不必要地增加主代码的负担。然后,我们将创建一个与将要放入其中的类同名的文件。虽然这是良好的编程实践,但并非强制性的。因此,新的主代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #include <Market Replay\Auxiliar\C_Terminal.mqh> 09. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 10. //+------------------------------------------------------------------+ 11. input color user00 = clrRoyalBlue; //Color Line Price 12. input color user01 = clrForestGreen; //Color Line Take Profit 13. input color user02 = clrFireBrick; //Color Line Stop Loss 14. //+------------------------------------------------------------------+ 15. C_Terminal *Terminal; 16. C_IndicatorPosition *Positions; 17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. Terminal = new C_Terminal(); 21. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 22. Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID); 23. 24. return INIT_SUCCEEDED; 25. } 26. //+------------------------------------------------------------------+ 27. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 28. { 29. return rates_total; 30. } 31. //+------------------------------------------------------------------+ 32. void OnDeinit(const int reason) 33. { 34. delete Terminal; 35. delete Positions; 36. } 37. //+------------------------------------------------------------------+
仓位视图源代码
类代码去哪儿了?现在它位于上面代码第 09 行指定的文件中。请注意,该类带走了之前位于主代码中的一些数据。这是因为,如前所述,用户无需了解所有内容的具体排列方式。用户只需知道具体需要调用什么以及应该向类代码传递哪些参数即可。调用方并不需要关心该类如何处理这些数据。所以,请看下面所示的 C_IndicatorPosition 头文件代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixLinePrice "Price" 05. #define def_SufixLineTake "Take" 06. #define def_SufixLineStop "Stop" 07. //+------------------------------------------------------------------+ 08. class C_IndicatorPosition 09. { 10. private : 11. struct st00 12. { 13. long id; 14. string szPrefixName; 15. }m_Infos; 16. //+------------------------------------------------------------------+ 17. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 18. { 19. if (price <= 0) return; 20. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == user00 ? ePriorityNull : (ePriorityOrders + (cor == user02)))); 21. ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price); 22. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription); 23. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription); 24. ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != user00); 25. } 26. //+------------------------------------------------------------------+ 27. public : 28. //+------------------------------------------------------------------+ 29. C_IndicatorPosition(const long Id) 30. { 31. ZeroMemory(m_Infos); 32. m_Infos.id = Id; 33. m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 34. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), user00, "Position opening price."); 35. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), user01, "Take Profit point."); 36. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), user02, "Stop Loss point."); 37. } 38. //+------------------------------------------------------------------+ 39. ~C_IndicatorPosition() 40. { 41. if (m_Infos.id > 0) 42. ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName); 43. } 44. //+------------------------------------------------------------------+ 45. }; 46. //+------------------------------------------------------------------+ 47. #undef def_SufixLinePrice 48. #undef def_SufixLineTake 49. #undef def_SufixLineStop 50. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh 的源代码
请注意,代码保持不变。文件末尾只添加了三行新内容。它们的目的是移除那些仅在该头文件中才有意义的定义。换句话说,我们正在运用最小权限原则。或者,更明确地说:与自己无关的事,无需告知他人。这可以防止信息在您不知情的情况下泄露。此外,这当然也是良好的编程习惯,因为其他代码片段可能会使用你在此处定义的相同名称。如果你不在这里移除它们,就不得不在其他地方移除,这在改进非常大的代码时会造成很多不便,因为定义冲突会产生毫无意义的错误。
好,但是,如果你现在只看头文件中的代码,你会发现它声明并使用了依赖于主代码的值。这会产生一个问题,因为当主代码发生更改时,这个头文件也必须相应地做出更改。换言之,这是一项庞大的工作量,因此我们需要解决它。一个好的解决方案是让主代码独立传递这些值。因此,下面是修改后的新头文件。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixLinePrice "Price" 05. #define def_SufixLineTake "Take" 06. #define def_SufixLineStop "Stop" 07. //+------------------------------------------------------------------+ 08. class C_IndicatorPosition 09. { 10. private : 11. struct st00 12. { 13. long id; 14. string szPrefixName; 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. (*Terminal).CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop)))); 22. ObjectSetDouble(m_Infos.id, szObjName, OBJPROP_PRICE, price); 23. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TEXT, szDescription); 24. ObjectSetString(m_Infos.id, szObjName, OBJPROP_TOOLTIP, szDescription); 25. ObjectSetInteger(m_Infos.id, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice); 26. } 27. //+------------------------------------------------------------------+ 28. public : 29. //+------------------------------------------------------------------+ 30. C_IndicatorPosition(const long Id, color corPrice, color corTake, color corStop) 31. { 32. ZeroMemory(m_Infos); 33. m_Infos.id = Id; 34. m_Infos.szPrefixName = IntegerToString(PositionGetInteger(POSITION_TICKET)); 35. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice = corPrice, "Position opening price."); 36. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake = corTake, "Take Profit point."); 37. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop = corStop, "Stop Loss point."); 38. } 39. //+------------------------------------------------------------------+ 40. ~C_IndicatorPosition() 41. { 42. if (m_Infos.id > 0) 43. ObjectsDeleteAll(m_Infos.id, m_Infos.szPrefixName); 44. } 45. //+------------------------------------------------------------------+ 46. }; 47. //+------------------------------------------------------------------+ 48. #undef def_SufixLinePrice 49. #undef def_SufixLineTake 50. #undef def_SufixLineStop 51. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
请注意,构造函数现在需要接受三个新参数。它们传递每条线的颜色。因此,必须在以下位置修改主代码,如下代码片段所示:
17. //+------------------------------------------------------------------+ 18. int OnInit() 19. { 20. Terminal = new C_Terminal(); 21. if (!PositionSelect((*Terminal).GetInfoTerminal().szSymbol)) return INIT_FAILED; 22. Positions = new C_IndicatorPosition((*Terminal).GetInfoTerminal().ID, user00, user01, user02); 23. 24. return INIT_SUCCEEDED; 25. } 26. //+------------------------------------------------------------------+
指标片段
一切都很简单。我们现在拥有的代码几乎是完全独立的。这是因为我们仍然依赖于主代码来读取是否存在任何仓位的信息。这是在前一个片段的第 21 行完成的。所以,让我们来解决这个问题,因为我们希望该指标在锁仓账户上也能正常工作。在这种情况下,我们可以同时有多个同一交易品种的持仓。但要解决这个问题,我们不得不对代码进行更为实质性的修改。然而,既然我们已经把事情分开处理,那么做出这样的改变就变得容易和愉快多了。因此,新的指标代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Indicator for tracking an open position on the server." 04. #property version "1.00" 05. #property indicator_chart_window 06. #property indicator_plots 0 07. //+------------------------------------------------------------------+ 08. #define def_ShortName "Position View" 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\Order System\C_IndicatorPosition.mqh> 11. //+------------------------------------------------------------------+ 12. input color user00 = clrRoyalBlue; //Color Line Price 13. input color user01 = clrForestGreen; //Color Line Take Profit 14. input color user02 = clrFireBrick; //Color Line Stop Loss 15. //+------------------------------------------------------------------+ 16. C_IndicatorPosition *Positions; 17. //+------------------------------------------------------------------+*/ 18. int OnInit() 19. { 20. IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); 21. Positions = new C_IndicatorPosition(user00, user01, user02); 22. if (!Positions.CheckCatch()) 23. { 24. ChartIndicatorDelete(ChartID(), 0, def_ShortName); 25. return INIT_FAILED; 26. } 27. 28. return INIT_SUCCEEDED; 29. } 30. //+------------------------------------------------------------------+ 31. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 32. { 33. return rates_total; 34. } 35. //+------------------------------------------------------------------+ 36. void OnDeinit(const int reason) 37. { 38. delete Positions; 39. } 40. //+------------------------------------------------------------------+
仓位视图源代码
请注意,OnInit 过程代码已进行了一些非常简单的更改。从本质上讲,我们现在的工作方式是,如果指标不满足继续保留在图表上的条件,就会将其从图表中移除。这是通过第 20 行和第 24 行实现的。如果指标无法继续保留在图表上,第 24 行代码正是用于移除该指标的。那么,究竟是什么能阻止指标一直停留在图表上呢?好了,现在只剩最后一个条件了。为了理解这一点,让我们看一下第 22 行调用的代码。接下来,我们来看新的类代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define def_SufixLinePrice "Price" 05. #define def_SufixLineTake "Take" 06. #define def_SufixLineStop "Stop" 07. //+------------------------------------------------------------------+ 08. #include "..\Auxiliar\C_Terminal.mqh" 09. //+------------------------------------------------------------------+ 10. class C_IndicatorPosition : private C_Terminal 11. { 12. private : 13. struct st00 14. { 15. ulong ticket; 16. string szPrefixName; 17. color corPrice, corTake, corStop; 18. }m_Infos; 19. //+------------------------------------------------------------------+ 20. void CreateLineInfos(const string szObjName, const double price, const color cor, const string szDescription = "\n") 21. { 22. if (price <= 0) return; 23. CreateObjectGraphics(szObjName, OBJ_HLINE, cor, (EnumPriority)(cor == m_Infos.corPrice ? ePriorityNull : (ePriorityOrders + (cor == m_Infos.corStop)))); 24. ObjectSetDouble(GetInfoTerminal().ID, szObjName, OBJPROP_PRICE, price); 25. ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TEXT, szDescription); 26. ObjectSetString(GetInfoTerminal().ID, szObjName, OBJPROP_TOOLTIP, szDescription); 27. ObjectSetInteger(GetInfoTerminal().ID, szObjName, OBJPROP_SELECTABLE, cor != m_Infos.corPrice); 28. } 29. //+------------------------------------------------------------------+ 30. public : 31. //+------------------------------------------------------------------+ 32. C_IndicatorPosition(color corPrice, color corTake, color corStop) 33. :C_Terminal() 34. { 35. ZeroMemory(m_Infos); 36. m_Infos.corPrice = corPrice; 37. m_Infos.corTake = corTake; 38. m_Infos.corStop = corStop; 39. } 40. //+------------------------------------------------------------------+ 41. ~C_IndicatorPosition() 42. { 43. if (m_Infos.ticket != 0) 44. ObjectsDeleteAll(GetInfoTerminal().ID, m_Infos.szPrefixName); 45. } 46. //+------------------------------------------------------------------+ 47. bool CheckCatch(void) 48. { 49. for (int count = PositionsTotal() - 1; count >= 0; count--, m_Infos.ticket = 0) 50. if ((m_Infos.ticket = PositionGetTicket(count)) > 0) 51. if (PositionGetString(POSITION_SYMBOL) == GetInfoTerminal().szSymbol) 52. { 53. m_Infos.szPrefixName = IntegerToString(m_Infos.ticket); 54. if (ObjectFind(GetInfoTerminal().ID, m_Infos.szPrefixName + def_SufixLinePrice) < 0) 55. break; 56. } 57. if (m_Infos.ticket == 0) return false; 58. IndicatorSetString(INDICATOR_SHORTNAME, IntegerToString(m_Infos.ticket)); 59. CreateLineInfos(m_Infos.szPrefixName + def_SufixLinePrice, PositionGetDouble(POSITION_PRICE_OPEN), m_Infos.corPrice, "Position opening price."); 60. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineTake, PositionGetDouble(POSITION_TP), m_Infos.corTake, "Take Profit point."); 61. CreateLineInfos(m_Infos.szPrefixName + def_SufixLineStop, PositionGetDouble(POSITION_SL), m_Infos.corStop, "Stop Loss point."); 62. 63. return true; 64. } 65. //+------------------------------------------------------------------+ 66. }; 67. //+------------------------------------------------------------------+ 68. #undef def_SufixLinePrice 69. #undef def_SufixLineTake 70. #undef def_SufixLineStop 71. //+------------------------------------------------------------------+
C_IndicatorPosition.mqh
请注意,代码正在增长,且变得越来越复杂。尽管如此,我还是会循序渐进,以便你们能真正跟上我们的进度。这是因为,在未来,深入理解这段代码将至关重要。所以不要着急。请冷静、仔细地观察正在发生的变化,因为我会逐步地展示和解释这些变化,以便您尽可能地全面理解。
请注意,我们上次查看这段代码时,它不包含 include 指令。现在它有了。而这正是使得主代码所需编写的代码减少的原因,当然,它也有助于编写代码的其他方面。在第 10 行,我们通过继承将 C_Terminal 类包含在 C_IndicatorPosition 类中。这种继承方式使我们(至少目前)能够在 C_IndicatorPosition 类中直接使用 C_Terminal 类的公共方法。这给人一种印象,即 C_IndicatorPosition 类具有比 MQL5 中更多的函数,但这只是一种错觉,因为实际使用的函数实际上位于 C_Terminal 类中。
但是,请记住,您将无法在 C_IndicatorPosition 类之外使用 C_Terminal 类的这些相同函数。这是因为继承是私有的,这阻止了通过 C_IndicatorPosition 类对 C_Terminal 类方法的外部访问。
但是因为我们继承了 C_Terminal 类,所以在使用 C_IndicatorPosition 类之前需要对其进行初始化。这是在第 33 行完成的。现在请注意,在构造函数中,我们删除了之前存在的代码,只保留了初始化类内部变量的代码。好,现在让我们继续看第47行,这里有一个函数,用于检查初始化过程中指标是否应保留在图表上。
从这一点开始,在第 49 行的循环中,我们开始考虑锁仓账户的仓位。在此之前,我们只考虑过单边账户。请注意,我们将遍历未平仓位列表,寻找尚未分配给指标的仓位。那么,我们该如何操作呢?在循环的每次迭代中,我们将在第 50 行执行检查。如果此检查为肯定,我们将在第 51 行执行新的检查。现在请注意:如果第 51 行的第二次检查为肯定,我们将在第 53 行为要创建的对象的名称生成前缀。到目前为止,一切都保持不变。当然,不同的是,现在我们能够处理同一交易品种上的多个未平仓位,而这正是锁仓账户上的情况。
非常好,现在,最精彩的部分开始了。该指标如何判断该仓位是否已被分析过?注意这一点。图表上的每个仓位指标都会跟踪(或者更准确地说,是指向)一个特定的仓位。可能有多个未平仓位。但是,每个指标都只代表一个仓位。同一个指标不会跟踪多个仓位。因此,为了使指标知道某个仓位是否已被另一个仓位指标分析,我们需要遍历图表对象。这是在第 54 行完成的。
如果 MetaTrader 5 报告该对象已在图表上,我们将寻找同一交易品种的新开仓位。第 49 行的循环将再次执行。这种情况会不断重复,直到满足以下两个条件之一为止。第一个是我们目前没有任何未平仓位。第二个是执行第 55 行代码。
无论如何,第 57 行代码迟早都会执行。如果 ticket 变量不包含值,我们将返回 false,告诉主代码必须从图表中删除该指标。如果 ticket 变量有值,我们将为该指标分配一个新的简称,以便我们以后可以引用它,并且我们将创建如前所示的线。最后,在第 63 行,我们返回 true。因此,我们确保该指标在锁仓账户和单边账户上以相同的方式运行,无需任何额外的检查。
最后的想法
在本文中,我们介绍了必要的更改,以使持仓指标在锁仓账户和单边账户上都真正发挥作用。事实上,这一点很容易注意到,该指标除了显示与给定仓位对应的价格线之外,什么也没做。此外,该指标无法确定该仓位是否已不存在。作为交易者,要想真正注意到这一点,你需要更改图表的时间周期。
这样,该指标将检测到该仓位已被平仓。一旦检测到这种情况,指标将从图表中移除,因此无需任何额外干预即可自然移除。在下一篇文章中,我们将通过为该指标添加几个新函数来进一步完善它。
| 文件 | 描述 |
|---|---|
| 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/13167
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
MetaTrader 5中交易品种及其价差的小时级波动分析
从新手到专家:使用MQL5实现动态新闻标题(9)—— 新闻交易中的单图表多品种管理
通过图表同步简化技术分析
从基础到中级:在 MetaTrader 5 沙箱中处理文件