
从头开始开发智能交易系统(第 10 部分):访问自定义指标
概述
一款交易 EA 仅在能够使用自定义指标的情况下才是真正有用;否则,它只是一组代码和指令而已,其设计可以很优秀,有助于持仓管理、或执行市场交易,也可能所有这些。
好吧,在 MetaTrader 5 图表上添加指标并不是最难的部分。 但若是没有适当规划的情况下,直接在智能交易系统中访问这些指标计算出的数据则几乎是不可能的任务。 如果我们不知道该怎么做,我们就只能限于标准指标。 然而,为了交易我们还需要更多。 一个很好的例子是 VWAP(成交量加权平均价格)指标。 对于在巴西证券交易所进行期货交易的人来说,这是一款非常重要的移动平均线。 该均线不是 MetaTrader 中的标准指标,但我们可以创建一个自定义指标来计算 VWAP,并在屏幕上显示它。 然而,当我们决定在 EA 的分析系统中使用相同的指标时,事情变得更加复杂了。 如果缺乏相关知识,我们就无法在 EA 中使用该自定义指标。 在本文中,我们将看到如何绕过这个限制,并解决这一难题。
计划
首先,我们尝试创建在自定义指标里要采用的算法。 幸运的是,我们示例采用的 VWAP 计算公式非常简单。
当翻译成编程语言时,我们得到以下 MQL5 内容:
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 &tick_volume[], const long &volume[], const int &spread[]) { double Price = 0; ulong Volume = 0; static int siPos = 0; if (macroGetDate(time[rates_total - 1]) != macroGetDate(time[siPos])) { for (int c0 = rates_total - 1; macroGetDate(time[siPos]) != macroGetDate(time[c0]); siPos++); ArrayInitialize(VWAP_Buff, EMPTY_VALUE); } for (int c0 = siPos; c0 < rates_total; c0++) { Price += ((high[c0] + low[c0] + close[c0]) / 3) * volume[c0]; Volume += volume[c0]; VWAP_Buff[c0] = Price / Volume; } return rates_total; }
含有算法的行高亮显示,而函数的其余部分则用于正确初始化日线 VWAP。 然而,我们的指标仍然无法在图表上运行,我们需要在代码中添加更多内容。 其余代码如下所示:
#property copyright "Daniel Jose - Indicador VWAP ( IntraDay )" #property version "1.01" #property indicator_chart_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_width1 2 #property indicator_type1 DRAW_LINE #property indicator_color1 clrBlack //+------------------------------------------------------------------+ #define macroGetDate(A) (A - (A % 86400)) //+------------------------------------------------------------------+ double VWAP_Buff[]; //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0, VWAP_Buff, INDICATOR_DATA); return INIT_SUCCEEDED; }
这样就可以在图表上显示VWAP,如前所示:
好吧,这部分并不太复杂。 现在,我们需要找到一种方法令 EA 能看到 VWAP,以便它能够以某种特定的方式分析指标。 如此即可在交易中从该指标受益。
为了更容易使用指标,我们保存 VWAP,如此即可轻松访问。
在那之后,我们可以跃进到一种新的投影方式。 虽然 VWAP 指标基本正确,但在 EA 中使用时编程有误。 为什么? 问题在于 EA 无法知道指标是否在图表上。 如果不晓得这一点,它就无法从指标读取数值。
问题出在,文件名对于系统来说无关紧要。 您可以在文件名称中写入任意内容,但指标名称应能反映其计算用途。 我们的指标还没有一个来反映它的名称。 即使它被称为 VWAP,而这对系统也毫无意义。 出于该原因,EA 就无法知道该指标是否在图表上出现。
为了令指标能反映出它的计算用途,我们需要在代码中有所示意。 以这种方式,我们就能创建唯一的名称,且它不必与文件名关联。 在我们的例子中,指标初始化代码应该是这样的。 在我们的例子中,指标初始化代码应该是这样的:
int OnInit() { SetIndexBuffer(0, VWAP_Buff, INDICATOR_DATA); IndicatorSetString(INDICATOR_SHORTNAME, "VWAP"); return INIT_SUCCEEDED; }
通过简单地添加高亮显示的行,我们就解决了这个问题。 在某些情况下,这可能会更加困难 — 我们稍后将回到这一点。 首先,我们以 MetaTrader 5 函数库中的自定义移动平均指标代码为例。 其代码如下:
void OnInit() { SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA); IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1); PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpMAPeriod); PlotIndexSetInteger(0,PLOT_SHIFT,InpMAShift); string short_name; switch(InpMAMethod) { case MODE_EMA : short_name="EMA"; break; case MODE_LWMA : short_name="LWMA"; break; case MODE_SMA : short_name="SMA"; break; case MODE_SMMA : short_name="SMMA"; break; default : short_name="unknown ma"; } IndicatorSetString(INDICATOR_SHORTNAME, short_name + "(" + string(InpMAPeriod) + ")"); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0); }
高亮显示的部分示意我们所需的名称。 请注意,它与文件名无关。 但这必须完全在自定义指标内部完成。
现在我们已经完成了这项工作,并确保 EA 能够检查自定义指标是否在图表上运行,如此我们即可继续下一步。
通过 EA 访问指标
我们可以继续早前的做法。 但在理想情况下,为了真正理解正在发生的事情,您应该创建全新的代码。 既然我们的理念是从零开始学习开发智能交易系统,那么我就要们经历这个阶段。 因此,在继续我们的旅程当中,我们将创建一个孤立的智能交易系统。 然后我们可以在最终代码中包含或不包含它。 现在我们来继续编写代码。 EA 从干净的代码开始,如下所示:
//+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick(){} //+------------------------------------------------------------------+ void OnTimer(){} //+------------------------------------------------------------------+
我们来完成以下操作:首先,我们假设 VWAP 指标在图表上,并将指标计算的最后一个值加载到智能交易系统中。 我们每秒钟重复一次。 但应当怎么做呢? 很简单。 查看修改后的 EA 代码模样:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ int handle; double Buff[]; //+------------------------------------------------------------------+ int OnInit() { handle = ChartIndicatorGet(ChartID(), 0, "VWAP"); SetIndexBuffer(0, Buff, INDICATOR_DATA); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { int i; if (handle != INVALID_HANDLE) { i = CopyBuffer(handle, 0, 0, 1, Buff); Print(Buff[0]); } } //+------------------------------------------------------------------+
高亮显示的部分就是我们在干净代码中加入的内容。 结果如下:
它为什么能工作? 这是因为 MQL5 提供了在系统之间读写数据的方法。 读取的方法之一是调用 CopyBuffer 函数。 其工作原理如下:
如此,我们可以从任意自定义指标中读取数据,即我们不受限于标准 MetaTrader 5 指标。 这意味着我们可以创建任意指标,且它能发挥作用。
现在考虑另一个场景。 这次 VWAP 在图表上不存在。 但 EA 依然需要它,因此我们需要将其加载到图表中。 如何来做呢? 这也十分简单。 甚至,我们之前曾将其用于其它目的 — 在为智能交易系统创建子窗口的时候。 我们现在要做的是调用 iCustom 函数。 但这次我们将加载一个自定义指标。 然后 EA 代码如下:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ int handle; double Buff[]; //+------------------------------------------------------------------+ int OnInit() { handle = ChartIndicatorGet(ChartID(), 0, "VWAP"); SetIndexBuffer(0, Buff, INDICATOR_DATA); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { int i; if (handle == INVALID_HANDLE) handle = iCustom(NULL, PERIOD_CURRENT, "VWAP.EX5");else { i = CopyBuffer(handle, 0, 0, 1, Buff); Print(Buff[0]); } } //+------------------------------------------------------------------+
高亮显示的代码就是我们在原始系统里所做的仅有附加。 现在运行 EA 会产生以下结果:
下面的示意图显示出我们所实现的内容:
这就是您在最基本层面需要的一切。 但若您仔细观察,就会发现 VWAP 在图表上并不可见。 即使 EA 正在用到它,用户也不知道发生了什么。 这也可以很容易地修复,最终代码如下所示。 记住这一点:它有个好处就是总能分析和观察 EA 正在做的事情,由于赋予它完全的自由不是很安全,所以我并不建议这样做:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ int handle; long id; double Buff[]; string szCmd; //+------------------------------------------------------------------+ int OnInit() { szCmd = "VWAP"; handle = ChartIndicatorGet(id = ChartID(), 0, szCmd); SetIndexBuffer(0, Buff, INDICATOR_DATA); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ChartIndicatorDelete(id, 0, szCmd); IndicatorRelease(handle); EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { int i; if (handle == INVALID_HANDLE) { if ((handle = iCustom(NULL, PERIOD_CURRENT, "VWAP.EX5")) != INVALID_HANDLE) ChartIndicatorAdd(id, 0, handle); }else { i = CopyBuffer(handle, 0, 0, 1, Buff); Print(Buff[0]); } } //+------------------------------------------------------------------+
上述 EA 代码会读取 VWAP 的最后一个计算值,并将其显示在屏幕上。 如果指标不在图表上,则会加载并显示该指标。 如果我们从图表中删除 EA,VWAP 也将从屏幕上删除。 因此,EA 会始终拥有执行计算所需的东西。 我曾解释的结果如下所示:
有人可能认为这不太可行,因为显然我们没有对指标作任何修改。 但即遵照上述步骤,我们也能够实现与自定义指标相关的任何需求。 为了解释完整,我们来研究另一个例子。 我们来应用均线,并在智能交易系统里遵照与 VWAP 相同的方式使用,只是现在我们将会指定均线的参数。
第二种情况:使用移动平均值
如何计算移动平均值在此并不重要,因为我们的重点是关注如何将参数传递给自定义指标。 这是新的自定义指标:
#property copyright "Daniel Jose 16.05.2021" #property description "Basic Moving Averages (Optimizes Calculation)" #property indicator_chart_window //+------------------------------------------------------------------+ enum eTypeMedia { MME, //Exponential moving average MMA //Arithmetic moving average }; //+------------------------------------------------------------------+ #property indicator_buffers 1 #property indicator_plots 1 #property indicator_type1 DRAW_LINE #property indicator_width1 2 #property indicator_applied_price PRICE_CLOSE //+------------------------------------------------------------------+ input color user00 = clrRoyalBlue; //Cor input int user01 = 9; //Periods input eTypeMedia user02 = MME; //MA type input int user03 = 0; //Displacement //+------------------------------------------------------------------+ double Buff[], f_Expo; //+------------------------------------------------------------------+ int OnInit() { string sz0 = "MM" + (user02 == MME ? "E": (user02 == MMA ? "A" : "_")) + (string)user01; f_Expo = (double) (2.0 / (1.0 + user01)); ArrayInitialize(Buff, EMPTY_VALUE); SetIndexBuffer(0, Buff, INDICATOR_DATA); PlotIndexSetInteger(0, PLOT_LINE_COLOR, user00); PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, user01); PlotIndexSetInteger(0, PLOT_SHIFT, user03); IndicatorSetString(INDICATOR_SHORTNAME, sz0); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { double Value; int c0; switch (user02) { case MME: if (user01 < rates_total) { for (c0 = (prev_calculated > 0 ? prev_calculated - 1 : 0); c0 < rates_total - user03; c0++) Buff[c0] = (c0 > 0? ((price[c0] - Buff[c0 - 1]) * f_Expo) + Buff[c0 - 1] : price[c0] * f_Expo); for (; c0 < rates_total; c0++) Buff[c0] = EMPTY_VALUE; } break; case MMA: if (user01 < rates_total) { if (prev_calculated == 0) { Value = 0; for (int c1 = 0; c1 < user01; c1++) Value += price[user01 - c1]; Buff[user01] = Value / user01; } for (c0 = (prev_calculated > 0 ? prev_calculated - 1 : user01 + 1); c0 < rates_total - user03; c0++) Buff[c0] = ((Buff[c0 - 1] * user01) - price[c0 - user01] + price[c0]) / user01; for (; c0 < rates_total; c0++) Buff[c0] = EMPTY_VALUE; } break; } return rates_total; } //+------------------------------------------------------------------+
现在,指标名称将依赖若干个因素。 稍后,我们能够让 EA 检查,并根据每种情况进行调整。 例如,假设我们的 EA 用到两个移动均线,并将其显示在图表上。 请注意上述代码中高亮显示的部分 — 启用 EA,以及在这种情况下的 iCustom 函数,如此来更改和配置指标参数。 理解这一点很重要,从而在需要时能够实现它。 那么,其中一个平均值是周期为 17 的指数均线,另一个是周期为 52 的算术均线。 周期为 17 的均线呈绿色,周期为 52 的均线呈红色。 EA 会把指标视为以下形式的函数:
Average (Color, Period, Type, Shift) 故现在指标不是一个单独的文件,而是一个 EA 函数。 这在编程中很常见,因为我们采用相关参数调用一个程序来执行某个任务,并在最后我们会更容易地得到结果。 但问题是:我们如何让 EA 遵照与 VWAP 相同的方式创建和管理该场景?
为此,我们需要修改 EA 代码。 新 EA 的完整代码如下所示:
#property copyright "Daniel Jose" #property version "1.00" //+------------------------------------------------------------------+ long id; int handle1, handle2; double Buff1[], Buff2[]; string szCmd1, szCmd2; //+------------------------------------------------------------------+ int OnInit() { szCmd1 = "MME17"; szCmd2 = "MMA52"; id = ChartID(); handle1 = ChartIndicatorGet(id, 0, szCmd1); handle2 = ChartIndicatorGet(id, 0, szCmd2); SetIndexBuffer(0, Buff1, INDICATOR_DATA); SetIndexBuffer(0, Buff2, INDICATOR_DATA); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ChartIndicatorDelete(id, 0, szCmd1); ChartIndicatorDelete(id, 0, szCmd2); IndicatorRelease(handle1); IndicatorRelease(handle2); EventKillTimer(); } //+------------------------------------------------------------------+ void OnTick() { } //+------------------------------------------------------------------+ void OnTimer() { int i1, i2; if (handle1 == INVALID_HANDLE) { if ((handle1 = iCustom(NULL, PERIOD_CURRENT, "Media Movel.EX5", clrGreen, 17, 0)) != INVALID_HANDLE) ChartIndicatorAdd(id, 0, handle1); }; if (handle2 == INVALID_HANDLE) { if ((handle2 = iCustom(NULL, PERIOD_CURRENT, "Media Movel.EX5", clrRed, 52, 1)) != INVALID_HANDLE) ChartIndicatorAdd(id, 0, handle2); }; if ((handle1 != INVALID_HANDLE) && (handle2 != INVALID_HANDLE)) { i1 = CopyBuffer(handle1, 0, 0, 1, Buff1); i2 = CopyBuffer(handle2, 0, 0, 1, Buff2); Print(Buff1[0], "<< --- >>", Buff2[0]); } } //+------------------------------------------------------------------+
而此处为其结果:
注意 EA 代码中高亮显示的部分。 这正是我们需要的:我们遵照与 VWAP 相同的机制将参数传递给指标。 然而,在 VWAP 的情况下,没有必要传递任何参数,而移动平均值确实需要传递参数。 所有这些都提供了很大的自由度。
结束语
本文未包含通用代码。 无论如何,我们详细介绍了两个不同的智能交易系统,和两个不同的自定义指标,从而了解如何在更复杂和更周到的智能交易系统运用这种系统。 我相信,有了这些知识,我们就可以运用到自己的自定义指标。 甚至我们的 EA 也可以提供非常有趣的分析。 所有这些都证明了 MetaTrader 5 就是交易者梦寐以求的最通用平台。 如果还有人不理解这一点,那么他们就是没有把它钻研彻底。
运用本文中介绍的知识,可令 MetaTrader 5 为您提供远超其它平台迄今所能做的一切。
下一篇文章见。
参考链接
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10329

