从基础到中级:指标(五)
概述
在上一篇文章“从基础到中级:继承”中,我们讨论了如何在结构化编程环境中应用继承。为了避免信息量过大,在今天的文章中,我们将探讨一些更简单的内容,或者至少是与之前文章中的内容并行学习和练习的材料。
我理解,这个话题一开始可能有些难以理解,而且考虑到在此之前已经连续六篇文章,内容相当丰富,我建议放松一下,先看看一些更简单、更实用的内容。
在关于指标的文章中,系列文章的最新一篇是“从基础到中级:指标(四)”,我们探讨了如何使用一种完全静态且非常简单的实现方式,以及一种直接创建指标的方法,将某些内容直接显示在图表上。尽管这些文章中展示的格式对于你可能会创建的几乎任何指标来说都是理想的,但我们实际上并不只限于这种格式。如果可以把它们视为这类动态指标的话,我们还可以选择创建动态指标。
但为了让你们理解我为何认为之前的格式是静态的,以及它与动态创建的格式有何不同,我需要解释几点。为了更好地做到这一点,让我们继续讨论一个新的话题。
静态指标与动态指标之间的差异
很可能,许多其他程序员会不同意我接下来要说的话,但这只是一个小细节,毕竟每个人都有权持有自己的观点。只要理由明确且解释清楚,一种观点并不一定会否定或推翻其他观点。
要理解我为何称之前讨论的指标为“静态”,我们需要查看它们的代码。的确,当时我并没有深入细节,部分原因是这种实现方式很容易理解。但是为了理解我们今天将看到的内容,我们首先需要理解之前的那些代码片段。
让我们以其中一段代码为例。下面你可以看到我们将用来开始讲解的示例。
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(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); 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. 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. //+------------------------------------------------------------------+
代码 01
运行时,代码 01 会把图表显示改为类似指标的绘制方式。在这种情况下,它是一个内含线指标,如下图所示。

图 01
因此,我们之所以知道代码 01 是如何工作的,至少在其基本部分,正是因为在引入它时,已经解释了如何声明某些参数。但是,据我了解,这个标记为代码 01 的指标是一个静态指标。原因就在于第 04 行和第 05 行的声明。亲爱的读者们,请不要误会我的意思。我的意思是,代码 01 中的指标是静态的,因为编译后我们无法更改指标本身,因为它是用 MQL5 编译指令声明的,这些指令中就包含了刚才提到的几行代码。
然而,真正重要的是指标是否有效,以及它是否能完成其设计任务。由于这是通过一种相当简单的方式实现的,因此使用这种静态实现没有任何问题。然而,至少从实现和展示的角度来看,我们可以创建完全相同的代码,或者更确切地说,创建一个非常相似的指标(但具有动态属性)。
动态指标是如何运作的?实现它有哪些优势?让我们一步一步来。首先,动态指标的代码中不应包含第 04 行和第 05 行,这两行在代码 01 中可以看到。这是第一个也是最重要的区别。这样做的好处恰恰在于没有这两行代码。乍一看,这可能有些奇怪,但当我们继续讨论更复杂的模型时,就会变得更清楚。
但正是因为我们没有声明这些代码行,我们可以在不编译代码的情况下更改指标的信息显示方式,从而改变其呈现方式。然而,或许最大的优势在于,我们无需在指标代码中创建一个真正的逻辑结构来选择如何在图表上显示信息。
现在要注意了,我们在这里所做的不会改变代码 01 指标的运行原理。我们将通过代码来实现这些操作。换言之,通常由指令执行的部分操作将通过调用 MQL5 标准库来实现。首先要做的是删除将指标从代码 01 变为静态指标的代码行。去除静态部分后,我们得到以下结果:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property indicator_buffers 5 05. #property indicator_plots 1 06. //+----------------+ 07. double gl_Buff_High[], 08. gl_Buff_Open[], 09. gl_Buff_Close[], 10. gl_Buff_Low[], 11. gl_Buff_Color[]; 12. //+------------------------------------------------------------------+ 13. int OnInit() 14. { 15. SetIndexBuffer(1, gl_Buff_High, INDICATOR_DATA); 16. SetIndexBuffer(0, gl_Buff_Open, INDICATOR_DATA); 17. SetIndexBuffer(3, gl_Buff_Close, INDICATOR_DATA); 18. SetIndexBuffer(2, gl_Buff_Low, INDICATOR_DATA); 19. SetIndexBuffer(4, gl_Buff_Color, INDICATOR_COLOR_INDEX); 20. 21. return INIT_SUCCEEDED; 22. }; 23. //+------------------------------------------------------------------+ 24. 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[]) 25. { 26. static double high = DBL_MIN, 27. low = DBL_MAX; 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. if ((c - 1) > 0) 38. { 39. high = (High[c - 1] > high ? High[c - 1] : high); 40. low = (Low[c - 1] < low ? Low[c - 1] : low); 41. gl_Buff_Color[c] = ((high > High[c]) && (low < Low[c]) ? 1 : gl_Buff_Color[c]); 42. if (gl_Buff_Color[c] != 1) 43. { 44. high = DBL_MIN; 45. low = DBL_MAX; 46. } 47. } 48. } 49. 50. return rates_total; 51. }; 52. //+------------------------------------------------------------------+
代码 02
代码 02 虽然可以编译,但无法运行。至少,它不会以任何方式影响它所放置的图表。但这其实并不是个问题。这是因为我们移除了向 MetaTrader 5 明确说明指标使用方式的部分。因此,MetaTrader 5 只知道我们有四个数据缓冲区和一个颜色缓冲区,并且只会绘制一个绘图序列。但是 MetaTrader 5 不知道 如何使用这些缓冲区,即使它们已配置为显示一些信息。因此,图表上什么也不会发生。
好的,我们该如何解决这个问题?我们如何告诉 MetaTrader 5 如何使用代码 02 中声明的缓冲区?嗯,这才是最有趣的部分,因为接下来我们将动态配置 MetaTrader 5 中的缓冲区使用情况。这是通过在代码中添加几行新内容来实现的,如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property indicator_buffers 5 05. #property indicator_plots 1 06. //+----------------+ 07. double gl_Buff_High[], 08. gl_Buff_Open[], 09. gl_Buff_Close[], 10. gl_Buff_Low[], 11. gl_Buff_Color[]; 12. //+------------------------------------------------------------------+ 13. int OnInit() 14. { 15. const color cor[] = {clrRed, clrRoyalBlue, clrGreen}; 16. 17. PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES); 18. PlotIndexSetInteger(0, PLOT_COLOR_INDEXES, cor.Size()); 19. for (uint c = 0; c < cor.Size(); c++) 20. PlotIndexSetInteger(c, PLOT_LINE_COLOR, c, cor[c]); 21. 22. SetIndexBuffer(1, gl_Buff_High, INDICATOR_DATA); 23. SetIndexBuffer(0, gl_Buff_Open, INDICATOR_DATA); 24. SetIndexBuffer(3, gl_Buff_Close, INDICATOR_DATA); 25. SetIndexBuffer(2, gl_Buff_Low, INDICATOR_DATA); 26. SetIndexBuffer(4, gl_Buff_Color, INDICATOR_COLOR_INDEX); 27. 28. return INIT_SUCCEEDED; 29. }; 30. //+------------------------------------------------------------------+ . . .
代码 03
由于最终结果与图 01 所示一致,因此我不再重复。但是,当使用我们在代码 03 片段中看到的动态指标时,一切都将与使用代码 01 中的静态指标有所不同。
但在继续之前,让我们先了解一下代码 03 片段中做了什么,这里只显示我们感兴趣的部分。提醒一下,完整代码见附件。请注意,第 15 行定义了一个常量颜色数组。颜色序列与代码 01 第 05 行相同。为了理解代码的实际工作原理,你理解这一点很重要。现在,当执行代码 03 中的第 17 行时,我们将得到与编译器读取代码 01 中的第 04 行时相同的效果。注意这里存在的细微差别。与代码 01 不同,在编译代码 01 时,我们指定了所有内容的外观,而在这里我们则没有这样做。我们只需告诉编译器:生成代码并使用此值。但这会带来严重的后果,我们稍后会看到。
紧接着,在代码 03 的第 18 行,我们告诉 MetaTrader 5 颜色缓冲区中有多少种颜色。注意:在第 18 行指定颜色数量并不能告诉我们每种颜色将具有什么值。因此,如果我们不对它们进行初始化,MetaTrader 5 会以某种方式自行初始化,这可能会导致配色方案完全混乱。因此,我们使用第 19 行的循环,通过调用第 20 行的函数来初始化颜色。所有这些都相当于代码 01 中的第 05 行。
然而,即便如此,相似之处也就到此为止了。因为,通过应用代码 03 片段中的这个指标,我们会发现自己处于一种至少可以说是非常不寻常的情况。如下图所示。

图 02
“嘿!请等一等,指标将使用哪些颜色?”我认为你和看到图片 02 的用户都会对此感到非常惊讶,而看到这张图片的用户会完全感到困惑。但是,让我们看看如果将这个指标应用于图表,会发生什么。这一点可以从下面的动画中看到。

动画 01
“真是太可怕了。我不太明白。为何在如图 02 所示无法调整颜色的情况下,指标仍然产生了与图 01 相同的结果?这真是太奇怪了。”
事实上,这种行为似乎并不那么奇怪。这是因为我们动态地定义了参数。由于 MetaTrader 5 尚不知道如何创建颜色,因此无法使用这些颜色,或者更确切地说,无法在配置中展示这些颜色,因为它尚不知道会用到多少种颜色,以及分别是哪几种颜色。这确实很有趣,未来我们将能够以不同的方式研究这个问题。现在,我们先集中讨论要点。
鉴于配色方案与我们在图 01 中看到的配色方案相符,我们知道该指标有效。我们还可以向用户展示在代码编写阶段定义的模板。“但是,允许用户选择想要的颜色,难道不能改变这种情况吗?”至于这一点,亲爱的读者,基本上没有问题,因为指标出现在图表上以后,用户就可以打开其属性并进行更改。在这种情况下,请看动画 02 中发生了什么。

动画 02
嗯,很有趣;现在我们可以看看这个指标中定义了哪些颜色。“但是等一下。你说过我们可以改变颜色。然而,我突然想起一件事。在代码 03 的片段第 15 行,我们精确地定义了动画 02 中可见的颜色,但它们被定义为常量。这似乎有些矛盾。如果它们被定义为常量,用户如何更改配色方案?”
在这方面,我们的优势在于 MetaTrader 5 能够理解正在进行的操作,因为我们只是简单地更改指标的某些参数。MetaTrader 5 不会将其从图表中完全移除。从某种意义上说,这将使它在图表上保持不变,同时仅对必要部分进行更改。下面的动画展示了一个示例。

动画 03
请注意,这确实有效。然而,这种方法有一个小小的缺点。当用户从图表中移除指标时,或者更准确地说,当用户要求 MetaTrader 5 重建图表时,第 15 行声明的值将再次被使用,因此用户所做的任何配置更改都将丢失,必须重新进行配置。
图表刷新请求的一个示例可能是更改图表时间周期的简单请求。虽然这看起来很荒谬,但对于许多用户来说,仅仅更改设置(例如 5 分钟),然后立即要求 MetaTrader 5 使用周期(例如 10 分钟),就会导致指标创建时定义的设置再次被使用,因此所做的更改将会丢失。在另一篇文章中,我们已经解释了如何解决或克服这种情况。因此,我们在此不再赘述。
经过这个简单的演示,我想你已经理解了静态实现和动态实现之间的区别。然而,对于那些想了解我们能够走多远的人来说,MQL5 文档中包含了一个表格,可以说明这些问题。参见:指标属性与相应函数之间的关系,允许您确定哪些指令或属性可以动态实现,哪些不能动态实现。
“很好,但说实话,我仍然不明白使用这种动态系统的优势是什么。无论如何,我不明白它如何能以任何方式帮助我。”好吧,我理解你对这种实现方案缺乏热情。的确,这看起来微不足道,尤其是考虑到在使用这种指标实现时经常会出现的一些困难。
然而,在专注于个人使用的某些非常特殊的情况下,你可能会对这种方法感兴趣。为了证明这一点,让我们考虑一个非常具体的实现示例。这将在下一主题中讨论。
不同图表类型下的内含线指标
我们在上一主题中讨论的指标主要用于蜡烛图。但众所周知,MetaTrader 5 不仅允许您使用蜡烛图,还允许您使用柱状图,当然还有折线图。如果用户不小心切换到蜡烛图以外的其他显示类型,将会遇到非常不愉快的图表行为。让我们看看下面的内容:

图 03
图 03 显示了在柱状图上使用的内含线指标。请注意,信息的显示方式非常不寻常,如下图所示。

图 04
这里我们遇到的问题与图 03 中的问题非常相似。但是,对于图 04,我们要求使用折线图以及内含线指标。或许对大多数人而言,在现实中,这种情况并不合理,因为在使用折线图时,我们仅从开盘价、收盘价、最高价和最低价(OHLC)中的一个值中获取信息,而几乎总是最后这个值对我们来说最重要。但事实并非如此。问题在于,即便我们要求以另一种格式展示图表,我们仍然会看到蜡烛图被显示出来。这个问题必须解决。
原则上,有两种方法可以做到这一点。我说“两个”是因为其实还有第三个,那就是为每种图表模式创建一个指标。但这将使用户在使用上变得极其混乱。让我们重点关注另外两种方法。一种方法是基于编译器将创建的内容来设计一套执行逻辑。另一种方法是创建一个动态系统来处理将要监控的信息。
为了避免不必要的复杂性,我们将修改代码 03(在前一个主题中讨论过),使其成为一个从一开始就可以指定指标颜色的版本。但是,我们不会定义图表上将显示的指标类型。因此,代码片段 03 将如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property indicator_color1 clrRed, clrRoyalBlue, clrGreen 05. //+----------------+ 06. #property indicator_buffers 5 07. #property indicator_plots 1 08. //+----------------+ 09. double gl_Buff_High[], 10. gl_Buff_Open[], 11. gl_Buff_Close[], 12. gl_Buff_Low[], 13. gl_Buff_Color[]; 14. //+------------------------------------------------------------------+ 15. int OnInit() 16. { 17. PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES); 18. 19. SetIndexBuffer(1, gl_Buff_High, INDICATOR_DATA); 20. SetIndexBuffer(0, gl_Buff_Open, INDICATOR_DATA); 21. SetIndexBuffer(3, gl_Buff_Close, INDICATOR_DATA); 22. SetIndexBuffer(2, gl_Buff_Low, INDICATOR_DATA); 23. SetIndexBuffer(4, gl_Buff_Color, INDICATOR_COLOR_INDEX); 24. 25. return INIT_SUCCEEDED; 26. }; 27. //+------------------------------------------------------------------+ 28. 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[]) 29. { 30. static double high = DBL_MIN, 31. low = DBL_MAX; 32. 33. for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++) 34. { 35. gl_Buff_High[c] = High[c]; 36. gl_Buff_Open[c] = Open[c]; 37. gl_Buff_Close[c] = Close[c]; 38. gl_Buff_Low[c] = Low[c]; 39. 40. gl_Buff_Color[c] = (Open[c] > Close[c] ? 0 : 2); 41. if ((c - 1) > 0) 42. { 43. high = (High[c - 1] > high ? High[c - 1] : high); 44. low = (Low[c - 1] < low ? Low[c - 1] : low); 45. gl_Buff_Color[c] = ((high > High[c]) && (low < Low[c]) ? 1 : gl_Buff_Color[c]); 46. if (gl_Buff_Color[c] != 1) 47. { 48. high = DBL_MIN; 49. low = DBL_MAX; 50. } 51. } 52. } 53. 54. return rates_total; 55. }; 56. //+------------------------------------------------------------------+
代码 04
现在,代码 04 对于任何指标用户来说都更加方便,因为我们可以在一开始就指定颜色。这一切都是因为我们在代码 04 的第 04 行定义了指标属性。请注意,这就是我们为何从 OnInit 事件处理块中移除了负责加载颜色的代码部分。因此,我们得到了一个需要进行处理的基础指标。
现在我们来到最有趣的部分:如果我们在代码 04 中看到的这个指标既能动态工作,同时又有一定的静态部分来显示颜色模板,那么我们该如何使其工作并适应所查看的图表类型呢?
这就是许多人在查看我的代码时通常会起鸡皮疙瘩的地方。请注意,我们在第 17 行指定该指标将以蜡烛图的形式绘制。但是,当将图表切换为柱状图时,就会出现问题,如前面的图片所示。但是,如果我们研究一下文档,就会发现标准库中一个相当有趣的特性。它可以让我们知道当前使用的是哪种图表类型。这指的是 ChartGetInteger 函数。当我们向此函数请求有关所用图表类型的信息时,它会返回一个枚举,在本例中为 ENUM_CHART_MODE。
这正是我们需要知道的。但在将其应用于实践之前,我们先来看看该指标在初始阶段的表现如何。为此,我们对代码进行了修改;现在它看起来像这样:
. . . 14. //+------------------------------------------------------------------+ 15. #define PrintX(X) Print(__FUNCTION__, " => ",#X, " :: ",X); 16. //+------------------------------------------------------------------+ 17. int OnInit() 18. { 19. PrintX(EnumToString((ENUM_CHART_MODE)ChartGetInteger(0, CHART_MODE))); 20. PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES); 21. 22. SetIndexBuffer(1, gl_Buff_High, INDICATOR_DATA); 23. SetIndexBuffer(0, gl_Buff_Open, INDICATOR_DATA); 24. SetIndexBuffer(3, gl_Buff_Close, INDICATOR_DATA); 25. SetIndexBuffer(2, gl_Buff_Low, INDICATOR_DATA); 26. SetIndexBuffer(4, gl_Buff_Color, INDICATOR_COLOR_INDEX); 27. 28. return INIT_SUCCEEDED; 29. }; 30. //+------------------------------------------------------------------+ . . .
代码 05
现在请注意,否则你会对要做的事情的描述感到困惑。这段代码片段 05 显示了为了确定当前正在使用的图表类型而必须对代码 04 进行的更改。然后,在第 19 行,我们发出一个调用,最终,根据第 15 行给出的定义,该调用将被转换为将在 MetaTrader 5 终端中显示的信息。这些信息可以在下面的图片中看到。

图 05
图 05 显示我们正在使用 CHART_CANDLES 图表。

图 06
在图 06 中,我们可以清楚地看到图表名为 CHART_BARS。

图 07
最后,在图 07 中,我们看到图表名为 CHART_LINE。好的,但是我们如何才能利用这一点呢?嗯,一切都很简单,除了 CHART_LINE 图表,我们稍后会讨论它的解决方案。为了使指标使用正确的图表绘制模式,我们只需要在第 20 行(如本代码 05 片段所示)调用函数,在蜡烛图上指定 DRAW_COLOR_CANDLES 类型,在柱状图上指定 DRAW_COLOR_BARS 类型。
后面的实现就顺理成章了。任何人都能轻松做到这一点,如果 MetaTrader 5 从初始化事件应用指标,则效果完美。换句话说,如果我们想以最简单的方式使用它,代码应该如下所示。
. . . 14. //+------------------------------------------------------------------+ 15. int OnInit() 16. { 17. switch ((ENUM_CHART_MODE)ChartGetInteger(0, CHART_MODE)) 18. { 19. case CHART_BARS: 20. PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_BARS); 21. break; 22. case CHART_CANDLES: 23. PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES); 24. break; 25. case CHART_LINE: 26. break; 27. } 28. 29. SetIndexBuffer(1, gl_Buff_High, INDICATOR_DATA); 30. SetIndexBuffer(0, gl_Buff_Open, INDICATOR_DATA); 31. SetIndexBuffer(3, gl_Buff_Close, INDICATOR_DATA); 32. SetIndexBuffer(2, gl_Buff_Low, INDICATOR_DATA); 33. SetIndexBuffer(4, gl_Buff_Color, INDICATOR_COLOR_INDEX); 34. 35. return INIT_SUCCEEDED; 36. }; 37. //+------------------------------------------------------------------+ . . .
代码 06
请注意,在代码片段 06 中,我们没有考虑折线图绘制模式。这一点稍后会变得很清楚。但请注意我们如何显示其他模式。我们在代码 06 中看到的是可行的,但它存在一个小问题。在下面的动画中,你可以看到这个问题是什么。

动画 04
在动画 04 中,我们看到当图表切换到另一种绘图模式时,指标无法再跟上 MetaTrader 5 的操作。许多人会放弃,认为不可能正确创建指标。然而,他们并不了解 MetaTrader 5 的实际运作方式。每当有事情发生时 — 通常是图表上的变化 — MetaTrader 5 都会触发一个特定事件,该事件包含一些信息,有助于我们确定发生了什么。有些事件可以启用或禁用,而有些则不能。我们启用或禁用的每个事件都会影响 MetaTrader 5 的整体性能,因为系统会因此过载,并被迫处理那些它通常不会监控的事件。
对于那些追求高性能系统的人来说,这一点其实很明显。许多交易者在计划使用 MetaTrader 5 时,根本不会使用任何相关工具,例如指标或 EA。这是因为此类应用程序通常会在市场波动剧烈期间降低 MetaTrader 5 的运行速度。正是在这样的时刻,这些交易者想要获利;因此,他们只使用 MetaTrader 5 的基本版本,不使用任何其他附加功能。
好的,但在此我们不会关注仅使用最基础配置的 MetaTrader 5 进行交易。是的,我们想在图表中添加应用程序,以便它们能为我们提供帮助。因此,我们需要捕获 MetaTrader 5 每次在图表上进行操作时触发的特定事件,无论该事件的性质如何。
我们想要捕获的事件是 ChartEvent。在我们的代码中,它由 OnChartEvent 函数处理。各位读者请注意,每当图表上发生任何变化时,都会触发 ChartEvent 事件。然而,我们可以启用和禁用某些参数,以便不捕捉对图表所做的每一个更改。然而,当向图表中添加指标时,MetaTrader 5 会依次触发两个事件。在某些情况下,还会触发第三个事件,但我们只关注前两个事件。
首先触发的事件是初始化(Init),该事件由 OnInit 函数拦截和处理,自本专题讨论指标之初,我们就一直在考虑这个函数。Init 事件处理完毕后,即使我们还没有执行任何操作,也会立即触发一个新事件。此事件为 ChartEvent,而正是在这一时刻,指标被实际放置在图表上。
好的,但要理解接下来会发生什么,我们需要查看拦截此 ChartEvent 的过程的声明。该声明就在下方。
void OnChartEvent(const int id, // Event ID const long& lparam, // Parameter of type long event const double& dparam, // Parameter of type double event const string& sparam // Parameter of type string events );
代码 07
现在,如果我们查看 OnChartEvent 的文档,我们会看到一个小表格。从这张表格中,我们感兴趣的是以下值:CHARTEVENT_CHART_CHANGE — 图表发生变化时发生的事件。因此,这正是 ChartEvent 首次触发时出现的值。现在,知道了这一点,我们就可以在代码中做一些非常有趣的事情了。更新后,代码将如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #property indicator_color1 clrRed, clrRoyalBlue, clrGreen 05. //+----------------+ 06. #property indicator_buffers 5 07. #property indicator_plots 1 08. //+----------------+ 09. double gl_Buff_High[], 10. gl_Buff_Open[], 11. gl_Buff_Close[], 12. gl_Buff_Low[], 13. gl_Buff_Color[]; 14. //+------------------------------------------------------------------+ 15. int OnInit() 16. { 17. SetIndexBuffer(0, gl_Buff_Open, INDICATOR_DATA); 18. SetIndexBuffer(1, gl_Buff_High, INDICATOR_DATA); 19. SetIndexBuffer(2, gl_Buff_Low, INDICATOR_DATA); 20. SetIndexBuffer(3, gl_Buff_Close, INDICATOR_DATA); 21. SetIndexBuffer(4, gl_Buff_Color, INDICATOR_COLOR_INDEX); 22. 23. return INIT_SUCCEEDED; 24. }; 25. //+------------------------------------------------------------------+ 26. 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[]) 27. { 28. static double high = DBL_MIN, 29. low = DBL_MAX; 30. 31. for (int c = (prev_calculated > 0 ? prev_calculated - 1 : 0); c < rates_total; c++) 32. { 33. gl_Buff_High[c] = High[c]; 34. gl_Buff_Open[c] = Open[c]; 35. gl_Buff_Close[c] = Close[c]; 36. gl_Buff_Low[c] = Low[c]; 37. 38. gl_Buff_Color[c] = (Open[c] > Close[c] ? 0 : 2); 39. if ((c - 1) > 0) 40. { 41. high = (High[c - 1] > high ? High[c - 1] : high); 42. low = (Low[c - 1] < low ? Low[c - 1] : low); 43. gl_Buff_Color[c] = ((high > High[c]) && (low < Low[c]) ? 1 : gl_Buff_Color[c]); 44. if (gl_Buff_Color[c] != 1) 45. { 46. high = DBL_MIN; 47. low = DBL_MAX; 48. } 49. } 50. } 51. 52. return rates_total; 53. }; 54. //+------------------------------------------------------------------+ 55. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 56. { 57. switch (id) 58. { 59. case CHARTEVENT_CHART_CHANGE: 60. switch ((ENUM_CHART_MODE)ChartGetInteger(0, CHART_MODE)) 61. { 62. case CHART_BARS: 63. PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_BARS); 64. break; 65. case CHART_CANDLES: 66. PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES); 67. break; 68. case CHART_LINE: 69. break; 70. } 71. break; 72. } 73. ChartRedraw(); 74. }; 75. //+------------------------------------------------------------------+
代码 08
唔,“我不知道。我认为这段代码不会起作用,因为我们在执行 OnInit 函数时,没有告诉 MetaTrader 5 应使用哪种图表绘制方式。我不知道。我几乎可以肯定,结果会和代码 02 中的错误一样。”
看来我们当中出了一位怀疑论者。因此,我们要让他知道,这个世界远比他想象的要大得多。为了证明代码 08 的确有效,让我们来看下面的动画。

动画 05
请注意,唯一与 MetaTrader 5 在图表上的操作不匹配的是切换到 CHART_LINE 模式。在这种情况下,我们的指标没有使用图表绘制模型,因此无法跟上用户在 MetaTrader 5 中的操作。
最后的想法
在今天的文章中,我们展示了一个基本但非常有趣的指标运用原则。尽管我们没有展示如何解决使用指标绘制图表并满足 MetaTrader 5 所有要求的问题,但我们设法让一个简单的指标执行了与图表绘制模式相对应的命令。这部分原因在于,我们尚未展示如何根据用户在平台上的选择,使指标显示折线图。
然而,由于目前很难演示从绘制蜡烛图或柱状图(需要四个数据缓冲区和一个颜色缓冲区)过渡到绘制彩色折线图(需要一个数据缓冲区和一个颜色缓冲区),所以我们将稍后再讨论这个问题。为此,我们需要采用一种略有不同的方法,即通过调用标准库来加载和卸载指标。这件事现在对我来说有点难以解释,对你们这些亲爱的读者来说,也很难理解。
无论如何,随着我们继续学习更复杂的材料,在某个时候,我会展示如何解决这里讨论过的问题。目前,我建议您学习本文中介绍的材料,最重要的是,不要忘记重读前面文章中涵盖的内容。因为在下一篇文章中,我们将把一些概念与指标这一主题联系起来,这将非常有趣。所以千万不要错过。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15932
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
新手在交易中的10个基本错误
市场模拟(第 20 部分):SQL 入门(三)