
从头开始开发智能交易系统(第 21 部分):新订单系统 (IV)
概述
在上一篇文章《从头开始开发智能系统(第 20 部分)》中,我们曾研究了获取可视化订单系统所需的主要更改。 然而,深化的步骤需要更多的解释,所以我决定将文章分成几个部分。 在此,我们将完成主要更改。 这只是它们当中很少一部份,但都是必要的。 嗯,整个工作将非常有趣。 然而,我不会在此完成这项工作,因为要真正完成这个系统,还有更多事情要做。 无论如何,到本文结束时,系统将具备几乎所有必要的功能。
我们来直接讨论实现问题。
1.0. 实现
首先,我们先为订单添加一个平仓或取消按钮。 负责按钮的类如下所示。
1.0.1. C_Object_BtnBitMap 类
这个类负责支持图表上的位图按钮,正如您在下所见。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_Base.mqh" //+------------------------------------------------------------------+ #define def_BtnClose "Images\\NanoEA-SIMD\\Btn_Close.bmp" //+------------------------------------------------------------------+ #resource "\\" + def_BtnClose //+------------------------------------------------------------------+ class C_Object_BtnBitMap : public C_Object_Base { public : //+------------------------------------------------------------------+ void Create(string szObjectName, string szResource1, string szResource2 = NULL) { C_Object_Base::Create(szObjectName, OBJ_BITMAP_LABEL); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 0, "::" + szResource1); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_BMPFILE, 1, "::" + (szResource2 == NULL ? szResource1 : szResource2)); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE, false); }; //+------------------------------------------------------------------+ bool GetStateButton(string szObjectName) const { return (bool) ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_STATE); } //+------------------------------------------------------------------+ };
当编写这个类的代码时,我意识到定位类可以移到 C_Object_Base 类。 整个 C_Object_BackGround 类将剔除此代码,因为它属于较低层的类。 如所知这是代码重用。 这种方法涉及更少的编程,提升了性能,但随着验证修改更加频繁,如上所有代码变得更加稳定。
为了添加 CLOSE 按钮,我们将执行以下操作:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_TradeLine.mqh" #include "C_Object_BtnBitMap.mqh" //+------------------------------------------------------------------+ class C_ObjectsTrade { // ... Class code ... }
下一步
enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE};
再下一步
C_Object_BackGround m_BackGround;
C_Object_TradeLine m_TradeLine;
C_Object_BtnBitMap m_BtnClose;
再下一步
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2; string sz0; // ... Internal function code ... switch (it) { case IT_TAKE: case IT_STOP: m_BackGround.Size(sz0, 92, 22); break; case IT_PENDING: m_BackGround.Size(sz0, 110, 22); break; } m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose); }
再下一步
#define macroDelete(A) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE)); \ } inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it) else { macroDelete(IT_PENDING); macroDelete(IT_RESULT); macroDelete(IT_TAKE); macroDelete(IT_STOP); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); } #undef macroDelete
及最终,最后一步...
#define macroSetAxleY(A) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y); \ m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y); \ } #define macroSetAxleX(A, B) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B); \ m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3);\ } inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy) { // ... Internal code... } #undef macroSetAxleX #undef macroSetAxleY
当您执行此系统时,您将得到以下结果:
但是这个按钮仍然不起作用,尽管 MetaTrader 5 会生成一个事件,令智能系统能够处理该按钮。 我们尚未实现此功能。 我们将在本文稍后的部分回来讨论它。
1.0.2. C_Object_Edit 类
如果系统不能通知交易员正在交易的数值,那么它将毫无用处。 为此,我们有 C_Object_Edit 类。 该类稍后必须进行一些更改,为的是增加功能,但现在我们将保持原样:它将通知交易员发生了什么。 为了实现这一点,我们需要在类中添加一些代码行。 第一个代码段就包含新代码:
void Create(string szObjectName, color cor, int InfoValue) { C_Object_Base::Create(szObjectName, OBJ_EDIT); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, "Lucida Console"); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, 10); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_ALIGN, ALIGN_CENTER); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrBlack); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_COLOR, clrBlack); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_READONLY, true); SetTextValue(szObjectName, InfoValue, cor); }
高亮显示的代码防止由交易员更改数值,但正如我所说,这在将来会改变。 这也许会需要一些此刻不相关的其它更改。
以下函数可显示文本。 注意此函数中的一个细节:
void SetTextValue(string szObjectName, int InfoValue, color cor = clrNONE) { color clr; clr = (cor != clrNONE ? cor : (InfoValue < 0 ? def_ColorNegative : def_ColoPositive)); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, IntegerToString(InfoValue < 0 ? -(InfoValue) : InfoValue)); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, clr); }
所选代码将根据输入值显示文本背景颜色。 我们在此这样做很重要,因为我们不想持续不断地猜测一个数值是负值还是正值,或者花费大量时间试图判定文本中是否有负值。 查看并立即了解该值是正值还是负值非常方便。 这就是代码的所作所为:现在您可以根据颜色立即判定值是负值还是正值。 但有一个条件要求,即颜色不应事先定义。 这在以后也会有用。
接下来,我们有这个类的最后一个函数,如下所示。
long GetTextValue(string szObjectName) const { return (StringToInteger(ObjectGetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT)) * (ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR) == def_ColorNegative ? -1 : 1)); };
您也许会注意到,当我们表达数值时,由于其格式,它们总是正值。 但是当我们检查对象的内容时,我们必须有正确的信息。 这是采用高亮代码显示的地方。 这是颜色信息的用处:如果颜色指示值为负值,则会进行更正,从而向 EA 提供正确的信息。 如果颜色指示为正值,则只需简单地保存该值。
颜色定义位于类本身当中。 如果您想稍后设置其它颜色,可以修改这些颜色,但请确保取用不同的颜色,以便之前的函数正常工作,否则 EA 将得到不明确的数值。 如此,EA 可能将负值视作正值,这将导致 EA 执行的分析整体出现问题。
1.0.3. C_Object_Label 类
这是我们在这个阶段需要的最后一个类。 实际上,我曾想过不创建这个类,因为它的动过类似于 C_Object_BtnBitMap 类。 但由于我希望添加文本信息时,能够独立于 C_Object_Edit 类,所以我决定在这里创建一个新类。
它的代码非常简单,如下所示。
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_Edit.mqh" //+------------------------------------------------------------------+ class C_Object_Label : public C_Object_Edit { public : //+------------------------------------------------------------------+ void Create(string szObjectName, string Font = "Lucida Console", string szTxt = "", int FontSize = 10, color cor = clrBlack) { C_Object_Base::Create(szObjectName, OBJ_LABEL); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_FONT, Font); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TEXT, szTxt); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_FONTSIZE, FontSize); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor); }; //+------------------------------------------------------------------+ };
我们在这个类中不需要任何其它内容,因为所有其它操作都已经由较低级别的对象实现。
正如您所看到的,OOP 是一个非常强力的工具。 我们把更多的代码规划到越多的类中,就越不需要为彼此相似的类编写代码。
但我们需要实现一个小型的改变。 在实验过程中,我注意到解释面板数据非常困难,所以我将其更改如下。
这样的方式更容易显示较大的数值。 这就是成果对象的样子:上部显示合约数量,或持仓的杠杆系数,而下部显示持仓结果。
为了实现这一点,我们需要修改主要负责对象定位的 C_Object_Base 类。 修改在下面的代码中高亮显示。
virtual void PositionAxleY(string szObjectName, int Y, int iArrow = 0) { int desl = (int)ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, (iArrow == 0 ? Y - (int)(desl / 2) : (iArrow == 1 ? Y : Y - desl))); };
然后,我们可以进入下一步:修改 C_ObjectsTrade 类。
1.0.4 C_ObjectsTrade 类
现在,我们来完成所需对象的绘制,之后我们将真正能够在图表上获得所需的结果:我们将拥有所有显示的信息,及其所有连接的对象。 这并不难,我们将一步一步地分析采取的行动。 如果您了解它是如何做到的,您就可以简单地按照说明添加您想要的任何其它信息,您会越来越好。 我们需要做的第一件事是定义对象应该响应的新事件。 它们在下面的代码中高亮显示。
enum eEventType {EV_GROUND = 65, EV_LINE, EV_CLOSE, EV_EDIT, EV_VOLUME, EV_MOVE};
现在,我们来添加所需的对象。 新对象也在下面的代码中高亮显示:
C_Object_BackGround m_BackGround; C_Object_TradeLine m_TradeLine; C_Object_BtnBitMap m_BtnClose; C_Object_Edit m_EditInfo, m_InfoVol; C_Object_Label m_BtnMove;
此后,我们创建对象,并定义它们在屏幕上的外观。 请注意,创建对象的顺序必须与它们出现的顺序相同:首先是背景对象,然后是要放置在背景上的下一个对象,依此类推,直到所有对象创建完毕。 如果按错误的顺序执行,且其中一个对象被掩盖,只需在代码中更改其出现的位置。 现在,代码如下:
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2, cor3; string sz0; int infoValue; switch (it) { case IT_TAKE : infoValue = m_BaseFinance.FinanceTake; cor1 = clrForestGreen; cor2 = clrDarkGreen; cor3 = clrNONE; break; case IT_STOP : infoValue = - m_BaseFinance.FinanceStop; cor1 = clrFireBrick; cor2 = clrMaroon; cor3 = clrNONE; break; case IT_PENDING: infoValue = m_BaseFinance.Leverange; cor1 = clrCornflowerBlue; cor2 = clrDarkGoldenrod; cor3 = clrLightBlue; break; case IT_RESULT : default: infoValue = m_BaseFinance.Leverange; cor1 = clrDarkBlue; cor2 = clrDarkBlue; cor3 = clrSilver; break; } m_TradeLine.Create(MountName(ticket, it, EV_LINE), cor2); if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE)); m_BackGround.Create(sz0 = MountName(ticket, it, EV_GROUND), cor1); switch (it) { case IT_TAKE: case IT_STOP: case IT_PENDING: m_BackGround.Size(sz0, 92, 22); break; case IT_RESULT: m_BackGround.Size(sz0, 84, 34); break; } m_BtnClose.Create(MountName(ticket, it, EV_CLOSE), def_BtnClose); m_EditInfo.Create(sz0 = MountName(ticket, it, EV_EDIT), cor3, infoValue); m_EditInfo.Size(sz0, 60, 14); if (it != IT_RESULT) m_BtnMove.Create(MountName(ticket, it, EV_MOVE), "Wingdings", "u", 17, cor2); else { m_InfoVol.Create(sz0 = MountName(ticket, it, EV_VOLUME), clrNONE, infoValue); m_InfoVol.Size(sz0, 60, 14); } }
针对来自上一篇文章中介绍的上一个版本,所有高亮显示的行都是对其代码的补充。 现在我们可以为以下函数编写代码。
#define macroDelete(A) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT)); \ if (A != IT_RESULT) \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE)); \ else \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_VOLUME)); \ } inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it) else { macroDelete(IT_PENDING); macroDelete(IT_RESULT); macroDelete(IT_TAKE); macroDelete(IT_STOP); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); } #undef macroDelete
注意使用宏替换的优点:我只需添加高亮显示的部分,这样我们就可以删除面板的所有对象。 现在我们能在 4 个面板中使用 6 个对象。 如果以不同的方式实现,这需要太多的工作,因此出错的可能性越高。 我们来完成定位函数。
#define macroSetAxleY(A) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y); \ m_BtnClose.PositionAxleY(MountName(ticket, A, EV_CLOSE), y); \ m_EditInfo.PositionAxleY(MountName(ticket, A, EV_EDIT), y, (A == IT_RESULT ? -1 : 0)); \ if (A != IT_RESULT) \ m_BtnMove.PositionAxleY(MountName(ticket, A, EV_MOVE), y); \ else \ m_InfoVol.PositionAxleY(MountName(ticket, A, EV_VOLUME), y, 1); \ } #define macroSetAxleX(A, B) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B); \ m_BtnClose.PositionAxleX(MountName(ticket, A, EV_CLOSE), B + 3); \ m_EditInfo.PositionAxleX(MountName(ticket, A, EV_EDIT), B + 21); \ if (A != IT_RESULT) \ m_BtnMove.PositionAxleX(MountName(ticket, A, EV_MOVE), B + 80); \ else \ m_InfoVol.PositionAxleX(MountName(ticket, A, EV_VOLUME), B + 21); \ } inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy) { double ad; int x, y; ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y); macroSetAxleY(it); macroSetAxleX(it, m_PositionMinimalAlxeX); if (Leverange == 0) return; if (it == IT_PENDING) { ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal()); ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y); macroSetAxleY(IT_TAKE); macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + 110); ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y); macroSetAxleY(IT_STOP); macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + 220); } } #undef macroSetAxleX #undef macroSetAxleY
再次,添加很少的代码。 即使如此,该函数也可以操控所有元素,因为致谢宏替换的运用,它可以正确地定位它们。 编译本阶段的 EA 之后,我们得到以下结果:
虽然一切看起来都很好,但这些控件仍然不起作用:我们必须为每个对象实现事件。 如果没有事件,这个界面几乎毫无用处,因为它实际上只会替换原来所用的内容。
2.0. 如何解决问题
如果它很简单,则每个人都可以做到。 但我们总是有问题要解决,这是开发过程的一部分。 我仅提供解决方案,替代演示如何解决。 但我希望这些文章能够激励您处理问题,并学习如何实际编程。 因此,本章节将有一些有趣的内容。
2.0.1. 随着图表更新调整内容
这是我们遇到的第一个问题。 这是由于对象的位置没有随着图表的更新而更新而导致的。 若要理解这一点,请看下面的图片:
这样的事情可能会让人发疯,但解决方案非常简单:MetaTrader5 本身会生成一个事件,通知图表需要更新。 如此,我们需要做的就是捕获事件,并更新我们的订单系统。
应在 CHARTEVENT_CHART_CHANGE 事件调用中捕获变化。 如果调用 EA 代码中的 UpdatePosition 函数,则更新更容易。 所有我们要做的就是在代码中添加一行代码。 这已在 C_OrderView 中完成,如下所示:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { // ... Code .... switch (id) { case CHARTEVENT_CHART_CHANGE: SetPositionMinimalAxleX(); UpdatePosition(); break; // ... The rest of the code...
这个简单的解决方案有一个问题:如果您有一个资产的多笔订单,可能需要一些时间,因为 EA 可能会在继续深入处理之前,陷入更新的困境。 还有其它更复杂的解决方案可以加快这个过程。 但这个解决方案对这个系统来说已经足够了。 结果如下。
一切看似都是正确的,不是吗? 但这里有一处错误。 在我们测试系统之前,很难看出它。 但这个系统确实存在一个缺陷,即使在修复之后,也会一事无成。
2.0.2. EA,请停止自动选择元素
如果您看上面的图片,您会注意到破位线被选中了,尽管我们没有选择它。 EA 每次都这样做。 根据面板创建时的设置,EA 可能会在每次移动图表时选择止盈或持仓线。
您可能会疯狂地试图理解发生了什么,但解决方案比前一个更简单:只需在同一代码中添加一行,EA 就会自动停止选择一行。 该修复在下面的代码中高亮显示。
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2, cor3; string sz0; double infoValue; // ... Internal code... Select(NULL); }
这些事情在开发过程中总会发生。 我的意思是,那些容易发现的和不太明显的漏洞,有时我们一开始并没有注意到它们。 无论如何,它们都有可能发生。 因此,学习编程是一个持续的过程。 有时您可以自己解决这些问题,并与大家分享,帮助他们解决同样的问题。 试着这样做。 我个人实际上就是这样做的,因为我就是这样学会编程的。 同样,您也可以学习如何创建程序。 通常,获取源代码并对其进行修改是学习的一部分。 您应该用功能代码来做这件事,这样理解它是如何构建的就更简单了,而且这通常会产生很好的效果,因为我们理解了很多有关每个程序员如何解决特定问题的认知。
但这只是为了激励您学习。 我们继续做一些真正重要的事情。
3.0. 事件处理
我们将建立一个报告持仓结果的系统。 这将取代 Chart Trade 中的相关区域,但我将保持 Chart Trade 不变,因为它指示持仓的总结果。 如果账户支持对冲,那么该值将不同于订单系统中指定的值,因为一个是局部值,另一个代表总值。 其它类型的账户不会有这样的差异,因此如果您愿意,可以从 Chart Trade 中删除结果系统。
3.0.1. 查看持仓结果
那些第一次看到代码的人可能会迷路,不知道在哪里搜索信息,并且可能会创建其它操作来执行原始代码已经能执行的操作。 而这通常会导致许多问题 — 我们可能会生成额外的代码,从而引发原始代码不曾有的额外漏洞。 这不符合重用规则,根据该规则,只有在真正需要时才应编程。 故此,了解 MetaTrader 5 是如何工作的,并且了解 EA 已能如何工作,您应该找到 Chart Trade 中显示的结果的生成位置。 因为如果给出了持仓的结果,我们应该使用它。 请注意下面的代码。
void OnTick() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, OrderView.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]); TimesAndTrade.Update(); }
接下来,我们转到高亮显示的位置。 源代码如下所示。
inline double CheckPosition(void) { double Res = 0, sl, profit, bid, ask; ulong ticket; bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID); ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (ask < sl) ClosePosition(ticket); }else { if ((bid > sl) && (sl > 0)) ClosePosition(ticket); } Res += profit; } return Res; };
好吧,有人也许会想:但如果无法知道应该引用哪些对象,我如何从那里获取数据,并将其应用到面板? 这些被创建的物体看起来是松散的,没有任何准则或谨慎... 嗯,这不是真的。 如果您这样认为或想象,最好从头了解更多 MetaTrader 5 平台的实际工作原理。 确实如此,我没有创建任何类型的列表、数组或结构来引用正在创建的对象,但这是有意如此做的。 因为我知道它有效。 我将向您展示它确实有效:您可以引用某个对象,而无需借助任何结构来存储图表上的对象。 哪些是真正需要的,它们应该是正确为对象名称建模。 就是这样。
问: 对象名称是如何建模的?
答案如下:
1 - 头部的顺序 | 此顺序将订单系统中使用的对象,与所有其它对象区分开来。 |
2 - 限制字符 | 指示要遵循一些其它信息 |
3 - 类型指示 | 区分止盈和止损。 |
4 - 限制字符 | 与 2 相同。 |
5 - 订单或仓位凭证 | 记住订单凭证 - 链接 OCO 订单数据,并区分订单 |
6 - 限制字符 | 与 2 相同。 |
7 - 事件指示 | 区分同一面板中的对象 |
也就是说,建模就是一切,即使在那些没有实际编程经验的人看来,我们正在创建重复的东西,实际上我们正在创建独特的东西 — 每个对象都是唯一的,能够由简单的规则引用。 此规则由以下代码创建:
inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev) { return StringFormat("%s%c%c%c%d%c%c", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)ev); }
因此,如果您告诉前面的代码要访问哪笔订单、哪个指标和哪个事件,那么您就拥有特定对象的名称。 这允许操纵其属性。 知道这是第一步,我们需要再做一个决定:如何保证这种操纵安全,而不会对代码造成破坏,也不会把它变成弗兰肯斯坦(漏洞百出的拼凑)?
现在我们就来做这件事。 我们转到 C_ObjectsTrade 类,并添加以下代码。
inline void SetResult(ulong ticket, double dVolume, double dResult) { m_InfoVol.SetTextValue(MountName(ticket, IT_RESULT, EV_VOLUME), (dVolume / Terminal.GetVolumeMinimal()), def_ColorVolumeResult); m_EditInfo.SetTextValue(MountName(ticket, IT_RESULT, EV_EDIT), dResult); }
现在,我们转到 C_Router 类,并添加高亮显示的代码。
inline double CheckPosition(void) { double Res = 0, sl, profit, bid, ask; ulong ticket; bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID); ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); SetResult(ticket, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT)); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (ask < sl) ClosePosition(ticket); }else { if ((bid > sl) && (sl > 0)) ClosePosition(ticket); } Res += profit; } return Res; };
这解决了问题之一。 但我们还有其它未解决的问题。
3.0.2. 指示挂单的交易量
现在,我们解决挂单中指示的交易量问题。 为此,我们必须在 C_ObjectsTrade 类中创建一个新函数。
inline void SetVolumePendent(ulong ticket, double dVolume) { m_EditInfo.SetTextValue(MountName(ticket, IT_PENDING, EV_EDIT), dVolume / Terminal.GetVolumeMinimal(), def_ColorVolumeEdit); }
一旦完成,我们调用 C_Router 类中的 UpdatePosition 函数,更新将顺利进行。
void UpdatePosition(int iAdjust = -1) { // ... Internal code .... for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { price = OrderGetDouble(ORDER_PRICE_OPEN); take = OrderGetDouble(ORDER_TP); stop = OrderGetDouble(ORDER_SL); bTest = CheckLimits(price); vol = OrderGetDouble(ORDER_VOLUME_CURRENT); // ... Internal code... CreateIndicatorTrade(ul, price, IT_PENDING); SetVolumePendent(ul, vol); CreateIndicatorTrade(ul, take, IT_TAKE); CreateIndicatorTrade(ul, stop, IT_STOP); } };
这样,问题就解决了。 现在我们必须解决止盈和止损价位指示问题,因为这些数值在我们下单后,显示在图表上的数值就不对了。
3.0.3. 面板 Close 按钮点击事件
删除订单或其中一个破位级别的唯一安全方法,就是位于每个数值边角的 Close 按钮。 但此处我们有一个错误的事件实现。 我们来修复它。
单击事件实际上应该在 C_OrderView 类中实现。 我们用高亮显示的代码替换旧系统。
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, pp, pt, ps; eIndicatorTrade it; eEventType ev; switch (id) { // ... Internal code... case CHARTEVENT_OBJECT_CLICK: if (GetInfosOrder(sparam, ticket, price, it, ev)) { switch (ev) { case EV_CLOSE: if (OrderSelect(ticket)) switch (it) { case IT_PENDING: RemoveOrderPendent(ticket); break; case IT_TAKE: ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL)); break; case IT_STOP: ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0); break; } if (PositionSelectByTicket(ticket)) switch (it) { case IT_RESULT: ClosePosition(ticket); break; case IT_TAKE: ModifyPosition(ticket, 0, PositionGetDouble(POSITION_SL)); break; case IT_STOP: ModifyPosition(ticket, PositionGetDouble(POSITION_TP), 0); break; } break; // ... Rest of the code...
在该类里还有一件事要补充。 如果交易员意外删除了一笔已报告持仓数据的相应对象,会发生什么? 为了避免这种情况,系统中添加了以下代码。
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, pp, pt, ps; eIndicatorTrade it; eEventType ev; switch (id) { case CHART_EVENT_OBJECT_DELETE: case CHARTEVENT_CHART_CHANGE: SetPositionMinimalAxleX(); UpdatePosition(); break; // ... Rest of the code...
以这种方式,如果操作员删除了不应该删除的内容,EA 将快速重置被删除的对象。
以下视频显示了系统当前的工作方式。 还有一些我在文章中没有涉及的修改,因为这些次要修改不会影响解说。
结束语
虽然系统看起来是完整的,您可能想用它进行交易,但我必须警告您,它尚未完工。 本文旨在向您展示如何添加和更改内容,令订单系统更实用、更易于操作。 但它仍然缺乏负责移动持仓的系统,这些能令 EA 的使用非常有指导性、实用性和直观性。 但我们将把它留给下一篇文章。
附件包含当前开发阶段的系统。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10499
注意: 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.


已发表文章:从零开始开发商业 EA(第 21 部分):新订单系统(IV):
作者:丹尼尔-何塞
EA 在编译时会产生错误。应注释掉 #52 行
//C_Terminal 终端;