从基础到中级:指标(四)
概述
在上一篇文章“从基础到中级:指标(三)”中,我们讨论了如何使用编译指令以简单实用的方式创建任何类型的指标。但是,我们不能也不应该总是这样做。这种方法被称为静态实现,因为一旦定义了指标类型,我们就不能让(或者更确切地说,不允许)用户更改执行的渲染类型。
然而,指标的数量可能远远超过目前所显示的。事实上,对于很多活动而言,这种实现方式已经绰绰有余了。然而,在某些情况下,交易或市场分析可能需要一些不同的指标。
在本文中,我们将学习如何创建几种不同的指标。这些指标对于与其他交易者沟通以及自动分析某些类型的价格变动非常有用。
所以,让我们从一些非常简单的事情开始,在我看来,非常有趣。我们可以使用一个非常简单易懂的概念来创建许多其他指标。
内含线指标
有一种指标,或者更确切地说是一种交易方法,它使用所谓的内含线。内含线是指位于其前一根 K 线内部的 K 线或烛形。然而,根据交易者和交易品种的不同,位于另一根 K 线内部的任何 K 线(不一定是紧邻的前一根 K 线)都可以被视为内含线,前提是价格走势没有突破该 K 线,并且该 K 线可以位于一定的距离内。但后一种判断内含线的方法并不是大多数交易者所接受的原则。
无论如何,这种交易设置是存在的,在不同的时间被证明是非常有说服力的。对许多人、尤其是初学者来说,不断分析图表以了解我们是否看到内含线是相当困难的。因此,我们可以使用一个指标来跟踪图表,并在图表打开和活动时显示是否存在这种形态。
重要提示:内含线不一定是十字星。
“好的,但是你为什么决定演示如何创建这个指标呢?”原因很简单,我的朋友。许多新手交易员尝试创建指标,为烛形或常说的 K 线添加颜色模式。这些初级程序员最终会因为不知道如何声明指标而放弃。在烛形上设计颜色图案并不是一件难事。问题出在别处:正如人们所想象的那样,创建指标本身是一项相当简单的任务。
然而,与前几篇文章中显示的其他指标一样,在声明缓冲区时必须谨慎。如果你曾经尝试过在烛形上创建彩色图案,你就会知道,这对任何初学者来说都是一项具有挑战性的任务。因此,如果我们以错误的顺序执行此操作或以错误的顺序输入缓冲区索引,MetaTrader 5 将在图表上显示与我们预期不同的内容。你不能责怪 MetaTrader 5 或程序员,因为根据我们想要实现的内容,对一些人来说这是一个错误,而对另一些人来说,这正是所需要的。
因此,我们将从声明指标本身开始,甚至在创建内含线指标之前。通常,所有内容都可以用不同的方式声明,如下面的代码所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property indicator_type1 DRAW_COLOR_CANDLES 05. #property indicator_color1 clrRed, clrRoyalBlue, clrGreen 06. //+----------------+ 07. #property indicator_buffers 5 08. #property indicator_plots 1 09. //+----------------+ 10. double gl_Buff_High[], 11. gl_Buff_Open[], 12. gl_Buff_Close[], 13. gl_Buff_Low[], 14. gl_Buff_Color[]; 15. //+------------------------------------------------------------------+ 16. int OnInit() 17. { 18. SetIndexBuffer(0, gl_Buff_High, INDICATOR_DATA); 19. SetIndexBuffer(1, gl_Buff_Open, INDICATOR_DATA); 20. SetIndexBuffer(2, gl_Buff_Close, INDICATOR_DATA); 21. SetIndexBuffer(3, gl_Buff_Low, INDICATOR_DATA); 22. SetIndexBuffer(4, gl_Buff_Color, INDICATOR_COLOR_INDEX); 23. 24. return INIT_SUCCEEDED; 25. }; 26. //+------------------------------------------------------------------+ 27. int OnCalculate(const int rates_total, const int prev_calculated, const datetime &Time[], const double &Open[], const double &High[], const double &Low[], const double &Close[], const long &TickVolume[], const long &Volume[], const int &Spread[]) 28. { 29. for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++) 30. { 31. gl_Buff_High[c] = High[c]; 32. gl_Buff_Open[c] = Open[c]; 33. gl_Buff_Close[c] = Close[c]; 34. gl_Buff_Low[c] = Low[c]; 35. 36. gl_Buff_Color[c] = (Open[c] > Close[c] ? 0 : 2); 37. } 38. 39. return rates_total; 40. }; 41. //+------------------------------------------------------------------+
代码 01
亲爱的读者们,现在我问你们:我们在代码 01 中看到的是正确的还是错误的?这个问题的正确答案是:这一切都取决于我们追求的目标。总之,这种对代码 01 的实现方式并非完全错误,因为该代码将被编译并可能有用。但根据预期目标,此代码可能不正确。因此,请注意在任何图表上使用此代码时会发生什么。这可以在下面的动画中看到:

动画 01
换句话说,结果与预期略有不同。为什么呢?“我不明白,代码看起来是正确的。我在声明缓冲区时没有做错任何事。然而,结果不出所料:K 线不仅颜色发生了变化,格式也发生了变化。我搞不清楚哪里出了问题。有没有什么诀窍能让一切顺利进行,并达到预期的结果?”在这种情况下,是的,亲爱的读者们。
虽然代码是正确的,因为它按预期更改了 K 线的颜色,但格式更改却并非如此。如果目标是改变颜色并同时修改 K 线,那么是的。然而,这并非我们的本意。原因恰恰在于缓冲区声明顺序错误。当 MetaTrader 5 使用缓冲区时,它会按照特定的顺序进行操作。由于顺序错误,K 线的格式发生了改变。知道了这一点,我们只需要将这个顺序改成合适的顺序即可。下面的代码中可以看到这个简单的改动:
. . . 18. SetIndexBuffer(1, gl_Buff_High, INDICATOR_DATA); 19. SetIndexBuffer(0, gl_Buff_Open, INDICATOR_DATA); 20. SetIndexBuffer(3, gl_Buff_Close, INDICATOR_DATA); 21. SetIndexBuffer(2, gl_Buff_Low, INDICATOR_DATA); . . .
代码 02
请注意,在代码片段 02 中,唯一改变的是每个缓冲区的索引值。使用此缓冲区序列(开盘价、最高价、最低价和收盘价),代码 01 生成的结果如下所示:

动画 02
你有没有注意到 MetaTrader 5 现在可以识别每根 K 线或蜡烛图的绘制方式?对于那些已经知道如何解决这个问题的人来说,这是一项非常简单的任务。然而,对于那些不理解的人来说,这似乎非常困难。无论如何,现在你知道如何做到这一点,并且可以继续构建颜色模式来创建指标并对所需的交易系统进行建模。为了演示如何实现这一点,让我们考虑解决这个问题,并直接在图表上标出内含线。
要解决此问题,请查看第 36 行,其中描述了我们如何计算颜色索引。对于卖出 K 线,索引为 0;对于买入 K 线,索引为 2。但这个索引对我们有什么用呢?我们将这个索引留作未来内含线的参考。
现在到了关键时刻。从第 29 行开始的循环从一个旧点开始,该点可能是(也可能不是)图表上的第一个柱形,并继续搜索图表上存在的最后一个柱形。了解和理解这一点很重要,因为只有当存在具有特定特征的前一根柱形时,才能标记内含线。否则,当前柱形将无法被视为内含线并显示在图表上。
所以,一切都很清楚了;剩下的就是落实验证标准了。为此,我们必须修改上面的代码,以便它可以为柱形和蜡烛图分配颜色,此外,还要检查是否存在内含线。此改进在代码片段 03 中显示:
. . . 36. gl_Buff_Color[c] = (Open[c] > Close[c] ? 0 : 2); 37. if ((c - 1) > 0) 38. gl_Buff_Color[c] = ((High[c - 1] > High[c]) && (Low[c - 1] < Low[c]) ? 1 : gl_Buff_Color[c]); 39. } 40. 41. return rates_total; 42. }; 43. //+------------------------------------------------------------------+
代码 03
现在,对于指标接收到的每个计算事件,我们将检查图表上是否存在内含线。但是等等,为什么我们要在每一步都尝试这样做呢?原因很简单:在交易阶段,图表上可能会出现内含线。然而,它出现配置错误的情况并不少见。这是因为我们只能在 K 线或蜡烛图关闭后才能考虑内含线是否存在。在此之前,断言图表中存在内含线还为时过早。
然而,由于第 38 行会定期检查内含线条件是否可以应用,结果发现,如果内含线被取消配置,测试最终就会失败。因此,由于我们已经在第 36 行更新了颜色,上述标示不再显示在图表上。
虽然我们知道这种方法有效,但理想情况下,我们应该看看它在实践中是如何发生的,以了解我们为什么以这种方式实施它。然而,在下面的动画中,我们可以观察到初始过程。但要展示第二步 —— 即取消原本应该是内含线的结构 —— 就没那么简单了,因为它需要一个非常特定的时刻,而捕捉到这个确切的时刻需要花费大量时间。

动画 03
太好了,这里我们可以看到内含线应该是什么样子。然而,正如上文所述,有些交易者会考虑多个 K 线,并将所有这些新 K 线视为内含线,只要这些新 K 线位于前一个 K 线之内即可。这就引出了一个问题:我们如何通过简单地修改先前的指标来实现相同类型的指标?
这个问题可能有不同的答案,或者更确切地说,有不同的方法来实施所涉及的机制。真正的问题不是代码的精确实现,而是弄清楚如何在很长一段时间内保持一个值。下面提供一个可能的建议。当然,根据具体情况,您可能会采用您认为更有趣或更合适的其他方法。
由于我们希望尽可能简化流程,因此我们只会对 OnCalculate 事件处理函数的实现进行一处更改。因此,生成动画 03 的源代码将进行如下修改:
. . . 26. //+------------------------------------------------------------------+ 27. int OnCalculate(const int rates_total, const int prev_calculated, const datetime &Time[], const double &Open[], const double &High[], const double &Low[], const double &Close[], const long &TickVolume[], const long &Volume[], const int &Spread[]) 28. { 29. static double high = DBL_MIN, 30. low = DBL_MAX; 31. 32. for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++) 33. { 34. gl_Buff_High[c] = High[c]; 35. gl_Buff_Open[c] = Open[c]; 36. gl_Buff_Close[c] = Close[c]; 37. gl_Buff_Low[c] = Low[c]; 38. 39. gl_Buff_Color[c] = (Open[c] > Close[c] ? 0 : 2); 40. if ((c - 1) > 0) 41. { 42. high = (High[c - 1] > high ? High[c - 1] : high); 43. low = (Low[c - 1] < low ? Low[c - 1] : low); 44. gl_Buff_Color[c] = ((high > High[c]) && (low < Low[c]) ? 1 : gl_Buff_Color[c]); 45. if (gl_Buff_Color[c] != 1) 46. { 47. high = DBL_MIN; 48. low = DBL_MAX; 49. } 50. } 51. } 52. 53. return rates_total; 54. }; 55. //+------------------------------------------------------------------+
代码 04
现在要注意了,如果将代码 04 中显示的片段放入生成动画 03 的代码中,则结果将与之前的动画不同。为了确认这一点,让我们仔细看看并将下面的动画与上面的动画进行比较。

动画 04
在动画 04 中,我们正在做的正是许多交易者认为的内含线。也就是说,只要没有“突破”前一根 K 线或蜡烛图的最高价或最低价,我们就将分析的K线视为内部K线。我知道这可能看起来很令人困惑,特别是如果您不习惯交易或观察内含线。但是,即使出现多根 K 线,并且它们都位于一个更老的 K线内,代码片段 04 也能识别出这种形态。
那么,这段代码片段是如何工作的,它又是如何检测到动画 04 中所示的内含线的呢?要理解这一点,首先需要了解之前那个更简单的指标是如何运作的。只有这样你才能理解这件事。此外,您还必须了解静态变量的工作原理。由于这个主题已经在另一篇文章中介绍过,我假设您已经熟悉静态变量。
如果你还没读过那篇文章,这里是链接:“从基础到中级:变量(二) “。现在,查看代码 04,可以清楚地看出,必要的更改恰好在内含线检查中进行了。检查仍然在那里进行。但是,它的位置已经发生了变化,因为在此之前,我们还要进行两项检查。第一个检查点在第 42 行,我们在这里检查高点是否已被突破。在第 43 行,我们执行类似的检查,但在这里,我们验证向下突破。因此,我们考虑的是内含线以存在于多个不同柱形之间的边界,而不仅仅是紧邻的前一个柱形。只有了解这些边界,我们才能检查所分析的柱形是否为连续柱形。这是在第 44 行完成的。
另一个重点是第 45 行。这里我们检查第 44 行的结果是否表明存在内含线。如果没有内含线,则必须破坏人为设定的边界,以寻找新内含线的形成。如果不执行第 45 行的这项检查,就会不断产生错误的读数。
为了帮助您理解我们在这里展示的内含线指标是什么,让我们看一下下面的动画。

动画 05
在动画 05 中,我们可以观察到一些非常有趣的事情:在某个特定时刻存在一个内含线。然而,由于发生了突破 —— 在这种情况下,低点被突破了 —— 曾经的内含线不再是内含线,而变成了与其他任何柱形一样的东西。即使价格回落到我们认为可能再次出现内含线的位置,该指标也不会显示出来,如下图所示。

图 01
请注意,该交易品种正在回落到产生前三个内含线指示的 K 线或蜡烛图上。然而,根据该指标中实施的标准,最后一根 K 线不能再被视为内含线。简而言之,该指标有效,但我们需要定义并明确制定公认的标准,以便为我们提供适当和所需的信息。
很好,前面已经详细解释了如何将颜色指示应用于蜡烛图。现在我们可以讨论其他可能(也可能不)与指标相关的问题。然而,由于指标概念的性质,我认为在这种情况下展示其他东西是合适的。
展示什么,不展示什么
在市场上,我们采用不同的交易策略并不罕见。许多交易者更喜欢在图表上不断显示各种类型的信息。另一些人则倾向于获取最少的信息。具体显示哪些内容取决于每个交易者或他们的工作习惯。然而,对于我们程序员来说,这完全无关紧要。我们不在乎我们喜欢如何设置自己的图表。我们只关心提供简单方便的工具,以便每个人都可以根据自己的喜好自定义图表。
MetaTrader 5 允许我们配置其中的大部分内容,而无需使用外部应用程序。然而,有些元素无法使用 MetaTrader 5 提供的选项删除或更改 —— 这时就需要编程来帮忙了。作为程序员,你们必须明白,对于我们能做什么或不能做什么,没有任何限制。实际上存在的是每个程序员技术能力的强弱差异。有些人可以用更简单的方法解决问题,而有些人则需要更复杂的工具。
总之,我们的目标是开发一款能够将图表转换成我们所需格式的应用程序,即使对其他交易者来说,这可能看起来完全是一团糟。但如果你了解你所创造的东西,并且从交易中获利,那才是真正重要的。
因此,我们可以使用 MQL5 来定义和控制各种图表元素。首先,我们将做出一些简单的更改,这些更改在某些时候可能会引起人们的兴趣。目的是为了表明,在使用 MQL5 时,我们可以完全自由地控制任何图表元素。例如,您是否知道我们可以从图表中删除显示报价和时区的区域?如果我们尝试直接在 MetaTrader 5 中执行此操作,我们将无法成功。然而,实现这一目标的一种方法是通过代码。顺便一提,我们需要实现的代码非常简单。那么,我们就从这里开始吧。初始代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. int OnInit() 05. { 06. ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false); 07. ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false); 08. 09. return INIT_SUCCEEDED; 10. }; 11. //+------------------------------------------------------------------+ 12. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 13. { 14. return rates_total; 15. }; 16. //+------------------------------------------------------------------+
代码 05
请注意代码 05 是多么简单。我们无需解释什么;我们所做的事情显而易见。事实上,如果我们在图表上运行这段代码,我们将得到以下结果:

动画 06
确实,我们的做法相当粗暴,因为未经用户许可就移除价格和时间刻度,即使移除指标后,我们也不会在图表上恢复它们。虽然许多人可能认为这是合适的,但也有人认为这种行为是对应用程序的不尊重,因为恢复时间和价格刻度需要关闭并重新打开图表,而大多数用户都不愿意这样做。
但问题是:我们如何才能将图表恢复到原始状态?如果可能的话,什么时候是做这件事的最佳时机?其实,可以通过拦截 Deinit 事件来实现这一点。但是,我们必须明白:没有“最佳”时机,只有“合适”时机。我这么说是因为我们的应用程序可能会执行某些操作,导致 MetaTrader 5 将其从图表中删除。如果发生这种情况,则不会触发 Deinit 事件,因为会强制删除。结果,我们的应用程序所做的所有更改都将保留在图表上,我们必须关闭并重新打开它,以将所有内容恢复到原始状态。
因此,此类更改务必谨慎进行。并且,尽可能让用户选择从图表中删除哪些内容,尤其是在您的应用程序旨在方便用户工作的情况下。
好的,让我们用更礼貌的方式来实现动画 06 中展示的内容。为此,我们将对代码 05 进行如下修改:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. input bool user01 = true; //Show time scale 05. input bool user02 = true; //Show price scale 06. //+----------------+ 07. struct st_Mem 08. { 09. long View_DateScale, 10. View_PriceScale; 11. }gl_StyleGraphic; 12. //+------------------------------------------------------------------+ 13. int OnInit() 14. { 15. gl_StyleGraphic.View_DateScale = ChartGetInteger(0, CHART_SHOW_DATE_SCALE); 16. gl_StyleGraphic.View_PriceScale = ChartGetInteger(0, CHART_SHOW_PRICE_SCALE); 17. 18. ChartSetInteger(0, CHART_SHOW_DATE_SCALE, user01); 19. ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, user02); 20. 21. return INIT_SUCCEEDED; 22. }; 23. //+------------------------------------------------------------------+ 24. int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) 25. { 26. return rates_total; 27. }; 28. //+------------------------------------------------------------------+ 29. void OnDeinit(const int reason) 30. { 31. ChartSetInteger(0, CHART_SHOW_DATE_SCALE, gl_StyleGraphic.View_DateScale); 32. ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, gl_StyleGraphic.View_PriceScale); 33. }; 34. //+------------------------------------------------------------------+
代码 06
请注意,我们现在变得礼貌多了。这是因为在第 04 行和第 05 行中,我们请求用户允许删除价格和时间刻度。在第 07 行,我们使用一个结构来存储原始值,以便无论用户进行了什么更改,我们都可以将图表恢复到应用指标之前的状态。
在这里,我们的操作与代码 05 类似,该代码简单明了。不过,我想提请您注意另一件事:Deinit 事件处理函数。它在第 29 行。问题是,即使从图表中移除指标,MetaTrader 5 也需要一些时间才能更新指标所在位置的图表 —— 有时只需几秒钟,有时则需要更长时间。在此期间,部分信息可能缺失,而其他信息尚未被删除。这可能会给人留下平台运行缓慢或用户硬件过时的印象。然而,问题实际上在于 MetaTrader 5 正在忙于其他任务。
为了使过程更加顺畅,我建议在特定时刻调用 MQL5 标准库,以强制 MetaTrader 5 尽快更新图表。在某些情况下,这可能完全没有必要。但是,如果您注意到事件之间存在延迟,您可以使用此调用跳过 MetaTrader 5 当前正在处理的事件队列。
需要进行的更改可以在下面的代码片段中看到:
. . . 28. //+------------------------------------------------------------------+ 29. void OnDeinit(const int reason) 30. { 31. ChartSetInteger(0, CHART_SHOW_DATE_SCALE, gl_StyleGraphic.View_DateScale); 32. ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, gl_StyleGraphic.View_PriceScale); 33. ChartRedraw(); 34. }; 35. //+------------------------------------------------------------------+
代码 07
只需添加代码 07 中的第 33 行,用户(也包括您)就能明显感受到 MetaTrader 5 性能的提升。但是,需要注意的是,这种提升只是表面上的,并没有实际的性能提升。第 33 行的调用通知 MetaTrader 5,我们希望跳过事件队列,以便尽快更新图表。但是,请记住:跳过队列会推迟一个可能很重要且可能在其他时间触发的事件。因此,更改执行顺序时要谨慎。
总结性思考
在本文中,我们已经了解了创建和实现蜡烛图着色操作方法是多么容易 —— 这是一个受到许多交易者高度重视的概念。在实现此类操作时,必须注意确保柱形或烛形保持其原有的外观,并且不会妨碍逐根烛形的解读。
此外,我们还演示了如何修改各种图表元素,尽管本文只展示了如何使用两个属性:时间和价格刻度。反过来,普通用户无法使用任何可直接获得的资源来移除这些刻度。然而,这完全可以通过代码实现。需要注意的是,应尽可能将图表恢复到原始状态,因为这可以提升用户体验,并表明该应用程序设计良好。
由于可以修改的属性和特性有很多,逐一处理会很麻烦,所以我鼓励您通过 MQL5 代码来练习和试验使用和更改这些属性。为了方便起见,您可以参考图表属性列表,因为许多显示的元素和值只能通过 MQL5 代码访问。请记住,应用程序中应该只包含练习和学习文章中演示内容真正必要的代码。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15833
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
MQL5交易管理面板开发(第十二部分):外汇估值计算器的集成
价格行为分析工具包开发(第二十七部分):利用移动平均线进行流动性扫单
新手在交易中的10个基本错误