从头开始开发智能交易系统(第 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