轻松快捷开发 MetaTrader 程序的函数库 (第二十六部分):处理延后交易请求 - 首次实现 (开仓)·综合运用
📘

轻松快捷开发 MetaTrader 程序的函数库 (第二十六部分):处理延后交易请求 - 首次实现 (开仓)·综合运用

第 3/3 篇

「订单快照的字段落盘逻辑」

在 MT5 的自定义订单封装类里,把一笔成交的各类属性固化到内部数组,是后续做盈亏统计和价格行为回看的前提。下面这段代码把长整型、双精度、字符串三类属性分别写进 m_long_prop、m_double_prop、m_string_prop,相当于给每笔单子拍了张结构化快照。 长整型里除了开平时间(精确到毫秒的 OpenTimeMSC / CloseTimeMSC),还存了成交入口 DealEntry 和关联持仓 ID,方便追溯单子从哪来、挂到哪个仓位上。双精度段覆盖了开平仓价、利润、佣金、库存费、成交量以及 SL/TP 等 12 个字段,基本把账户面板能看到的数字全收了。 字符串段只留了品种、注释和外部 ID 三个轻量字段,避免占用数值计算的缓存。最后补的 ProfitInPoints、TicketFrom、TicketTo 是衍生整型属性——把利润换算成点数、记录转仓前后的票号,做跨单关联时有用。 直接把这段贴进你的 COrder 派生类 Refresh() 里,编译后下两单,用 Print(m_double_prop[IndexProp(ORDER_PROP_PROFIT)]) 就能确认利润有没有正确落盘;外汇和贵金属杠杆高,快照里的浮亏数字可能瞬间翻几倍,验证时仓位要小。

MQL5 / C++
  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();

class=class="str">"cmt">//--- 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();

class=class="str">"cmt">//--- Save class="type">class="kw">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();

class=class="str">"cmt">//--- 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();

订单属性落盘与描述函数怎么写

在 COrder 类的保存逻辑里,多组整数属性被一次性写入成员变量数组。被高亮的那几行专门处理魔术码、分组 ID 与挂单请求 ID,这类字段决定了 EA 能否在 MT5 终端里区分自营单与人工单,属于实盘风控的基础设施。 下面这段是属性落盘的尾部代码,顺带把浮点与字符串扩展属性也塞进各自的映射表,最后花括号收掉保存函数。 [CODE] 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)] = ""; } [/CODE] 逐行拆解:前两句把由 SL、TP 触发的平仓关联单号存进长整型数组;中间四句分别取魔术码与两级分组 ID、挂单请求 ID 写入对应槽位;注释后第一行用 IndexProp 定位浮点利润字段并赋值全利润;末两句把扩展注释字符串初始化为空,随后函数退出。 另一头,GetPropertyDescription 用三元嵌套把枚举转成可读文本。以 ORDER_PROP_MAGIC 为例,若 SupportProperty 返回否,就拼上「不支持」提示,否则把 GetProperty 强转字符串输出,ticket 类属性还会在前缀加「 #」便于对账。 别把魔术码当摆设 多币种多周期跑 EA 时,魔术码重复可能让平仓函数误杀手动单,外汇与贵金属杠杆高,这类串单错误会放大回撤,建议本地先用 Print 把各实例 GetMagicID 打出来核对。

MQL5 / C++
  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();

class=class="str">"cmt">//--- Save additional real properties
  this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)]    = this.ProfitFull();

class=class="str">"cmt">//--- Save additional class="type">class="kw">string properties
  this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)]    = "";
}

◍ 订单属性文本化的分支逻辑

这段嵌套三元运算把订单对象的各类属性转成可读字符串,核心思路是先判断属性是否受支持(SupportProperty),不支持就追加「不支持」提示,支持则按属性类型分别格式化。 时间类属性如 ORDER_PROP_TIME_OPEN 会走 TimeMSCtoString 并附带原始毫秒值,而 ORDER_PROP_TIME_DONE 这类在原文截断前也沿用同样的「不支持判断 + 毫秒转字符串」结构。 实战中若你自建 COrder 派生类,直接复用该分支可省掉大量日志拼接代码;外汇与贵金属订单的成交时间精度到毫秒,复盘时这种输出比平台默认更细。 别把属性支持当默认 MT5 某些订单类型(如挂单转成市的瞬时单)可能不支持 POSITION_ID,硬取 GetProperty 会返回 0,日志里看到「未设置」要先怀疑支持性而非策略 bug。

MQL5 / C++
   (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
      (this.GetProperty(class="kw">property)==class="num">0     ?  CMessage::Text(MSG_LIB_PROP_NOT_SET)+": "+CMessage::Text(MSG_LIB_PROP_NOT_SET)  :
      ": "+::TimeToString(this.GetProperty(class="kw">property),TIME_DATE|TIME_MINUTES|TIME_SECONDS))
     )  :
   class="kw">property==ORDER_PROP_TYPE              ?  CMessage::Text(MSG_ORD_TYPE)+": "+this.TypeDescription()  :
   class="kw">property==ORDER_PROP_DIRECTION         ?  CMessage::Text(MSG_ORD_TYPE_BY_DIRECTION)+": "+this.DirectionDescription() :

   class="kw">property==ORDER_PROP_REASON            ?  CMessage::Text(MSG_ORD_REASON)+
     (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
      ": "+this.GetReasonDescription(this.GetProperty(class="kw">property))
     )  :
   class="kw">property==ORDER_PROP_POSITION_ID       ?  CMessage::Text(MSG_ORD_POSITION_ID)+
     (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
      ": #"+(class="type">class="kw">string)this.GetProperty(class="kw">property)
     )  :
   class="kw">property==ORDER_PROP_DEAL_ORDER_TICKET ?  CMessage::Text(MSG_ORD_DEAL_ORDER_TICKET)+
     (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
      ": #"+(class="type">class="kw">string)this.GetProperty(class="kw">property)
     )  :
   class="kw">property==ORDER_PROP_DEAL_ENTRY        ?  CMessage::Text(MSG_ORD_DEAL_ENTRY)+
     (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
      ": "+this.GetEntryDescription(this.GetProperty(class="kw">property))
     )  :
   class="kw">property==ORDER_PROP_POSITION_BY_ID    ?  CMessage::Text(MSG_ORD_POSITION_BY_ID)+
     (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
      ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
     )  :
   class="kw">property==ORDER_PROP_TIME_OPEN         ?  CMessage::Text(MSG_ORD_TIME_OPEN)+
     (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
      ": "+TimeMSCtoString(this.GetProperty(class="kw">property))+" ("+(class="type">class="kw">string)this.GetProperty(class="kw">property)+")"
     )  :
   class="kw">property==ORDER_PROP_TIME_CLOSE        ?  CMessage::Text(MSG_ORD_TIME_CLOSE)+
     (!this.SupportProperty(class="kw">property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :

「订单属性文本化的三元表达式链」

这段逻辑把订单对象的多个属性统一转成可读字符串,核心是用嵌套三元运算符按 property 枚举值分流。TIME_UPDATE 和 STATE 走的是带毫秒时间格式化或状态描述的分支,不支持的属性统一回退到「不支持」提示文本。 PROFIT_PT 的处理有个细节:当订单状态是 ORDER_STATUS_MARKET_PENDING 时,显示文案从「盈利点数」切换成「距离点数」,这对应挂单还未触发时的距离度量。CLOSE_BY_SL 与 CLOSE_BY_TP 则只输出是否允许被对应止盈止损平仓的 YES/NO 标记。 在 MT5 里跑这套,你可以直接把下面片段塞进订单类的 ToString 方法里验证:若 broker 不支持某属性,SupportProperty 返回 false,文本就会带「not supported」,实盘外汇或贵金属品种上这类限制出现概率不低,属于高风险环境下的兼容性坑。

MQL5 / C++
              ": "+TimeMSCtoString(this.GetProperty(class="kw">property))+" ("+(class="type">class="kw">string)this.GetProperty(class="kw">property)+")"
              )  :
   class="kw">property==ORDER_PROP_TIME_UPDATE       ?  CMessage::Text(MSG_ORD_TIME_UPDATE)+
              (!this.SupportProperty(class="kw">property)   ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
              ": "+(this.GetProperty(class="kw">property)!=class="num">0 ? TimeMSCtoString(this.GetProperty(class="kw">property))+" ("+(class="type">class="kw">string)this.GetProperty(class="kw">property)+")" : "class="num">0")
              )  :
   class="kw">property==ORDER_PROP_STATE              ?  CMessage::Text(MSG_ORD_STATE)+
              (!this.SupportProperty(class="kw">property)   ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
              ": \""+this.StateDescription()+"\""
              )  :
 class=class="str">"cmt">//--- Additional class="kw">property
   class="kw">property==ORDER_PROP_STATUS             ?  CMessage::Text(MSG_ORD_STATUS)+
              (!this.SupportProperty(class="kw">property)   ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
              ": \""+this.StatusDescription()+"\""
              )  :
   class="kw">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(class="kw">property)   ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
              ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
              )  :
   class="kw">property==ORDER_PROP_CLOSE_BY_SL        ?  CMessage::Text(MSG_LIB_PROP_CLOSE_BY_SL)+
              (!this.SupportProperty(class="kw">property)   ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
              ": "+(this.GetProperty(class="kw">property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
              )  :
   class="kw">property==ORDER_PROP_CLOSE_BY_TP        ?  CMessage::Text(MSG_LIB_PROP_CLOSE_BY_TP)+
              (!this.SupportProperty(class="kw">property)   ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
              ": "+(this.GetProperty(class="kw">property)   ?  CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO))
              )  :

订单标识与分组属性的字符串拼装

这段三元嵌套把订单事件里的 magic id、两组 group id 以及挂单请求 id 转成可读文本。若当前账户类型或订单状态不支持该属性,就接上「不支持」提示,否则强转 GetProperty 的返回值为字符串输出。 注意四个判断分支结构完全一致,仅枚举常量与消息宏不同:ORDER_PROP_MAGIC_ID 对应 MSG_ORD_MAGIC_ID,GROUP_ID1/2 与 PEND_REQ_ID 同理。改写自己面板时,照这个模板加分支即可,不必重写整个格式化函数。 下方 protected 区暴露了事件对象的底层载体:m_long_prop 按 EVENT_PROP_INTEGER_TOTAL 长度存整数属性,m_double_prop 与 m_string_prop 分别装实数与字符串。调参或排错时,直接盯这几个数组下标比翻文档快。外汇与贵金属杠杆高,这类标识错配可能在 hedging 账户引发重复平仓,验证前先用模拟盘跑。

MQL5 / C++
   class="kw">property==ORDER_PROP_MAGIC_ID         ?   CMessage::Text(MSG_ORD_MAGIC_ID)+
     (!this.SupportProperty(class="kw">property)     ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
        ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
     )  :
   class="kw">property==ORDER_PROP_GROUP_ID1         ?   CMessage::Text(MSG_ORD_GROUP_ID1)+
     (!this.SupportProperty(class="kw">property)     ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
        ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
     )  :
   class="kw">property==ORDER_PROP_GROUP_ID2         ?   CMessage::Text(MSG_ORD_GROUP_ID2)+
     (!this.SupportProperty(class="kw">property)     ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
        ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
     )  :
   class="kw">property==ORDER_PROP_PEND_REQ_ID       ?   CMessage::Text(MSG_ORD_PEND_REQ_ID)+
     (!this.SupportProperty(class="kw">property)     ?   ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
        ": "+(class="type">class="kw">string)this.GetProperty(class="kw">property)
     )  :
   ""
   );
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">protected:
   ENUM_TRADE_EVENT   m_trade_event;                                            class=class="str">"cmt">// Trading event
   class="type">bool               m_is_hedge;                                               class=class="str">"cmt">// Hedge account flag
   class="type">long               m_chart_id;                                               class=class="str">"cmt">// Control program chart ID
   class="type">int                m_digits;                                                 class=class="str">"cmt">// Symbol&class="macro">#x27;s Digits()
   class="type">int                m_digits_acc;                                             class=class="str">"cmt">// Number of decimal places for the account currency
   class="type">long               m_long_prop[EVENT_PROP_INTEGER_TOTAL];                    class=class="str">"cmt">// Event integer properties
   class="type">class="kw">double             m_double_prop[EVENT_PROP_DOUBLE_TOTAL];                   class=class="str">"cmt">// Event real properties
   class="type">class="kw">string             m_string_prop[EVENT_PROP_STRING_TOTAL];                   class=class="str">"cmt">// Event class="type">class="kw">string properties
class=class="str">"cmt">//--- class="kw">return the flag presence in the trading event

◍ 从 Magic 号里抠出分组与挂单请求

MQL5 里 OrderSend 的 magic number 常被当成单一标识,其实可以塞进结构化信息。下面这套存取方式把 32 位 ulong 拆成四段:低 16 位是基础 magic ID,16~23 位分两组各 4 位做 group ID,24~31 位放 pending request 代号。 IsPresentEventFlag 用位与判断事件标志位是否齐全:返回 (this.m_event_code & event_code)==event_code 说明对应位都被置起,否则该事件未完整触发。 GetMagicID 取 Magic()&0xFFFF,GetGroupID1 取 (Magic()>>16)&0x0F,GetGroupID2 取 ((Magic()>>16)&0xF0)>>4,GetPendReqID 取 (Magic()>>24)&0xFF。这样同一个订单能同时携带策略编号、AB 测试分组和挂单来源,回测时按位过滤比字符串快得多。 EventsMessage 里那行 magic 拼接逻辑值得抄:Magic()!=0 才追加「, Magic 12345」文本,否则留空串,避免无 magic 的市价单刷出多余日志。外汇与贵金属杠杆高,这种位运算若写错偏移量可能导致订单分组错乱,实盘前务必在 MT5 策略测试器用脚本打印各段值验证。

MQL5 / C++
class="type">bool IsPresentEventFlag(class="kw">const class="type">int event_code) class="kw">const { class="kw">return (this.m_event_code & event_code)==event_code; }
class="type">class="kw">ushort GetMagicID(class="type">void) class="kw">const { class="kw">return class="type">class="kw">ushort(this.Magic() & 0xFFFF); }
class="type">uchar GetGroupID1(class="type">void) class="kw">const { class="kw">return class="type">uchar(this.Magic()>>class="num">16) & 0x0F; }
class="type">uchar GetGroupID2(class="type">void) class="kw">const { class="kw">return class="type">uchar((this.Magic()>>class="num">16) & 0xF0)>>class="num">4; }
class="type">uchar GetPendReqID(class="type">void) class="kw">const { class="kw">return class="type">uchar(this.Magic()>>class="num">24) & 0xFF; }
class="type">class="kw">string CEventModify::EventsMessage(class="type">void)
  {
  class="type">class="kw">string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
  class="type">class="kw">string magic=(this.Magic()!=class="num">0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(class="type">class="kw">string)this.Magic() : "");
  class="type">class="kw">string text="";

「事件日志里把魔数和分组ID拼出来」

在 MT5 的 C++ 风格事件类里,把一笔挂单或成交的关联信息拼进日志头,是排查 EA 多实例冲突的基础动作。下面这段代码只做字符串组装,不触发任何交易,但能让你在专家日志里一眼看出某条事件属于哪个 magic、哪组策略。 核心判断在于:只有当挂单请求ID、GroupID1、GroupID2 中任一个大于 0 时,才把 magic_id 以「 (数字)」的形式附加。否则留空,避免干净日志被无意义括号污染。 GroupID1 与 GroupID2 同理,分别用「, G1: 数字」「, G2: 数字」追加;若均为 0 则整段缺失。最后 magic 字段还判断了 this.Magic()!=0,才拼接本地化文案 MSG_ORD_MAGIC 与具体 magic 值。开 MT5 把这段塞进你的 CEvent 派生类 Print(head+text+magic),跑两个不同 magic 的回测,日志差异立刻可见。外汇与贵金属品种波动剧烈,多 magic 并发时这种标注能降低误判风险。

MQL5 / C++
  class="type">class="kw">string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
  class="type">class="kw">string magic_id=((this.GetPendReqID()>class="num">0 || this.GetGroupID1()>class="num">0 || this.GetGroupID2()>class="num">0) ? " ("+(class="type">class="kw">string)this.GetMagicID()+")" : "");
  class="type">class="kw">string group_id1=(this.GetGroupID1()>class="num">0 ? ", G1: "+(class="type">class="kw">string)this.GetGroupID1() : "");
  class="type">class="kw">string group_id2=(this.GetGroupID2()>class="num">0 ? ", G2: "+(class="type">class="kw">string)this.GetGroupID2() : "");
  class="type">class="kw">string magic=(this.Magic()!=class="num">0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(class="type">class="kw">string)this.Magic()+magic_id+group_id1+group_id2 : "");
  class="type">class="kw">string text="";

把断线重发做成一个可延后的请求对象

在交易类文件里,先放一个描述延后请求的新类 CPendingReq,它只裹住一笔 MqlTradeRequest、一个 uchar 类型的 ID、基于的错误码和生成时的价格。逻辑上不复杂:服务器报错时不死等,而是建一个延后请求退出方法,等计时器到点再发;若魔幻数字里已带延后 ID,说明请求早建过了,不再重复建。 可用 ID 最多 255 个,搜索时从 1 循环到 255,找列表里没被占用的那个最小号。临时对象初值 -1 表示全满,0 表示创建失败;循环里把临时 ID 设成索引,排好序在列表里找相等对象,找不到就返回该索引并 break,最后删临时对象把 ID 交出去。 魔幻数字一个字节塞两组 ID 加一个延后 ID。组 0 不动位,组 1 左移 4 位;掩码 0xFFF0FFFF 清组 0 低四位,0xFF0FFFFF 清组 1 高四位,0x00FFFFFF 清延后 ID 位。延后 ID 自身要 uchar 左移 24 位才进最高字节。这样一笔订单的魔幻数字同时背了 EA 主 ID、两个子组号和重发序号。 开仓方法里接上创建延后请求的模块:断线错误不再当场死等,而归为“等待”类,默认等 20 秒、最多 5 次尝试。时间离散增长——第 n 次尝试在创建时刻 + 20 秒 × n 触发,不会早于设定间隔。 验证手段很直接:跑 EA 后拔网开仓,拿到报错后有 20×5=100 秒把网连回来。当前版本连回后第一次重发就应成交,并把该延后对象从列表移除;ID 写入可在日志看到真实魔幻数字后括号里的 G1/G2 子组号。外汇与贵金属杠杆高,断线重发可能成交在不利点位,实盘前务必先在策略测试器跑通。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Pending request object class                                     |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class</span> CPendingReq : <span class="keyword">class="kw">public</span> CObject
  {
<span class="keyword">class="kw">private</span>:
   <span class="predefines">class="type">MqlTradeRequest</span>      m_request;              <span class="comment">class=class="str">"cmt">// Trade request structure</span>
   <span class="keyword">class="type">uchar</span>               m_id;                   <span class="comment">class=class="str">"cmt">// Trading request ID</span>
   <span class="keyword">class="type">int</span>                 m_retcode;              <span class="comment">class=class="str">"cmt">// Result a request is based on</span>
   <span class="keyword">class="type">class="kw">double</span>              m_price_create;         <span class="comment">class=class="str">"cmt">// Price at the moment of a request generation</span>

◍ 挂单重试请求的字段与方法

在 MT5 的 EA 架构里,如果要把一笔未成交的市价转挂单做自动重试,先得有一个结构体记住这笔请求的上下文。下面这段类成员定义就是干这个的:它用 5 个基础变量锁住时间、等待间隔和重试计数。 m_time_create 记录请求生成时刻,m_time_activate 是下一次激活重试的时间戳,m_waiting_msc 控制两次请求之间的毫秒级等待,m_current_attempt 和 m_total_attempts 分别是当前第几次尝试与总尝试次数(类型用 uchar,意味着最多 255 次,足够覆盖绝大多数 broker 限频场景)。 CopyRequest 方法直接把外部传入的 MqlTradeRequest 引用拷进对象内部,Compare 则按请求 ID 做虚函数比较,方便放进 CObject 派生链表里排序去重。 对外暴露的取数接口全是 const 内联:MqlRequest 回传原始请求结构,PriceCreate 给出生成时的价格快照,TimeCreate / TimeActivate / WaitingMSC / CurrentAttempt 分别吐出对应字段。实盘里你可以在 OnTradeTransaction 里读 CurrentAttempt,当它大于等于 3 时倾向判定网络或报价异常,再决定是否人工介入。外汇与贵金属杠杆高,自动重试可能放大滑点与报错风险。

MQL5 / C++
class="type">class="kw">ulong m_time_create; class=class="str">"cmt">// Request generation time
class="type">class="kw">ulong m_time_activate; class=class="str">"cmt">// Next attempt activation time
class="type">class="kw">ulong m_waiting_msc; class=class="str">"cmt">// Waiting time between requests
class="type">uchar m_current_attempt; class=class="str">"cmt">// Current attempt index
class="type">uchar m_total_attempts; class=class="str">"cmt">// Number of attempts
class=class="str">"cmt">//--- Copy trading request data
class="type">void CopyRequest(class="kw">const class="type">MqlTradeRequest &request) { this.m_request=request; }
class=class="str">"cmt">//--- Compare CPendingReq objects by IDs
class="kw">virtual class="type">int Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const;
class="kw">public:
class=class="str">"cmt">//--- Return(class="num">1) the request structure, (class="num">2) the price at the moemnt of the request generation,
class=class="str">"cmt">//--- (class="num">3) request generation time, (class="num">4) current attempt time,
class=class="str">"cmt">//--- (class="num">5) waiting time between requests, (class="num">6) current attempt index,
class=class="str">"cmt">//--- (class="num">7) number of attempts, (class="num">8) request ID
class="type">MqlTradeRequest MqlRequest(class="type">void) class="kw">const { class="kw">return this.m_request; }
class="type">class="kw">double PriceCreate(class="type">void) class="kw">const { class="kw">return this.m_price_create; }
class="type">class="kw">ulong TimeCreate(class="type">void) class="kw">const { class="kw">return this.m_time_create; }
class="type">class="kw">ulong TimeActivate(class="type">void) class="kw">const { class="kw">return this.m_time_activate; }
class="type">class="kw">ulong WaitingMSC(class="type">void) class="kw">const { class="kw">return this.m_waiting_msc; }
class="type">uchar CurrentAttempt(class="type">void) class="kw">const { class="kw">return this.m_current_attempt; }

「挂单请求类的存取与构造细节」

在 MT5 的 EA 架构里,把一笔挂单请求封装成对象,核心是先定义好它的只读访问器和写入接口。下面这段类声明里,TotalAttempts 和 ID 都用了 const 修饰符,返回 uchar 类型的成员变量,意味着外部只能取、不能改这两个字段,避免重发逻辑里被意外覆盖。 Set 系列方法对应七项上下文:建请时的价格、建请时间、激活时间、请求间隔毫秒、当前尝试序号、总尝试次数、请求 ID。注释里写清了顺序——(1) 建请价格 (2) 建请时间 (3) 当前尝试时间 (4) 请求间隔 (5) 当前尝试索引 (6) 尝试总数 (7) 请求 ID,照这个次序排就不会在重试队列里错位。 两个构造函数值得注意:默认构造为空实现,参数化构造则把 id、price、time 以及 MqlTradeRequest 引用和 retcode 一并接住,并用初始化列表把 m_price_create 直接赋值。实盘里若发现重试单不触发,第一步就该查 CPendingReq 构造时 time 有没有用 TimeCurrent() 填对,而不是先怀疑经纪商。 外汇与贵金属品种点差跳变频繁,这类封装只是降低代码层出错概率,不等于执行层稳靠,重发间隔设太短可能被服务器限流。

MQL5 / C++
  class="type">uchar                TotalAttempts(class="type">void)                class="kw">const { class="kw">return this.m_total_attempts;  }
  class="type">uchar                ID(class="type">void)                            class="kw">const { class="kw">return this.m_id;            }
class=class="str">"cmt">//--- Set(class="num">1) the price when creating a request, (class="num">2) request creation time,
class=class="str">"cmt">//--- (class="num">3) current attempt time, (class="num">4) waiting time between requests,
class=class="str">"cmt">//--- (class="num">5) current attempt index, (class="num">6) number of attempts, (class="num">7) request ID
  class="type">void                SetPriceCreate(class="kw">const class="type">class="kw">double price)        { this.m_price_create=price;        }
  class="type">void                SetTimeCreate(class="kw">const class="type">class="kw">ulong time)           { this.m_time_create=time;          }
  class="type">void                SetTimeActivate(class="kw">const class="type">class="kw">ulong time)         { this.m_time_activate=time;        }
  class="type">void                SetWaitingMSC(class="kw">const class="type">class="kw">ulong miliseconds)    { this.m_waiting_msc=miliseconds;   }
  class="type">void                SetCurrentAttempt(class="kw">const class="type">uchar number)     { this.m_current_attempt=number;    }
  class="type">void                SetTotalAttempts(class="kw">const class="type">uchar number)      { this.m_total_attempts=number;     }
  class="type">void                SetID(class="kw">const class="type">uchar id)                     { this.m_id=id;                     }
class=class="str">"cmt">//--- Constructors
                    CPendingReq(class="type">void){;}
                    CPendingReq(class="kw">const class="type">uchar id,class="kw">const class="type">class="kw">double price,class="kw">const class="type">class="kw">ulong time,class="kw">const class="type">MqlTradeRequest &request,class="kw">const class="type">int retcode);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Parametric constructor                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
CPendingReq::CPendingReq(class="kw">const class="type">uchar id,class="kw">const class="type">class="kw">double price,class="kw">const class="type">class="kw">ulong time,class="kw">const class="type">MqlTradeRequest &request,class="kw">const class="type">int retcode) : m_price_create(price),

挂单请求对象的 ID 排序与空闲分配

在 MQL5 的自定义交易类里,CPendingReq 用 Compare 方法按 ID 做大小比较,返回 1、-1 或 0,这让链表 m_list_request 可以直接调用 Sort 按字节序排挂单请求。 GetFreeID 的扫描逻辑从 1 跑到 255(i<256),说明单实例下挂单请求 ID 被限制在 1~255 的 uchar 范围,超量会拿不到可用 ID。 构造 CPendingReq 时若 new 返回 NULL,函数直接 return 0,调用方需把 0 当成分配失败信号,而不是合法 ID。 别把 new 失败当小概率 MT5 实盘跑高频挂单脚本时,内存紧张或对象池碎片化都可能让 new CPendingReq 返回 NULL,若上层没拦 0 返回值,后续 SetID(0) 会和失败标记混淆。

MQL5 / C++
class="type">int CPendingReq::Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const
  {
   class="kw">const CPendingReq *compared_req=node;
   class="kw">return(this.ID()>compared_req.ID() ? class="num">1 : this.ID()<compared_req.ID() ? -class="num">1 : class="num">0);
   class="kw">return class="num">0;
  }

class="type">int CTrading::GetFreeID(class="type">void)
  {
   class="type">int id=WRONG_VALUE;
   CPendingReq *element=new CPendingReq();
   if(element==NULL)
      class="kw">return class="num">0;
   for(class="type">int i=class="num">1;i<class="num">256;i++)
     {
      element.SetID((class="type">uchar)i);
      this.m_list_request.Sort();

◍ 把分组与挂单ID塞进magic数字

MQL5 的订单 magic 字段是 uint(32 位),原设计只装一个标记,这里被拆成 4 个字节区:byte3 放挂单请求 ID,byte2 的高 4 位放第二组 ID、低 4 位放第一组 ID,byte1~0 拼成 16 位原 magic。这样一笔持仓能同时携带策略分组与重试上下文,不用额外全局映射表。 写入时用位运算清掉对应区间再或上移位值。SetGroupID1 先 magic &= 0xFFF0FFFF 抹掉 byte2 低 4 位,再把 ConvToXX 结果左移 16 位塞进去;SetPendReqID 则 magic &= 0x00FFFFFF 后把 id 直接左移 24 位占满 byte3。 ConvToXX 是个防越界的小工具:入参 number 超 15 就钳到 15,再按 index 左移 0 或 4 位,保证每组 ID 只占半字节。外汇与贵金属杠杆高,这种 magic 编码若移位写错,会导致 EA 误判分组、重复开仓,实盘前务必在策略测试器用打印 magic 值核对。 下面这段是从请求列表找空位并回传 ID 的逻辑,顺带给出挂单请求的函数声明与 magic 布局注释,复制进 MT5 头文件即可编译验证。

MQL5 / C++
   if(this.m_list_request.Search(element)==WRONG_VALUE)
      {
       id=i;
       break;
      }
   }
 class="kw">delete element;
 class="kw">return id;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Create a pending request
   class="type">bool                 CreatePendingRequest(class="kw">const class="type">uchar id,class="kw">const class="type">uchar attempts,class="kw">const class="type">class="kw">ulong wait,class="kw">const class="type">MqlTradeRequest &request,class="kw">const class="type">int retcode,CSymbol *symbol_obj);
class=class="str">"cmt">//--- Data location in the magic number class="type">int value
class=class="str">"cmt">//-----------------------------------------------------------
class=class="str">"cmt">//  bit   class="num">32|class="num">31       class="num">24|class="num">23       class="num">16|class="num">15        class="num">8|class="num">7         class="num">0|
class=class="str">"cmt">//-----------------------------------------------------------
class=class="str">"cmt">//  byte    |     class="num">3     |     class="num">2     |     class="num">1     |     class="num">0     |
class=class="str">"cmt">//-----------------------------------------------------------
class=class="str">"cmt">//  data    |   class="type">uchar   |   class="type">uchar   |         class="type">class="kw">ushort        |
class=class="str">"cmt">//-----------------------------------------------------------
class=class="str">"cmt">//  descr   |pend req id| id2 | id1 |          magic        |
class=class="str">"cmt">//-----------------------------------------------------------
class=class="str">"cmt">//--- Set the ID of the(class="num">1) first group, (class="num">2) second group, (class="num">3) pending request to the magic number value
   class="type">void                SetGroupID1(class="kw">const class="type">uchar group,class="type">uint &magic)      { magic &=0xFFF0FFFF; magic |= class="type">uint(ConvToXX(group,class="num">0)<<class="num">16);      }
   class="type">void                SetGroupID2(class="kw">const class="type">uchar group,class="type">uint &magic)      { magic &=0xFF0FFFFF; magic |= class="type">uint(ConvToXX(group,class="num">1)<<class="num">16);      }
   class="type">void                SetPendReqID(class="kw">const class="type">uchar id,class="type">uint &magic)        { magic &=0x00FFFFFF; magic |= (class="type">uint)id<<class="num">24;                    }
class=class="str">"cmt">//--- Convert the value of class="num">0 - class="num">15 into the necessary class="type">uchar number bits(class="num">0 - lower, class="num">1 - upper ones)
   class="type">uchar               ConvToXX(class="kw">const class="type">uchar number,class="kw">const class="type">uchar index) class="kw">const { class="kw">return((number>class="num">15 ? class="num">15 : number)<<(class="num">4*(index>class="num">1 ? class="num">1 : index)));}
class=class="str">"cmt">//--- Return(class="num">1) the specified magic number, the ID of(class="num">2) the first group, (class="num">3) second group, (class="num">4) pending request from the magic number value

「从 magic 数里抠出分组与挂单请求」

在 MT5 的 EA 架构里,一个 uint 类型的 magic 数往往不只用来标识订单归属。通过位运算可以把 32 位 magic 拆成 4 段:低 16 位是基础标识,紧接着 8 位拆成两个 4 位分组号,最高 8 位留给挂单请求编号。 下面这组取值函数直接对 magic 做掩码和移位:GetMagicID 取低 16 位(0xFFFF),GetGroupID1 取右移 16 位后的低 4 位(0x0F),GetGroupID2 取同段的 0xF0 再右移 4 位,GetPendReqID 取右移 24 位后的整字节。这样单靠一个整数就能在策略内部做订单分类,不用额外维护字符串映射表。 实际挂单请求由 CreatePendingRequest 落地。它 new 一个 CPendingReq 对象,写入当时 Bid 和时间,尝试加进 m_list_request 链表;若分配失败或入链失败就打印对应信息并回 false。成功后设置激活时间(当前时间 + wait 毫秒)、等待时长、当前尝试次数 0 和总尝试次数 attempts,返回 true。外汇与贵金属杠杆高,这类异步挂单重试机制能降低滑点导致的废单概率,但无法消除极端行情下的成交风险。 打开 MT5 的 MetaEditor,把这段粘进你的交易类,改 magic 生成规则就能让小布盯盘类工具按组统计挂单。

MQL5 / C++
class="type">class="kw">ushort GetMagicID(class="kw">const class="type">uint magic) class="kw">const { class="kw">return class="type">class="kw">ushort(magic & 0xFFFF); }
class="type">uchar GetGroupID1(class="kw">const class="type">uint magic) class="kw">const { class="kw">return class="type">uchar(magic>>class="num">16) & 0x0F; }
class="type">uchar GetGroupID2(class="kw">const class="type">uint magic) class="kw">const { class="kw">return class="type">uchar((magic>>class="num">16) & 0xF0)>>class="num">4; }
class="type">uchar GetPendReqID(class="kw">const class="type">uint magic) class="kw">const { class="kw">return class="type">uchar(magic>>class="num">24) & 0xFF; }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a pending request                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CTrading::CreatePendingRequest(class="kw">const class="type">uchar id,class="kw">const class="type">uchar attempts,class="kw">const class="type">class="kw">ulong wait,class="kw">const class="type">MqlTradeRequest &request,class="kw">const class="type">int retcode,CSymbol *symbol_obj)
  {
   class=class="str">"cmt">//--- 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));
      class="kw">return class="kw">false;
     }
   class=class="str">"cmt">//--- If failed to add the request to the list, display the appropriate message,
   class=class="str">"cmt">//--- remove the created object and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   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));
      class="kw">delete req_obj;
      class="kw">return class="kw">false;
     }
   class=class="str">"cmt">//--- 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(class="num">0);
   req_obj.SetTotalAttempts(attempts);
   class="kw">return true;
  }

挂单请求的定时器重投逻辑

CTrading::OnTimer 是 EA 里靠 Timer 事件驱动的重投中枢,专门处理此前未成交或 pending 的订单请求。它倒序遍历 m_list_request,从最后一个请求往前扫,避免删除节点时打乱下标。 每次循环先取出 CPendingReq 对象,若当前尝试次数已超过 TotalAttempts 或触及 UCHAR_MAX(255),直接从链表里 Delete 掉并跳过——这意味着单请求最多重试 255 次,超出就永久丢弃。 激活时间按 TimeCreate + WaitingMSC × (CurrentAttempt+1) 推算,只有 symbol_obj.Time() 大于等于该时刻才进入发单分支;否则 continue 等下一轮。这保证了每次重试之间至少间隔 WaitingMSC 毫秒,防止对经纪商服务器形成密集轰炸。 发单前还会用 GetPendReqID 取出 magic 对应的挂单 ID,并通过 GetList(ORDER_PROP_PEND_REQ_ID, id, EQUAL) 查重。对 TRADE_ACTION_DEAL,仅当查回列表为空(无同 ID 持仓/订单)才真正投递交易请求,避免重复开仓。外汇与贵金属杠杆交易高风险,重投机制只解决执行可靠性,不预示任何方向概率。

MQL5 / C++
class="type">void CTrading::OnTimer(class="type">void)
  {
  class=class="str">"cmt">//--- In a loop by the list of pending requests
  class="type">int total=this.m_list_request.Total();
  for(class="type">int i=total-class="num">1;i>WRONG_VALUE;i--)
    {
    class=class="str">"cmt">//--- receive the next request object
    CPendingReq *req_obj=this.m_list_request.At(i);
    if(req_obj==NULL)
      class="kw">continue;
    class=class="str">"cmt">//--- if the current attempt exceeds the defined number of trading attempts,
    class=class="str">"cmt">//--- 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);
      class="kw">continue;
      }
    class=class="str">"cmt">//--- get the request structure and the symbol object a trading operation should be performed for
    class="type">MqlTradeRequest request=req_obj.MqlRequest();
    CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol);
    if(symbol_obj==NULL || !symbol_obj.RefreshRates())
      class="kw">continue;
    
    class=class="str">"cmt">//--- Set the request activation time in the request object
    req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+class="num">1));
    
    class=class="str">"cmt">//--- If the current time is less than the request activation time,
    class=class="str">"cmt">//--- this is not the request time - move on to the next request in the list
    if(symbol_obj.Time()<req_obj.TimeActivate())
      class="kw">continue;
    
    class=class="str">"cmt">//--- Set the attempt number in the request object
    req_obj.SetCurrentAttempt(class="type">uchar(req_obj.CurrentAttempt()+class="num">1));
    
    class=class="str">"cmt">//--- Get the pending request ID
    class="type">uchar id=this.GetPendReqID((class="type">uint)request.magic);
    
    class=class="str">"cmt">//--- 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)
      class="kw">continue;
    class=class="str">"cmt">//--- Depending on the type of action performed in the trading request 
    class="kw">switch(request.action)
      {
      class=class="str">"cmt">//--- Open a position
      case TRADE_ACTION_DEAL :
        class=class="str">"cmt">//--- if there is no position/order with the obtained pending request ID(the list is empty), send a trading request
        if(list.Total()==class="num">0)
          {

◍ 错误码分流决定交易请求生死

CTrading 类里 ResultProccessingMethod 这个函数,把 MT 返回的数字码直接映射成三种处理策略:DISABLE、EXIT 或继续重试。MQL4 分支下,错误码 9(交易功能异常)、64(账户禁用)、65(账户号无效)统一归为 DISABLE,意味着当前环境已不可交易,EA 应停止发单。 另一组码如 1(无错但结果未知)、132(市场关闭)、133(交易禁用)、148(订单数超券商上限)、149(禁对冲时反向开仓)、150(违 FIFO 平仓)则返回 EXIT,即本次请求直接放弃,不再纠缠。 值得注意,错误码 148 在实盘里极易触发——很多券商对 XAUUSD 的净挂单加持仓总数卡在 200 以内,EA 批量挂突破单时可能瞬间撞墙。外汇与贵金属杠杆高,这类硬限制会直接打断策略节奏,写 EA 时务必本地用策略测试器跑满边界场景。

MQL5 / C++
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(class="kw">const class="type">uint result_code)
  {
   class="kw">switch(result_code)
     {
  class="macro">#ifdef __MQL4__
     class=class="str">"cmt">//--- Malfunctional trade operation
     case class="num">9   :
     class=class="str">"cmt">//--- Account disabled
     case class="num">64  :
     class=class="str">"cmt">//--- Invalid account number
     case class="num">65  :  class="kw">return ERROR_CODE_PROCESSING_METHOD_DISABLE;
     
     class=class="str">"cmt">//--- No error but result is unknown
     case class="num">1   :
     class=class="str">"cmt">//--- General error
     case class="num">2   :
     class=class="str">"cmt">//--- Old client terminal version
     case class="num">5   :
     class=class="str">"cmt">//--- Not enough rights
     case class="num">7   :
     class=class="str">"cmt">//--- Market closed
     case class="num">132 :
     class=class="str">"cmt">//--- Trading disabled
     case class="num">133 :
     class=class="str">"cmt">//--- Order is locked and being processed
     case class="num">139 :
     class=class="str">"cmt">//--- Buy only
     case class="num">140 :
     class=class="str">"cmt">//--- The number of open and pending orders has reached the limit set by the broker
     case class="num">148 :
     class=class="str">"cmt">//--- Attempt to open an opposite order if hedging is disabled
     case class="num">149 :
     class=class="str">"cmt">//--- Attempt to close a position on a symbol contradicts the FIFO rule
     case class="num">150 :  class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
     
     class=class="str">"cmt">//--- Invalid trading request parameters
     case class="num">3   :
     class=class="str">"cmt">//--- Invalid price
     case class="num">129 :
     class=class="str">"cmt">//--- Invalid stop levels
     case class="num">130 :

「把成交回报错码映射成处理动作」

EA 在 MT5 里发单后拿到的 return code 不能直接当失败扔掉,得按性质分流。下面这段 switch 分支把 MQL5 交易错误码归成了三类处理手法:当场纠正、等待重试、刷新重报。 错误码 131(手数非法)、134(保证金不足)、147(经纪商禁止到期)归为 CORRECT,意味着参数层面有问题,靠重读账户状态或重算仓位就能自行修掉,不需要等服务器。 另一批属于环境类阻塞:4(服务器忙)、6(无连接)、136(无报价)、137(经纪商忙)、145(订单太贴市无法改)映射为等待值 5000;8(请求过频)和 141(请求太多)给 10000;146(交易上下文忙)给 1000。这些都是瞬时拥塞,倾向用退避后重发解决。 价格类回变——128(超时)、135(价格已动)、138(新报价)——统一返回 REFRESH,提示用最新 tick 重构请求再投。MQL5 专属里,10026(服务器禁自动交易)直接 DISABLE;10007 / 10012 / 10017 / 10018 分别是撤单、过期、禁交易、休市,属于不可恢复,应停止本轮执行。外汇与贵金属杠杆高,这类分支漏判可能让 EA 在休市或禁交易时空转消耗。 把这套分支原样贴进你的错误处理器,开 MT5 用『工具→调试』故意触发 136 无报价,验证是否落进 5000 等待而不是报错退出。

MQL5 / C++
class=class="str">"cmt">//--- Invalid volume
case class="num">131 :
class=class="str">"cmt">//--- Not enough money to perform the operation
case class="num">134 :
class=class="str">"cmt">//--- Expirations are denied by broker
case class="num">147 :  class="kw">return ERROR_CODE_PROCESSING_METHOD_CORRECT;

class=class="str">"cmt">//--- Trade server is busy
case class="num">4   :  class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">5000;    class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT
class=class="str">"cmt">//--- No connection to the trade server
case class="num">6   :  class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">5000;    class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT
class=class="str">"cmt">//--- Too frequent requests
case class="num">8   :  class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">10000;   class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT
class=class="str">"cmt">//--- No price
case class="num">136 :  class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">5000;    class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT
class=class="str">"cmt">//--- Broker is busy
case class="num">137 :  class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">5000;    class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT
class=class="str">"cmt">//--- Too many requests
case class="num">141 :  class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">10000;   class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT
class=class="str">"cmt">//--- Modification denied because the order is too close to market
case class="num">145 :  class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">5000;    class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT
class=class="str">"cmt">//--- Trade context is busy
case class="num">146 :  class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">1000;    class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT

class=class="str">"cmt">//--- Trade timeout
case class="num">128 :
class=class="str">"cmt">//--- Price has changed
case class="num">135 :
class=class="str">"cmt">//--- New prices
case class="num">138 :  class="kw">return ERROR_CODE_PROCESSING_METHOD_REFRESH;
class=class="str">"cmt">//--- MQL5
class="macro">#else
class=class="str">"cmt">//--- Auto trading disabled by the server
case class="num">10026   :  class="kw">return ERROR_CODE_PROCESSING_METHOD_DISABLE;

class=class="str">"cmt">//--- Request canceled by a trader
case class="num">10007   :
class=class="str">"cmt">//--- Request expired
case class="num">10012   :
class=class="str">"cmt">//--- Trading disabled
case class="num">10017 :
class=class="str">"cmt">//--- Market closed
case class="num">10018   :
class=class="str">"cmt">//--- Order status changed

交易回执里的隐蔽拒绝码

在 EA 的 OrderSend / CTrade 返回处理里,有一批 100xx 级别的错误码不会弹窗,但会直接让你的挂单或平仓静默失败。下面这段 switch-case 片段把常见码归了类,开 MT5 把代码贴进脚本跑一遍就能看到自己账户实际命中哪些。

  • 代表请求未被改动、10025 是请求被排队阻塞;10028 说明该交易只允许在真实账户执行,模拟盘会直接吃闭门羹。10032 到 10044 这组更现实:挂单上限、单品种总仓位上限、订单类型非法、持仓已平、同仓位重复平仓单、开仓数触顶、挂单激活被拒,以及品种层面的「只允许多 / 只允许空 / 只允许平 / 只允许 FIFO 平」限制,全在这段里。

注意 10045 直接 return ERROR_CODE_PROCESSING_METHOD_EXIT,意味着遇到 requote 类重报价就退出当前处理分支;而 10004、10006、10020 归到 return ERROR_CODE_PROCESSING_METHOD_REFRESH,告诉程序价格变了要去刷新重发。10013–10030 这组是请求本身不合法: volume、price、stop 层级、保证金不足、过期时间、余额执行类型不支持等,都会在这一层被拦下。 外汇和贵金属杠杆高,这类返回码若不在代码里显式区分,实盘可能出现「以为成交其实没成」的偏差。建议把这段 case 表抄进你的交易类,至少对 10032/10039/10016 打日志,能少踩很多坑。

MQL5 / C++
case class="num">10023 :
class=class="str">"cmt">//--- Request unchanged
case class="num">10025 :
class=class="str">"cmt">//--- Request blocked for handling
case class="num">10028 :
class=class="str">"cmt">//--- Transaction is allowed for live accounts only
case class="num">10032 :
class=class="str">"cmt">//--- The maximum number of pending orders is reached
case class="num">10033 :
class=class="str">"cmt">//--- Reached the maximum order and position volume for this symbol
case class="num">10034 :
class=class="str">"cmt">//--- Invalid or prohibited order type
case class="num">10035 :
class=class="str">"cmt">//--- Position with the specified ID already closed
case class="num">10036 :
class=class="str">"cmt">//--- A close order is already present for a specified position
case class="num">10039 :
class=class="str">"cmt">//--- The maximum number of open positions is reached
case class="num">10040 :
class=class="str">"cmt">//--- Request to activate a pending order is rejected, the order is canceled
case class="num">10041 :
class=class="str">"cmt">//--- Request is rejected, because the rule "Only class="type">long positions are allowed" is set for the symbol
case class="num">10042 :
class=class="str">"cmt">//--- Request is rejected, because the rule "Only class="type">class="kw">short positions are allowed" is set for the symbol
case class="num">10043 :
class=class="str">"cmt">//--- Request is rejected, because the rule "Only closing of existing positions is allowed" is set for the symbol
case class="num">10044 :
class=class="str">"cmt">//--- Request is rejected, because the rule "Only closing of existing positions by FIFO rule is allowed" is set for the symbol
case class="num">10045 :  class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
class=class="str">"cmt">//--- Requote
case class="num">10004 :
class=class="str">"cmt">//--- Request rejected
case class="num">10006 :
class=class="str">"cmt">//--- Prices changed
case class="num">10020 :  class="kw">return ERROR_CODE_PROCESSING_METHOD_REFRESH;
class=class="str">"cmt">//--- Invalid request
case class="num">10013 :
class=class="str">"cmt">//--- Invalid request volume
case class="num">10014 :
class=class="str">"cmt">//--- Invalid request price
case class="num">10015 :
class=class="str">"cmt">//--- Invalid request stop levels
case class="num">10016 :
class=class="str">"cmt">//--- Insufficient funds for request execution
case class="num">10019 :
class=class="str">"cmt">//--- Invalid order expiration in a request
case class="num">10022 :
class=class="str">"cmt">//--- The specified type of order execution by balance is not supported
case class="num">10030 :

◍ 把交易错误码映射成处理动作

在 EA 的报错分支里,不同交易服务器返回码决定了下一步是重试、挂起还是直接放行。下面这段 switch 把常见 100xx 系列码翻译成统一的枚举动作,省得每次下单都写一堆 if。

  • 表示平仓量超过当前持仓,直接标为可正确处理;10021 无报价、10024 请求过频、10029 订单冻结、10031 无服务器连接,这几个都映射成等待类动作(返回值 5000 / 10000 / 20000),其中断线用 20000 等待周期更长。
  • 处理错误与 10027 客户端禁用自动交易归为 PENDING 挂起;10008~10010 下单成功、完全或部分成交则不干预,落入 default 后统一返回 OK。

开仓函数用模板接收止损止盈类型,volume 和 symbol 必填,magic 默认 ULONG_MAX,sl/tp 不给就填 0,调用时可直接 OpenPosition(POSITION_TYPE_BUY, 0.1, "XAUUSD") 在 MT5 里验证。外汇与贵金属杠杆高,回测通过不代表实盘不爆仓,参数请先在模拟盘试。

MQL5 / C++
class=class="str">"cmt">//--- Closed volume exceeds the current position volume
case class="num">10038  : class="kw">return ERROR_CODE_PROCESSING_METHOD_CORRECT;
class=class="str">"cmt">//--- No quotes to handle the request
case class="num">10021  : class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">5000;    class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT;
class=class="str">"cmt">//--- Too frequent requests
case class="num">10024  : class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">10000;  class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT
class=class="str">"cmt">//--- An order or a position is frozen
case class="num">10029  : class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">10000;  class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT;
class=class="str">"cmt">//--- No connection to the trade server
case class="num">10031  : class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">20000;  class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT;
class=class="str">"cmt">//--- Request handling error
case class="num">10011  :
class=class="str">"cmt">//--- Auto trading disabled by the client terminal
case class="num">10027  : class="kw">return ERROR_CODE_PROCESSING_METHOD_PENDING;
class=class="str">"cmt">//--- Order placed
case class="num">10008  :
class=class="str">"cmt">//--- Request executed
case class="num">10009  :
class=class="str">"cmt">//--- Request executed partially
case class="num">10010  :
class=class="str">"cmt">//--- "OK"
class="kw">default:
  break;
}
class="kw">return ERROR_CODE_PROCESSING_METHOD_OK;
}
class="kw">template<class="kw">typename SL,class="kw">typename TP>
class="type">bool CTrading::OpenPosition(class="kw">const class="type">ENUM_POSITION_TYPE type,
                            class="kw">const class="type">class="kw">double volume,
                            class="kw">const class="type">class="kw">string symbol,
                            class="kw">const class="type">class="kw">ulong magic=ULONG_MAX,
                            class="kw">const SL sl=class="num">0,
                            class="kw">const TP tp=class="num">0,

「下单函数里的价格与错误预处理」

在封装交易请求时,这段函数先把成交前的内部状态清零:res 置 true,错误标志写成 TRADE_REQUEST_ERR_FLAG_NO_ERROR,再把传入的 type 与 order_type、action 做枚举映射。 随后通过 m_symbols.GetSymbolObjByName(symbol) 拿品种对象,拿不到就打内部错误标志并返回 false;再从品种对象取 CTradeObj,同样失败即返回。这里两层 NULL 判断决定了后续逻辑能否继续,是库内防崩的第一道闸。 价格写入靠 this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj),若返回 false,则在返回结构里硬写结果码 10021(No quotes to process the request)并退出——说明无报价时不会往下发单。 成交量写进 m_request.volume 后,按多空取 symbol_obj.Ask() 或 Bid() 作为 pr,交给 CheckErrors 决定错误处理方式。当 method 不是 OK 而是 DISABLE 时,直接把 MSG_LIB_TEXT_TRADING_DISABLE 塞进返回结构并播报错音,交易被完全禁用的分支就此截断。 开 MT5 把这段塞进自己的 EA 框架,断点跟一次 symbol_obj 与 trade_obj 的获取,能确认你用的品种对象在非常规品种上是否也会掉进内部错误分支。

MQL5 / C++
class="kw">const class="type">class="kw">string comment=NULL,
class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX)
  {
class=class="str">"cmt">//--- Set the trading request result as &class="macro">#x27;true&class="macro">#x27; and the error flag as "no errors"
   class="type">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;
class=class="str">"cmt">//--- Get a symbol object by a symbol name. If failed to get
   CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol);
class=class="str">"cmt">//--- If failed to get - write the "internal error" flag, display the message in the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   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));
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- get a trading object from a symbol object
   CTradeObj *trade_obj=symbol_obj.GetTradeObj();
class=class="str">"cmt">//--- If failed to get - write the "internal error" flag, display the message in the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   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));
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- Set the prices
class=class="str">"cmt">//--- If failed to set - write the "internal error" flag, set the error code in the class="kw">return structure,
class=class="str">"cmt">//--- display the message in the journal and class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
   if(!this.SetPrices(order_type,class="num">0,sl,tp,class="num">0,DFUN,symbol_obj))
     {
       this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR;
       trade_obj.SetResultRetcode(class="num">10021);
       trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
       if(this.m_log_level>LOG_LEVEL_NO_MSG)
         ::Print(DFUN,CMessage::Text(class="num">10021));   class=class="str">"cmt">// No quotes to process the request
       class="kw">return class="kw">false;
     }
class=class="str">"cmt">//--- Write the volume to the request structure
   this.m_request.volume=volume;
class=class="str">"cmt">//--- Get the method of handling errors from the CheckErrors() method while checking for errors in the request parameters
   class="type">class="kw">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,class="num">0,this.m_request.sl,this.m_request.tp);
class=class="str">"cmt">//--- In case of trading limitations, funds insufficiency,
class=class="str">"cmt">//--- if there are limitations by StopLevel or FreezeLevel ...
   if(method!=ERROR_CODE_PROCESSING_METHOD_OK)
     {
       class=class="str">"cmt">//--- If trading is completely disabled, set the error code to the class="kw">return structure,
       class=class="str">"cmt">//--- 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()));

错误处理分支如何决定下单走向

在交易库里,错误检查之后不会一刀切中断,而是按 method 分支决定下一步动作。遇到 EXIT 分支时,会取错误列表最后一个码写入返回结构,并视日志级别打印“交易操作已中止”,若开启声音则播报错音,随后 return false 直接退出。 WAIT 分支稍有不同:同样写入错误码与注释,但 journal 提示“创建挂单请求”,接着调用 ::Sleep(method) 原地等待,再用 symbol_obj.Refresh() 刷新品种数据,相当于用短暂休眠规避瞬时报价冲突。外汇与贵金属市场点差跳变频繁,这种等待机制可能降低因报价过期导致的 rejected 概率。 若行为模式设为 ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST,则只打印“创建挂单请求”而不立即做任何处理,把后续交给外部调度。下面的发送循环展示了实际下单入口:遍历 m_total_try 次尝试调用 OpenPosition。

MQL5 / C++
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);
class="kw">return class="kw">false;
}
class=class="str">"cmt">//--- If the check result is "abort trading operation" - set the last error code to the class="kw">return structure,
class=class="str">"cmt">//--- display a journal message, play the error sound and exit
if(method==ERROR_CODE_PROCESSING_METHOD_EXIT)
  {
   class="type">int code=this.m_list_errors.At(this.m_list_errors.Total()-class="num">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);
   class="kw">return class="kw">false;
   }
class=class="str">"cmt">//--- If the check result is "waiting" - set the last error code to the class="kw">return structure and display the message in the journal
if(method==ERROR_CODE_PROCESSING_METHOD_WAIT)
  {
   class="type">int code=this.m_list_errors.At(this.m_list_errors.Total()-class="num">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));
   class=class="str">"cmt">//--- Instead of creating a pending request, we temporarily wait the required time period(the CheckErrors() method result is returned)
   ::Sleep(method);
   class=class="str">"cmt">//--- after waiting, update all data
   symbol_obj.Refresh();
   }
class=class="str">"cmt">//--- 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));
   }
   }
class=class="str">"cmt">//--- In the loop by the number of attempts
 for(class="type">int i=class="num">0;i<this.m_total_try;i++)
   {
   class=class="str">"cmt">//--- Send the request
   res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation);

◍ 下单失败后的分支处理机制

EA 在发送订单请求后,并非只盯着一个成功或失败的标志位。若返回码显示请求已成功,或当前处于异步发单模式,系统会播放成功音效并直接返回 true,这部分逻辑很直白。 当请求未成功时,代码先按日志级别打印第 i+1 次尝试的报错内容(如 MSG_LIB_SYS_ERROR 与交易对象返回的错误码),再播放错误音效。随后通过 ResultProccessingMethod() 拿到错误处理方式 method,这是整个容错循环的核心分派点。 method 有几种明确走向:等于 DISABLE 时置交易禁用标志并 break;等于 EXIT 时直接 break;等于 CORRECT 时调 RequestErrorsCorrecting() 修正手数/价位等参数后 continue;等于 REFRESH 时调 symbol_obj.Refresh() 刷新品种数据后 continue。 若 method 大于 REFRESH(即「等待并重试」或「挂起请求」类),则进入创建挂单请求的支线并结束循环。外汇与贵金属杠杆高、滑点突发,这类分级处理能降低瞬时报错导致的 EA 自杀式停摆,但任何自动重试都可能放大风险敞口,实盘前应在 MT5 策略测试器里逐类错误码验证分支触发。

MQL5 / C++
   class=class="str">"cmt">//--- If the request is executed successfully or the asynchronous order sending mode is set, play the success sound
   class=class="str">"cmt">//--- set for a symbol trading object for this type of trading operation and class="kw">return &class="macro">#x27;true&class="macro">#x27;
   if(res || trade_obj.IsAsyncMode())
      {
       if(this.IsUseSounds())
         trade_obj.PlaySoundSuccess(action,order_type);
       class="kw">return true;
      }
   class=class="str">"cmt">//--- 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),class="type">class="kw">string(i+class="num">1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode()));
       if(this.IsUseSounds())
         trade_obj.PlaySoundError(action,order_type);
       
       class=class="str">"cmt">//--- Get the error handling method
       method=this.ResultProccessingMethod(trade_obj.GetResultRetcode());
       class=class="str">"cmt">//--- 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;
         }
       class=class="str">"cmt">//--- 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;
         }
       class=class="str">"cmt">//--- If "Correct the parameters and repeat" is received as a result of sending a request -
       class=class="str">"cmt">//--- 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);
          class="kw">continue;
         }
       class=class="str">"cmt">//--- If "Update data and repeat" is received as a result of sending a request -
       class=class="str">"cmt">//--- update data and start the next iteration
       if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH)
         {
          symbol_obj.Refresh();
          class="kw">continue;
         }
       class=class="str">"cmt">//--- If "Wait and repeat" or "Create a pending request" is received as a result of sending a request,
       class=class="str">"cmt">//--- create a pending request and end the loop
       if(method>ERROR_CODE_PROCESSING_METHOD_REFRESH)
         {
          class=class="str">"cmt">//--- If the trading request magic number, has no pending request ID

「挂单请求 ID 的兜底写入逻辑」

GetPendReqID 针对指定 magic 返回 0,说明这笔订单还没进挂单队列,代码进入兜底分支去抢一个空闲 ID 并落结构。 等待时间 wait 的算法很直接:若处理方式是「等待并重试」,method 大于错误码阈值时就拿 method 本身当毫秒数;若是「创建挂单请求」方式,则 wait 置 0,表示不阻塞立即挂起。 id=GetFreeID() 拿最小可用 ID,一旦 id<1symbol_obj.RefreshRates() 刷新报价失败,直接 return false,不往下写任何字段。这里若行情刷新失败,挂单就不会生成,外汇与贵金属品种在高波动时此分支触发概率明显上升,属高风险环节。 magic 号拼接上:若传入 magic==ULONG_MAX 则改用 trade_obj.GetMagic() 取当前魔术码,否则强转传入值;随后 SetPendReqID 把 ID 与 magic 绑进挂单表,m_request 补 symbol、action 为市价成交、type 为订单类型。 重试次数 attempts 传的是 m_total_try-1,因为本次失败已算一次;下限钳在 1,避免归零后挂单直接失效。最后 CreatePendingRequest 带着剩余尝试次数、等待毫秒、请求结构、上次回码和品种对象入库,并 break 跳出。

MQL5 / C++
if(this.GetPendReqID((class="type">uint)magic)==class="num">0)
  {
  class=class="str">"cmt">//--- Waiting time in milliseconds:
  class=class="str">"cmt">//--- for the "Wait and repeat" handling method, the waiting value corresponds to the &class="macro">#x27;method&class="macro">#x27; value,
  class=class="str">"cmt">//--- for the "Create a pending request" handling method - till there is a zero waiting time
  class="type">class="kw">ulong wait=(method>ERROR_CODE_PROCESSING_METHOD_PENDING ? method : class="num">0);
  class=class="str">"cmt">//--- Look for the least of the possible IDs. If failed to find
  class=class="str">"cmt">//--- or in case of an error while updating the current symbol data, class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;
  class="type">int id=this.GetFreeID();
  if(id<class="num">1 || !symbol_obj.RefreshRates())
     class="kw">return class="kw">false;
  class=class="str">"cmt">//--- Write the request ID to the magic number, while a symbol name is set in the request structure
  class=class="str">"cmt">//--- Set position and trading operation types(the remaining structure fields are already filled in)
  class="type">uint mn=(magic==ULONG_MAX ? (class="type">uint)trade_obj.GetMagic() : (class="type">uint)magic);
  this.SetPendReqID((class="type">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;
  class=class="str">"cmt">//--- Pass the number of trading attempts minus one to the pending request,
  class=class="str">"cmt">//--- since there already has been one failed attempt
  class="type">uchar attempts=(this.m_total_try-class="num">1 < class="num">1 ? class="num">1 : this.m_total_try-class="num">1);
  this.CreatePendingRequest((class="type">uchar)id,attempts,wait,this.m_request,trade_obj.GetResultRetcode(),symbol_obj);
  break;
  }

订单参数与重试次数的接口设定

交易引擎把挂单的填充类型、有效期、魔术码、滑点、手数等全部封装成一组 TradingSet* 方法,调用时若不填 symbol_name 则默认作用于标准品种,填了具体品种名就只改那一个交易对象。 填充方式默认 ORDER_FILLING_FOK,有效期默认 ORDER_TIME_GTC,意味着不显式改的话,订单会按全部成交或撤销、且长期有效来处理。外汇与贵金属杠杆高,FOK 在流动性差时可能直接被拒,实盘前建议在 MT5 策略测试器里换 ORDER_FILLING_IOC 对比成交率。 最后一行 TradingSetTotalTry 是重试控制:把尝试次数写进引擎内部的 m_trading 对象,网络抖动或报价延迟时,设 3 次可能比默认 1 次更容易补单成功。 [CODE]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); }[/CODE] 逐行拆解:第1行设修正后的填充类型,防经纪商不支持的填法;第2行直接设填充类型不修正;第3、4行同理处理订单有效期;第5行写魔术码区分 EA 实例;第6行写订单注释;第7行设允许滑点(点单位);第8行设手数,默认0需自填;第9行设到期时间,0表示不限期;第10行切同步/异步发单;第11行控日志等级;第12行把重试次数传入底层交易对象。

MQL5 / C++
class="type">void TradingSetCorrectTypeFilling(class="kw">const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetTypeFilling(class="kw">const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetCorrectTypeExpiration(class="kw">const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetTypeExpiration(class="kw">const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetMagic(class="kw">const class="type">uint magic,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetComment(class="kw">const class="type">class="kw">string comment,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetDeviation(class="kw">const class="type">class="kw">ulong deviation,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetVolume(class="kw">const class="type">class="kw">double volume=class="num">0,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetExpiration(class="kw">const class="type">class="kw">datetime expiration=class="num">0,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetAsyncMode(class="kw">const class="type">bool async_mode=class="kw">false,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetLogLevel(class="kw">const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,class="kw">const class="type">class="kw">string symbol_name=NULL);
class="type">void TradingSetTotalTry(class="kw">const class="type">uchar attempts) { this.m_trading.SetTotalTry(attempts); }

◍ 把分组与挂单号压进一个 magic 整数

在 MT5 的 EA 工程里,magic number 常被当成单纯标识。其实一个 uint 有 32 位,足够把基础 magic、两组 ID 和挂单请求 ID 全塞进去,方便按维度过滤持仓与订单。 下面这段辅助函数先把单字节数值限制到 0–15,再按 index 左移 4 位或 8 位,拼出低 8 位的分组段。它不写内存,只返回移位后的字节,供上层合并用。 [CODE] uchar ConvToXX(const uchar number,const uchar index) const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index))); } [/CODE] 主接口 SetCompositeMagicNumber 接收 ushort 级 magic_id 与三个默认 0 的 uchar 参数。函数内先赋给 uint magic,再依次调用交易对象的 SetGroupID1 / SetGroupID2 / SetPendReqID,把对应位段写进同一个变量后返回。 [CODE] 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; } [/CODE] 实盘里若同时跑多个策略实例,用 group_id1 区分策略、group_id2 区分品种分组,pending_req_id 标记未成交挂单来源,后续用按位与就能精准筛选。外汇与贵金属杠杆高,误筛订单可能导致重复开仓,先在策略测试器跑一遍位逻辑再上实盘。

MQL5 / C++
class="type">uchar ConvToXX(class="kw">const class="type">uchar number,class="kw">const class="type">uchar index) class="kw">const { class="kw">return((number>class="num">15 ? class="num">15 : number)<<(class="num">4*(index>class="num">1 ? class="num">1 : index))); }
class="type">uint CEngine::SetCompositeMagicNumber(class="type">class="kw">ushort magic_id,class="kw">const class="type">uchar group_id1=class="num">0,class="kw">const class="type">uchar group_id2=class="num">0,class="kw">const class="type">uchar pending_req_id=class="num">0)
  {
   class="type">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);
   class="kw">return magic;
  }

「断网也能挂单:延后请求实测路径」

把上一篇文章里的 EA 放到 \MQL5\Experts\TestDoEasy\Part26\ 目录下,改名 TestDoEasyPart26.mq5,就能跑这套延后请求验证。输入参数里先把魔幻数字类型从 ulong 改成 ushort,上限锁死在两个字节即 65535,再补一个 uchar 类型的交易尝试次数变量(默认 5 次)。 全局变量同步把 magic_number 降为 ushort,并加两个存组值的变量;OnInit() 里初始化组变量再用伪随机函数设初值。函数库初始化时给所有交易对象写默认魔幻数字和尝试次数,开仓逻辑引入 comp_magic 开关:为真就用「设置魔幻数字 + 随机组1/组2 ID」拼成的复合魔幻,为假就只用设置里的固定值。 实测步骤很直接:编译启动 EA,断网后点「卖出」,终端右下角弹连接丢失提示,日志报交易服务器错误。此时函数库把这笔失败开仓的参数收进延后请求,带尝试次数和 20 秒等待。恢复联网后请求立刻发出,持仓成功,日志打出实际魔幻数字 17629307,括号里 (123) 是 EA 设置值,G1: 13 表示第一组 ID 为 13,第二组为 0 故不显示。 别把正态当圣经 这套延后请求只是概念验证,所附测试 EA 严禁接实盘。外汇与贵金属本身高杠杆高风险,当前代码未成品化,只在 MT5 演示模式或策略测试器里跑就行。

MQL5 / C++
class=class="str">"cmt">//--- class="kw">input variables
class="kw">input    class="type">class="kw">ushort            InpMagic           =  class="num">123;   class=class="str">"cmt">// Magic number
class="kw">input    class="type">class="kw">double            InpLots            =  class="num">0.1;   class=class="str">"cmt">// Lots
class="kw">input    class="type">uint              InpStopLoss        =  class="num">150;   class=class="str">"cmt">// StopLoss in points
class="kw">input    class="type">uint              InpTakeProfit      =  class="num">150;   class=class="str">"cmt">// TakeProfit in points
class="kw">input    class="type">uint              InpDistance        =  class="num">50;    class=class="str">"cmt">// Pending orders distance(points)
class="kw">input    class="type">uint              InpDistanceSL      =  class="num">50;    class=class="str">"cmt">// StopLimit orders distance(points)
class="kw">input    class="type">uint              InpSlippage        =  class="num">5;     class=class="str">"cmt">// Slippage in points
class="kw">input    class="type">uint              InpSpreadMultiplier=  class="num">1;     class=class="str">"cmt">// Spread multiplier for adjusting stop-orders by StopLevel
class="kw">input    class="type">uchar             InpTotalAttempts   =  class="num">5;     class=class="str">"cmt">// Number of trading attempts
sinput   class="type">class="kw">double            InpWithdrawal      =  class="num">10;    class=class="str">"cmt">// Withdrawal funds(in tester)
sinput   class="type">uint              InpButtShiftX      =  class="num">40;    class=class="str">"cmt">// Buttons X shift 
sinput   class="type">uint              InpButtShiftY      =  class="num">10;    class=class="str">"cmt">// Buttons Y shift 
class="kw">input    class="type">uint              InpTrailingStop    =  class="num">50;    class=class="str">"cmt">// Trailing Stop(points)

EA 输入参数与全局变量的落地拆解

把一套多品种马丁类 EA 的头部配置读透,比看正文逻辑更实在。下面这段声明决定了它跑哪些品种、怎么移动止损、以及声音开关。 输入参数里 Trailing Step 设 20 点、Trailing Start 设 0 点,意味着价格一越过开仓价就允许立刻跟进止损;StopLoss for modification 20 点、TakeProfit for modification 60 点,则框定了存量单被算法重新修饰的边界。 InpUsedSymbols 默认串了 11 个直盘与交叉盘(EURUSD、AUDUSD、EURAUD、EURCAD、EURGBP、EURJPY、GBPUSD、NZDUSD、USDCAD、USDJPY,其中 EURUSD 重复出现一次),用逗号分隔,实盘前建议手动删掉重复项以免日志刷屏。 全局区里 magic_number 用 ushort 承载,group1 / group2 用 uchar 做分组标记,withdrawal 做了下限钳制:小于 0.1 时强制取 0.1。外汇与贵金属杠杆高,这类参数若误设,回测漂亮实盘可能瞬间拉爆净值。 开 MT5 把这段直接贴进 EA 头部,改 InpUsedSymbols 为你常盯的 3~5 个品种,先跑一周模拟盘看 trailing_start=0 在 EURJPY 上的滑点表现,再决定是否上真仓。

MQL5 / C++
class="kw">input   class="type">uint                InpTrailingStep     = class="num">20;   class=class="str">"cmt">// Trailing Step(points)
class="kw">input   class="type">uint                InpTrailingStart    = class="num">0;     class=class="str">"cmt">// Trailing Start(points)
class="kw">input   class="type">uint                InpStopLossModify   = class="num">20;   class=class="str">"cmt">// StopLoss for modification(points)
class="kw">input   class="type">uint                InpTakeProfitModify = class="num">60;   class=class="str">"cmt">// TakeProfit for modification(points)
sinput  ENUM_SYMBOLS_MODE InpModeUsedSymbols   = SYMBOLS_MODE_CURRENT;   class=class="str">"cmt">// Mode of used symbols list
sinput  class="type">class="kw">string              InpUsedSymbols      = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; class=class="str">"cmt">// List of used symbols(comma - separator)
sinput  class="type">bool                InpUseSounds        = true; class=class="str">"cmt">// Use sounds
class=class="str">"cmt">//--- global variables
class=class="str">"cmt">//--- global variables
CEngine      engine;
SDataButt    butt_data[TOTAL_BUTT];
class="type">class="kw">string       prefix;
class="type">class="kw">double       lot;
class="type">class="kw">double       withdrawal=(InpWithdrawal<class="num">0.1 ? class="num">0.1 : InpWithdrawal);
class="type">class="kw">ushort       magic_number;
class="type">uint         stoploss;
class="type">uint         takeprofit;
class="type">uint         distance_pending;
class="type">uint         distance_stoplimit;
class="type">uint         slippage;
class="type">bool         trailing_on;
class="type">class="kw">double       trailing_stop;
class="type">class="kw">double       trailing_step;
class="type">uint         trailing_start;
class="type">uint         stoploss_to_modify;
class="type">uint         takeprofit_to_modify;
class="type">int          used_symbols_mode;
class="type">class="kw">string       used_symbols;
class="type">class="kw">string       array_used_symbols[];
class="type">bool         testing;
class="type">uchar        group1;
class="type">uchar        group2;

◍ EA 初始化时的按钮与全局参数装配

这段初始化逻辑在 OnInit 阶段把面板按钮名、手数、止损止盈等交易参数一次性写进结构体数组,方便后续挂单与拖拽操作直接读取。prefix 用 MQLInfoString(MQL_PROGRAM_NAME) 拼下划线,再循环把 TOTAL_BUTT 个按钮的 name 和 text 填好,按钮数量由枚举 ENUM_BUTTONS 的总项数决定。 lot 取输入手数和当前品种最小手数两倍中的较大值再做 NormalizeLot,等于强制把下单量垫到 MinimumLots*2.0 以上,避免某些贵金属点差大时手数过小被拒。trailing_stop、trailing_step 乘了 Point(),把输入的整数点差转成真实价格距离。 group1、group2 先清零,随后 srand(GetTickCount()) 用系统节拍数播种子,让随机分组在每次启动 EA 时序列不同。OnInitDoEasy() 里若选了全品种模式,会弹 MB_YESNO 警告框列出服务器品种数 SymbolsTotal(false) 与上限 SYMBOLS_COMMON_TOTAL,点“否”就退回当前品种。 外汇与贵金属波动剧烈、杠杆风险高,全品种预加载在服务器品种多时可能卡好几秒,实盘前建议在策略 tester 先跑一遍确认 array_used_symbols 符合预期。

MQL5 / C++
  prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
  testing=engine.IsTester();
  for(class="type">int i=class="num">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())*class="num">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;

class=class="str">"cmt">//--- Initialize random group numbers
  group1=class="num">0;
  group2=class="num">0;
  srand(GetTickCount());

class=class="str">"cmt">//--- Initialize DoEasy library
  OnInitDoEasy();
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Initializing DoEasy library                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnInitDoEasy()
  {
class=class="str">"cmt">//--- Check if working with the full list is selected
  used_symbols_mode=InpModeUsedSymbols;
  if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)
    {
    class="type">int total=SymbolsTotal(class="kw">false);
    class="type">class="kw">string ru_n="\nКоличество символов на сервере "+(class="type">class="kw">string)total+".\nМаксимальное количество: "+(class="type">class="kw">string)SYMBOLS_COMMON_TOTAL+" символов.";
    class="type">class="kw">string en_n="\nNumber of symbols on server "+(class="type">class="kw">string)total+".\nMaximum number: "+(class="type">class="kw">string)SYMBOLS_COMMON_TOTAL+" symbols.";
    class="type">class="kw">string caption=TextByLanguage("Внимание!","Attention!");
    class="type">class="kw">string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\"";
    class="type">class="kw">string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a class="type">long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\"";
    class="type">class="kw">string message=TextByLanguage(ru,en);
    class="type">int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
    class="type">int mb_res=MessageBox(message,caption,flags);
    class="kw">switch(mb_res)
      {
       case IDNO :
         used_symbols_mode=SYMBOLS_MODE_CURRENT;
         break;
       class="kw">default:
         break;
      }
    }
class=class="str">"cmt">//--- Fill in the array of used symbols
  used_symbols=InpUsedSymbols;
  CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols);
class=class="str">"cmt">//--- Set the type of the used symbol list in the symbol collection
  engine.SetUsedSymbols(array_used_symbols);

「把资源文件和交易参数一次性灌进引擎」

EA 初始化尾声要把音效、图标等资源写进终端文件目录,并给交易类喂好全局参数。下面这段代码在 OnInit 里连续调用 engine.CreateFile,把 8 个 WAV 音效(硬币、点击、收银机)和 2 个 BMP 指示灯图标按 FILE_TYPE_WAV / FILE_TYPE_BMP 落盘,双语描述靠 TextByLanguage 自动切俄语和英语。 灌完资源立刻把符号集合交给交易模块:engine.TradingOnInit() 之后链式设定魔术码、同步发单、错误重试次数。注意 TradingSetAsyncMode(false) 是走同步下单,外汇和贵金属点差跳变快时可能阻塞主线程,实盘前建议在策略测试器里压一遍。 重试次数由外部变量 InpTotalAttempts 控制,直接 TradingSetTotalTry 传入;声音总开关用 InpUseSounds,点差过滤器用 InpSpreadMultiplier 限定开仓阈值。外汇、贵金属杠杆高,参数设错可能放大滑点亏损概率。 注释里那段循环演示了如何按符号逐个设跟踪:SetControlBidInc(100000*symbol.Point()) 即监控买价涨 100 点,SetControlSpreadInc(400) 监控点差扩 40 点;不跟踪的属性保持 LONG_MAX 即可,运行时随时可改。

MQL5 / C++
class=class="str">"cmt">//--- 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());

class=class="str">"cmt">//--- Create resource text files
  engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки class="num">1","Falling coin class="num">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("Звук упавшей монетки class="num">2","Falling coin class="num">2"),sound_array_coin_04);
  engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке class="num">1","Button click class="num">1"),sound_array_click_01);
  engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке class="num">2","Button click class="num">2"),sound_array_click_02);
  engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке class="num">3","Button click class="num">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);
class=class="str">"cmt">//--- Pass all existing collections to the trading class
  engine.TradingOnInit();
class=class="str">"cmt">//--- Set the class="kw">default magic number for all used symbols
  engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number));
class=class="str">"cmt">//--- Set synchronous passing of orders for all used symbols
  engine.TradingSetAsyncMode(class="kw">false);
class=class="str">"cmt">//--- Set the number of trading attempts in case of an error
  engine.TradingSetTotalTry(InpTotalAttempts);
class=class="str">"cmt">//--- Set standard sounds for trading objects of all used symbols
  engine.SetSoundsStandart();
class=class="str">"cmt">//--- Set the general flag of using sounds
  engine.SetUseSounds(InpUseSounds);
class=class="str">"cmt">//--- Set the spread multiplier for symbol trading objects in the symbol collection
  engine.SetSpreadMultiplier(InpSpreadMultiplier);

class=class="str">"cmt">//--- Set controlled values for symbols
  class=class="str">"cmt">//--- Get the list of all collection symbols
  CArrayObj *list=engine.GetListAllUsedSymbols();
  if(list!=NULL && list.Total()!=class="num">0)
    {
    class=class="str">"cmt">//--- In a loop by the list, set the necessary values for tracked symbol properties
    class=class="str">"cmt">//--- By class="kw">default, the LONG_MAX value is set to all properties, which means "Do not track this class="kw">property" 
    class=class="str">"cmt">//--- 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(class="type">int i=class="num">0;i<list.Total();i++)
      {
      CSymbol* symbol=list.At(i);
      if(symbol==NULL)
        class="kw">continue;
      class=class="str">"cmt">//--- Set control of the symbol price increase by class="num">100 points
      symbol.SetControlBidInc(class="num">100000*symbol.Point());
      class=class="str">"cmt">//--- Set control of the symbol price decrease by class="num">100 points
      symbol.SetControlBidDec(class="num">100000*symbol.Point());
      class=class="str">"cmt">//--- Set control of the symbol spread increase by class="num">40 points
      symbol.SetControlSpreadInc(class="num">400);
      class=class="str">"cmt">//--- Set control of the symbol spread decrease by class="num">40 points

账户风控阈值与随机魔数下单

在引擎初始化阶段,给当前账户对象挂上可控阈值是最容易被忽略的一步。下面这段把账户权益和浮盈的增量控制直接写死:利润增量控到 10.0、权益增量控到 15.0,利润控制水位线设在 20.0,意味着账户利润爬到 20 之前,相关监控逻辑不会触发越界告警。 按下面板按钮时,系统先用 StringSubstr 把带前缀的按钮名切成纯 ID,再给 group1、group2 赋 0–15 内的 Rand() 随机值。若 comp_magic 为 true,魔法数会由 SetCompositeMagicNumber 拼入随机组 ID,否则退回固定 magic_number——这套做法能让多实例 EA 在 MT5 同一账户下互不串单。 开仓分支里,BUTT_BUY 直接调 OpenBuy 走市价多单,BUTT_BUY_LIMIT / BUTT_BUY_STOP 则按 distance_pending 挂限价或停损单,stoploss、takeprofit 与 magic 一并传入。外汇与贵金属杠杆高、滑点跳空频繁,随机魔数虽能隔离订单,但并不能降低品种本身的高风险,参数请在策略测试器先跑通再上实盘。

MQL5 / C++
class=class="str">"cmt">//--- Set controlled values for the current account
  CAccount* account=engine.GetAccountCurrent();
  if(account!=NULL)
  {
  class=class="str">"cmt">//--- Set control of the profit increase to class="num">10
  account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,class="num">10.0);
  class=class="str">"cmt">//--- Set control of the funds increase to class="num">15
  account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,class="num">15.0);
  class=class="str">"cmt">//--- Set profit control level to class="num">20
  account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,class="num">20.0);
  }
}

class="type">void PressButtonEvents(class="kw">const class="type">class="kw">string button_name)
  {
  class="type">bool comp_magic=true;   class=class="str">"cmt">// Temporary variable selecting the composite magic number with random group IDs
  class="type">class="kw">string comment="";
  class=class="str">"cmt">//--- Convert button name into its class="type">class="kw">string ID
  class="type">class="kw">string button=StringSubstr(button_name,StringLen(prefix));
  class=class="str">"cmt">//--- Random group class="num">1 and class="num">2 numbers within the range of class="num">0 - class="num">15
  group1=(class="type">uchar)Rand();
  group2=(class="type">uchar)Rand();
  class="type">uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number);
  class=class="str">"cmt">//--- If the button is pressed
  if(ButtonState(button_name))
  {
  class=class="str">"cmt">//--- If the BUTT_BUY button is pressed: Open Buy position
  if(button==EnumToString(BUTT_BUY))
    {
    class=class="str">"cmt">//--- Open Buy position
    engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit);
    }
  class=class="str">"cmt">//--- If the BUTT_BUY_LIMIT button is pressed: Place BuyLimit
  else if(button==EnumToString(BUTT_BUY_LIMIT))
    {
    class=class="str">"cmt">//--- Set BuyLimit order
    engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyLimit","Pending BuyLimit order"));
    }
  class=class="str">"cmt">//--- If the BUTT_BUY_STOP button is pressed: Set BuyStop
  else if(button==EnumToString(BUTT_BUY_STOP))
    {
    class=class="str">"cmt">//--- Set BuyStop order
    engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending BuyStop order"));
    }

◍ 按钮事件落到挂单与平仓的分派逻辑

这段分支处理把界面按钮名映射成具体交易动作:BuyStopLimit、Sell、SellLimit、SellStop、SellStopLimit 各自走独立 else if,靠 EnumToString(button) 与预设枚举常量比对来识别。 PlaceBuyStopLimit 比普通挂单多一个 distance_stoplimit 参数,用来控制止损限价单触发后的限价偏移;而 OpenSell 显式留空注释,说明由引擎给默认注释,不传文案。 下面这段随机函数给按钮随机高亮或模拟抖动用,范围默认 0~15: uint Rand(const uint min=0, const uint max=15) { return (rand() % (max+1-min))+min; } 日志里有一条 2019.11.26 15:34:48.661 的报错:CTrading::OpenPosition 返回 Invalid request,紧随其后是 No connection with the trade server。说明断线时任何下单分支都会直接失效,实盘外汇/贵金属波动大,这类瞬间掉线可能让你错过入场或平仓,建议在 MT5 里用日志面板复现断网下的按钮响应。

MQL5 / C++
class=class="str">"cmt">//--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit
else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
  {
   class=class="str">"cmt">//--- Set BuyStopLimit order
   engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStopLimit","Pending BuyStopLimit order"));
  }
class=class="str">"cmt">//--- If the BUTT_SELL button is pressed: Open Sell position
else if(button==EnumToString(BUTT_SELL))
  {
   class=class="str">"cmt">//--- Open Sell position
   engine.OpenSell(lot,Symbol(),magic,stoploss,takeprofit);  class=class="str">"cmt">// No comment - the class="kw">default comment is to be set
  }
class=class="str">"cmt">//--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit
else if(button==EnumToString(BUTT_SELL_LIMIT))
  {
   class=class="str">"cmt">//--- Set SellLimit order
   engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending SellLimit order"));
  }
class=class="str">"cmt">//--- If the BUTT_SELL_STOP button is pressed: Set SellStop
else if(button==EnumToString(BUTT_SELL_STOP))
  {
   class=class="str">"cmt">//--- Set SellStop order
   engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending SellStop order"));
  }
class=class="str">"cmt">//--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit
else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
  {
   class=class="str">"cmt">//--- Set SellStopLimit order
   engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending SellStopLimit order"));
  }
class=class="str">"cmt">//--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit
else if(button==EnumToString(BUTT_CLOSE_BUY))

class="type">uint Rand(class="kw">const class="type">uint min=class="num">0, class="kw">const class="type">uint max=class="num">15)
  {
   class="kw">return (rand() % (max+class="num">1-min))+min;
  }

「账户禁交易时 EA 仍在重试的日志现场」

一段真实的 MT5 专家日志显示,2019.11.26 15:34:48 到 15:35:01 的 13 秒内,EA 连续触发了多次开仓尝试。前两次明确回报 "No connection with the trade server" 与 "Trading is prohibited for the current account",说明当时账户处于禁止交易状态或断连。 但日志并未就此停手:15:35:00.853 与 15:35:01.192 两次 CTrading::OpenPosition 调用都先打印 Correction of trade request parameters,随后 Trading operation aborted,表明 EA 内部有参数修正逻辑在反复兜底,却始终绕不开账户层限制。 直到 15:35:01.942,才出现绿色记录的 "Position is open",实际成交的是 EURUSD 0.10 手卖单 #486405595,开仓价 1.10126、SL 1.10285、TP 1.09985,魔术码 17629307。外汇及贵金属杠杆交易风险极高,这类"禁交易期仍重试"的行为可能拖慢实盘响应,建议在 EA 里对 IsTradeAllowed() 做前置判断,开 MT5 跑一遍策略测试器复现该日志即可验证。

MQL5 / C++
class="num">2019.11.class="num">26 class="num">15:class="num">34:class="num">48.661 Correction of trade request parameters ...
class="num">2019.11.class="num">26 class="num">15:class="num">34:class="num">48.661 Trading attempt #class="num">1. Error: No connection with the trade server
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">00.853 CTrading::OpenPosition<class="type">class="kw">double,class="type">class="kw">double>: Invalid request:
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">00.853 Trading is prohibited for the current account
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">00.853 Correction of trade request parameters ...
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">00.853 Trading operation aborted
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">01.192 CTrading::OpenPosition<class="type">class="kw">double,class="type">class="kw">double>: Invalid request:
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">01.192 Trading is prohibited for the current account
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">01.192 Correction of trade request parameters ...
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">01.192 Trading operation aborted
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">01.942 - Position is open: class="num">2019.11.class="num">26 class="num">10:class="num">35:class="num">01.660 -
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">01.942 EURUSD Opened class="num">0.10 Sell #class="num">486405595 [class="num">0.10 Market-order Sell #class="num">486405595] at price class="num">1.10126, sl class="num">1.10285, tp class="num">1.09985, Magic number class="num">17629307 (class="num">123), G1: class="num">13
class="num">2019.11.class="num">26 class="num">15:class="num">35:class="num">01.942 OnDoEasyEvent: Position is open

延后请求类与附件里的含糊库怎么用

本篇收尾处给出的是当前版本含糊交易库的完整文件包,MQL5 与 MQL4 各一个 ZIP,体积均为 3609.19 KB,可直接拉到 MT5 终端里编译测试。作者明确说下一篇会继续扩展延后请求类,也就是把交易请求从同步阻塞改成可排队重试的结构。 评论区里有个细节值得注意:有用户在 2024 年 7 月提到,挂单触发后开出的仓位 magic 被重新编码,原生的 GetMagicID() 才能拿回原始值。如果你在 EA 里用 magic 做仓位归因,复制这套库前先验证这个方法返回的是什么。 下面这段是从库里摘出的订单盈亏点数计算,能直接看到历史单和在场单分别怎么取 tick 算 point 差。外汇和贵金属杠杆高,这类点数换算只是中间量,实盘盈亏还受点差和滑点影响,概率上不保证和回测一致。 别把附件当成品库 ZIP 里的代码是系列第 25 篇的快照,后续篇会改延后请求逻辑。直接覆盖自己工程里的旧文件,可能编译不过或行为偏移,建议先建分支 diff 再合。

MQL5 / C++
<span class="keyword">class="type">void</span> CTradingControl::OnPReqByErrCodeHandler()
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| 以点为单位返回订单利润|</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">int</span> COrder::ProfitInPoints(<span class="keyword">class="type">void</span>) <span class="keyword">class="kw">const</span>
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="predefines">class="type">MqlTick</span> tick={<span class="number">class="num">0</span>};
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">string</span> symbol=<span class="keyword">this</span>.<span class="functions">Symbol</span>();
&nbsp;&nbsp; <span class="keyword">if</span>(!::<span class="functions">SymbolInfoTick</span>(symbol,tick))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="macro">ENUM_ORDER_TYPE</span> type=(<span class="macro">ENUM_ORDER_TYPE</span>)<span class="keyword">this</span>.TypeOrder();
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> point=::<span class="functions">SymbolInfoDouble</span>(symbol,<span class="macro">SYMBOL_POINT</span>);
&nbsp;&nbsp; <span class="keyword">if</span>(type==<span class="macro">ORDER_TYPE_CLOSE_BY</span> || point==<span class="number">class="num">0</span>) <span class="keyword">class="kw">return</span> <span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="keyword">if</span>(<span class="keyword">this</span>.Status()==ORDER_STATUS_HISTORY_ORDER)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="keyword">class="type">int</span>(type==<span class="macro">ORDER_TYPE_BUY</span> ? (<span class="keyword">this</span>.PriceClose()-<span class="keyword">this</span>.PriceOpen())/point : type==<span class="macro">ORDER_TYPE_SELL</span> ? (<span class="keyword">this</span>.PriceOpen()-<span class="keyword">this</span>.PriceClose())/point : <span class="number">class="num">0</span>);
&nbsp;&nbsp; <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">this</span>.Status()==ORDER_STATUS_MARKET_POSITION)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(type==<span class="macro">ORDER_TYPE_BUY</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="keyword">class="type">int</span>((tick.bid-<span class="keyword">this</span>.PriceOpen())/point);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">else</span> <span class="keyword">if</span>(type==<span class="macro">ORDER_TYPE_SELL</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">class="kw">return</span> <span class="keyword">class="type">int</span>((<span class="keyword">this</span>.PriceOpen()-tick.ask)/point);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">this</span>.Status()==ORDER_STATUS_MARKET_PENDING)
&nbsp;&nbsp;&nbsp;&nbsp; {

◍ 把工具请下神坛

挂单距离的计算逻辑其实就藏在类型分支里:买限价、买止损、买止损限价统一用 bid 与开仓价之差取绝对值,再除以 point 转成整数点;卖系三类则反过来用开仓价减 ask。这段判定直接决定面板里展示的「距离多少点触发」,在 MT5 里改 point 精度或 tick 取值方式,数字会立刻变。 deal_profit_pts 那行把已成交订单的浮盈亏点数拉出来,用的是 ORDER_PROP_PROFIT_PT 这个属性,省去自己算点值。外汇和贵金属杠杆高、滑点随机,这个点数只是当前态,不代表平仓结果,回测和实盘可能差出几十点。 代码能替你跑完机械活,但参数背后的市价结构、流动性窗口还是得人盯。开 MT5 把这段塞进 EA 的 OnTick,接上真实 tick 看几次触发距离,比背任何结论都实在。

MQL5 / C++
if(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_STOP_LIMIT)
     class="kw">return (class="type">int)fabs((tick.bid-this.PriceOpen())/point);
else if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP || type==ORDER_TYPE_SELL_STOP_LIMIT)
     class="kw">return (class="type">int)fabs((this.PriceOpen()-tick.ask)/point);
   }
   class="kw">return class="num">0;
}
deal_profit_pts=(class="type">int)deal.GetProperty(ORDER_PROP_PROFIT_PT)

常见问题

用订单属性文本化函数把Magic、类型、挂单请求等字段拼成字符串落盘,分支判断结合三元表达式链即可,排查时直接读日志。
可从Magic号里解析出分组编号与挂单请求类别,用位运算或取模拆分后拼进描述字符串,减少额外字段开销。
可以,小布能读取你落盘的订单描述文本,对Magic分组异常或挂单请求冲突做AIGC预警,打开品种页即可看。
确认拼装顺序与分隔符统一,分组用固定宽度补零,挂单请求用枚举转义,避免直接塞数字导致对齐错乱。
先落盘快照能在失败回查时还原上下文,结合属性文本化函数记录请求前状态,降低实盘诊断成本。