我们的 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.... } }