
轻松快捷开发 MetaTrader 程序的函数库 (第二十六部分):处理延后交易请求 - 首次实现 (开仓)
内容
概念
我曾在之前的许多文章中提到了延后请求的概念。
在本文中,我们将理清它是什么,以及为什么需要它,并开始实现延后请求。
当接收和处理交易服务器错误时,有时我们需要等待并重复请求。 在最简单的情况下,这可通过执行 Sleep() 函数,等待必需的毫秒数,再重复请求来实现。 在许多程序中这已经足够。 不过,在等待期间,程序会停顿,并等待暂停完成,然后再接续其逻辑。 所有这些都是在交易方法里发生的,所有其他程序函数均不可访问。
为避免此缺陷,若导致错误的交易请求需要在等待后重复请求,则我们可以创建其副本,并将请求放置在交易请求列表中,然后退出交易方法。 如此,在交易方法内部的等待时,可令我们将程序从无所事事状态中解放出来,并根据内置逻辑接续其操作。 交易类持续查看延后请求的列表。 当轮到执行请求之时(等待时间已结束),函数库将采用对应的交易请求调用必要的交易方法。 然后一切都以相同的顺序发生 - 如果我们再次遇到错误,则在下一次交易请求之前退出交易方法。 如果该请求执行后未出错,并已在服务器上排队,则延后请求会从交易请求列表中删除。
这是应用延后请求的可能方式之一,令我们在等待时不必中断程序执行,尤其是等待需要花费一段时间的情况。
应用延后请求的另一种方式是在 MQL4 中实现 StopLimit(止价后限价)订单。 什么是 StopLimit(止价后限价)订单? 这是一个包含止价(Stop)和限价(Limit)单价格的双重挂单。 当价格达到止价(Stop)挂单设置的价位后,将设置一笔限价(Limit)挂单。 因此,延后请求令我们能够轻松实现 StopLimit 订单操作逻辑:我们只需创建一个下达限价挂单的延后请求,且将下单的时刻(应为发出请求的价位)写入延后请求的参数。 一旦价格达到指定数值,下达限价订单的请求会被发送到服务器。 如此逻辑完全重复 StopLimit 挂单的操作逻辑。
应用延后请求的另一种方式是,当价格达到指定价位,或指定时间,或两者皆有时,自动发送交易请求开仓。
通常,有很多选择可以根据给定条件自动执行发送交易请求的过程。
利用魔幻数字作为数据存储
当创建新的延后请求时,我们需要以某种方式对其进行标记,以便程序知道这笔确切订单是根据确切的延后请求放置的。 换言之,我们需要准确地识别与特定延后请求关联的订单或仓位。 甚或,这种关联应维持在紧急状况下。
我思考了很长时间来组织这种关联的各种可能性,并决定将延后请求 ID 设置在订单/仓位的魔幻数字之中。 这是唯一在订单初期即出现,并保持不变的参数。 所有其他方法要么不可靠(存储在订单/仓位注释中),要么需要扩展资源来创建和维护(存储在文件中)。 我不曾考虑终端全局变量,因为在紧急情况下也许仍然没有足够的时间来写入,所以它也不能完全保证数据相关性。
我能看到的只有一种解决方案 — 将数据存储在魔幻数字值之中。 以前,我们将组 ID 添加到订单对象里。 该 ID 允许将订单/仓位分组为一个公共组,这对于各种 EA(例如,网格 EA)很有用。 仅在订单对象物理性出现在服务器上之后,我们才能将其添加到订单对象中。 而它在紧急情况下会丢失。 当然,所有集合随同其对象都会保存到硬盘,但此动作很靠后才会完成。 现在,我们可以将组 ID 与延后请求 ID 一起添加到订单魔幻数字之中。
我们能够在魔幻数字值中存储若干个 ID:
- 魔幻数字 ID(在 EA 输入中设置)
- 第一个组 ID(子组编号从 0 到 15,零 — 不属于该组)
- 第二个组 ID(子组编号从 0 到 15,零 — 不属于该组)
- 延后请求 ID(可能的数字从 0 到 255,零 — 无 ID)
因此,我们将能够设置第一和第二订单组的数字。 每个订单组最多可以包含十五个子组。 这对我们有什么用? 假设我们有 20 笔订单/持仓,我们希望根据特定条件将其分组到一个子组。 我们在第一组中将它们的子组编号设为 1。 此外,我们还有另外 20 笔订单/仓位,我们希望根据其他标准将它们分组到相同第一组的另一个子组之中。 我们为它们设置了子组编号 2。 再另外的 20 笔订单/仓位分配为子组 3。 现在我们有了三组订单/仓位,我们可以利用处理程序轻松地一并处理第一组当中的每个子组。 如果我们需要利用其他处理程序以其他方式处理/分析三组中的两个组,且不丢失已建立的分组隶属关系,则我们可以将它们分配到第二组(最多 15 个子组)。 与单一分组相比,将订单/仓位组合到不同的分组中,为我们提供了更多的灵活性,尽管子组编号数量更大。
正如我们所见,我们已经做了很多计划,但这有什么收获呢? 问题在于,用于存储 MQL4的魔幻数字整数值的大小仅为 4 个字节(int)。 所以,我们在自定义程序中必须牺牲设置魔幻数字值。 对于 MQL5,魔幻数字大小设置为 ulong 类型。 我们能够在那处设置更大的数值,并存储更多的数据。 但是尚有一个兼容性问题,这意味着我们必须牺牲一些东西,即魔幻数字大小 — 它仅等于两个字节(ushort),而释放的两个字节如此将分配:两个组的 ID(uchar),和挂单 ID(uchar)。
下表展示了魔幻数字的结构和其内部 uint 类型数据的位置:
------------------------------------------------------------------------- | bit |31 24|23 20|19 16|15 8|7 0| ------------------------------------------------------------------------- | byte | 3 | 2 | 1 | 0 | ------------------------------------------------------------------------- | type | uchar | uchar | ushort | ------------------------------------------------------------------------- | descr | request id | id2 | id1 | magic | -------------------------------------------------------------------------
从表中可以我们可见,
- 魔幻数字值的大小为 2 个字节,并存储在 uint 类型的索引 0 和 1 两个低位字节中(字节 0 — 15),对应于 ushort 类型。 我们将不得不在程序中使用此魔幻数字类型,其可能值介于 0 到 65535 之间。
- 层次结构中的下一个是 uint 数字的字节 2,存储两个组的 ID,其大小为 uchar(字节16 — 23)。
第一个组 ID 存储在 uchar 数字的低四位 (位 16 — 19),而第二个组 ID 存储在该 uchar 数字的高四位 (位 20 — 23)。
因此,我们可以将两个组保存在一个字节的 uchar 值中,其中每个组的编号可以从零(无组)到 15(在四个位中可存储的最大值)之间变化。 - 在第三个和最后一个 uint 数字字节中,我们将存储延后请求 ID 的 uchar 值 (位 24 — 31),其值范围可能从零(无 ID)到 255。 这意味着我们也许同时有高达 255 个活动的延后请求。
我们来改进抽象订单类和事件类,以便将数据存储在“魔幻数字”属性值中。
但首先,在 Defines.mqh 中,为抽象订单对象添加新的整数型属性:
//+------------------------------------------------------------------+ //| Order, deal, position integer properties | //+------------------------------------------------------------------+ enum ENUM_ORDER_PROP_INTEGER { ORDER_PROP_TICKET = 0, // Order ticket ORDER_PROP_MAGIC, // Real order magic number ORDER_PROP_TIME_OPEN, // Open time in milliseconds (MQL5 Deal time) ORDER_PROP_TIME_CLOSE, // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE) ORDER_PROP_TIME_EXP, // Order expiration date (for pending orders) ORDER_PROP_STATUS, // Order status (from the ENUM_ORDER_STATUS enumeration) ORDER_PROP_TYPE, // Order/deal type ORDER_PROP_REASON, // Deal/order/position reason or source ORDER_PROP_STATE, // Order status (from the ENUM_ORDER_STATE enumeration) ORDER_PROP_POSITION_ID, // Position ID ORDER_PROP_POSITION_BY_ID, // Opposite position ID ORDER_PROP_DEAL_ORDER_TICKET, // Ticket of the order that triggered a deal ORDER_PROP_DEAL_ENTRY, // Deal direction – IN, OUT or IN/OUT ORDER_PROP_TIME_UPDATE, // Position change time in milliseconds ORDER_PROP_TICKET_FROM, // Parent order ticket ORDER_PROP_TICKET_TO, // Derived order ticket ORDER_PROP_PROFIT_PT, // Profit in points ORDER_PROP_CLOSE_BY_SL, // Flag of closing by StopLoss ORDER_PROP_CLOSE_BY_TP, // Flag of closing by TakeProfit ORDER_PROP_MAGIC_ID, // Order's "magic number" ID ORDER_PROP_GROUP_ID1, // First order/position group ID ORDER_PROP_GROUP_ID2, // Second order/position group ID ORDER_PROP_PEND_REQ_ID, // Pending request ID ORDER_PROP_DIRECTION, // Type by direction (Buy, Sell) }; #define ORDER_PROP_INTEGER_TOTAL (24) // Total number of integer properties #define ORDER_PROP_INTEGER_SKIP (0) // Number of order properties not used in sorting //+------------------------------------------------------------------+
这些属性存储先前描述的 ID。 ID 将存储在魔幻数字值当中。 由于我们添加了三个新的属性,并更改了一个,因此在相应的宏替换中将整数型属性总数从 21 更改为 24。
还有,按这些属性的排序添加到可能的订单和交易排序标准的枚举之中:
//+------------------------------------------------------------------+ //| Possible criteria of sorting orders and deals | //+------------------------------------------------------------------+ #define FIRST_ORD_DBL_PROP (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP) #define FIRST_ORD_STR_PROP (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP) enum ENUM_SORT_ORDERS_MODE { //--- Sort by integer properties SORT_BY_ORDER_TICKET = 0, // Sort by an order ticket SORT_BY_ORDER_MAGIC, // Sort by an order magic number SORT_BY_ORDER_TIME_OPEN, // Sort by an order open time in milliseconds SORT_BY_ORDER_TIME_CLOSE, // Sort by an order close time in milliseconds SORT_BY_ORDER_TIME_EXP, // Sort by an order expiration date SORT_BY_ORDER_STATUS, // Sort by an order status (market order/pending order/deal/balance and credit operation) SORT_BY_ORDER_TYPE, // Sort by an order type SORT_BY_ORDER_REASON, // Sort by a deal/order/position reason/source SORT_BY_ORDER_STATE, // Sort by an order status SORT_BY_ORDER_POSITION_ID, // Sort by a position ID SORT_BY_ORDER_POSITION_BY_ID, // Sort by an opposite position ID SORT_BY_ORDER_DEAL_ORDER, // Sort by the order a deal is based on SORT_BY_ORDER_DEAL_ENTRY, // Sort by a deal direction – IN, OUT or IN/OUT SORT_BY_ORDER_TIME_UPDATE, // Sort by position change time in seconds SORT_BY_ORDER_TICKET_FROM, // Sort by a parent order ticket SORT_BY_ORDER_TICKET_TO, // Sort by a derived order ticket SORT_BY_ORDER_PROFIT_PT, // Sort by order profit in points SORT_BY_ORDER_CLOSE_BY_SL, // Sort by the flag of closing an order by StopLoss SORT_BY_ORDER_CLOSE_BY_TP, // Sort by the flag of closing an order by TakeProfit SORT_BY_ORDER_MAGIC_ID, // Sort by an order/position "magic number" ID SORT_BY_ORDER_GROUP_ID1, // Sort by the first order/position group ID SORT_BY_ORDER_GROUP_ID2, // Sort by the second order/position group ID SORT_BY_ORDER_PEND_REQ_ID, // Sort by a pending request ID SORT_BY_ORDER_DIRECTION, // Sort by direction (Buy, Sell) //--- Sort by real properties SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP, // Sort by open price SORT_BY_ORDER_PRICE_CLOSE, // Sort by close price SORT_BY_ORDER_SL, // Sort by StopLoss price SORT_BY_ORDER_TP, // Sort by TakeProfit price SORT_BY_ORDER_PROFIT, // Sort by profit SORT_BY_ORDER_COMMISSION, // Sort by commission SORT_BY_ORDER_SWAP, // Sort by swap SORT_BY_ORDER_VOLUME, // Sort by volume SORT_BY_ORDER_VOLUME_CURRENT, // Sort by unexecuted volume SORT_BY_ORDER_PROFIT_FULL, // Sort by profit+commission+swap SORT_BY_ORDER_PRICE_STOP_LIMIT, // Sort by Limit order when StopLimit order is activated //--- Sort by string properties SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP, // Sort by symbol SORT_BY_ORDER_COMMENT, // Sort by comment SORT_BY_ORDER_COMMENT_EXT, // Sort by custom comment SORT_BY_ORDER_EXT_ID // Sort by order ID in an external trading system }; //+------------------------------------------------------------------+
若要在日志中正确显示订单属性,请在 Datas.mqh 文件中添加新属性的索引和相应的消息:
MSG_ORD_PROFIT_PT, // Profit in points MSG_ORD_MAGIC_ID, // Magic number ID MSG_ORD_GROUP_ID1, // First group ID MSG_ORD_GROUP_ID2, // Second group ID MSG_ORD_PEND_REQ_ID, // Pending request ID MSG_ORD_PRICE_OPEN, // Open price {"Прибыль в пунктах","Profit in points"}, {"Идентификатор магического номера","Magic number's identifier"}, {"Идентификатор первой группы","First group's identifier"}, {"Идентификатор второй группы","Second group's identifier"}, {"Идентификатор отложенного запроса","Pending request's identifier"}, {"Цена открытия","Price open"},
在 Order.mqh 文件的抽象订单类中加入必要的修改。
在类的私密部分,加入四个方法,从订单属性值里提取“魔幻数字”,并返回魔幻数字的 ID (程序设置里设定的魔幻数值),第一组和第二组的 ID,和延后请求 ID:
//+------------------------------------------------------------------+ //| Abstract order class | //+------------------------------------------------------------------+ class COrder : public CObject { private: ulong m_ticket; // Selected order/deal ticket (MQL5) long m_long_prop[ORDER_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[ORDER_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[ORDER_PROP_STRING_TOTAL]; // String properties //--- Return the index of the array the order's (1) double and (2) string properties are located at int IndexProp(ENUM_ORDER_PROP_DOUBLE property) const { return(int)property-ORDER_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_ORDER_PROP_STRING property) const { return(int)property-ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_DOUBLE_TOTAL; } //--- Data location in the magic number int value //----------------------------------------------------------- // bit 32|31 24|23 16|15 8|7 0| //----------------------------------------------------------- // byte | 3 | 2 | 1 | 0 | //----------------------------------------------------------- // data | uchar | uchar | ushort | //----------------------------------------------------------- // descr |pend req id| id2 | id1 | magic | //----------------------------------------------------------- //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(void) const { return ushort(this.Magic() & 0xFFFF); } uchar GetGroupID1(void) const { return uchar(this.Magic()>>16) & 0x0F; } uchar GetGroupID2(void) const { return uchar((this.Magic()>>16) & 0xF0)>>4; } uchar GetPendReqID(void) const { return uchar(this.Magic()>>24) & 0xFF; } public: //--- Default constructor COrder(void){;}
若要从订单魔幻数字的 uint 值返回 ushort 魔幻数字值,请用掩码(0xFFFF),仅保留 uint 数字的两个低位字节不变,而 uint 数字的两个高位字节则被清零。 将 uint 转换为 ushort 时,高位两个字节将自动被舍弃。
为了提取第一个组 ID,我们首先需要将魔幻数字属性右移 16 位(如此,组 ID 的 uchar 值就转移到 uint 的零号字节)。 然后得到的数字用 0x0F 掩码处理。 掩码仅保留移位期间所得数值的低四位。 将 uint 转换为 uchar 会舍弃该数字的所有高位字节,并用掩码只保留低字节。 因此,我们得到四位(bit)数值从 0 到 15。
提取第二个组 ID有所不同,因为所要数值位于 uchar 值的高四位当中。 所以,我们首先执行提取第一个组 ID 时得相同操作 — 将魔幻数字属性值右移 16 位(令组 ID 的 uchar 值位于 uint 数值的零号字节上),并用掩码 0xF0 处理所得的数字。 掩码仅保留移位期间所得值的高四位。 接下来,将获得的值再次右移四位,以便将存储 ID 数字的高位调整为 0 和 15。
为了提取延后请求 ID,将 uint 数字右移 24 位,如此一字节的 uchar 值就转移到 uint 数字得零号位,并用 0xFF 掩码处理(实际上,这无必要,因为将 uint 数字转换为 uchar 类型时低位的单字节依然被保留)。
在方法模块中添加返回四个新属性的方法,以便简化访问抽象订单对象属性:
//+------------------------------------------------------------------+ //| Methods of a simplified access to the order object properties | //+------------------------------------------------------------------+ //--- Return (1) ticket, (2) parent order ticket, (3) derived order ticket, (4) magic number, (5) order reason, //--- (6) position ID, (7) opposite position ID, (8) first group ID, (9) second group ID, //--- (10) pending request ID, (11) magic number ID, (12) type, (13) flag of closing by StopLoss, //--- (14) flag of closing by TakeProfit (15) open time, (16) close time, //--- (17) order expiration date, (18) state, (19) status, (20) type by direction long Ticket(void) const { return this.GetProperty(ORDER_PROP_TICKET); } long TicketFrom(void) const { return this.GetProperty(ORDER_PROP_TICKET_FROM); } long TicketTo(void) const { return this.GetProperty(ORDER_PROP_TICKET_TO); } long Magic(void) const { return this.GetProperty(ORDER_PROP_MAGIC); } long Reason(void) const { return this.GetProperty(ORDER_PROP_REASON); } long PositionID(void) const { return this.GetProperty(ORDER_PROP_POSITION_ID); } long PositionByID(void) const { return this.GetProperty(ORDER_PROP_POSITION_BY_ID); } long MagicID(void) const { return this.GetProperty(ORDER_PROP_MAGIC_ID); } long GroupID1(void) const { return this.GetProperty(ORDER_PROP_GROUP_ID1); } long GroupID2(void) const { return this.GetProperty(ORDER_PROP_GROUP_ID2); } long PendReqID(void) const { return this.GetProperty(ORDER_PROP_PEND_REQ_ID); } long TypeOrder(void) const { return this.GetProperty(ORDER_PROP_TYPE); } bool IsCloseByStopLoss(void) const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL); } bool IsCloseByTakeProfit(void) const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP); } long TimeOpen(void) const { return this.GetProperty(ORDER_PROP_TIME_OPEN); } long TimeClose(void) const { return this.GetProperty(ORDER_PROP_TIME_CLOSE); } datetime TimeExpiration(void) const { return (datetime)this.GetProperty(ORDER_PROP_TIME_EXP); } ENUM_ORDER_STATE State(void) const { return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE); } ENUM_ORDER_STATUS Status(void) const { return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS); } ENUM_ORDER_TYPE TypeByDirection(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); }
另外,加入三种方法来设置抽象订单的新属性:
//--- Get the full order profit double ProfitFull(void) const { return this.Profit()+this.Comission()+this.Swap(); } //--- Get order profit in points int ProfitInPoints(void) const; //--- Set (1) the first group ID, (2) the second group ID, (3) the pending request ID, (4) custom comment void SetGroupID1(const long group_id) { this.SetProperty(ORDER_PROP_GROUP_ID1,group_id); } void SetGroupID2(const long group_id) { this.SetProperty(ORDER_PROP_GROUP_ID2,group_id); } void SetPendReqID(const long req_id) { this.SetProperty(ORDER_PROP_PEND_REQ_ID,req_id); } void SetCommentExt(const string comment_ext) { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext); } //+------------------------------------------------------------------+
在封闭的类构造函数中,利用上述方法按 ID 值填写新的属性字段:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) { //--- Save integer properties this.m_ticket=ticket; this.m_long_prop[ORDER_PROP_STATUS] = order_status; this.m_long_prop[ORDER_PROP_MAGIC] = this.OrderMagicNumber(); this.m_long_prop[ORDER_PROP_TICKET] = this.OrderTicket(); this.m_long_prop[ORDER_PROP_TIME_EXP] = this.OrderExpiration(); this.m_long_prop[ORDER_PROP_TYPE] = this.OrderType(); this.m_long_prop[ORDER_PROP_STATE] = this.OrderState(); this.m_long_prop[ORDER_PROP_DIRECTION] = this.OrderTypeByDirection(); this.m_long_prop[ORDER_PROP_POSITION_ID] = this.OrderPositionID(); this.m_long_prop[ORDER_PROP_REASON] = this.OrderReason(); this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET] = this.DealOrderTicket(); this.m_long_prop[ORDER_PROP_DEAL_ENTRY] = this.DealEntry(); this.m_long_prop[ORDER_PROP_POSITION_BY_ID] = this.OrderPositionByID(); this.m_long_prop[ORDER_PROP_TIME_OPEN] = this.OrderOpenTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_CLOSE] = this.OrderCloseTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_UPDATE] = this.PositionTimeUpdateMSC(); //--- Save real properties this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)] = this.OrderOpenPrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)] = this.OrderClosePrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)] = this.OrderProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)] = this.OrderCommission(); this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)] = this.OrderSwap(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)] = this.OrderVolume(); this.m_double_prop[this.IndexProp(ORDER_PROP_SL)] = this.OrderStopLoss(); this.m_double_prop[this.IndexProp(ORDER_PROP_TP)] = this.OrderTakeProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)] = this.OrderVolumeCurrent(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)] = this.OrderPriceStopLimit(); //--- Save string properties this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)] = this.OrderSymbol(); this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)] = this.OrderComment(); this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)] = this.OrderExternalID(); //--- Save additional integer properties this.m_long_prop[ORDER_PROP_PROFIT_PT] = this.ProfitInPoints(); this.m_long_prop[ORDER_PROP_TICKET_FROM] = this.OrderTicketFrom(); this.m_long_prop[ORDER_PROP_TICKET_TO] = this.OrderTicketTo(); this.m_long_prop[ORDER_PROP_CLOSE_BY_SL] = this.OrderCloseByStopLoss(); this.m_long_prop[ORDER_PROP_CLOSE_BY_TP] = this.OrderCloseByTakeProfit(); this.m_long_prop[ORDER_PROP_MAGIC_ID] = this.GetMagicID(); this.m_long_prop[ORDER_PROP_GROUP_ID1] = this.GetGroupID1(); this.m_long_prop[ORDER_PROP_GROUP_ID2] = this.GetGroupID2(); this.m_long_prop[ORDER_PROP_PEND_REQ_ID] = this.GetPendReqID(); //--- Save additional real properties this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)] = this.ProfitFull(); //--- Save additional string properties this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)] = ""; } //+------------------------------------------------------------------+
在返回整数型属性描述的方法中加入显示抽象订单所有新属性描述:
//+------------------------------------------------------------------+ //| Return description of an order's integer property | //+------------------------------------------------------------------+ string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property) { return ( //--- General properties property==ORDER_PROP_MAGIC ? CMessage::Text(MSG_ORD_MAGIC)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET ? CMessage::Text(MSG_ORD_TICKET)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_FROM ? CMessage::Text(MSG_ORD_TICKET_FROM)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_TO ? CMessage::Text(MSG_ORD_TICKET_TO)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_EXP ? CMessage::Text(MSG_ORD_TIME_EXP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : (this.GetProperty(property)==0 ? CMessage::Text(MSG_LIB_PROP_NOT_SET)+": "+CMessage::Text(MSG_LIB_PROP_NOT_SET) : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)) ) : property==ORDER_PROP_TYPE ? CMessage::Text(MSG_ORD_TYPE)+": "+this.TypeDescription() : property==ORDER_PROP_DIRECTION ? CMessage::Text(MSG_ORD_TYPE_BY_DIRECTION)+": "+this.DirectionDescription() : property==ORDER_PROP_REASON ? CMessage::Text(MSG_ORD_REASON)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetReasonDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_ID ? CMessage::Text(MSG_ORD_POSITION_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ORDER_TICKET ? CMessage::Text(MSG_ORD_DEAL_ORDER_TICKET)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ENTRY ? CMessage::Text(MSG_ORD_DEAL_ENTRY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetEntryDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_BY_ID ? CMessage::Text(MSG_ORD_POSITION_BY_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_OPEN ? CMessage::Text(MSG_ORD_TIME_OPEN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_CLOSE ? CMessage::Text(MSG_ORD_TIME_CLOSE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_UPDATE ? CMessage::Text(MSG_ORD_TIME_UPDATE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0") ) : property==ORDER_PROP_STATE ? CMessage::Text(MSG_ORD_STATE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": \""+this.StateDescription()+"\"" ) : //--- Additional property property==ORDER_PROP_STATUS ? CMessage::Text(MSG_ORD_STATUS)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": \""+this.StatusDescription()+"\"" ) : property==ORDER_PROP_PROFIT_PT ? ( this.Status()==ORDER_STATUS_MARKET_PENDING ? CMessage::Text(MSG_ORD_DISTANCE_PT) : CMessage::Text(MSG_ORD_PROFIT_PT) )+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_CLOSE_BY_SL ? CMessage::Text(MSG_LIB_PROP_CLOSE_BY_SL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : property==ORDER_PROP_CLOSE_BY_TP ? CMessage::Text(MSG_LIB_PROP_CLOSE_BY_TP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : property==ORDER_PROP_MAGIC_ID ? CMessage::Text(MSG_ORD_MAGIC_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_GROUP_ID1 ? CMessage::Text(MSG_ORD_GROUP_ID1)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_GROUP_ID2 ? CMessage::Text(MSG_ORD_GROUP_ID2)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_PEND_REQ_ID ? CMessage::Text(MSG_ORD_PEND_REQ_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : "" ); } //+------------------------------------------------------------------+
抽象订单类已准备就绪。 现在我们需要更改事件类。
在 Event.mqh 文件的抽象事件类中,将返回新 ID 的方法添加到类的受保护部分:
protected: ENUM_TRADE_EVENT m_trade_event; // Trading event bool m_is_hedge; // Hedge account flag long m_chart_id; // Control program chart ID int m_digits; // Symbol's Digits() int m_digits_acc; // Number of decimal places for the account currency long m_long_prop[EVENT_PROP_INTEGER_TOTAL]; // Event integer properties double m_double_prop[EVENT_PROP_DOUBLE_TOTAL]; // Event real properties string m_string_prop[EVENT_PROP_STRING_TOTAL]; // Event string properties //--- return the flag presence in the trading event bool IsPresentEventFlag(const int event_code) const { return (this.m_event_code & event_code)==event_code; } //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(void) const { return ushort(this.Magic() & 0xFFFF); } uchar GetGroupID1(void) const { return uchar(this.Magic()>>16) & 0x0F; } uchar GetGroupID2(void) const { return uchar((this.Magic()>>16) & 0xF0)>>4; } uchar GetPendReqID(void) const { return uchar(this.Magic()>>24) & 0xFF; } //--- Protected parametric constructor CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
这些方法与上述抽象订单类方法类似。
现在,从抽象事件类派生的五个类中(在文件 EventModify.mqh, EventOrderPlaced.mqh,EventOrderRemoved.mqh,EventPositionClose.mqh 和 EventPositionOpen.mqh),即在事件简要描述的方法中,替换单个字符串
//+------------------------------------------------------------------+ //| Create and return a short event message | //+------------------------------------------------------------------+ string CEventModify::EventsMessage(void) { //--- (1) header, (2) magic number string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic() : ""); string text="";
为每个类添加以下字符串:
//+------------------------------------------------------------------+ //| Create and return a short event message | //+------------------------------------------------------------------+ string CEventModify::EventsMessage(void) { //--- (1) header, (2) magic number string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string magic_id=((this.GetPendReqID()>0 || this.GetGroupID1()>0 || this.GetGroupID2()>0) ? " ("+(string)this.GetMagicID()+")" : ""); string group_id1=(this.GetGroupID1()>0 ? ", G1: "+(string)this.GetGroupID1() : ""); string group_id2=(this.GetGroupID2()>0 ? ", G2: "+(string)this.GetGroupID2() : ""); string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic()+magic_id+group_id1+group_id2 : ""); string text="";
当在单一的魔幻数字值中存储多个数据时,在日志中显示该数字时会出现一个完全不同的值(不是程序设置中所设值),因为魔幻数字仅存储在两个低位字节中,而组和延后请求 ID 存储在两个高位字节中。 因此,如果在魔幻数字值中添加了 ID(或其中至少一个),则在日志中显示每个单独 ID 值相应的描述。
我们为了将数据存储在魔幻数字值中,已实现了所有必要的修改。 现在,我们移到延后请求类,并在开仓出错的情况下生成延后请求的第一个实现。
延后请求类,首次实现
在 Trading.mqh 交易类文件中,于 CTrading 交易类主体之前,添加描述延后请求对象的新类:
//+------------------------------------------------------------------+ //| Pending request object class | //+------------------------------------------------------------------+ class CPendingReq : public CObject { private: MqlTradeRequest m_request; // Trade request structure uchar m_id; // Trading request ID int m_retcode; // Result a request is based on double m_price_create; // Price at the moment of a request generation ulong m_time_create; // Request generation time ulong m_time_activate; // Next attempt activation time ulong m_waiting_msc; // Waiting time between requests uchar m_current_attempt; // Current attempt index uchar m_total_attempts; // Number of attempts //--- Copy trading request data void CopyRequest(const MqlTradeRequest &request) { this.m_request=request; } //--- Compare CPendingReq objects by IDs virtual int Compare(const CObject *node,const int mode=0) const; public: //--- Return (1) the request structure, (2) the price at the moemnt of the request generation, //--- (3) request generation time, (4) current attempt time, //--- (5) waiting time between requests, (6) current attempt index, //--- (7) number of attempts, (8) request ID MqlTradeRequest MqlRequest(void) const { return this.m_request; } double PriceCreate(void) const { return this.m_price_create; } ulong TimeCreate(void) const { return this.m_time_create; } ulong TimeActivate(void) const { return this.m_time_activate; } ulong WaitingMSC(void) const { return this.m_waiting_msc; } uchar CurrentAttempt(void) const { return this.m_current_attempt; } uchar TotalAttempts(void) const { return this.m_total_attempts; } uchar ID(void) const { return this.m_id; } //--- Set (1) the price when creating a request, (2) request creation time, //--- (3) current attempt time, (4) waiting time between requests, //--- (5) current attempt index, (6) number of attempts, (7) request ID void SetPriceCreate(const double price) { this.m_price_create=price; } void SetTimeCreate(const ulong time) { this.m_time_create=time; } void SetTimeActivate(const ulong time) { this.m_time_activate=time; } void SetWaitingMSC(const ulong miliseconds) { this.m_waiting_msc=miliseconds; } void SetCurrentAttempt(const uchar number) { this.m_current_attempt=number; } void SetTotalAttempts(const uchar number) { this.m_total_attempts=number; } void SetID(const uchar id) { this.m_id=id; } //--- Constructors CPendingReq(void){;} CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode); }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CPendingReq::CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode) : m_price_create(price), m_time_create(time), m_id(id), m_retcode(retcode) { this.CopyRequest(request); } //+------------------------------------------------------------------+ //| Compare CPendingReq objects by IDs | //+------------------------------------------------------------------+ int CPendingReq::Compare(const CObject *node,const int mode=0) const { const CPendingReq *compared_req=node; return(this.ID()>compared_req.ID() ? 1 : this.ID()<compared_req.ID() ? -1 : 0); return 0; } //+------------------------------------------------------------------+
我相信,这个类很简单。 无需说明其中注释掉的代码。 所有方法名称和类成员变量都一目了然。 不过,我认为我应该解释一下该对象、相关方法和交易类功能应如何工作。
当收到来自服务器的错误时,我们要创建另一个服务器请求,并退出交易方法。 当新创建的请求的等待时间已过,它将被再次发送到服务器。 如果再次收到错误,我们似乎需要创建一个延后请求。 但是,第一次收到服务器错误时它已生成了。 所以,在接收到的交易请求的魔幻数字中检查是否存在延后请求 ID。 如果存在该请求,则该请求已经创建,且尝试在某个时刻发送到服务器,这意味着不需要新的请求。 如果交易请求魔幻数字没有 ID,则会生成一个具有最低 ID 的新延后请求,并退出交易方法,释放该程序去执行其他操作。
交易请求列表始终可以在交易类计时器中看到。 如果下一个请求的等待时间到了,将从计时器中调用相应的交易方法。 当依次检查来自延后请求列表中的请求时,会检查在场订单和仓位列表中是否存在相应的仓位或订单。 如果存在符合当前 ID 的订单或仓位,则延后订单已完成其使命,应将其从请求列表中删除。
我们开始实现。
我们已经将该类添加到 Trading.mqh 文件中。
现在,在其私密部分中声明搜索并返回第一个最低的未使用的延后请求 ID 的方法:
//--- Look for the first free pending request ID int GetFreeID(void); public: //--- Constructor CTrading();
我们在类主体之外编写其实现:
//+------------------------------------------------------------------+ //| Look for the first free pending request ID | //+------------------------------------------------------------------+ int CTrading::GetFreeID(void) { int id=WRONG_VALUE; CPendingReq *element=new CPendingReq(); if(element==NULL) return 0; for(int i=1;i<256;i++) { element.SetID((uchar)i); this.m_list_request.Sort(); if(this.m_list_request.Search(element)==WRONG_VALUE) { id=i; break; } } delete element; return id; } //+------------------------------------------------------------------+
我们也许总共有 255 个独立的延后请求。 每个请求都有其自己的属性,在两次交易尝试之间都有自己的等待时间,并且有自己的延后请求对象生存期。 有关这点,可能存在这样的情况,即请求 ID 已用到 255 的数字,而数字 0 或 1 或任何较低的 ID 却已被释放,并可以用于新的交易请求。 该方法用于搜索释放的最低 ID 编号。
首先创建临时的延后请求类对象且其 ID 值为 -1。 该值表明没有可用的 ID,所有 255 都被占用,而值 0 表示临时对象创建错误。 进而,按仓位 ID 索引,从 1 到 255 进行循环,检查是否存在与延后请求列表中当前循环索引处请求对象 ID 相等的请求对象。 为此,我们首先设置 ID 等于临时对象的循环索引号,设置列表的排序列表标志,并简单地在列表中查找具有相同 ID 的请求对象。 换句话说,我们寻找与临时对象相等的请求对象,并为其设置循环索引作为 ID。 如果在列表中未找到这样的对象,则方法返回的值设为循环索引,并中断循环。。
完成循环后,删除临时请求对象,然后返回 ID 值,该值可以是 -1,也可以是 1 到 255。
在类的公开部分中,声明创建延后请求的方法,添加方法用来在“魔幻数字”订单/仓位属性值里设置/返回 ID 值:
//--- Create a pending request bool CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj); //--- Data location in the magic number int value //----------------------------------------------------------- // bit 32|31 24|23 16|15 8|7 0| //----------------------------------------------------------- // byte | 3 | 2 | 1 | 0 | //----------------------------------------------------------- // data | uchar | uchar | ushort | //----------------------------------------------------------- // descr |pend req id| id2 | id1 | magic | //----------------------------------------------------------- //--- Set the ID of the (1) first group, (2) second group, (3) pending request to the magic number value void SetGroupID1(const uchar group,uint &magic) { magic &=0xFFF0FFFF; magic |= uint(ConvToXX(group,0)<<16); } void SetGroupID2(const uchar group,uint &magic) { magic &=0xFF0FFFFF; magic |= uint(ConvToXX(group,1)<<16); } void SetPendReqID(const uchar id,uint &magic) { magic &=0x00FFFFFF; magic |= (uint)id<<24; } //--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones) uchar ConvToXX(const uchar number,const uchar index) const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));} //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(const uint magic) const { return ushort(magic & 0xFFFF); } uchar GetGroupID1(const uint magic) const { return uchar(magic>>16) & 0x0F; } uchar GetGroupID2(const uint magic) const { return uchar((magic>>16) & 0xF0)>>4; } uchar GetPendReqID(const uint magic) const { return uchar(magic>>24) & 0xFF; }
我们已经研究过返回值的方法。 此处使用相同的内容。 我们研究设置不同 ID 的方法。
由于两个组 ID 存储在一个字节中,并且数字 ID 值只能取 0 到 15(4 位)的值,因此我们需要将其值左移 4 位以便设置第二个组 ID。 这将令我们能够将其存储在一个字节数字的高四位中。 这是利用 ConvToXX() 方法完成的。 根据组索引(0 或 1),它要么将传递给它的数字(0-15)左移 4 位(第二组,索引 1),要么不移位(第一组,索引 0)
为了设置第一个组 ID 值,我们首先需要将一个字节里保存 ID 值的低四位重置。 这可通过应用掩码于魔幻数字值来完成。 掩码 F 数值用于半字节(4位)。
换句话说,十进制数 15(F)的十六进制值,可用来保留数位不变,而用零则是将数位清零。 因此,应用于魔幻数字值的掩码如下: 0x FFF0FFFF。
其中:
- FFFF — 保留魔幻数字 ID(在 EA 设置中预设的魔幻数字),
- F0 — 删除(0)存储组 ID 的字节的低四位,而高位保留设置为(F)— 第二组 ID 存储在此处,
- FF — 保留延后请求 ID 值
接下来,从 ConvToXX() 方法获得的组号放置在索引为 0 的位置,并将字节左移 16 位准备存储组 ID,如此所获得的组号即可存储到所需字节的相应位置。
若要设置第二组 ID 值,重置字节的高四位,此处将保存 ID 值。 我们通过将 0xFF0FFFFF 掩码应用于魔幻数字上来实现。
其中:
- FFFF — 保留魔幻数字 ID(在 EA 设置中预设的魔幻数字),
- 0F — 删除(0)存储组 ID的字节高四位,而低位保持设置为(F)— 第一个组 ID存储在此处,
- FF — 保留延后请求 ID 值
接下来,从 ConvToXX() 方法获得的组号放置在索引为 1 的位置,并将字节左移 16 位准备存储组 ID,如此所获得的组号即可存储到所需字节的相应位置。
为了设置延后请求 ID 值,重置字节值,并保存 ID 值。 我们通过将 0x00FFFFFF 掩码应用于魔幻数字上来实现。
其中:
- FFFF — 保留魔幻数字 ID(在 EA 设置中预设的魔幻数字),
- FF — 保留组 ID 值,
- 00 — 删除延后请求 ID 值
接下来,将 ID 的 uchar 值左移 24 位,以便在字节里存储延后请求 ID,如此获得的数字即可在字节所需位置存储延后请求 ID。
在类主体之外创建延后请求对象的方法实现:
//+------------------------------------------------------------------+ //| Create a pending request | //+------------------------------------------------------------------+ bool CTrading::CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj) { //--- Create a new pending request object CPendingReq *req_obj=new CPendingReq(id,symbol_obj.Bid(),symbol_obj.Time(),request,retcode); if(req_obj==NULL) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); return false; } //--- If failed to add the request to the list, display the appropriate message, //--- remove the created object and return 'false' if(!this.m_list_request.Add(req_obj)) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); delete req_obj; return false; } //--- Filled in the fields of a successfully created object by the values passed to the method req_obj.SetTimeActivate(symbol_obj.Time()+wait); req_obj.SetWaitingMSC(wait); req_obj.SetCurrentAttempt(0); req_obj.SetTotalAttempts(attempts); return true; } //+------------------------------------------------------------------+
该方法很简单 — 创建一个新的请求对象,并将其添加到延后请求列表中。 传递给该方法的数值将添加到对象字段(计算请求激活的时间,则是请求创建时间+等待时间),并返回 true。 否则,该方法返回 false。
我们在上一篇文章中,在交易类里开发了计时器,添加了处理延后请求的逻辑:
//+------------------------------------------------------------------+ //| Timer | //+------------------------------------------------------------------+ void CTrading::OnTimer(void) { //--- In a loop by the list of pending requests int total=this.m_list_request.Total(); for(int i=total-1;i>WRONG_VALUE;i--) { //--- receive the next request object CPendingReq *req_obj=this.m_list_request.At(i); if(req_obj==NULL) continue; //--- if the current attempt exceeds the defined number of trading attempts, //--- remove the current request object and move on to the next one if(req_obj.CurrentAttempt()>req_obj.TotalAttempts() || req_obj.CurrentAttempt()>=UCHAR_MAX) { this.m_list_request.Delete(i); continue; } //--- get the request structure and the symbol object a trading operation should be performed for MqlTradeRequest request=req_obj.MqlRequest(); CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol); if(symbol_obj==NULL || !symbol_obj.RefreshRates()) continue; //--- Set the request activation time in the request object req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+1)); //--- If the current time is less than the request activation time, //--- this is not the request time - move on to the next request in the list if(symbol_obj.Time()<req_obj.TimeActivate()) continue; //--- Set the attempt number in the request object req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1)); //--- Get the pending request ID uchar id=this.GetPendReqID((uint)request.magic); //--- Get the list of orders/positions containing the order/position with the pending request ID CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL); if(::CheckPointer(list)==POINTER_INVALID) continue; //--- Depending on the type of action performed in the trading request switch(request.action) { //--- Open a position case TRADE_ACTION_DEAL : //--- if there is no position/order with the obtained pending request ID (the list is empty), send a trading request if(list.Total()==0) { this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation); } //--- if a position/order with the obtained pending request ID is already present (the list is empty), the request has been handled and should be removed else this.m_list_request.Delete(i); break; //--- default: break; } } } //+------------------------------------------------------------------+
该操作逻辑在代码注释中进行了详细描述,故无需解释。 唯一值得注意的是下一次交易请求激活时间的计算。 该时间计算方法:“请求对象创建时间” + 等待时间(以毫秒为单位)* 下一次尝试的索引。 因此,请求时间绑定到第一个请求创建时间和尝试的索引。 尝试次数越高,从对象生成到激活之间应该花费的时间更多。 时间是离散性增长:如果我们等待 10 秒,则第一次尝试应在 10 秒内发生,第二次 — 在 20 秒内,第三次 — 在 30 秒内,以此类推。 因此,两次尝试之间的间隔将始终不会小于两次尝试之间的指定等待时间。
在返回错误处理方式的方法中,将交易服务器连接不存在错误的代码移至返回“等待”处理类型的模块。 以前,该代码的处理依照“创建延后交易请求”:
//+------------------------------------------------------------------+ //| Return the error handling method | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(const uint result_code) { switch(result_code) { #ifdef __MQL4__ //--- Malfunctional trade operation case 9 : //--- Account disabled case 64 : //--- Invalid account number case 65 : return ERROR_CODE_PROCESSING_METHOD_DISABLE; //--- No error but result is unknown case 1 : //--- General error case 2 : //--- Old client terminal version case 5 : //--- Not enough rights case 7 : //--- Market closed case 132 : //--- Trading disabled case 133 : //--- Order is locked and being processed case 139 : //--- Buy only case 140 : //--- The number of open and pending orders has reached the limit set by the broker case 148 : //--- Attempt to open an opposite order if hedging is disabled case 149 : //--- Attempt to close a position on a symbol contradicts the FIFO rule case 150 : return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- Invalid trading request parameters case 3 : //--- Invalid price case 129 : //--- Invalid stop levels case 130 : //--- Invalid volume case 131 : //--- Not enough money to perform the operation case 134 : //--- Expirations are denied by broker case 147 : return ERROR_CODE_PROCESSING_METHOD_CORRECT; //--- Trade server is busy case 4 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- No connection to the trade server case 6 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Too frequent requests case 8 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- No price case 136 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Broker is busy case 137 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Too many requests case 141 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Modification denied because the order is too close to market case 145 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Trade context is busy case 146 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)1000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Trade timeout case 128 : //--- Price has changed case 135 : //--- New prices case 138 : return ERROR_CODE_PROCESSING_METHOD_REFRESH; //--- MQL5 #else //--- Auto trading disabled by the server case 10026 : return ERROR_CODE_PROCESSING_METHOD_DISABLE; //--- Request canceled by a trader case 10007 : //--- Request expired case 10012 : //--- Trading disabled case 10017 : //--- Market closed case 10018 : //--- Order status changed case 10023 : //--- Request unchanged case 10025 : //--- Request blocked for handling case 10028 : //--- Transaction is allowed for live accounts only case 10032 : //--- The maximum number of pending orders is reached case 10033 : //--- Reached the maximum order and position volume for this symbol case 10034 : //--- Invalid or prohibited order type case 10035 : //--- Position with the specified ID already closed case 10036 : //--- A close order is already present for a specified position case 10039 : //--- The maximum number of open positions is reached case 10040 : //--- Request to activate a pending order is rejected, the order is canceled case 10041 : //--- Request is rejected, because the rule "Only long positions are allowed" is set for the symbol case 10042 : //--- Request is rejected, because the rule "Only short positions are allowed" is set for the symbol case 10043 : //--- Request is rejected, because the rule "Only closing of existing positions is allowed" is set for the symbol case 10044 : //--- Request is rejected, because the rule "Only closing of existing positions by FIFO rule is allowed" is set for the symbol case 10045 : return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- Requote case 10004 : //--- Request rejected case 10006 : //--- Prices changed case 10020 : return ERROR_CODE_PROCESSING_METHOD_REFRESH; //--- Invalid request case 10013 : //--- Invalid request volume case 10014 : //--- Invalid request price case 10015 : //--- Invalid request stop levels case 10016 : //--- Insufficient funds for request execution case 10019 : //--- Invalid order expiration in a request case 10022 : //--- The specified type of order execution by balance is not supported case 10030 : //--- Closed volume exceeds the current position volume case 10038 : return ERROR_CODE_PROCESSING_METHOD_CORRECT; //--- No quotes to handle the request case 10021 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT; //--- Too frequent requests case 10024 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- An order or a position is frozen case 10029 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT; //--- No connection to the trade server case 10031 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)20000; // ERROR_CODE_PROCESSING_METHOD_WAIT; //--- Request handling error case 10011 : //--- Auto trading disabled by the client terminal case 10027 : return ERROR_CODE_PROCESSING_METHOD_PENDING; //--- Order placed case 10008 : //--- Request executed case 10009 : //--- Request executed partially case 10010 : #endif //--- "OK" default: break; } return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
为什么? 首先,为了测试生成需等待的延后请求,返回请求之间的 20 秒等待时间。 此外,在等待交易服务器恢复连接时,如此这般可令执行多次交易尝试更加方便。 无论如何,这是处理延后请求的第一个测试版本,以后它会得到改进和修改。
由于我们在此只是测试概念,因此我们将创建一个延后请求,仅开仓并仅获取交易服务器错误。 在检查交易请求的有效性时,我们不会创建延后请求,依旧在开仓方法中等待。
将延后请求创建模块添加到开仓方法之中:
//+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CTrading::OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX) { //--- Set the trading request result as 'true' and the error flag as "no errors" bool res=true; this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)type; ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type; //--- Get a symbol object by a symbol name. If failed to get CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return false; } //--- get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return false; } //--- Set the prices //--- If failed to set - write the "internal error" flag, set the error code in the return structure, //--- display the message in the journal and return 'false' if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; trade_obj.SetResultRetcode(10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); // No quotes to process the request return false; } //--- Write the volume to the request structure this.m_request.volume=volume; //--- Get the method of handling errors from the CheckErrors() method while checking for errors in the request parameters double pr=(type==POSITION_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.Bid()); ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,pr,action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp); //--- In case of trading limitations, funds insufficiency, //--- if there are limitations by StopLevel or FreezeLevel ... if(method!=ERROR_CODE_PROCESSING_METHOD_OK) { //--- If trading is completely disabled, set the error code to the return structure, //--- display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "abort trading operation" - set the last error code to the return structure, //--- display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { int code=this.m_list_errors.At(this.m_list_errors.Total()-1); if(code!=NULL) { trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "waiting" - set the last error code to the return structure and display the message in the journal if(method==ERROR_CODE_PROCESSING_METHOD_WAIT) { int code=this.m_list_errors.At(this.m_list_errors.Total()-1); if(code!=NULL) { trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); //--- Instead of creating a pending request, we temporarily wait the required time period (the CheckErrors() method result is returned) ::Sleep(method); //--- after waiting, update all data symbol_obj.Refresh(); } //--- If the check result is "create a pending request", do nothing temporarily if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); } } //--- In the loop by the number of attempts for(int i=0;i<this.m_total_try;i++) { //--- Send the request res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation); //--- If the request is executed successfully or the asynchronous order sending mode is set, play the success sound //--- set for a symbol trading object for this type of trading operation and return 'true' if(res || trade_obj.IsAsyncMode()) { if(this.IsUseSounds()) trade_obj.PlaySoundSuccess(action,order_type); return true; } //--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRY_N),string(i+1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode())); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); //--- Get the error handling method method=this.ResultProccessingMethod(trade_obj.GetResultRetcode()); //--- If "Disable trading for the EA" is received as a result of sending a request, enable the disabling flag and end the attempt loop if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { this.SetTradingDisableFlag(true); break; } //--- If "Exit the trading method" is received as a result of sending a request, end the attempt loop if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { break; } //--- If "Correct the parameters and repeat" is received as a result of sending a request - //--- correct the parameters and start the next iteration if(method==ERROR_CODE_PROCESSING_METHOD_CORRECT) { this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj,trade_obj); continue; } //--- If "Update data and repeat" is received as a result of sending a request - //--- update data and start the next iteration if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH) { symbol_obj.Refresh(); continue; } //--- If "Wait and repeat" or "Create a pending request" is received as a result of sending a request, //--- create a pending request and end the loop if(method>ERROR_CODE_PROCESSING_METHOD_REFRESH) { //--- If the trading request magic number, has no pending request ID if(this.GetPendReqID((uint)magic)==0) { //--- Waiting time in milliseconds: //--- for the "Wait and repeat" handling method, the waiting value corresponds to the 'method' value, //--- for the "Create a pending request" handling method - till there is a zero waiting time ulong wait=(method>ERROR_CODE_PROCESSING_METHOD_PENDING ? method : 0); //--- Look for the least of the possible IDs. If failed to find //--- or in case of an error while updating the current symbol data, return 'false' int id=this.GetFreeID(); if(id<1 || !symbol_obj.RefreshRates()) return false; //--- Write the request ID to the magic number, while a symbol name is set in the request structure //--- Set position and trading operation types (the remaining structure fields are already filled in) uint mn=(magic==ULONG_MAX ? (uint)trade_obj.GetMagic() : (uint)magic); this.SetPendReqID((uchar)id,mn); this.m_request.magic=mn; this.m_request.symbol=symbol_obj.Name(); this.m_request.action=TRADE_ACTION_DEAL; this.m_request.type=order_type; //--- Pass the number of trading attempts minus one to the pending request, //--- since there already has been one failed attempt uchar attempts=(this.m_total_try-1 < 1 ? 1 : this.m_total_try-1); this.CreatePendingRequest((uchar)id,attempts,wait,this.m_request,trade_obj.GetResultRetcode(),symbol_obj); break; } } } } //--- Return the result of sending a trading request in a symbol trading object return res; } //+------------------------------------------------------------------+
代码注释包含所有详细信息。 不过,如果您有任何疑问,欢迎使用评论板面。
我们针对函数库的 CEngine 基准对象类的 Engine.mqh 文件进行一些改进。
在放置交易对象属性的方法模块中添加设置交易尝试次数的方法:
//--- Set the following for the trading classes: //--- (1) correct filling policy, (2) filling policy, //--- (3) correct order expiration type, (4) order expiration type, //--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date, //--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) number of trading attempts void TradingSetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL); void TradingSetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL); void TradingSetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL); void TradingSetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL); void TradingSetMagic(const uint magic,const string symbol_name=NULL); void TradingSetComment(const string comment,const string symbol_name=NULL); void TradingSetDeviation(const ulong deviation,const string symbol_name=NULL); void TradingSetVolume(const double volume=0,const string symbol_name=NULL); void TradingSetExpiration(const datetime expiration=0,const string symbol_name=NULL); void TradingSetAsyncMode(const bool async_mode=false,const string symbol_name=NULL); void TradingSetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL); void TradingSetTotalTry(const uchar attempts) { this.m_trading.SetTotalTry(attempts); } //--- Set standard sounds (symbol==NULL) for a symbol trading object, (symbol!=NULL) for trading objects of all symbols
该方法简单地调用交易类里的同名方法。
在类的私密部分中,添加将组 ID 值转换为 uchar 值的方法,而在公开部分中,声明创建和返回复合魔幻数字值的方法:
//--- Constructor/destructor CEngine(); ~CEngine(); private: //--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones) uchar ConvToXX(const uchar number,const uchar index) const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index))); } public: //--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID uint SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0); }; //+------------------------------------------------------------------+
我们已研究完毕上面的转换方法。 创建复合魔幻数字的方法可将魔幻数字、第一组和第二组、以及延后订单 ID 的值组合成单一魔幻数字,该数值可作为订单魔幻数字发送到服务器。
我们在类主体之外编写其实现:
//+------------------------------------------------------------------+ //| Create and return the composite magic number | //| from the specified magic number value, | //| first and seconf group IDs and | //| the pending request ID | //+------------------------------------------------------------------+ uint CEngine::SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0) { uint magic=magic_id; this.m_trading.SetGroupID1(group_id1,magic); this.m_trading.SetGroupID2(group_id2,magic); this.m_trading.SetPendReqID(pending_req_id,magic); return magic; } //+------------------------------------------------------------------+
利用先前研究的设置 ID 的交易类方法,该方法接收加到魔幻数字值里的所有 ID(从该方法返回)。
针对所提出的思路,这就是我们要做的全部工作。
为了测试生成和处理延后请求,我们需要模拟一个需要等待后二次请求的错误。 您可能还记得,我们针对“没有连接到交易服务器”错误的处理规定了等待 20 秒。 默认情况下,我们有五次交易尝试。 这意味着我们只需要启动 EA,禁用互联网(与交易服务器断开连接),并尝试开仓(测试 EA 交易面板上的买入/卖出按钮)。 得到错误后,我们将有 20 * 5 = 100 秒的时间来再次启用互联网,并观察 EA 如何处理已创建的延后请求。 100 秒(必须完成五次重复尝试)之后,延后请求应自动从请求列表中删除(恢复服务器连接后,因为只有在连接处于活动状态时才能获取时间)。 由于我们当前正在测试延后请求操作,因此尚未实现此功能。 此外,该功能仍在开发中,需要修改,以便实现所有其余功能。 这意味着,与交易服务器的连接恢复之后,无论如何,EA 都会开始发送请求对象中设置的交易请求。 在第一次重复尝试之后,应有一笔开仓,并从请求列表中删除延后请求对象。
我们已实现了在魔幻数字值中存储伴随延后请求的多个 ID。 为了测试将这些 ID 添加到所发送请求的魔幻数字当中,我们随机选择组 1 和 2 中的第一个和第二个子组编号,并将它们写入魔幻数字订单属性。 当开仓时,日志会显示真实的持仓/下单的魔幻数字,和在设置中预设的魔幻数字 ID(在魔幻数字实际值之后的括号中),以及第一个和第二个中的子组 ID (表示 G1 和 G2)。
测试
若要测试延后请求,请使用上一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part26\ 之下,命名为 TestDoEasyPart26.mq5。
在 EA 输入中,将魔幻数字类型从 ulong 更改为 ushort — 现在,魔幻数字的最大值不会超过两个字节(65535)。 另外,再添加一个变量 — the number of trading attempts(交易尝试次数):
//--- input variables input ushort InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 150; // StopLoss in points input uint InpTakeProfit = 150; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpSlippage = 5; // Slippage in points input uint InpSpreadMultiplier = 1; // Spread multiplier for adjusting stop-orders by StopLevel input uchar InpTotalAttempts = 5; // Number of trading attempts sinput double InpWithdrawal = 10; // Withdrawal funds (in tester) sinput uint InpButtShiftX = 40; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput bool InpUseSounds = true; // Use sounds //--- global variables
在全局变量中,将 magic_number 变量类型从 ulong 更改为 ushort,并添加两个用于存储组值的变量:
//--- global variables CEngine engine; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ushort magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string used_symbols; string array_used_symbols[]; bool testing; uchar group1; uchar group2; //+------------------------------------------------------------------+
在 OnInit() 处理程序中,初始化组变量,然后生成伪随机数设置初始状态:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- Initialize random group numbers group1=0; group2=0; srand(GetTickCount()); //--- Initialize DoEasy library OnInitDoEasy();
在函数库初始化函数中,设置所有交易对象的默认魔幻数字,和交易尝试次数:
//+------------------------------------------------------------------+ //| Initializing DoEasy library | //+------------------------------------------------------------------+ void OnInitDoEasy() { //--- Check if working with the full list is selected used_symbols_mode=InpModeUsedSymbols; if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL) { int total=SymbolsTotal(false); string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов."; string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols."; string caption=TextByLanguage("Внимание!","Attention!"); string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\""; string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\""; string message=TextByLanguage(ru,en); int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); int mb_res=MessageBox(message,caption,flags); switch(mb_res) { case IDNO : used_symbols_mode=SYMBOLS_MODE_CURRENT; break; default: break; } } //--- Fill in the array of used symbols used_symbols=InpUsedSymbols; CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols); //--- Set the type of the used symbol list in the symbol collection engine.SetUsedSymbols(array_used_symbols); //--- Displaying the selected mode of working with the symbol object collection Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Number of used symbols: ",". Number of symbols used: "),engine.GetSymbolsCollectionTotal()); //--- Create resource text files engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Falling coin 1"),sound_array_coin_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","Falling coin 2"),sound_array_coin_04); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Button click 1"),sound_array_click_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Button click 2"),sound_array_click_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Button click 3"),sound_array_click_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red); //--- Pass all existing collections to the trading class engine.TradingOnInit(); //--- Set the default magic number for all used symbols engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number)); //--- Set synchronous passing of orders for all used symbols engine.TradingSetAsyncMode(false); //--- Set the number of trading attempts in case of an error engine.TradingSetTotalTry(InpTotalAttempts); //--- Set standard sounds for trading objects of all used symbols engine.SetSoundsStandart(); //--- Set the general flag of using sounds engine.SetUseSounds(InpUseSounds); //--- Set the spread multiplier for symbol trading objects in the symbol collection engine.SetSpreadMultiplier(InpSpreadMultiplier); //--- Set controlled values for symbols //--- Get the list of all collection symbols CArrayObj *list=engine.GetListAllUsedSymbols(); if(list!=NULL && list.Total()!=0) { //--- In a loop by the list, set the necessary values for tracked symbol properties //--- By default, the LONG_MAX value is set to all properties, which means "Do not track this property" //--- It can be enabled or disabled (by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program /* for(int i=0;i<list.Total();i++) { CSymbol* symbol=list.At(i); if(symbol==NULL) continue; //--- Set control of the symbol price increase by 100 points symbol.SetControlBidInc(100000*symbol.Point()); //--- Set control of the symbol price decrease by 100 points symbol.SetControlBidDec(100000*symbol.Point()); //--- Set control of the symbol spread increase by 40 points symbol.SetControlSpreadInc(400); //--- Set control of the symbol spread decrease by 40 points symbol.SetControlSpreadDec(400); //--- Set control of the current spread by the value of 40 points symbol.SetControlSpreadLevel(400); } */ } //--- Set controlled values for the current account CAccount* account=engine.GetAccountCurrent(); if(account!=NULL) { //--- Set control of the profit increase to 10 account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0); //--- Set control of the funds increase to 15 account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0); //--- Set profit control level to 20 account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0); } } //+------------------------------------------------------------------+
为了测试含有随机组 ID 值的魔幻数字,我们将引入 comp_magic 布尔变量,该变量等于 true,并在开仓/下挂单函数中指明复合魔幻数字的用法。 代替使用 magic_number 变量,而是引入新的 magic变量,该变量根据 comp_magic 变量值存储魔幻值 。
当设置magic 的数值(设置中定义的永久魔幻数字,或由指定魔幻数字 + 随机 1 组和 2 组 ID 值组成的复合魔幻数字)时,我们会检查 comp_magic 的值。 若为 true,则用复合魔幻数字。 若为 false,则用设置里定义的那个值。
在处理 EA 交易面板按钮的 PressButtonEvents() 函数中进行修改:
//+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { bool comp_magic=true; // Temporary variable selecting the composite magic number with random group IDs string comment=""; //--- Convert button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- Random group 1 and 2 numbers within the range of 0 - 15 group1=(uchar)Rand(); group2=(uchar)Rand(); uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number); //--- If the button is pressed if(ButtonState(button_name)) { //--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { //--- Open Buy position engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit); // No comment - the default comment is to be set } //--- If the BUTT_BUY_LIMIT button is pressed: Place BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Set BuyLimit order engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyLimit","Pending BuyLimit order")); } //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- Set BuyStop order engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending BuyStop order")); } //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- Set BuyStopLimit order engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStopLimit","Pending BuyStopLimit order")); } //--- If the BUTT_SELL button is pressed: Open Sell position else if(button==EnumToString(BUTT_SELL)) { //--- Open Sell position engine.OpenSell(lot,Symbol(),magic,stoploss,takeprofit); // No comment - the default comment is to be set } //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Set SellLimit order engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending SellLimit order")); } //--- If the BUTT_SELL_STOP button is pressed: Set SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- Set SellStop order engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending SellStop order")); } //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- Set SellStopLimit order engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending SellStopLimit order")); } //--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY))
在调用函数库交易方法的所有代码中,将 magic_number 变量替换为 magic。
为了给组 ID 设置随机值,添加 Rand() 函数返回的值,该函数返回的伪随机值已拥有最小和最大范围:
//+------------------------------------------------------------------+ //| A random value within the range | //+------------------------------------------------------------------+ uint Rand(const uint min=0, const uint max=15) { return (rand() % (max+1-min))+min; } //+------------------------------------------------------------------+
编译并启动 EA。 断开互联网,然后等待以下图像出现在终端的右下角:
禁用互联网并单击“卖出”后,交易服务器返回错误,且日志中显示以下记录:
2019.11.26 15:34:48.661 CTrading::OpenPosition<uint,uint>: Invalid request: 2019.11.26 15:34:48.661 No connection with the trade server 2019.11.26 15:34:48.661 Correction of trade request parameters ... 2019.11.26 15:34:48.661 Trading attempt #1. Error: No connection with the trade server
收到错误后,函数库会采用未成功开立空头持仓的参数集合来创建延后请求。
延后请求还具有尝试次数和 20 秒的等待时间。
然后启用互联网,恢复与交易服务器的连接:
连接一旦恢复后,函数库将立即处理延后请求,并将其发送到服务器。 结果则为,我们成功开仓,且日志里也有记录:
2019.11.26 15:35:00.853 CTrading::OpenPosition<double,double>: Invalid request: 2019.11.26 15:35:00.853 Trading is prohibited for the current account 2019.11.26 15:35:00.853 Correction of trade request parameters ... 2019.11.26 15:35:00.853 Trading operation aborted 2019.11.26 15:35:01.192 CTrading::OpenPosition<double,double>: Invalid request: 2019.11.26 15:35:01.192 Trading is prohibited for the current account 2019.11.26 15:35:01.192 Correction of trade request parameters ... 2019.11.26 15:35:01.192 Trading operation aborted 2019.11.26 15:35:01.942 - Position is open: 2019.11.26 10:35:01.660 - 2019.11.26 15:35:01.942 EURUSD Opened 0.10 Sell #486405595 [0.10 Market-order Sell #486405595] at price 1.10126, sl 1.10285, tp 1.09985, Magic number 17629307 (123), G1: 13 2019.11.26 15:35:01.942 OnDoEasyEvent: Position is open
如我们所见,在交易服务器恢复连接后,当前帐户能够延迟交易。
但无论如何,延后请求只是玩了把戏...
此外,我们可以在日志中看到实际的魔幻数字 17629307,然后在括号(123)中是 EA 设置中定义的魔幻数字,再加上另一个记录 G1: 13 表示第一个组 ID 等于13,而第二个组 ID 缺失 — 其值实际上为零,因此没有第二个含有 G2: XX 的记录。
请注意:
请勿在真实交易中使用本文中所述的交易类延后请求结果,以及所附的测试 EA!本文随附的材料和结果仅是针对延后请求概念的测试。 在当前状态下,它不是成品,故绝不要用于实盘交易。 取而代之,它仅适用于演示模式或测试器。
下一步是什么?
在后续文章中,我们将继续开发延后请求类。
文后附有当前版本含糊库的所有文件,以及测试 EA 文件,供您测试和下载。
请在评论中留下您的问题、意见和建议。
系列中的前几篇文章:
第一部分 概念,数据管理
第二部分 历史订单和成交集合
第三部分 在场订单和持仓集合,安排搜索
第四部分 交易事件, 概念
第五部分 交易事件类和集合。 将事件发送至程序
第六部分 净持帐户事件
第七部分 StopLimit 挂单激活事件,为订单和持仓修改事件准备功能
第八部分 订单和持仓修改事件
第九部分 Compatibility with MQL4 — Preparing data
第十部分 与 MQL4 的兼容性 - 开仓和激活挂单事件
第十一部分 与 MQL4 的兼容性 - 平仓事件
第十二部分 帐户对象类和帐户对象集合
第十三部分 账户对象事件
第十四部分 品种对象
第十五部份 品种对象集合
第十六部分 品种集合事件
第十七部分 函数库对象之间的交互
第十八部分 帐户与任意其他函数库对象的交互
第十九部分 函数库消息类
第二十部分 创建和存储程序资源
第二十一部分 交易类 - 基准跨平台交易对象
第二十二部分 交易类 - 基准交易类,限制验证
第二十三部分 交易类 - 基准交易类,有效参数验证
第二十四部分 交易类 - 基准交易类,自动纠正无效参数
第二十五部分 交易类 - 基准交易类,处理交易服务器返回的错误
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/7394
注意: 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.



库中是否有打开即时市场执行 的功能?
能否请您告诉我,您是通过哪种方法对挂单请求中的编码魔法进行解码的?
在开仓时出错(资金不足),然后触发挂单请求后,我创建了一个魔法完全不同的仓位(显然是编码的那个)。
在哪里解码?我想要一个原生的马吉克,而不是一个新的。
void CTradingControl::OnPReqByErrCodeHandler()
这里应该调用返回原始魔力的方法。那是什么方法? GetMagicID()?
为什么没有计算 ORDER_STATUS_DEAL 类型?
一般来说,不清楚如何获得平仓交易或头寸的利润点数......
而且总是 0:
deal_profit_pts=(int)deal.GetProperty(ORDER_PROP_PROFIT_PT)