轻松快捷开发 MetaTrader 程序的函数库 (第二十六部分):处理延后交易请求 - 首次实现 (开仓)·综合运用
- 订单快照的字段落盘逻辑
- 订单属性落盘与描述函数怎么写
- 订单属性文本化的分支逻辑
- 订单属性文本化的三元表达式链
- 订单标识与分组属性的字符串拼装
- 从 Magic 号里抠出分组与挂单请求
- 事件日志里把魔数和分组ID拼出来
- 把断线重发做成一个可延后的请求对象
- 挂单重试请求的字段与方法
- 挂单请求类的存取与构造细节
- 挂单请求对象的 ID 排序与空闲分配
- 把分组与挂单ID塞进magic数字
- 从 magic 数里抠出分组与挂单请求
- 挂单请求的定时器重投逻辑
- 错误码分流决定交易请求生死
- 把成交回报错码映射成处理动作
- 交易回执里的隐蔽拒绝码
- 把交易错误码映射成处理动作
- 下单函数里的价格与错误预处理
- 错误处理分支如何决定下单走向
- 下单失败后的分支处理机制
- 挂单请求 ID 的兜底写入逻辑
- 订单参数与重试次数的接口设定
- 把分组与挂单号压进一个 magic 整数
- 断网也能挂单:延后请求实测路径
- EA 输入参数与全局变量的落地拆解
- EA 初始化时的按钮与全局参数装配
- 把资源文件和交易参数一次性灌进引擎
- 账户风控阈值与随机魔数下单
- 按钮事件落到挂单与平仓的分派逻辑
- 账户禁交易时 EA 仍在重试的日志现场
- 延后请求类与附件里的含糊库怎么用
- 把工具请下神坛
「订单快照的字段落盘逻辑」
在 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)]) 就能确认利润有没有正确落盘;外汇和贵金属杠杆高,快照里的浮亏数字可能瞬间翻几倍,验证时仓位要小。
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 打出来核对。
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。
(!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」,实盘外汇或贵金属品种上这类限制出现概率不低,属于高风险环境下的兼容性坑。
": "+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 账户引发重复平仓,验证前先用模拟盘跑。
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 策略测试器用脚本打印各段值验证。
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 并发时这种标注能降低误判风险。
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 子组号。外汇与贵金属杠杆高,断线重发可能成交在不利点位,实盘前务必先在策略测试器跑通。
<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 时倾向判定网络或报价异常,再决定是否人工介入。外汇与贵金属杠杆高,自动重试可能放大滑点与报错风险。
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() 填对,而不是先怀疑经纪商。 外汇与贵金属品种点差跳变频繁,这类封装只是降低代码层出错概率,不等于执行层稳靠,重发间隔设太短可能被服务器限流。
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) 会和失败标记混淆。
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 头文件即可编译验证。
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 生成规则就能让小布盯盘类工具按组统计挂单。
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 持仓/订单)才真正投递交易请求,避免重复开仓。外汇与贵金属杠杆交易高风险,重投机制只解决执行可靠性,不预示任何方向概率。
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 时务必本地用策略测试器跑满边界场景。
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 等待而不是报错退出。
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 打日志,能少踩很多坑。
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 里验证。外汇与贵金属杠杆高,回测通过不代表实盘不爆仓,参数请先在模拟盘试。
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 的获取,能确认你用的品种对象在非常规品种上是否也会掉进内部错误分支。
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。
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 策略测试器里逐类错误码验证分支触发。
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<1 或 symbol_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 跳出。
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行把重试次数传入底层交易对象。
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 标记未成交挂单来源,后续用按位与就能精准筛选。外汇与贵金属杠杆高,误筛订单可能导致重复开仓,先在策略测试器跑一遍位逻辑再上实盘。
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 演示模式或策略测试器里跑就行。
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 上的滑点表现,再决定是否上真仓。
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 符合预期。
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 即可,运行时随时可改。
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 一并传入。外汇与贵金属杠杆高、滑点跳空频繁,随机魔数虽能隔离订单,但并不能降低品种本身的高风险,参数请在策略测试器先跑通再上实盘。
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 里用日志面板复现断网下的按钮响应。
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 跑一遍策略测试器复现该日志即可验证。
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 再合。
<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> { <span class="predefines">class="type">MqlTick</span> tick={<span class="number">class="num">0</span>}; <span class="keyword">class="type">class="kw">string</span> symbol=<span class="keyword">this</span>.<span class="functions">Symbol</span>(); <span class="keyword">if</span>(!::<span class="functions">SymbolInfoTick</span>(symbol,tick)) <span class="keyword">class="kw">return</span> <span class="number">class="num">0</span>; <span class="macro">ENUM_ORDER_TYPE</span> type=(<span class="macro">ENUM_ORDER_TYPE</span>)<span class="keyword">this</span>.TypeOrder(); <span class="keyword">class="type">class="kw">double</span> point=::<span class="functions">SymbolInfoDouble</span>(symbol,<span class="macro">SYMBOL_POINT</span>); <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>; <span class="keyword">if</span>(<span class="keyword">this</span>.Status()==ORDER_STATUS_HISTORY_ORDER) <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>); <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">this</span>.Status()==ORDER_STATUS_MARKET_POSITION) { <span class="keyword">if</span>(type==<span class="macro">ORDER_TYPE_BUY</span>) <span class="keyword">class="kw">return</span> <span class="keyword">class="type">int</span>((tick.bid-<span class="keyword">this</span>.PriceOpen())/point); <span class="keyword">else</span> <span class="keyword">if</span>(type==<span class="macro">ORDER_TYPE_SELL</span>) <span class="keyword">class="kw">return</span> <span class="keyword">class="type">int</span>((<span class="keyword">this</span>.PriceOpen()-tick.ask)/point); } <span class="keyword">else</span> <span class="keyword">if</span>(<span class="keyword">this</span>.Status()==ORDER_STATUS_MARKET_PENDING) {
◍ 把工具请下神坛
挂单距离的计算逻辑其实就藏在类型分支里:买限价、买止损、买止损限价统一用 bid 与开仓价之差取绝对值,再除以 point 转成整数点;卖系三类则反过来用开仓价减 ask。这段判定直接决定面板里展示的「距离多少点触发」,在 MT5 里改 point 精度或 tick 取值方式,数字会立刻变。 deal_profit_pts 那行把已成交订单的浮盈亏点数拉出来,用的是 ORDER_PROP_PROFIT_PT 这个属性,省去自己算点值。外汇和贵金属杠杆高、滑点随机,这个点数只是当前态,不代表平仓结果,回测和实盘可能差出几十点。 代码能替你跑完机械活,但参数背后的市价结构、流动性窗口还是得人盯。开 MT5 把这段塞进 EA 的 OnTick,接上真实 tick 看几次触发距离,比背任何结论都实在。
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)