
从头开始开发智能交易系统(第 9 部分):概念上的飞跃 (II)
概述
在之前的部分里,我们创建了一个基本系统,允许在浮动窗口中使用模板。 虽然我们已完成了很多修改,但代码依旧尚未完成。 之前这样做是为了保持解释的简单,因为虽然在浮动窗口中使用模板确实相当简单,但对象的使用则要复杂得多。 故此,准备好迎接一份全新的工作吧。
事实上,相对于我们利用对象在浮动窗口里创建图表交易界面,最大的困难是 MetaTrader 5 本意并非出于此目的。 一些读者可能会说,我们可以使用标准库来创建图表交易窗口。 但我喜欢把事情复杂化,我想让每个人都能像几篇文章之前讨论的那样自行创建界面。 然而,在那篇文章中,一切都还很简单。 现在我们需要了解 MetaTrader 5 中的限制,以便绕过它们。
计划
我们需从基础开始。 以下代码的行为将符合我们的预期:
#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ int OnInit() { long id = ChartID(); string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(); ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0); ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+
这里并无什么复杂的事情,因为影响完全符合预期。 但是 MQL5 允许您更进一步,尽管若您尝试做一些超出系统最初设计目标的事情时,可能会遇到困难。 因此,如果我们将上面的代码更改为下面的代码,事情就会变得有趣。
int OnInit() { long id = ChartID(), handle; string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand(); ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0); ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false); handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID); ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0); ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false); ChartRedraw(handle); return INIT_SUCCEEDED; }
我们已将高亮显示的行添加到代码之中。 如果您在图表上运行它,结果如下:
发生了什么? 我们在图表上又放了一个图表。 我们可以在此放置任何对象,因为 MQL5 允许这样做,但这样的话优缺点兼备。 查看代码中的下一处修改,以便理解该步骤的优点。
int OnInit() { long id = ChartID(), handle; string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand(); ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0); ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true); ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true); handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID); ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0); ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false); ChartRedraw(handle); return INIT_SUCCEEDED; }
高亮显示的附加行产生了以下结果:
这意味着对象内的所有内容都将保留在对象内。 当我们使用浮动窗口时,这是必需的,因为这能大大简化控制逻辑。 但并非一切都是完美的:MetaTrader 5 最初并不是为此而设计的。 故此,当一个对象处于另一个对象内部时,就会出现一个问题 — 我们无法向内部对象发送事件。 为了理解这一点,我们对代码进行更多的修改。 最终代码如下:
#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ int OnInit() { long id = ChartID(), handle; string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand(); ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0); ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10); ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300); ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false); ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true); ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true); handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID); ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0); ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50); ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300); ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false); ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false); ObjectSetInteger(handle, sz1, OBJPROP_SELECTABLE, true); ObjectSetInteger(handle, sz1, OBJPROP_SELECTED, true); ChartRedraw(handle); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (id == CHARTEVENT_OBJECT_CLICK) Print(sparam); } //+------------------------------------------------------------------+
此处是在平台中运行该代码的结果:
请注意,当我们单击内部对象时,实际上是在单击外部对象,这就是事情变得复杂的所在。 但程序员总是努力成为解决问题的专家:你必须解决问题才能得到想要的结果。 通过运用相关知识,我们将构建该系统,从而在浮动窗口中创建图表交易,并确保其功能正常,且具有个性化外观。
在该规划过程中,还有最后一个阶段。 虽然这一部分对于现代计算机来说并不那么尖锐,但它仍然需要加以考虑:处理时间的优化。 该问题与处理器必须执行的操作数量有关,而非与处理信息所需的时间有关。 拟议中的浮动窗口系统包含四个对象,它们应该能够响应您的动作从而四处移动。 相应地,在观察窗口中的任何信息都将会受到窗口自身修改的影响。 至少图表交易会增加对象的数目。 虽然没有相应的计算成本,但代码变得令人不快,似乎优化得很差劲。 我们可以简单地添加一个控制系统,如此来解决问题。 但有一个更优雅的建议。 虽然它看起来更耗时、更费力,但实际上减少了所需维护和操控的对象数量。
实现
首先,我们把浮动窗口的创建过程分为几个步骤,以便支持代码重用。 接下来,我们将在 C_ChartFloating 对象类中创建两个新函数:
//+------------------------------------------------------------------+ bool StageLocal01(string sz0, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1) { m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS); m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS); if (m_MaxCounter >= def_MaxFloating) return false; CreateBarTitle(); CreateCaption(sz0); CreateBtnMaxMin(); CreateRegion(TimeFrame, Scale); m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID); return true; } //+------------------------------------------------------------------+ void StageLocal02(int x, int y, int w, int h) { y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y); m_Win[m_MaxCounter].PosX = -1; m_Win[m_MaxCounter].PosY = -1; m_Win[m_MaxCounter].PosX_Minimized = m_Win[m_MaxCounter].PosX_Maximized = x; m_Win[m_MaxCounter].PosY_Minimized = m_Win[m_MaxCounter].PosY_Maximized = y; SetDimension(w, h, true, m_MaxCounter); SetPosition(x, y, m_MaxCounter); ChartRedraw(m_Win[m_MaxCounter].handle); m_MaxCounter++; } //+------------------------------------------------------------------+
添加浮动窗口的新代码如下所示:
bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1) { if (!StageLocal01(sz0, TimeFrame, Scale)) return false; ChartApplyTemplate(m_Win[m_MaxCounter].handle, sz0 + ".tpl"); m_Win[m_MaxCounter].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand(); ObjectCreate(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJ_VLINE, 0, 0, 0); ObjectSetInteger(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJPROP_COLOR, clrBlack); StageLocal02(x, y, w, h); return true; }
这不会影响已组装完毕的系统,但提供了更佳功能。 注意高亮显示的行:现在我们将创建一个函数来使用我们的 IDE。 开头如下图所示:
bool Add_RAD_IDE(string sz0, int x, int y, int w, int h) { if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false; StageLocal02(x, y, w, h); return true; }
请注意,高亮显示的行与我们在前面的代码中所用的行相同。 即,我们正在重用代码,只有我们需要调整的地方才会不同。 现在我们可以通知我们的系统,我们已经有了管理 IDE 的工具。 我们修改 C_TemplateChart 对象类来实现这一点。 下面的代码精准示意了函数中将要修改的内容,因此从现在起,我们就可致力于利用 IDE 实现一个浮动窗口,因为所有必要的支持都将正常工作。
void AddTemplate(void) { // .... Function code.... if (h == 0) { SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w); if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl"); } if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD) { if ((h > 0) && (w > 0)) Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h); else { C_Chart_IDE::Create(GetIdSubWinEA()); m_Info[m_Counter - 1].szVLine = ""; } }else { if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else { m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand(); ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0); ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack); } } }
我们看看如何配置代码,令其尽可能灵活。 这也会防止系统变成弗兰肯斯坦(Frankenstein)。 当我们修改代码时,请始终记住这一点 — 不需要从头编写代码,也不需要多次验证相同的内容。 始终尝试只验证一次。 然后,您可以在进行新测试之前尽可能多地使用和探索这些内容。 因此,系统就会在良好的前提下成长,而代码将随着时间的推移保持可连贯性和可扩展性。
如果您现在运行该系统,它将在图表上显示一些内容。 但我们至少需要它来显示我们之前编写的界面。 因此,我们需要很对代码进行额外的修改。 现在我们有以下代码:
bool Add_RAD_IDE(string sz0, int x, int y, int w, int h) { if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false; ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl"); StageLocal02(x, y, w, h); return true; }
以下是应用程序发布的结果:
如果能够访问模板中的对象(模板加载在上面代码中高亮显示的行中),那将是非常良好和漂亮的。 然而,这是不可能的。 此处有一个重要的细节:我们只应创建将被操纵的对象,来替代我们之前研究时那样创建对象! 当我们必须移动窗口时,这将节省大量的处理时间。 我们还遇到另一个问题,但我们先要解决处理问题,并令系统正常工作。 实际上,这部分已经完成了,只需做一些调整即可。
我们从修改类之间的继承序列开始。 我们不得不如此做,因为我们没有多重继承,所以新结构将如下所示:
但这种变更不应该引起关注:继承序列的变更根本不会修改代码,但会令其近乎就绪。 已变更的部分在下面代码中会高亮显示。
bool Add_RAD_IDE(string sz0, int x, int y, int w, int h) { if ((w <= 0) || (h <= 0)) return false; if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false; ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl"); StageLocal02(x, y, w, h); return true; } void AddTemplate(void) { // ..... Código .... if (h == 0) { SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w); if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl"); } if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD) { C_Chart_IDE::Create(Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h)); m_Info[m_Counter - 1].szVLine = ""; }else { if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else { m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand(); ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0); ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack); } } } bool Create(bool bFloat) { 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 = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA()); 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, 0, 0, szMsgIDE[eLABEL_SYMBOL]); return true; } 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++); if (!b0 && m_IsFloating) return true; else c0 = (b0 ? c0 : m_CountObject); m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue); //... The rest of the function... }
这也许看起来很奇怪。 但这里并无什么复杂的东西。 请记住,当我们不用浮动窗口时,系统已经能够处理 IDE 中运行的事件。 但因为那扇浮动窗口,我们必须重建一切。 然而,我们不必从头开始做这件事 — 我们将修改现有代码,以便在正确的位置加入 IDE。 我们只需要添加接收事件的对象。 这些更改令我们明白,是需要创建所有元素,亦或只需要创建其中的一部分。
在这些修改之后,在图表上我们就有了 IDE 信息。 但是 IDE 对象会造成真正的混乱,因为这些对象与浮动窗口并无关联。 现在就需要解决这个问题。
首先,我们需要得到浮动窗口在图表上的位置。 知道对象在窗口里的呈现点位置就足够了。 但由于坚持面向对象编程,我们需要做一些修改,尽管主要结果是下面的代码。
//+------------------------------------------------------------------+ struct IDE_Struct { int X, Y, Index; bool IsMaximized; }; //+------------------------------------------------------------------+ class C_ChartFloating { // ... Class code ... //+------------------------------------------------------------------+ void SetPosition(const int X, const int Y, const int c0) { // ... Function code ... if (c0 == m_IDEStruct.Index) { m_IDEStruct.X = m_Win[c0].PosX + 3; m_IDEStruct.Y = m_Win[c0].PosY + def_SizeBarCaption + 3; m_IDEStruct.IsMaximized = m_Win[c0].IsMaximized; } } //+------------------------------------------------------------------+ // ... Class code ... //+------------------------------------------------------------------+ inline IDE_Struct GetIDE_Struct(void) const { return m_IDEStruct; } //+------------------------------------------------------------------+ bool Add_RAD_IDE(string sz0, int x, int y, int w, int h) { if ((w <= 0) || (h <= 0)) return false; if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false; ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl"); m_IDEStruct.Index = m_MaxCounter; StageLocal02(x, y, w, h); return true; } //+------------------------------------------------------------------+ //... The rest of the class code }
由于解决方案关注于一个窗口 — 我们的图表交易者 — 在这一点之后,没有必要让事情变得更复杂。 现在,我们可在窗口中正确定位对象,至少最初如此。 这是在以下函数中完成的。
void Resize(int x) { for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY); }else ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX); };
上述函数可提供对象的正确初始定位,结果如下所示:
除了正确的初始定位之外,对象已经可以对事件做出反应。 然而,该系统仍然存在漏洞,需要修复。 此处是我们要修改的第一件事:
如您所见,窗口已最小化,但对象仍保留在屏幕上。 这是因为对象不在窗口区域内(我曾在前面解释了原因)。 故在我们继续之前,需要先修复它。 这可以通过如下所示的简单代码来完成修改:
void Resize(int x) { for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating) { ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetWidth())); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetHeight())); }else ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX); };
结果如下:
现在,我们来修复移动的问题。 可以由以下代码解决此问题
void DispatchMessage(int id, long lparam, double dparam, string sparam) { static double AccumulatedRoof = 0.0; bool b0; double d0; static int px = -1, py = -1; C_ChartFloating::DispatchMessage(id, lparam, dparam, sparam); if (m_CountObject < eEDIT_STOP) return; switch (id) { case CHARTEVENT_MOUSE_MOVE: if ((GetIDE_Struct().X != px) || (GetIDE_Struct().Y != py)) { px = GetIDE_Struct().X; py = GetIDE_Struct().Y; Resize(-1); } break; //... The rest of the function ...
最终结果如下:
结束语
看看编程有多棒:我们从一个问题开始,然后,在不对代码进行重大更改的情况下,我们一个接一个地解决了问题,最后我们用尽可能少的代码编写出工作代码。
重要!如果您在图表交易背景上看到奇怪的信息,这是由于 MT5 的更新 3228,它会令用到 clrNONE 颜色的对象不透明。 可利用附带的 IDE 文件修复此问题。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10363

