
一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I)
概述
有很多人不知道如何编程,但他们很有创造力,亦有杰出的想法。 然而,由于缺乏编程知识,他们无法实现这些想法。 今天,我们将创建自己的图表交易界面,来发送市价订单、或设置挂单参数。 我们不需要编程就可以做到这一点,只需用到智能交易系统中的函数。 我们很好奇,所以我们看看它在我们的显示器上会是什么样子:
您可能会想,“您是怎么做到的? 我对编程一无所知,或者我所知道的相对来说还不够。“....上图中的图表交易是在 MetaTrader 5 平台中创建的,其设计如下图所示:
现在我们知道了这篇文章的内容,我们应该对自行创建图表充满激情和创意。 但我们需要完成几个步骤才能让一切顺利。 一旦辅助代码设定完毕,我们的创造力将成为自行设计图表交易 IDE 的唯一限制。 本文是前几篇文章的延续,因此为了全面了解,我建议阅读本系列的前几篇文章。
那么,我们开始工作吧。
计划
起步阶段,您应该编辑图表属性,为将来所用的 IDE 做准备。 这样做是为了减少潜在的副作用。 关键是,通过保持图表干净,可以更容易地构建和设计图表交易界面。 因此,打开图表属性并设置属性,如下图所示。
因此,屏幕应该绝对干净,没有任何能够干扰 IDE 开发的东西。 现在,请注意下面的解释。 我们的 IDE 将保存为设置文件,即作为一个模板;如此我们可以使用 MetaTrader 5 提供的任何对象,但出于实际原因,我们只会用到其中的一些对象。 有关所有可用对象,请参见 MetaTrader 5 中的对象类型。
对象 | 用于定位的坐标类型 | 对于 IDE 来说很有趣 |
---|---|---|
文本 | 日期和价格 | NO |
标签 | X 和 Y 位置 | YES |
按钮 | X 和 Y 位置 | YES |
图形 | X 和 Y 位置 | YES |
位图 | 日期和价格 | NO |
位图标签 | X 和 Y 位置 | YES |
编辑 | X 和 Y 位置 | YES |
事件 | 只用到日期 | NO |
矩形标签 | X 和 Y 位置 | YES |
我们将要使用的系统,可位于屏幕任何区域;这就是为什么不用 X 和 Y 坐标系进行对象定位是不切实际的,因为这样的对象会令 IDE 看起来完全不同。 因此,我们的系统限制为六个对象,这些对象足以创建一个界面。
该思路是按照逻辑顺序排列对象,就像您在屏幕上画东西一样。 我们首先从创建背景开始,然后将对象放在彼此的顶部,如同我们在开发界面时放置和调整对象一样。 此处是它如何做到的:
所有这一切都很简单,只需稍加练习就可以掌握这种自行设计和创建 IDE 的方法。 此处的思路与 RAD 程序中所用的创建可编程界面的思路非常相似,依据代码开发的用户界面可能非常复杂。 并不是说我们不能直接通过代码创建界面。 但运用这种方法使得进一步的修改更快捷、更容易,这对于那些想拥有自己独特风格界面的人来说非常理想。
一旦我们完成,我们就可能会得到一个像下面这样的界面,甚至更炫酷。 但在此处,我尝试使用尽可能多的对象,您也可以尝试它们。 您也可以创建自己偏好的界面。
这是创建 IDE 的第一步。 现在我们需要创建代码,真正支持该界面,并令其功能正常化。 虽然自行创建用户界面这一简单事实也应是动机的来源,而这种动机也将体现在代码之中。
下一步是将此界面保存为设置文件。 现在我们可以保存它,并用以前版本的代码将其显示为指针。 这意味着我们不需要对源代码进行重大更改。 然而,如果我们想测试接收事件、或向 IDE 发送事件的可能性,我们就会发现这是不可能的。 但如果界面是用 MetaTrader 5 中的对象创建的,为什么不能从这些对象发送和接收事件呢? 这个问题的答案听解释不如直观展示。 我们可以通过在 EA 的原始版本中添加以下代码来检验它。
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { switch (id) { case CHARTEVENT_OBJECT_CLICK: Print(sparam); break; // .... The rest of the code... } }
此代码报告收到单击事件,并生成事件对象的名称。 在这种情况下,该事件是 CHARTEVENT_OBJECT_CLICK。 然而,打印的消息将是 EA 所创建对象的名称,而非 IDE 中的对象名称。 这似乎是一个阻碍我们使用 IDE 的大问题,但有一个非常简单的解决方案:读取设置文件,然后创建该文件中指定的对象。 这将在图表上创建我们的 IDE。 因此,通过分析设置文件(TPL),我们可以找到我们所需用到的数据。
关键字 | 说明 |
---|---|
<chart> | 设置文件开始 |
</chart> | 设置文件结束 |
<window> | 图表上元素结构的开始。 |
</window> | 图表上元素结构的结束。 |
<indicator> | 提供与某个指标相关数据的结构开始 |
</indicator> | 提供与某个指标相关数据的结构结束 |
<object> | 提供有关某个对象数据的结构开始。 |
</object> | 提供有关某个对象数据的结构结束。 |
在 TPL 文件中的该结构如下所示。
<chart> .... DATA <window> ... DATA <indicator> ... DATA </indicator> <object> ... DATA </object> </window> </chart>
我们感兴趣的部分介于 <object> 和 </object> 之间。 可以有若干个这样的结构,每个都指代一个独有对象。 故此,首先我们需要更改文件的位置 — 我们应该将其添加到可以读取文件的位置。 这是 FILES 目录。 您可以更改位置,但在任何情况下,文件都必须位于 FILE 树中。
一个重要细节:虽然系统已得到修改,允许依据 IDE 配置文件清除图表,但理想情况下,在 Profiles\Templates 目录中还应该有一个同名的干净文件。 正如我们在前面的文章中看到的那样,这可以最大限度地减少默认模板中可能存在的任何遗留问题。 主要变化如下高亮:
#include <Auxiliar\Chart IDE\C_Chart_IDE.mqh> //+------------------------------------------------------------------+ class C_TemplateChart : public C_Chart_IDE { .... Other parts from code .... //+------------------------------------------------------------------+ void AddTemplate(const eTypeChart type, const string szTemplate, int scale, int iSize) { if (m_Counter >= def_MaxTemplates) return; if (type == SYMBOL) SymbolSelect(szTemplate, true); SetBase(szTemplate, (type == INDICATOR ? _Symbol : szTemplate), scale, iSize); if (!ChartApplyTemplate(m_handle, szTemplate + ".tpl")) if (type == SYMBOL) ChartApplyTemplate(m_handle, "Default.tpl"); if (szTemplate == "IDE") C_Chart_IDE::Create(m_IdSubWin); ChartRedraw(m_handle); } //+------------------------------------------------------------------+ void Resize(void) { #define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, A, B) int x0 = 0, x1, y = (int)(ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, m_IdSubWin)); x1 = (int)((ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS, m_IdSubWin) - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1)); for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++) { macro_SetInteger(OBJPROP_XDISTANCE, x0); macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1)); macro_SetInteger(OBJPROP_YSIZE, y); if (m_Info[c0].szTemplate == "IDE") C_Chart_IDE::Resize(x0); } ChartRedraw(); #undef macro_SetInteger } //+------------------------------------------------------------------+ ... The rest of the code }
请注意,我们将 IDE 界面作为一个新类添加,它继承自我们的原始类。 这意味着原始类的功能将会被扩展,且不会在原始代码中产生任何副作用。
到目前为止,这是最简单的部分。 现在我们需要做一些更复杂的事情来支持我们的 IDE。 首先,我们创建一个消息协议,以便系统将来使用。 该协议将允许系统如下所示工作:
请注意,我们可以更改系统数据,这在目前是不可能的;但通过添加消息协议,就可以令 IDE 正常工作。 那么,我们来定义几件事:
消息 | 目的 |
---|---|
MSG_BUY_MARKET | 发送市价买入订单 |
MSG_SELL_MARKET | 发送市价卖出订单 |
MSG_LEVERAGE_VALUE | 杠杆数据 |
MSG_TAKE_VALUE | 交易止盈数据 |
MSG_STOP_VALUE | 交易止损数据 |
MSG_RESULT | 当前持仓结果数据 |
MSG_DAY_TRADE | 通知交易是否在一天结束时平仓 |
这个协议是非常重要的一步。 定义之后,针对设置文件进行更改。 当您需要打开以下对象列表时:
我显示的界面将有一个对象列表,如图中所示。 请注意以下事实。 对象的 NAME 属性对应于我们将要用到的每个消息。 其它对象的名称无关紧要,因为它们只是帮助 IDE 建模,但拥有消息名称的对象将接收和发送消息。 如果您想使用更多消息、或不同类型的消息,只需对类代码进行必要的更改,MetaTrader 5 本身将提供在 IDE 和 EA 代码之间交换消息的方法。
但我们仍然需要研究 TPL 文件来学习如何创建我们的对象类。 现在,我们来了解如何在 TPL 文件中声明对象。 的确,与通过编程相比,我们对 TPL 文件中对象属性的访问更少,因为终端界面本身访问对象属性更少。 但即使如此,我们拥有的访问权限也足以令 IDE 正常工作。
因此,在 TPL 文件内有我们需要的结构:从 <object> 到 </object>。 基于结构内部的数据,似乎不清楚如何找出它的对象类型。 但如果仔细观察,可以发现对象类型是由 type 变量决定的。 它针对每个对象采用不同的值。 下表显示了我们打算用到的对象:
TYPE 变量相应的值 | 引用的对象 |
---|---|
102 | OBJ_LABEL |
103 | OBJ_BUTTON |
106 | OBJ_BITMAP_LABEL |
107 | OBJ_EDIT |
110 | OBJ_RECTANGLE_LABEL |
我们的类已经初步成形了。 以下是第一个函数代码:
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); return true; }
请注意,第一件要做的事就是在读取模式下以二进制文件的形式打开文件。 这样做是为了不错过任何东西。 使用 HEXA 编辑器时,TPL 文件如下所示。 请注意,它从一个非常有趣的值开始。
听起来很困惑? 事实并非如此。 文件采用 UTF-16 编码。 我们知道数据是按行组织的,所以我们创建一个函数来一次性读取整行数据。 为此目的,让我们编写以下代码:
bool FileReadLine(void) { int utf_16 = 0; bool b0 = false; m_szLine = m_szValue = ""; for (int c0 = 0; c0 < 500; c0++) { utf_16 = FileReadInteger(m_fp, SHORT_VALUE); if (utf_16 == 0x000D) { FileReadInteger(m_fp, SHORT_VALUE); return true; } else if (utf_16 == 0x003D) b0 = true; else if (b0) m_szValue = StringFormat("%s%c", m_szValue, (char)utf_16); else m_szLine = StringFormat("%s%c", m_szLine, (char)utf_16); if (FileIsEnding(m_fp)) break; } return (utf_16 == 0x003E); }
读取会尽可能的高效,所以当我们遇到等号(=)时,我们在读取过程中就已经将其分离了,以免稍后再次重做。 循环则限制字符串最多 500 个字符,但该值可在必要时任意更改。 发现每个新字符串后,函数将返回并提供字符串的内容,以便我们可以继续进行相应的分析。
我们需要某些变量来支持消息协议。 代码如下所示:
class C_Chart_IDE { protected: enum eObjectsIDE {eRESULT, eBTN_BUY, eBTN_SELL, eCHECK_DAYTRADE, eBTN_CANCEL, eEDIT_LEVERAGE, eEDIT_TAKE, eEDIT_STOP}; //+------------------------------------------------------------------+ #define def_HeaderMSG "IDE_" #define def_MaxObject eEDIT_STOP + 32 //+------------------------------------------------------------------+ private : int m_fp, m_SubWindow, m_CountObject; string m_szLine, m_szValue; bool m_IsDayTrade; struct st0 { string szName; int iPosX; }m_ArrObject[def_MaxObject]; // ... The rest of the class code....
def_MaxObject 定义指示我们可以保留的最大对象数量。 这个数字是基于消息的数量,再加上我们需用到的额外对象的数量而获得的。 在我们的例子中,最多有 40 个对象,但如有必要,可以对其进行更改。 前 8 个对象将用于在 IDE 和 MetaTrader 5 之间传递消息。 这些消息的别名可以在 eObjectsIDE 枚举中见到。 若您想要扩展系统、或将其用于其它用途时,记住这一点很重要。
这只是支持系统的第一部分。 还有一点需要注意:处理消息系统的常数。 事实上,对于那些曾用过 C/C++ 编程的人来说,MQL5 处理常量的方式可能有点混乱。 在 C/C++ 中,常量是在变量声明本身中声明的。 在 MQL5 中,它的创建方式会令代码稍微复杂一些。 然而,您能够忍受这一点,因为常数很少用到。 下面以粗体显示的,就是您应如何做到这一点。
public : static const string szMsgIDE[]; // ... The rest of the class code.... }; //+------------------------------------------------------------------+ static const string C_Chart_IDE::szMsgIDE[] = { "MSG_RESULT", "MSG_BUY_MARKET", "MSG_SELL_MARKET", "MSG_DAY_TRADE", "MSG_CLOSE_POSITION", "MSG_LEVERAGE_VALUE", "MSG_TAKE_VALUE", "MSG_STOP_VALUE" }; //+------------------------------------------------------------------+
所定义常量与界面中对象名称中使用的值完全相同。 系统设计时不区分大小写。 如果您愿意,您可以改变这种行为,但我不建议您这样做。
完成所有这些步骤之后,是时候进入下一步了。 那么,我们先回到 TPL 文件。 查看下面的文件片段:
在定义了所要用到的对象类型之后,我们有一系列数据来指示对象的属性,例如名称、位置、颜色、字体、等等。 这些属性应该传递给内部对象。 鉴于这是一件重复乏味的步骤,我们可以为其创建一个通用函数。 具体如下:
bool LoopCreating(ENUM_OBJECT type) { #define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B) #define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B) int c0; bool b0; string sz0 = m_szValue; while (m_szLine != "</object>") if (!FileReadLine()) return false; else { if (m_szLine == "name") { b0 = false; StringToUpper(m_szValue); for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++); c0 = (b0 ? c0 : m_CountObject); m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue); ObjectDelete(Terminal.Get_ID(), m_ArrObject[c0].szName); ObjectCreate(Terminal.Get_ID(), m_ArrObject[c0].szName, type, m_SubWindow, 0, 0); } if (m_szLine == "pos_x" ) m_ArrObject[c0].iPosX = (int) StringToInteger(m_szValue); if (m_szLine == "pos_y" ) macro_SetInteger(OBJPROP_YDISTANCE , StringToInteger(m_szValue)); if (m_szLine == "size_x" ) macro_SetInteger(OBJPROP_XSIZE , StringToInteger(m_szValue)); if (m_szLine == "size_y" ) macro_SetInteger(OBJPROP_YSIZE , StringToInteger(m_szValue)); if (m_szLine == "offset_x" ) macro_SetInteger(OBJPROP_XOFFSET , StringToInteger(m_szValue)); if (m_szLine == "offset_y" ) macro_SetInteger(OBJPROP_YOFFSET , StringToInteger(m_szValue)); if (m_szLine == "bgcolor" ) macro_SetInteger(OBJPROP_BGCOLOR , StringToInteger(m_szValue)); if (m_szLine == "color" ) macro_SetInteger(OBJPROP_COLOR , StringToInteger(m_szValue)); if (m_szLine == "bmpfile_on" ) ObjectSetString(Terminal.Get_ID() , m_ArrObject[c0].szName, OBJPROP_BMPFILE, 0, m_szValue); if (m_szLine == "bmpfile_off" ) ObjectSetString(Terminal.Get_ID() , m_ArrObject[c0].szName, OBJPROP_BMPFILE, 1, m_szValue); if (m_szLine == "fontsz" ) macro_SetInteger(OBJPROP_FONTSIZE , StringToInteger(m_szValue)); if (m_szLine == "fontnm" ) macro_SetString(OBJPROP_FONT , m_szValue); if (m_szLine == "descr" ) macro_SetString(OBJPROP_TEXT , m_szValue); if (m_szLine == "readonly" ) macro_SetInteger(OBJPROP_READONLY , StringToInteger(m_szValue) == 1); if (m_szLine == "state" ) macro_SetInteger(OBJPROP_STATE , StringToInteger(m_szValue) == 1); if (m_szLine == "border_type" ) macro_SetInteger(OBJPROP_BORDER_TYPE , StringToInteger(m_szValue)); } m_CountObject += (b0 ? 0 : (m_CountObject < def_MaxObject ? 1 : 0)); return true; #undef macro_SetString #undef macro_SetInteger }
每个对象都会得到一个名称,并将存储在相应的位置,但高亮显示的行示意出不同的内容。 当我们创建 IDE 时,它必须从图表的左上角开始,但这个 X 位置不一定是子窗口的左上角。 该位置必须对应于 IDE 所绑定 OBJ_CHART 对象的左上角。 加载 IDE 模板时会指示该对象,因此它可以位于子窗口中的任何位置。 如果其值不正确,IDE 则不会出现在正确的位置。 因此,保存 X 值,稍后用它在正确的位置显示对象。 函数正确渲染的 IDE 如下所示。
对象中所用的基础信息均已定义,但如果需要添加任何其它信息,只需将其添加到命令集合当中,并用相应的值更改属性即可。
void Resize(int x) { for (int c0 = 0; c0 < m_CountObject; c0++) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX); };
在研究消息是如何进行处理之前,我们分析另外两个同样重要的函数。 系统在初始化期间可以从 EA 接收参数值。 这些值必须正确表示和调整,以便在使用图表交易时,可以直接在其中配置订单,以便发送市价订单或挂单,而无需调用 EA。 这两个函数如下所示:
void UpdateInfos(bool bSwap = false) { int nContract, FinanceTake, FinanceStop; nContract = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT)); FinanceTake = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT)); FinanceStop = (int) StringToInteger(ObjectGetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT)); m_IsDayTrade = (bSwap ? (m_IsDayTrade ? false : true) : m_IsDayTrade); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade); NanoEA.Initilize(nContract, FinanceTake, FinanceStop, clrNONE, clrNONE, clrNONE, m_IsDayTrade); } //+------------------------------------------------------------------+ void InitilizeChartTrade(int nContracts, int FinanceTake, int FinanceStop, color cp, color ct, color cs, bool b1) { NanoEA.Initilize(nContracts, FinanceTake, FinanceStop, cp, ct, cs, b1); if (m_CountObject < eEDIT_STOP) return; ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_LEVERAGE].szName, OBJPROP_TEXT, IntegerToString(nContracts)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_TAKE].szName, OBJPROP_TEXT, IntegerToString(FinanceTake)); ObjectSetString(Terminal.Get_ID(), m_ArrObject[eEDIT_STOP].szName, OBJPROP_TEXT, IntegerToString(FinanceStop)); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eCHECK_DAYTRADE].szName, OBJPROP_STATE, m_IsDayTrade = b1); }
请注意,IDE 会与订单系统链接,因此对该系统所做的更改将反映在订单系统当中。 这样,我们就不必像以前那样更改 EA 中的数据。 现在,我们可以在 IDE、或图表交易中直接执行此操作,这是通过上面提到的与消息传递系统相关的两个函数完成的。
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: if (StringSubstr(szArg, 0, StringLen(def_HeaderMSG)) != def_HeaderMSG) return; szArg = StringSubstr(szArg, 9, StringLen(szArg)); StringToUpper(szArg); if ((szArg == szMsgIDE[eBTN_SELL]) || (szArg == szMsgIDE[eBTN_BUY])) NanoEA.OrderMarket(szArg == szMsgIDE[eBTN_BUY]); if (szArg == szMsgIDE[eBTN_CANCEL]) { NanoEA.ClosePosition(); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false); } if (szArg == szMsgIDE[eCHECK_DAYTRADE]) UpdateInfos(true); break; case CHARTEVENT_OBJECT_ENDEDIT: UpdateInfos(); break; } }
问题来了:就这些吗? 是的,正是消息系统允许 MetaTrader 5 平台与 IDE 交互。 这很简单,但我必须承认,如果没有这个函数,IDE 将无法工作,也不可能构建系统。 如何在 EA 中实现这一点似乎有点复杂,但实际上依靠 OOP,EA 代码将保持超级简单。 有点棘手的是,在 IDE 中如何更新出现的结果。 在 OnTick 函数里数值会得到更新,但为了简单起见,我采用了 MetaTrader 5 中提供的数据,所以函数看起来像这样。 这一部分是最重要的 — 这个函数是请求频率最高的,所以它也应该是最快的。
void OnTick() { SubWin.DispatchMessage(CHARTEVENT_CHART_CHANGE, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT], NanoEA.CheckPosition()); }
换言之,对于每次即时报价,都会向该类发送一条消息,并在操作中更新结果值。 但请不要忘记,这个函数必须得到很好的优化,否则我们可能遭遇严重的问题。
结束语
有时候一些事情似乎是不可能做到的,但我喜欢挑战。 这个示例展示了如何在一个平台内制作一个 RAD 系统,而这个平台最初并不是为此目的而开发的,这就十分有趣了。 我希望这个从简单的东西起步的系统能激励您尝试探索一些新的东西,之前很少有人敢这样做。
很快,我就会往智能交易系统里添加更多新内容!
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10277


新文章 一张图表上的多个指标(第 05 部分):将 MetaTrader 5 转变为 RAD 系统(I)已发布:
作者:Daniel Jose