English Русский Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
preview
一张图表上的多个指标(第 06 部分):将 MetaTrader 5 转变为 RAD 系统(II)

一张图表上的多个指标(第 06 部分):将 MetaTrader 5 转变为 RAD 系统(II)

MetaTrader 5交易系统 | 30 五月 2022, 09:42
2 450 0
Daniel Jose
Daniel Jose

概述

在我的上一篇文章中,我向您展示了如何利用 MetaTrader 5 对象创建图表交易,从而将平台转变为 RAD 系统。 该系统运行良好,可以肯定的是,许多读者也许已经考虑过创建一个函数库,令其能够在拟议的系统中扩展功能。 有基于此,就有可能开发一款更直观的智能交易系统,其界面更友好、更易于使用。

这个创意非常好,它促使我逐步向您展示如何开始添加功能。 在此,我将实现两个新的主要功能(我们在按照需要和想要的方式实现其它功能时,应把这些信息作为基础)。 唯一的限制是我们的创造力,因为元素本身能以广泛的方式运用。


计划

我们的 IDE 变更如下图所示:

         

正如您所看到的,设计本身有一些小的变化。 添加了两个新区域:一个将接收资产名称;另一个将接收当天的累计值。 好吧,这些东西对于我们来讲都是非刚需,它们不会影响我们的决定。 但无论如何,它们都很有趣。 我将展示往 IDE 中添加功能的最简单和正确的方法。 如此,在新界面中打开对象列表。 它如下显现:


这两个被圈起来的对象没有与之关联的事件,这意味着它们在 IDE 中不起作用。 所有其它对象均已某些事件正确关联,当这些事件在 EA 中发生时,MetaTrader 5 可以强制它们正确执行。 这就是说,我们可以根据自己的意愿修改 IDE 界面,但如果功能尚未实现,MetaTrader 5 则只在图表上显示对象。 我们需要那个 “EDIT 00” 对象来接收我们正在交易的资产名称,该名称应会显示在对象的中心。 “EDIT 01” 对象会接收某个时段内的累计值。 我们将采用日线时段来了解我们在日线中是盈利还是亏损。 如果该值为负值,则会以一种颜色显示;如果为正值,将采用另一种颜色。

这两个值显然不能由用户更改,因此您可以将其属性保留为只读,如下图所示。


但是,请记住,无法指定信息的显示方式,也就是说,我们无法对齐文本,使其显示在对象的中心。 如若需要,可以用代码完成此操作,因为有一个属性可以将文本设置为居中对齐。 参见 "对象属性" 了解更多详情。 请注意 表格里的 ENUM_ALIGN_MODE — 在它包含的对象上您可以使用对齐文本

因此,无论我们要实现什么样的修改,我们需要做的第一件事就是准备一个计划:定义新特性、它们的表示形式、以及用户与它们交互的方式。 这允许 MetaTrader 5 通过自己的界面尽可能多地选择正确的对象,并配置它。 结果就是,我们将有一个现成的 IDE,我们只需要通过 MQL5 代码对其进行调整,这样 IDE 最终将 100% 正常工作。 如此,我们来继续修改。


修改

为了防止代码成为真正的弗兰肯斯坦式悲剧(Frankenstein,科学怪人),我们必须尽可能地自我组织,检查哪些功能已经存在,哪些实际需求尚将实现。 在许多情况下,只需对现有代码进行略微修改,得到新代码,并对其进行测试就足够了。 这段新代码会在我们即将实现的代码中重用,唯一要测试的就是为了创建全新功能,由我们后来添加的小控制函数。 优秀的程序员总是这样做:他们尝试通过向现有代码添加控制点,以某种方式重用现有代码。


修改 1. 添加资产名称

为了实现这一部分,我们不需要进行实质性的更改,但这些更改应该在正确的地方实现。 首先,我们为枚举添加一个新值,源代码如下所示:

enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};


以下是新代码;高亮显示的部分是已添加的部分。 请注意,我不会将新值添加到枚举的开头或结尾。 这样做是为了避免与其它已经存在,并正在运行的代码部分发生冲突。

enum eObjectsIDE {eRESULT, eLABEL_SYMBOL, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};


如果您在枚举的开头或结尾添加新值,则必须查找并更改所有引用这些极值的位置。 在许多情况下,可能由于健忘而导致遗漏,这将引发难以预料的错误。 您可能会认为这些错误看起来是由于新添加的内容引起的,而实际上是由于遗忘。 因此,我们应在极值之间添加修改。

之后,我们需要立即向系统添加一条消息,否则会出现运行时错误。 因此,在源代码中添加以下行。

static const string C_Chart_IDE::szMsgIDE[] = {
                                                "MSG_RESULT",
                                                "MSG_NAME_SYMBOL",
                                                "MSG_BUY_MARKET",
                                                "MSG_SELL_MARKET",
                                                "MSG_DAY_TRADE",
                                                "MSG_CLOSE_POSITION",
                                                "MSG_LEVERAGE_VALUE",
                                                "MSG_TAKE_VALUE",
                                                "MSG_STOP_VALUE"
                                              };


正如您所看到的,我们把它加在同一个地方,从而保持了顺序。 但目前常量可加在任何点位,这不会有任何区别,因为它只用于检查哪个对象会接收消息。 出于结构化目的,我们在加入时把它作为第二条消息。

现在,我们回到 MetaTrader 5,并进行如下更改:

         

现在,MetaTrader 5 已经将 IDE 中的对象识别为接收消息的对象,剩下的只是创建发送消息的过程。 消息中的文本只应添加一次,只要 MetaTrader 5 把 IDE 放在图表上,它就可以被发送。 这可由简单地将所需代码添加到对象类的 Create 函数末尾来实现。 但为了不让代码变成满面疤痕的弗兰肯斯坦,我们将在 DispatchMessage 函数内添加新代码。 原始函数如下所示:

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if (szArg == szMsgIDE[eRESULT])
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:

// ... The rest of the code...

        }
}


以下是完成相关修改后的代码:

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if (szArg == szMsgIDE[eRESULT])
                        {
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                        }else if (szArg == szMsgIDE[eLABEL_SYMBOL])
                        {
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_TEXT, Terminal.GetSymbol());
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_ALIGN, ALIGN_CENTER);
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:

// ... The rest of the code

        }
}


创建 send 函数后,我们可以选择发送此消息的点。 最好的位置实际上是在对象类的 Create 函数的末尾,因此最终的代码如下所示:

bool Create(int nSub)
{
        m_CountObject = 0;
        if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
        FileReadInteger(m_fp, SHORT_VALUE);
                                
        for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
        m_SubWindow = nSub;
        m_szLine = "";
        while (m_szLine != "</chart>")
        {
                if (!FileReadLine()) return false;
                if (m_szLine == "<object>")
                {
                        if (!FileReadLine()) return false;
                        if (m_szLine == "type")
                        {
                                if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false;
                                if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false;
                                if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false;
                                if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false;
                                if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false;
                        }
                }
        }
        FileClose(m_fp);
        DispatchMessage(CHARTEVENT_CHART_CHANGE, szMsgIDE[eLABEL_SYMBOL]);
        return true;
}

添加的实际内容以绿色高亮显示。 请注意,在几乎不做任何更改的情况下,我们就已经有了一个 100% 实现的消息流,我们可以移步到需要实现的下一条消息。


修改2. 添加当天的累计值(覆盖点)

同样,我们遵循与添加资产名称时相同的逻辑,因此新代码如下所示:

enum eObjectsIDE {eRESULT, eLABEL_SYMBOL, eROOF_DIARY, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP};

// ... Rest of the code

static const string C_Chart_IDE::szMsgIDE[] = {
                                                "MSG_RESULT",
                                                "MSG_NAME_SYMBOL",
                                                "MSG_ROOF_DIARY",
                                                "MSG_BUY_MARKET",
                                                "MSG_SELL_MARKET",
                                                "MSG_DAY_TRADE",
                                                "MSG_CLOSE_POSITION",
                                                "MSG_LEVERAGE_VALUE",
                                                "MSG_TAKE_VALUE",
                                                "MSG_STOP_VALUE"
                                              };


之后,我们用一条新消息更改 IDE:

         

我们的新 IDE 已经准备就绪。 现在,我们将实现代码,来创建包含当天累计值的消息。 我们应该首先决定在哪个类中实现这个功能。 许多人可能会在 C_Chart_IDE 类中创建此函数,但出于组织原因,最好将其与处理订单的函数放在一起。 因此,代码是在 C_OrderView 类中实现的。 其代码示意如下:

double UpdateRoof(void)
{
        ulong   ticket;
        int     max;
        string  szSymbol = Terminal.GetSymbol();
        double  Accumulated = 0;
                                
        HistorySelect(macroGetDate(TimeLocal()), TimeLocal());
        max = HistoryDealsTotal();
        for (int c0 = 0; c0 < max; c0++) if ((ticket = HistoryDealGetTicket(c0)) > 0)
                if (HistoryDealGetString(ticket, DEAL_SYMBOL) == szSymbol)
                        Accumulated += HistoryDealGetDouble(ticket, DEAL_PROFIT);
                                                
        return Accumulated;
}


现在代码已经实现,我们需要将消息添加到系统中。 为了让操盘手的生活更轻松,我已经添加了代码来报告已完成的结果。 此为其代码:

void DispatchMessage(int iMsg, string szArg, double dValue = 0.0)
{
        static double AccumulatedRoof = 0.0;
        bool    b0;
        double  d0;

        if (m_CountObject < eEDIT_STOP) return;
        switch (iMsg)
        {
                case CHARTEVENT_CHART_CHANGE:
                        if ((b0 = (szArg == szMsgIDE[eRESULT])) || (szArg == szMsgIDE[eROOF_DIARY]))
                        {
                                if (b0)
                                {
                                        ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_BGCOLOR, (dValue < 0 ? clrLightCoral : clrLightGreen));
                                        ObjectSetString(Terminal.Get_ID(), m_ArrObject[eRESULT].szName, OBJPROP_TEXT, DoubleToString(dValue, 2));
                                }else
                                {
                                        AccumulatedRoof = dValue;
                                        dValue = 0;
                                }
                                d0 = AccumulatedRoof + dValue;
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eROOF_DIARY].szName, OBJPROP_TEXT, DoubleToString(MathAbs(d0), 2));
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eROOF_DIARY].szName, OBJPROP_BGCOLOR, (d0 >= 0 ? clrForestGreen : clrFireBrick));
                        }else   if (szArg == szMsgIDE[eLABEL_SYMBOL])
                        {
                                ObjectSetString(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_TEXT, Terminal.GetSymbol());
                                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eLABEL_SYMBOL].szName, OBJPROP_ALIGN, ALIGN_CENTER);
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:

// .... The rest of the code....

        }
}


高亮显示的部件支持如上所述的系统。 如果不按照这种方式来实现,我们必须向系统发送两条消息,才能正确更新信息。 但若按代码所用的实现方式,我们用一条消息就可以跟踪持仓结果和当天的结果。

EA 中的另一个修改涉及 OnTrade 函数。 看起来像这样:

void OnTrade()
{
        SubWin.DispatchMessage(CHARTEVENT_CHART_CHANGE, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY], NanoEA.UpdateRoof());
        NanoEA.UpdatePosition();
}


虽然此系统可以工作,但您需要小心 OnTrade 函数的执行时间,它与 OnTick 一起工作时可能会降低 EA 的性能。 若 OnTick 中包含代码的情况,这样做不是很好,所以优化至关重要。 其实用 OnTrade 更容易,而当持仓发生变化时,实际上会调用该函数。 知道了这一点,我们就有两种选择。 第一个是修改 UpdateRoof,从而限制其执行时间。 另一种选择是修改 OnTrade 函数本身。 出于实际原因,我们将修改 Update Roof 函数,因此,当我们有一笔持仓时,至少能稍微改善一点执行时间。 新函数如下:

double UpdateRoof(void)
{
        ulong           ticket;
        string  szSymbol = Terminal.GetSymbol();
        int             max;
        static int      memMax = 0;
        static double   Accumulated = 0;
                
        HistorySelect(macroGetDate(TimeLocal()), TimeLocal());
        max = HistoryDealsTotal();
        if (memMax == max) return Accumulated; else memMax = max;
        for (int c0 = 0; c0 < max; c0++) if ((ticket = HistoryDealGetTicket(c0)) > 0)
                if (HistoryDealGetString(ticket, DEAL_SYMBOL) == szSymbol)
                        Accumulated += HistoryDealGetDouble(ticket, DEAL_PROFIT);
                                                
        return Accumulated;
}

高亮显示的行示意在原始函数里添加的代码。 尽管看起来它们没有多大区别,但它们确实有很大差异。 我们看看为什么。 第一次引用代码时,如果在指定时段的历史订单记录中没有数值,则 memMax 静态变量和 Accumulated 值都将清零。 测试将反映这一点,例程将返回,但如果有任何数据,将会对其进行测试,并且 memMaxAccumulated 都将反映新的条件。 事实上,这些变量都是静态的,这意味着它们的值在调用之间均会保持。 因此,当持仓价值因资产的自然移动而改变时,MetaTrader 5 生成一个事件,并触发调用 OnTrade 函数。 此刻,我们有一个新的 UpdateRoof 函数调用,如果持仓尚未平仓,函数立即返回到控制点,这将加快返回过程。


结束语

在本文中,我们看到了如何向 RAD 系统添加新功能,逐步创建一个函数库,令系统非常适合创建 IDE 界面,在构建交互和控制界面时更简单,错误更少。 从现在起,唯一的真正限制就是您的创造力,因为此处我们只研究如何利用 MQL5,但您可以将相同的思路集成到外部函数库当中,从而极大扩展了创建 IDE 的可能性。


本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10301

附加的文件 |
EA_1.05.zip (3274.9 KB)
DoEasy 函数库中的图形(第一百部分):改进扩展标准图形对象的处理 DoEasy 函数库中的图形(第一百部分):改进扩展标准图形对象的处理
在本文中,我将剔除在画布上同时处理扩展(和标准)图形对象和窗体对象方面的明显缺陷,并修复在前一篇文章中执行测试期间检测到的错误。 本文总结了函数库说明的这一部分。
数据科学与机器学习(第 01 部分):线性回归 数据科学与机器学习(第 01 部分):线性回归
我们作为交易员,现在是时候基于数字所言来培训我们的系统,并自行制定决策了。 尽管我们的眼睛看不到,但我们的勇气让我们相信,这是世界前进的方向,所以,让我们顶着波浪的方向移动。
从头开始开发智能交易系统(第 7 部分):添加价格成交量(Volume)指标(I) 从头开始开发智能交易系统(第 7 部分):添加价格成交量(Volume)指标(I)
这是目前最强力的指标之一。 任何满怀信心尝试交易的人都必须在他们的图表上拥有这个指标。 最常用的指标都是那些喜欢在交易时“读磁带”的人所采用。 此外,而该指标则是那些交易时仅依据价格动作的人会采用。
学习如何设计基于 RSI 的交易系统 学习如何设计基于 RSI 的交易系统
在本文中,我将与您分享一个在交易领域最流行和最常用的指标,即 RSI。 您将学到如何基于该指标设计交易系统。