一张图表上多个指标(第 04 部分):晋升为一款智能交易系统
在我之前的文章里,我已经解释了如何创建拥有多个子窗口的指标,在使用自定义指标时如此这般会变得很有趣。 这很容易做到。 但当我们尝试在智能交易系统中实现相同的功能时,事情会越加变得复杂,因为在自定义指标中我们没有可用的工具。 在这一点上,编程变得至关重要:能够编写正确的代码来创建子窗口至关重要。 尽管这项任务并非那么容易,但知道如何在 EA 中设置子窗口并不需要很多编码,只需通晓 MQL5 的工作原理。
计划
我们已经有了自定义指标,也就是说,我们的对象类已经功能齐备,而且由于这是一个对象类,我们可以轻松地将其转换到其它模型。 然而,在我们的 EA 中简单地声明并尝试使用这个类,并不能让事情如同我们在自定义指标中一样工作,原因是我们的 EA 中未提供子窗口功能。 但又冒出了这样一个想法:“如果我们用一个已经编译过,且可工作的自定义指标,然后用 iCustom 命令从 EA 调用它,会怎么样? 好吧,这也许是可行的,因为这样不需要子窗口,命令如下所示:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ input string user01 = ""; //Used indicators input string user02 = ""; //Assets to follow //+------------------------------------------------------------------+ int OnInit() { int m_handleSub; //... Expert Advisor code ... if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED; if (!ChartIndicatorAdd(ChartID(), 0, m_handleSub)) return INIT_FAILED; //... Expert Advisor code ... ChartRedraw(); return(INIT_SUCCEEDED); } //...The rest of the Expert Advisor code ...
这个简单的代码片段能够加载我们的自定义指标,然而它还不能正常工作,因为我们没有子窗口。 在这种情况下,当代码在 EA 里执行时,EA 将直接在主窗口中应用我们的指标,这意味着图表会被指标加载的模板掩盖,这绝对不是我们想要的。
因此,我们真正的主要问题是创建一个可用的子窗口,以便我们可以使用已经功能齐备的指标。 但为何我们要为随后启动的指标创建一个子窗口呢? 这没有意义,最好直接往 EA 里添加功能,从而克服可能出现的任何限制。
有基于此,我们需要执行若干个任务:
任务 | 目的 |
---|---|
1 => 创建一个通用指标。 | 它允许在不污染图表的情况下创建和使用 iCustom 命令。 |
2 => 在 EA 里以某种方式包含该指标。 | 这令您能够毫无问题地将其转换为具有完整功能的智能交易系统。 |
3 => 针对子窗口生成通用对象类 | 允许经由 EA 添加子窗口 |
4 => 获取已绑定到 window 类的 C_TemplateChart 类。 | 这允许我们管理子窗口的内容,而无需更改功能齐备的代码中的任何内容。 |
虽然这看起来很难,但困难很简单就解决了。 那好,我们来逐点处理。
实现:创建通用指标
这一部分可以通过创建一个完全干净、但功能齐全的自定义指标代码来解决。 本例中的代码如下所示:
#property copyright "Daniel Jose" #property version "1.00" #property description "This file only enables support of indicators in SubWin." #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ int OnInit() { return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+
只有这些,没别的。 我们将此文件另存为 SubSupport.mq5。 但其所在与其它指标不同 — 我们将其移到智能交易系统的 RESOURCE 目录。 因此,文件结构如下图所示:
这有一个很好的理由,但我们暂时把它搁置一旁。 现在我们进入下一个任务。
实现:在 EA 里包含通用指标
为此,我们需要在 EA 的顶部添加以下代码。
//+------------------------------------------------------------------+ #define def_Resource "Resources\\SubSupport.ex5" //+------------------------------------------------------------------+ #resource def_Resource //+------------------------------------------------------------------+
这将把通用指标的编译代码包含到我们的 EA 当中。 这步一旦完成,即可把通用指标的 .ex5 文件删除,因为不再需要它。 现在,您应该注意这样一个事实:如果在编译 EA 代码时未找到 SubSupport.ex5 文件,编译器将自动编译通用指标的代码 SubSupport. mq5,并将这个新编译的可执行文件添加到我们的智能交易系统之中。 那么,如果您曾经编辑过 SubSupport.mq5 文件,并且需要将更改添加到智能交易系统当中,您应该先删除已有的 SubSupport.ex5;否则,新的更改不会被添加。
这个细节很重要:有时候您真的需要知道如何将新实现的修改添加到资源当中。
好了,通用指标现在是智能交易系统的一部分了,如此我们进入下一个任务。
实现 : 创建一个子窗口对象类
这一部分也很简单。 在此,我们需要在编码之前定义一些要点,即:在这个类中,我们真正需要什么特性? 最初,我决定如下使用:
函数 | 说明 |
---|---|
Init | 允许经由 EA 添加子窗口 |
Close | 允许经由 EA 添加子窗口 |
这些函数不会被测试,所以我假设它们在 EA 的生存期内只会被调用一次。 但随着我们的 EA 增长,故考虑让它在未来更加实用是个好主意。 因此,我们来创建一个名为 C_Terminal 的新对象类 — 这个类将支持一些与图形终端相关的东西。 稍后我们将学习更多有关它的内容。 我们看看最后一项任务,因为部分解决方案无法实现。
实现: C_TemplateChart 类继承
当我决定运用 OOP(面向对象编程)创建一些新东西时(我这么做是因为我已经知道运用这种方式有很大的优势,包括安全性和继承性)。 还有多态性,我们将在稍后创建交叉订单系统时用到它。 在这种特殊情况下,我们将用到 OOP 的一项优势 — 继承。 C_TemplateChart 已经是一个功能齐全的类。 看到这一点,您就不想再为所有内容重新编程,或冒着风险往类中添加代码,因为这会阻碍该类在其它地方使用。 解决方案是运用继承,它允许添加新代码或函数,而不必改变原始代码。
运用继承有很多优势,包括以下几点:已经测试过的代码仍然保留测试过的状态;复杂度会随着代码量的增长而增加;只有新功能真正需要测试;只是继承不会改变原类,故可提供稳定性。 换言之,事情会以最小的代价得以改善,但却会带来最大的安全性。 为了理解这一点,我们来看下面的示意图。
祖父类是最基本类,在其中我们拥有的数据操作级别最低,但当父类从祖父类继承某些内容时,祖父类中声明为公开的所有内容都可以由父类查看和使用。 我们还可以向父类添加新的内容,这不会影响其继承和支持的内容。 如果父类已经完成并可工作,且我们希望在不更改类中任何内容的情况下对其进行扩展,那么我们应创建一个子类,它将拥有前类的所有功能。 我们还可以改变工作方式,这是关于继承的有趣之处,因为这些更改不会影响其它类。 然而,这与允许多重继承的 C++ 不同,此处有一个限制。 如果子类可以同时从父类和祖父类继承函数,那么在 MQL5 中这是不可能的。 但您仍然从遗产中受益。 多重继承的例子如下所示:
好的,但是在 MQL5 中如何实现呢? 如何声明继承,以便我们可以利用它的优势? 理解这一点最准确的方法是阅读面向对象编程(OOP)的内容,但在这里我们直奔主题。 继承将使用以下行完成:
#include "C_TemplateChart.mqh" //+------------------------------------------------------------------+ class C_SubWindow : public C_TemplateChart { // ... Class code };
可见 C_SubWindow 类公开继承自 C_TemplateChart 类,那么现在我们可以使用 C_SubWindow 类来访问 C_TemplateChart 类的功能。
在上面的代码片段中,我强调了一件事。 请注意,它通常在引号(")中,而非尖括号(< > )里。 那我为什么要这么做? 与 C++ 语言一样,MQL5 也有一些非常有趣的东西,但有些东西会令那些刚刚开始学习编程艺术的人感到困惑。 当我们将头文件放在尖括号(<>)之间时,我们指示的是一个绝对路径 — 在这种情况下,编译器将完全遵循我们指定的路径。 但是当我们使用引号时(就像我们这次所做的那样),编译器将采用相对路径,或者更清楚地说,它将首先从工作文件所在的当前目录开始。 这可能看起来很奇怪,但有时我们会用到相同名称但不同内容的文件,且它们位于不同的目录中,若我们仍然希望引用当前目录,那么我们就可以用引号来表示。
我们在前面计划使用的两个函数 INIT 和 CLOSE 如下所示:
//+------------------------------------------------------------------+ bool Init(void) { if (m_handleSub != INVALID_HANDLE) return true; if ((m_handleSub = iCustom(NULL, 0, "::" + def_Resource)) == INVALID_HANDLE) return false; m_IdSub = (int) ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL); if (!ChartIndicatorAdd(Terminal.Get_ID(), m_IdSub, m_handleSub)) return false; return true; } //+------------------------------------------------------------------+ void Close(void) { ClearTemplateChart(); if (m_handleSub == INVALID_HANDLE) return; IndicatorRelease(m_IdSub); ChartIndicatorDelete(Terminal.Get_ID(), m_IdSub, ChartIndicatorName(Terminal.Get_ID(), m_IdSub, 0)); ChartRedraw(); m_handleSub = INVALID_HANDLE; } //+------------------------------------------------------------------+
看,代码非常简短。 但有些事情我们必须小心,注意高亮显示的部分。 在添加此部件时,您必须小心不要出错,因为如果不保持原样,则我们将要添加到 EA 中的 SubSupport.ex5 可执行文件在 EA 内部不可见 — 而在 EA 外部则可见。 相关的更多详情,请阅读参考资料。 但基本上,如果您使用 ( :: ),这表明 EA 应该使用其内部提供的内部资源。 但如果我们只指示资源的名称,EA 将在 MQL5 目录中搜索它,如果指定位置不存在该文件,即使该文件被添加为 EA 资源,该函数也将失败。
然后,一旦加载资源后,我们检查存在的子窗口的数量,并向该子窗口添加一个指标。
代码的实际操作,如下所见:
input string user01 = ""; //Used indicators input string user02 = ""; //Assets to follows //+------------------------------------------------------------------+ int OnInit() { int m_handleSub; //... if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED; if (!ChartIndicatorAdd(ChartID(), (int) ChartGetInteger(ChartID(), CHART_WINDOWS_TOTAL), m_handleSub)) return INIT_FAILED; //... ChartRedraw(); return(INIT_SUCCEEDED); } //...The rest of the Expert Advisor code ...