
从头开始开发智能交易系统(第 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
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.

