
从头开始开发智能交易系统(第 11 部分):交叉订单系统
概述
有一种类型的资产让交易员的生涯变得非常困难 — 那就是期货合约。 但为什么令他们的职业生涯变得如此困难? 当金融产品合约到期时,我们要创建一笔新合约,然后进行交易。 实际上,在合约到期时,我们需要对其完成全部分析,将所有内容保存为模板,并将此模板导入新合约,以便后续继续分析。 对于任何交易这类资产的人来说这都是很常见的,但即使是期货合约也会有一些历史,利用这些历史,我们可以深入持续地分析它们。
专业交易者喜欢分析某些过去的信息,在这种情况下,就需要第二张图表。 现在,如果我们使用了适当的工具,就不需要第二张图表了。 这其中的一个工具就是采用交叉订单系统。
计划
在本系列的第一篇文章中,我们已经提及了这种类型的订单,但我们并未谈及如何实现。 在那篇文章中,我们关注的是其它一些事情,因为我们正在启动一个可以在 MetaTrader 5 平台上工作的完整系统。 在本文中,我们将展示如何实现此功能。
若要理解创建此功能的原因,请参看以下两张图片:
左侧的图片是一个典型的期货合约,在这种情况下,它是迷你美元期货,从图表中可以看出,它是几天前开始的。 右侧的图表示意的是同一份合约,其包含的附加数据实际上代表了过期合约的数值,因此右侧的图表是一个历史图表。 右侧的图表更适合分析旧日的支撑和阻力价位。 但如果我们需要进行交易的话,一个问题就会出现。 如下所示:
如您所见,交易品种是在 CHART TRADE 中指定的,即使我们用历史 CHART TRADE 来讲,我们可以发送订单 — 这可以从工具栏上看到。 在左侧的图像中,图表已针对当前合约创建了订单;但在右侧的图像中,订单只在消息框中才能看到,而图表上没有任何可见的内容。
您也许会认为这只是一个显示问题,但错了,这里面的一切要更复杂得多。 这就是我们将在本文中讨论的内容。
重点!在此,我们将见识到如何创建规则,以便能够利用历史数据进行操作。 在我们的案例中,这些规则将重点关注于巴西交易所(B3)进行交易的迷你美元(WDO)和迷你指数(WIN)。 正确的理解将令您能够适应世界上任何交易所的任何类型的期货合约的规则。
该系统不局限于一种或另一种资产,其内容全部是关于代码部分的正确适配。 如果这一点正确完成了,那么我们就会拥有一款智能交易系统,我们不必再担心资产合约是否即将到期,以及下一份合约将在什么时候开始 — EA 将为我们做到这一点,根据需要用正确的合约替换旧合约。
如何理解游戏规则
WDO(迷你美元)、WIN(迷你指数)、DOL(美元期货)和 IND(指数期货)期货合约遵循有关非常具体的到期日,以及合约规格。 首先,我们来看看如何找到合约到期日:
请注意高亮显示的信息:蓝色表示合约到期日,红色表示合约存期终结日期,在此日期之后,合约将不可交易。 了解这一点非常重要。
合约期限在合约中有规定,但并未指定名称。 幸运的是,我们可以很容易地基于规则找到该名称,这些规则是严格的,并且整个市场都在用。 就美元和指数期货合约而言,我们有以下内容:
前三个字母表示合约类型:
代码 | 合约 |
---|---|
WIN | 迷你 Ibovespa 指数期货合约 |
IND | Ibovespa 指数期货合约 |
WDO | 迷你美元期货合约 |
DOL | 美元期货合约 |
该代码后面有一个字母,表示合同到期月份:
到期月份: | 字母代表 WDO 和 DOL | 字母代表 WIN 和 IND |
---|---|---|
January(一月份) | F | |
February(二月份) | G | G |
March(三月份) | H | |
April(四月份) | J | J |
May(五月份) | K | |
June(六月份) | M | M |
July(七月份) | N | |
August(八月份) | Q | Q |
September(九月份) | U | |
October(十月份) | V | V |
November(十一月份) | X | |
December(十二月份) | Z | Z |
后接的两位数字,表示合约到期年份。 例如,2022 年 4 月到期的美元期货合约表示为 DOLJ22。 这是一份可以交易到五月初的合约。 五月初的时候,合约到期。 由于 WIN 和 IND 的规则略有不同,合约实际上在最接近指定月份 15 日的周三到期。 故此,尽管规则更为复杂,但 EA 可以管控这一切,并能始终提供正确的合约。
实现
我们的 EA 已经具备了接受规则的必要条件。 在此,我们只需要实现一些关于订单发送系统的设置。 那好,我们开工吧。 首先,在 C_Terminal class 类对象里添加以下代码:
void CurrentSymbol(void) { MqlDateTime mdt1; string sz0, sz1; datetime dt = TimeLocal(); sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3); if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return; sz1 = ((sz0 == "WDO") || (sz0 == "DOL") ? "FGHJKMNQUVXZ" : "GJMQVZ"); TimeToStruct(TimeLocal(), mdt1); for (int i0 = 0, i1 = mdt1.year - 2000;;) { m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1); if (i0 < StringLen(sz1)) i0++; else { i0 = 0; i1++; } if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break; } }
这段代码利用我们上面看到的规则来生成资产名称。 为了确保我们能始终取用当前合约,我们将执行高亮显示行中所示的检查,即资产应在平台中有效,而 EA 将采用生成的名称。 如果您想处理其它期货合约,您应该修改前面的代码,以便能够正确地生成名称,因为名称可能因情况而异。 但该代码不仅限于与之链接的资产 — 只要您采用正确的规则,它可反映任何类型的期货合约。
接下来是订单的细节部分。 如果您使用此开发阶段的系统,您将看到以下行为:
换句话说,您已经可采用过交叉订单模式,但尚未完全实现 — 图表上没有订单指示。 实现并不像大多数人想象的那样困难,因为我们需要用水平线来示意订单。 但这还不是全部。 当我们使用交叉订单时,我们错过了 MetaTrader 5/ 提供的一些东西,因此我们需要实现缺失的逻辑,以便订单系统能够安全、稳定和可靠地工作。 否则,使用交叉订单可能会导致问题。
从这个角度来看,这似乎也并不那么简单。 事实上,它肯定不简单,因为我们必须创建 MetaTrader 平台最初提供的所有逻辑。 如此,首先要做的是忘记内部 MetaTrader 系统 — 从我们开始使用交叉订单系统的那一刻起,它就不可用了。
从现在起,订单票据将决定规则。 但这有一些负面后果。 最消极的一点是,我们不知道图表上会有多少订单。 限制它们的数量肯定会让交易员感到不快。 因此,我们需要做些什么,从而允许交易者以相同的方式使用系统,就如同正常的 MetaTrader 能做到的完整逻辑一样。 这是第一个要解决的问题。
类 C_HLineTrade
为了解决这个问题,我们将创建一个新的类 C_HLineTrade,它将取代 MetaTrader 5 提供的在图表上显示订单的系统。 那好,我们从类声明开始:
class C_HLineTrade { #define def_NameHLineTrade "*HLTSMD*" protected: enum eHLineTrade {HL_PRICE, HL_STOP, HL_TAKE}; private : color m_corPrice, m_corStop, m_corTake; string m_SelectObj;注意,这里定义了一些东西 — 它们将在代码中被频繁使用。 因此,提请注意它的进一步变化 — 事实上,会有很多变化。 接下来,我们声明类的构造函数和析构函数:
C_HLineTrade() : m_SelectObj("")
{
ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
RemoveAllsLines();
};
//+------------------------------------------------------------------+
~C_HLineTrade()
{
RemoveAllsLines();
ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, true);
};
构造函数会阻止原始行可见,而析构函数则会把它们放回到图表上。 这两个函数都有一个共同的函数,如下所示:
void RemoveAllsLines(void) { string sz0; int i0 = StringLen(def_NameHLineTrade); for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--) { sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1); if (StringSubstr(sz0, 0, i0) == def_NameHLineTrade) ObjectDelete(Terminal.Get_ID(), sz0); } }
高亮显示的行会检查对象(在本例中为水平线),是否为类中所用的对象之一。 如果是,它将删除该对象。 注意,我们不知道会有多少个对象,但系统会逐个检查对象,尝试清理由类创建的所有东西。 来自该类的下一个推荐函数如下所示:
inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select) { string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1; if (price <= 0) { ObjectDelete(Terminal.Get_ID(), sz0); return; } if (!ObjectGetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, 0, sz1)) { ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (hl == HL_PRICE ? m_corPrice : (hl == HL_STOP ? m_corStop : m_corTake))); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false); ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true); ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(hl), 3, 10)); } ObjectSetDouble(Terminal.Get_ID(), sz0, OBJPROP_PRICE, price); }对于该函数,无论它会创建多少个对象,也不管对象是否依旧存在。 它会确保创建该行,并将其放置在正确的位置。 这个创建行替换了最初在 MetaTrader 里使用的行。
我们的目的是使其行使功能,而不是单纯为了美观。 这就是为什么创建时不选择改行的原因 — 如果需要,可以更改此行为。 但我使用 MetaTrader 5 消息传递系统来定位该行。 所以,为了能够移动它们,您必须明确指出这一点。 为了指示正在调整的行,我们有另一个函数:
inline void Select(const string &sparam) { int i0 = StringLen(def_NameHLineTrade); if (m_SelectObj != "") ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false); m_SelectObj = ""; if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade) { if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE)) { ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true); m_SelectObj = sparam; }; } }该函数可实现行的选择。 如果选择了另一行,它将取消以前的选择。 这一切很简单。 该函数将仅操控由类实际处理过的行。 该类的另一个函数,值得一提,如下所示:
bool GetNewInfosOrder(const string &sparam, ulong &ticket, double &price, eHLineTrade &hl) { int i0 = StringLen(def_NameHLineTrade); if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade) { hl = (eHLineTrade) StringToInteger(StringSubstr(sparam, i0, 1)); ticket = (ulong)StringToInteger(StringSubstr(sparam, i0 + 1, StringLen(sparam))); price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE); return true; } return false; }该函数在该类中大概是最重要的:因为我们不知道图表上会有多少行,故我们需要知道用户正在操作哪一行。 该函数恰好就是做这个的 — 它告诉系统正在操作哪一行。
但这只是我们所需做的一小部分。 该系统仍远未发挥出其作用。 我们来继续下一步 — 我们将添加和修改 C_Router 类的函数,该类负责订单路由。 这个类继承了我们刚刚在 C_HLineTrade 类中创建的功能。 请参阅以下代码:
#include "C_HLineTrade.mqh"
//+------------------------------------------------------------------+
class C_Router : public C_HLineTrade
新的 C_Router 类
源 C_Router 类当中有一个限制,只允许有一笔开仓订单。 这一限制在此处将被取消,为此我们需要对 C_Router 类进行重要修改。
第一处修改是在类的更新函数,它现在看起来像这样:
void UpdatePosition(void) { static int memPositions = 0, memOrder = 0; ulong ul; int p, o; p = PositionsTotal() - 1; o = OrdersTotal() - 1; if ((memPositions != p) || (memOrder != o)) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); RemoveAllsLines(); ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); memOrder = o; memPositions = p; }; for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol()) { ul = PositionGetInteger(POSITION_TICKET); SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false); SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true); SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true); } for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol()) { SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true); SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true); SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true); } };
以前,该函数仅收集一笔持仓的数据,并将其保存到观测站。 现在,该函数将在图表上显示所有持仓和挂单。 它无疑是 MetaTrader 系统提供功能的替代品。 由于这些都是要紧的东西,了解其工作原理很重要,因为如果失败,它会影响整个交叉订单系统。 所以,在我们将其用于实盘帐户交易之前,我们理应在演示帐户上测试这个系统。 这种系统必须经过相应的测试,直到我们完全确定一切操作都正常为止。 首先,我们需要配置系统,因为它的操作方式与 MetaTrader 5 的方式略有不同。
参见高亮显示的行,并诚实地回答:是否清楚它们实际上在做什么? 这两段代码行出现在这里的原因,当我们在本文稍后讨论 C_OrderView 类时,就会变得很清楚。 若没有这两行,代码就非常不稳定,工作起来很奇怪。 至于其余的代码,它相当简单 — 它经由 C_HLineTrade 类对象创建每一行。 在这种情况下,我们只有一行无法选择。 这很容易示意,如下代码所示:
SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
换句话说,该系统变得非常简单明了。 EA 在事件处理期间 OnTrade 调用该函数:
C_TemplateChart Chart; // ... Expert Advisor code ... void OnTrade() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]); Chart.UpdatePosition(); } // ... The rest of the Expert Advisor code ...高亮显示的代码将启用屏幕的更新。 请注意,我们正在为此使用 C_TemplateChart 图表 — 这是因为系统中类结构已经更改。 新结构如下所示:
这种结构允许在 EA 内消息定向流动。 当您对消息流如何进入特定类有疑问时,请查看这个类继承图。 被认为唯一公开的类是 C_Terminal 对象类,而所有其它类都是通过类之间的继承来处理的,在这个系统中绝对没有公开变量。
现在,由于系统不仅仅只分析单笔订单,因此有必要了解其它内容:如何理解操作的结果? 为什么它很重要? 当您只有一笔持仓时,系统可以很容易地理解一切,但随着持仓数量的增加,您需要弄清楚发生了什么。 此处是函数能提供的信息:
void OnTick() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]); }
这里的变化不多。 查看高亮显示的函数代码:
inline double CheckPosition(void) { double Res = 0, last, sl; ulong ticket; last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST); for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol()) { ticket = PositionGetInteger(POSITION_TICKET); Res += PositionGetDouble(POSITION_PROFIT); sl = PositionGetDouble(POSITION_SL); if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { if (last < sl) ClosePosition(ticket); }else { if ((last > sl) && (sl > 0)) ClosePosition(ticket); } } return Res; };
该函数由三个高亮显示的部分组成:黄色部分通知持仓的结果,绿色部分检查仓位,以防由于高波动性而错过止损,在这种情况下,必须尽快将其平仓。 因此,此函数不会返回单笔持仓的结果,除非您为特定资产只创建了一笔持仓。
当我们使用交叉订单模型时,还有其它函数可以帮助系统继续工作。 请参看下面的代码:
bool ModifyOrderPendent(const ulong Ticket, const double Price, const double Take, const double Stop, const bool DayTrade = true) { if (Ticket == 0) return false; ZeroMemory(TradeRequest); ZeroMemory(TradeResult); TradeRequest.action = TRADE_ACTION_MODIFY; TradeRequest.order = Ticket; TradeRequest.price = NormalizeDouble(Price, Terminal.GetDigits()); TradeRequest.sl = NormalizeDouble(Stop, Terminal.GetDigits()); TradeRequest.tp = NormalizeDouble(Take, Terminal.GetDigits()); TradeRequest.type_time = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC); TradeRequest.expiration = 0; return OrderSend(TradeRequest, TradeResult); }; //+------------------------------------------------------------------+ bool ModifyPosition(const ulong Ticket, const double Take, const double Stop) { ZeroMemory(TradeRequest); ZeroMemory(TradeResult); if (!PositionSelectByTicket(Ticket)) return false; TradeRequest.action = TRADE_ACTION_SLTP; TradeRequest.position = Ticket; TradeRequest.symbol = PositionGetString(POSITION_SYMBOL); TradeRequest.tp = NormalizeDouble(Take, Terminal.GetDigits()); TradeRequest.sl = NormalizeDouble(Stop, Terminal.GetDigits()); return OrderSend(TradeRequest, TradeResult); };第一个负责修改仍然开立的订单,另一个负责修改持仓。 虽然它们看起来是一样的,但事实并非如此。 该系统还有另一个重要函数:
bool RemoveOrderPendent(ulong Ticket) { ZeroMemory(TradeRequest); ZeroMemory(TradeResult); TradeRequest.action = TRADE_ACTION_REMOVE; TradeRequest.order = Ticket; return OrderSend(TradeRequest, TradeResult); };
带上最后一个函数,我们完成了 C_Router 类。 我们已经实现了基本系统,该系统涵盖了 MetaTrader 中正常支持的功能 — 由于交叉订单系统,我们不能再依赖平台的这种支持。 然而,该系统尚未完善。 我们需要添加一些其它东西,从而令系统能够真正工作。 此刻,如果有一笔订单,它将如下所示。 为完成下一步,这一步是必须的。
仔细查看上面的图片。 消息框显示开立的订单和已开仓的资产。 已交易的资产在 CHART TRADE 中显示。 请注意,它与消息框中所示的资产相同。 现在,我们来检查图表上显示的资产。 可以在图表窗口标题中检查其名称。 但这是完全不同的 — 它并非图表上的资产,但它是迷你指数历史,这意味着现在我们未使用 MetaTrader 5 内部系统,而是使用本文中讲述的交叉订单系统。 现在我们只有显示订单所在位置的功能。 但这还不够,因为我们希望有一个功能齐全的系统,允许通过交叉订单系统进行操作。 所以,我们需要一些其它的东西。 对于与订单移动相关的事件,这将在另一个类中实现。
新功能会放在 C_OrderView 类中
而 C_OrderView 对象类虽然能够完成少量事务,但它还不能处理持仓或挂单数据。 然而,当我们向其添加消息传递系统时,我们有更多的可能性来使用它。 这是我们目前唯一要在类中添加的内容。 完整的函数代码如下所示:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { ulong ticket; double price, pp, pt, ps; eHLineTrade hl; switch (id) { case CHARTEVENT_MOUSE_MOVE: MoveTo((int)lparam, (int)dparam, (uint)sparam); break; case CHARTEVENT_OBJECT_DELETE: if (GetNewInfosOrder(sparam, ticket, price, hl)) { if (OrderSelect(ticket)) { switch (hl) { case HL_PRICE: RemoveOrderPendent(ticket); break; case HL_STOP: ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0); break; case HL_TAKE: ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL)); break; } }else if (PositionSelectByTicket(ticket)) { switch (hl) { case HL_PRICE: ClosePosition(ticket); break; case HL_STOP: ModifyPosition(ticket, OrderGetDouble(ORDER_TP), 0); break; case HL_TAKE: ModifyPosition(ticket, 0, OrderGetDouble(ORDER_SL)); break; } } } break; case CHARTEVENT_OBJECT_CLICK: C_HLineTrade::Select(sparam); break; case CHARTEVENT_OBJECT_DRAG: if (GetNewInfosOrder(sparam, ticket, price, hl)) { price = AdjustPrice(price); if (OrderSelect(ticket)) switch(hl) { case HL_PRICE: pp = price - OrderGetDouble(ORDER_PRICE_OPEN); pt = OrderGetDouble(ORDER_TP); ps = OrderGetDouble(ORDER_SL); if (!ModifyOrderPendent(ticket, price, (pt > 0 ? pt + pp : 0), (ps > 0 ? ps + pp : 0))) UpdatePosition(); break; case HL_STOP: if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition(); break; case HL_TAKE: if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), price, OrderGetDouble(ORDER_SL))) UpdatePosition(); break; } if (PositionSelectByTicket(ticket)) switch (hl) { case HL_PRICE: UpdatePosition(); break; case HL_STOP: ModifyPosition(ticket, PositionGetDouble(POSITION_TP), price); break; case HL_TAKE: ModifyPosition(ticket, price, PositionGetDouble(POSITION_SL)); break; } }; break; } }这段代码完成了交叉订单系统。 我们的能力已有所增强,如此我们就可无需交叉订单系统做到几乎相同的事情。 一般来说,函数已经相当清楚。 但它含有一种不常见的事件类型,即 CHARTEVENT_OBJECT_DELETE。 当用户删除一行时,它将反映在图表和订单系统中,因此在开始从图表中删除行时务必非常小心。 当我们从图表中删除 EA 时,我们不需要担心,因为订单将保持不变,如以下动画所示:
但是,如果 EA 还在图表上,我们在从图表中删除行时必须非常小心,尤其是那些隐藏在对象列表中的行。 否则,当我们删除交叉订单系统创建的行时,您会在下面看到订单系统发生了什么。
为了完成系统演示,我们来看看当我们拖动价格线时,订单会发生什么变化。 请记住以下内容:拖动行之前必须先选择;如果未选中,则无法移动它。 当在图表上释放拖动行时,价格将发生变化,此前,价格会一直保持在之前相同的位置。
如果很难知道是否选中了一行,那么我们就要修改选择代码。 下面的高亮显示出这些修改。 此更改已在所附版本中实现。
inline void Select(const string &sparam) { int i0 = StringLen(def_NameHLineTrade); if (m_SelectObj != "") { ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false); ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_WIDTH, 1); } m_SelectObj = ""; if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade) { if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE)) { ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true); ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_WIDTH, 2); m_SelectObj = sparam; }; } }代码修改的结果如下图所示。
结束语
所以,我在此展示了如何在 MetaTrader 中创建交叉订单系统。 我希望这个系统能帮到任何渴望这些知识的人。 但请记住以下几点:在开始于实盘账户使用该系统交易之前,您理应在许多不同的市场场景中尽可能彻底地测试它,因为虽然该系统是在 MetaTrader 平台上实现的,但在错误处理方面几乎没有得到平台方面的支持,因此如果偶然发生错误,您必须迅速采取行动,以免遭受重大损失。 通过在不同场景中测试,您可能会发现问题出现的地方:在波动中,您的计算机可以处理的最大订单数量,分析系统的最大允许点差,开立订单的允许波动水平,因为分析开立订单和信息的数量越多,遭遇不测发生的可能性就越大。 这是因为每笔订单都是在系统接收每次即时报价时刻进行分析的,当同时开立多笔订单时,这可能会有问题。
我建议不要迷信该系统,除非您在一个有很多场景的演示帐户上完全测试过它。 即使代码看起来很完美,它也没有任何错误分析。
我附上目前智能交易系统的所有代码。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/10383
注意: 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.


恭喜你写出了这篇出色的文章,丹尼尔。
我认为唯一的问题是在年末,"CurrentSymbol "函数需要查找下一年的符号名称。在我看来,i1 的值总是返回当年的数字(22),但到了 12 月,我们已经开始使用以 23 结尾的符号了。
实际上,这个问题不会发生,这也是循环结束 .... 的原因。
只有满足这个条件,循环才会结束,而i1 的值将一直递增......因此,当年份改变时,资产将自动修改 ....
事实上,这个问题不会发生,其原因就是导致 TIE 结束....
只有达到这个突出显示的条件,循环才会结束,而i1 的值将一直递增...因此,当年份改变时,资产将自动修改 ....
你说得对。
我没有注意 i1 值递增那行。