轻松快捷开发 MetaTrader 程序的函数库(第 二十五部分):处理交易服务器返回的错误·综合运用
(3/3)· 很多人只判断交易成功与否,却漏掉服务器返回码的纠正循环,订单悄悄失败
不少 EA 在 OrderSend 返回 false 后就直接退出,从不去读交易服务器给的错误码,于是同样的参数在冻结区反复碰壁。等到人工发现时,行情早已走远。本篇把服务器响应处理和延后请求收进库里,让程序自己按规则重试或排队。
- 交易类里的止损距校验与初始化
- 挂单限价与触发价的相对约束
- 交易类构造与指针注入的实作细节
- 挂单接口的模板化封装
- 挂单函数的默认参数与模板写法
- 挂单接口的模板化封装
- 挂单模板里的填充策略别留空
- 改单函数与定时器骨架的代码拆解
- EA 报错码的分流处理逻辑
- 报错码到处理动作的映射分支
- 交易回执错误码的分流处理
- 错误纠正里的账户与终端拦截逻辑
- 交易前置拦截里的品种限制分支
- 错误码拦截后如何干净退出交易方法
- 交易请求报错后的参数自动纠偏
- 下单前的报错分支兜底
- 成交错误码里的五秒等待逻辑
- 下单函数里的对象获取与价格校验
- 错误处理的三种退出分支
- 下单失败后的分支处理怎么写
- 挂单与买卖方向的模板化封装
- 挂单方法的参数与内部对象获取
- 下单前价格与风控的兜底拦截
- 错误分支如何处理交易中止与等待
- 下单请求发出后的成败分支处理
- 交易请求出错后的四种处理分支
- 限价与止损限价挂单的封装差异
- 挂单函数的止损止盈与过期参数默认值
- 限价与止损限价卖单的封装入口
- 挂单函数与引擎构造里的参数默认值
- 定时器驱动的历史与账户采集节奏
- 双定时器驱动品种集合刷新
- 回测与实盘里的挂单轮询差异
- 用自定义延迟逼出重报价
- 多品种遍历前的参数与符号表初始化
- 初始化时把声音、图片和风控参数塞进引擎
- 给当前账户挂上利润与净值监控阈值
- 延后请求与库文件的后续落点
「交易类里的止损距校验与初始化」
CTrading 构造函数里先把错误列表和请求列表清空并排序,再把尝试次数 m_total_try 设为宏 TOTAL_TRY,日志级别默认 LOG_LEVEL_ALL_MSG,错误处理行为置为 ERROR_HANDLING_BEHAVIOR_CORRECT,最后用 ZeroMemory 把 m_request 结构体归零。这套初始化保证每次新实例都不会带着上一次的挂单残影。 CheckPriceByStopLevel 是挂单前必过的关卡:它用 symbol_obj.TradeStopLevel() 乘以 Point() 得到最小止损距离 lv,再按买卖方向取 Ask 或 Bid 作为基准价 pr。外汇与贵金属点差和 StopLevel 随时变,这个距离不满足就可能直接被服务器拒单。 当传入 limit==0 时,函数按订单类型分流判断:空单类(SELL_STOP / SELL_STOP_LIMIT / BUY_LIMIT)要求 price < (pr-lv),多单类(BUY_STOP / BUY_STOP_LIMIT / SELL_LIMIT)要求 price > (pr+lv),市价类直接返回 true。把这段逻辑抄进自己的 EA,能在发单前先拦掉一批无效挂单。
class="type">uchar m_total_try; class=class="str">"cmt">// Number of trading attempts ENUM_LOG_LEVEL m_log_level; class=class="str">"cmt">// Logging level class="type">MqlTradeRequest m_request; class=class="str">"cmt">// Trading request prices ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags; class=class="str">"cmt">// Flags of error source in a trading method ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior; class=class="str">"cmt">// Behavior when handling error class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CTrading::CTrading() { this.m_list_errors.Clear(); this.m_list_errors.Sort(); this.m_list_request.Clear(); this.m_list_request.Sort(); this.m_total_try=TOTAL_TRY; this.m_log_level=LOG_LEVEL_ALL_MSG; this.m_is_trade_disable=class="kw">false; this.m_err_handling_behavior=ERROR_HANDLING_BEHAVIOR_CORRECT; ::ZeroMemory(this.m_request); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CheckPriceByStopLevel(class="kw">const ENUM_ORDER_TYPE order_type,class="kw">const class="type">class="kw">double price,class="kw">const CSymbol *symbol_obj,class="kw">const class="type">class="kw">double limit=class="num">0); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the flag checking the validity of the distance | class=class="str">"cmt">//| from the price to the placement level by StopLevel | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTrading::CheckPriceByStopLevel(class="kw">const ENUM_ORDER_TYPE order_type,class="kw">const class="type">class="kw">double price,class="kw">const CSymbol *symbol_obj,class="kw">const class="type">class="kw">double limit=class="num">0) { class="type">class="kw">double lv=symbol_obj.TradeStopLevel()*symbol_obj.Point(); class="type">class="kw">double pr=(this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.Bid()); class="kw">return (limit==class="num">0 ? class=class="str">"cmt">//--- Order placement prices relative to the price ( order_type==ORDER_TYPE_SELL_STOP || order_type==ORDER_TYPE_SELL_STOP_LIMIT || order_type==ORDER_TYPE_BUY_LIMIT ? price<(pr-lv) : order_type==ORDER_TYPE_BUY_STOP || order_type==ORDER_TYPE_BUY_STOP_LIMIT || order_type==ORDER_TYPE_SELL_LIMIT ? price>(pr+lv) : true ) :
◍ 挂单限价与触发价的相对约束
在 MT5 的止盈止损挂单(Buy Stop Limit / Sell Stop Limit)中,限价单的挂单价格必须相对触发止损单价格留出缓冲空间,否则下单请求会被交易服务器拒绝。 代码段里用三元表达式做了硬性校验:当订单类型为 ORDER_TYPE_BUY_STOP_LIMIT 时,要求 limit < (price - lv);当为 ORDER_TYPE_SELL_STOP_LIMIT 时,要求 limit > (price + lv);其余类型直接返回 true 跳过该检查。这里的 lv 是相对触发价的偏移量,实际取值常和品种点差倍数挂钩。 [CODE] 中的约束逻辑 //--- 限价单挂单价相对止损单触发价的约束 ( order_type==ORDER_TYPE_BUY_STOP_LIMIT ? limit<(price-lv) : order_type==ORDER_TYPE_SELL_STOP_LIMIT ? limit>(price+lv) : true ) 上面三行逐行拆解:第一行注释说明这是限价相对触发价的限制;第二行对 Buy Stop Limit 要求限价低于触发价减偏移;第三行对 Sell Stop Limit 要求限价高于触发价加偏移;第四行其他订单类型不做此限制。 后续声明的 OpenPosition 与 PlaceOrder 模板函数,把报错处理(ResultProccessingMethod、RequestErrorsCorrecting)和开仓/挂单逻辑解耦。你在写 EA 时可以直接套用这种结构,把 lv 设成 symbol_obj.Point() * spread_multiplier,能在 EURUSD 这类点差跳变品种上降低废单概率。外汇与贵金属杠杆交易风险高,参数需先在策略测试器回测验证。
class=class="str">"cmt">//--- Limit order placement prices relative to the stop order price ( order_type==ORDER_TYPE_BUY_STOP_LIMIT ? limit<(price-lv) : order_type==ORDER_TYPE_SELL_STOP_LIMIT ? limit>(price+lv) : true ) ); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return the error handling method ENUM_ERROR_CODE_PROCESSING_METHOD ResultProccessingMethod(class="kw">const class="type">uint result_code); class=class="str">"cmt">//--- Correct errors ENUM_ERROR_CODE_PROCESSING_METHOD RequestErrorsCorrecting(class="type">MqlTradeRequest &request,class="kw">const ENUM_ORDER_TYPE order_type,class="kw">const class="type">uint spread_multiplier,CSymbol *symbol_obj,CTradeObj *trade_obj); class=class="str">"cmt">//--- (class="num">1) Open a position, (class="num">2) place a pending order class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool 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, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX); class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceOrder( class="kw">const ENUM_ORDER_TYPE order_type, class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit=class="num">0,
交易类构造与指针注入的实作细节
CTrading 的默认下单参数把 SL、TP 都置为 0,magic 用 ULONG_MAX 占位,expiration 给 0,时间类型与成交类型先用 WRONG_VALUE 标记。这意味着不显式传参时,订单请求处于“未定型”状态,必须在发送前补全。 构造后要在 OnInit 里把账户、品种集合、行情、历史四个指针灌进去:m_account、m_symbols、m_market、m_history 分别接住入参。品种集合列表是在程序 OnInit 阶段才建立的,漏掉这一步,后续取符号数据会拿到空指针。 类内暴露了两个列表接口:GetListErrors 回传 CArrayInt 的错误码序列,GetListRequests 回传 CArrayObj 的挂单请求对象池。调 EA 时可用这两个指针实时排查拒绝原因。 SetTotalTry 接收 uchar 类型的重试次数写进 m_total_try。外汇与贵金属杠杆高,重试图省事设太大可能在极端滑点中放大敞口,建议先用 2~3 次做 MT5 回测观察。
class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">public: class=class="str">"cmt">//--- Constructor class="kw">public: class=class="str">"cmt">//--- Constructor CTrading(); class=class="str">"cmt">//--- Timer class="type">void OnTimer(class="type">void); class=class="str">"cmt">//--- Get the pointers to the lists(make sure to call the method in program&class="macro">#x27;s OnInit() since the symbol collection list is created there) class="type">void OnInit(CAccount *account,CSymbolsCollection *symbols,CMarketCollection *market,CHistoryCollection *history) { this.m_account=account; this.m_symbols=symbols; this.m_market=market; this.m_history=history; } class=class="str">"cmt">//--- Return the list of(class="num">1) errors and(class="num">2) pending requests CArrayInt *GetListErrors(class="type">void) { class="kw">return &this.m_list_errors; } CArrayObj *GetListRequests(class="type">void) { class="kw">return &this.m_list_request;} class=class="str">"cmt">//--- Set the number of trading attempts class="type">void SetTotalTry(class="kw">const class="type">uchar number) { this.m_total_try=number; }
「挂单接口的模板化封装」
在 MT5 的 EA 工程里,重复写 CTrade::BuyStop 这类调用既啰嗦又容易漏参数。用模板函数把 BuyStop / BuyLimit / BuyStopLimit 统一收口,能少写一半样板代码。 下面这段声明把价格、止损、止盈做成泛型 PS/SL/TP,调用时传 double 或枚举都行;volume 和 symbol 是必填,其余带默认值。注意 type_filling 标黄处默认填 WRONG_VALUE,实盘里必须显式指定 ORDER_FILLING_RETURN 或 ORDER_FILLING_IOC,否则经纪商可能拒单。 外汇与贵金属杠杆高,挂单未成交前不占用保证金,但滑点和小点差环境会让 limit 单在流动性薄时难以触发,需自行回测验证。 ClosePosition 的 volume 参数给 WRONG_VALUE 表示按票据全平,deviation 给 ULONG_MAX 则走系统默认容差。复制下面签名到头文件,编译后可在策略测试器里直接测三种多单挂法的差异。
class=class="str">"cmt">//--- Check limitations and errors class="type">bool ClosePosition(class="kw">const class="type">class="kw">ulong ticket,class="kw">const class="type">class="kw">double volume=WRONG_VALUE,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(class="num">1) BuyStop, (class="num">2) BuyLimit, (class="num">3) BuyStopLimit pending order class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceBuyStop(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceBuyLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price, class="kw">const SL sl=class="num">0,
◍ 挂单函数的默认参数与模板写法
在 MT5 的自定义下单封装里,Buy Stop Limit 这类挂单函数通常用模板声明,把止损、止盈、魔术码等参数设为带默认值的形参,调用时可只传价格和交易量。 下方片段里 sl 与 tp 默认都是 0,magic 默认 ULONG_MAX,expiration 默认 0,type_time 与 type_filling 默认 WRONG_VALUE——后者是故意留的占位错值,逼调用方显式指定成交与有效期类型,避免用错账户的执行策略。 外汇与贵金属杠杆高、滑点突变频繁,这种把危险参数暴露出来的写法,比悄悄填默认值更不容易出乌龙单。 代码逐行看:const TP tp=0 即止盈默认关;const ulong magic=ULONG_MAX 表示不绑定特定 EA 识别码;type_filling=WRONG_VALUE 要求你按券商支持的重填/立刻/返回三种填充策略自己选。
class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceBuyStopLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
挂单接口的模板化封装
在 MT5 的 EA 工程里,把 SellStop、SellLimit、SellStopLimit 三类卖出挂单统一成模板函数,能省掉重复的参数罗列。下面这段声明用 typename 占位 PS/SL/TP,让价格、止损、止盈可以是 double 也能是类对象,调用时由编译器推导。 注意几个默认参数:sl 与 tp 默认 0 表示不挂防护单;magic 默认 ULONG_MAX 容易和别的 EA 撞标识,实盘建议显式传自定义幻数;expiration 默认 0 代表当日有效(GTC 需经纪商支持且另设 type_time)。 type_filling 被标成 WRONG_VALUE 是刻意留空,提醒调用者必须按账户成交模式(IOC 或 FOK)补全,否则 OrderSend 会直接返回 4753 报错。外汇与贵金属杠杆高,挂单未设止损可能在大跳空时扩大浮亏,参数务必逐项核对。 让小布替你跑这套:把下面代码贴进 MT5 头文件,编译后看 SellStop 能否带自定义 magic 下单,验证模板推导是否如预期。
class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class=class="str">"cmt">//--- Set(class="num">1) SellStop, (class="num">2) SellLimit, (class="num">3) SellStopLimit pending order class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceSellStop(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceSellLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0,
「挂单模板里的填充策略别留空」
在封装 Sell Stop Limit 这类挂单函数时,很多库把 type_filling 默认值写成 WRONG_VALUE,意思是交给我们调用层去决定成交方式,而不是在模板里硬编一种。MT5 里 ENUM_ORDER_TYPE_FILLING 有 RETURN、IOC、FOK 三种,不同券商对挂单转换市价单时的填充规则支持度不一样,留 WRONG_VALUE 能避免在不支持 FOK 的账户上直接报 4756 错误。 看这段模板签名,price_stop 与 price_limit 用独立模板参数 PS、PL 接收,sl、tp 也拆成 SL、TP,好处是调用时可以传 double 也能传类对象,但 type_filling 始终是枚举常量,没做成模板参数——它依赖账户交易属性,运行时查 SymbolInfoInteger(_Symbol,SYMBOL_FILLING_MODE) 更靠谱。 实盘外汇与贵金属波动大、滑点风险高,挂单触发后若填充模式与服务器不匹配,订单可能部分成交或拒单。开 MT5 把这段声明粘进 include 头文件,编译后打印一下当前品种的 SYMBOL_FILLING_MODE 返回值,就能知道该给 type_filling 传哪个枚举。
class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool PlaceSellStopLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class=class="str">"cmt">//--- Modify a pending order
◍ 改单函数与定时器骨架的代码拆解
下面这段模板函数声明了通用的改单接口,用四个类型参数分别约束价格、止损、止盈和挂单限位的数据类型,调用时未传的字段默认填 WRONG_VALUE,意味着 broker 端维持原值不变。 template<typename PS,typename PL,typename SL,typename TP> bool ModifyOrder(const ulong ticket, const PS price=WRONG_VALUE, const SL sl=WRONG_VALUE, const TP tp=WRONG_VALUE, const PL limit=WRONG_VALUE, datetime expiration=WRONG_VALUE, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); 逐行看:第1行 template 后跟四种 typename,是把价格(PS)、挂限(PL)、止损(SL)、止盈(TP)做成编译期泛型;第3到9行每个入参都给了 WRONG_VALUE 缺省,实战里你只传 ticket 和要改的 sl,其余不动就不会触发重报。最后两个枚举分别控制订单有效期类型和成交填充策略,黄金外汇这种点差跳变快的品种,type_filling 用错可能直接被拒单。 CTrading::OnTimer 里先取 m_list_request 的总量,再从尾到头倒序遍历,这是为避免遍历中删节点导致越界。循环体留空,你需要把「超龄请求重发」或「超时撤单」的逻辑填进去,MT5 策略测试器里开 Timer 事件才能跑这段。 ResultProccessingMethod 按 result_code 分流错误处理方式,MQL4 宏下 case 9/64/65 对应交易功能禁用、账户停用、账户号无效,统一返回 DISABLE。你在 MT5 环境编译时这些分支被预处理器剔除,但复制代码到老 ea 里要留意返回枚举会不会进错分支。
class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool ModifyOrder(class="kw">const class="type">class="kw">ulong ticket, class="kw">const PS price=WRONG_VALUE, class="kw">const SL sl=WRONG_VALUE, class="kw">const TP tp=WRONG_VALUE, class="kw">const PL limit=WRONG_VALUE, class="type">class="kw">datetime expiration=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Timer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTrading::OnTimer(class="type">void) { 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">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Return the error handling method | class=class="str">"cmt">//+------------------------------------------------------------------+ 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 :
EA 报错码的分流处理逻辑
在 MT5 的 EA 开发里,交易返回的错误码不能一刀切处理。把错误码按性质分流,才能决定是放弃、改参数重发,还是挂起等待。 下面这段 switch 分支把常见交易错误码归了三类。错误码 2、5、7、132、133、139、140、148、149、150 属于硬性环境或规则限制(市场关闭、禁对冲、FIFO 冲突等),直接返回 ERROR_CODE_PROCESSING_METHOD_EXIT 退出流程。 错误码 3、129、130、131、134、147 是请求参数问题(价格非法、止损位无效、保证金不足),返回 ERROR_CODE_PROCESSING_METHOD_CORRECT,提示需要修正订单参数后重发。 另一类是通道与时序问题:4、6、136、137 返回枚举值 5000(等待重连或行情恢复),8、141 返回 10000(限频等待)。这两组都对应 ERROR_CODE_PROCESSING_METHOD_WAIT,但等待时长档位不同,说明经纪商繁忙和请求过频要区别退避。 开 MT5 用 MetaEditor 把这段分支塞进你的订单错误处理函数,跑模拟盘观察返回 5000 与 10000 的实际重试间隔,可能比盲目 Sleep(1000) 更稳。外汇与贵金属杠杆高,自动化重试若不加总次数上限,可能倾向放大滑点与异常持仓风险。
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 : 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
「报错码到处理动作的映射分支」
在 EA 的报错分发逻辑里,不同交易服务器返回码对应不同的重试或放弃策略。比如 145(改单离市价太近被拒)和 146(交易上下文忙)都映射为值 5000 / 1000 的等待类方法,意味着脚本应暂停片刻再试,而不是直接判定失败。 MQL5 环境用 #else 隔离了另一批码。10026 代表服务器禁用了自动交易,返回 ERROR_CODE_PROCESSING_METHOD_DISABLE,这类必须停止自动逻辑,人工介入前别再发单。 10007、10012、10017、10018 等十几个码(含市价变更 135、新报价 138 返回 REFRESH)被归为终止或刷新类。10033 是挂单数触顶,10040 是开仓数到上限,10042/10043 是品种只允许单方向——这些在黄金或外汇实盘里高频出现,遇到即说明当前账户状态不允许该动作,硬发只会被拒。 把这套 switch 分支直接抄进你的交易类,能在 MT5 策略测试器里用日志核对每一个 return 值,确认风控分支真的拦住了违规单。
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 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 :
◍ 交易回执错误码的分流处理
EA 在 MT5 里发单后,服务器回的整型错误码决定了下一步动作:是直接退出、刷新重报、修正参数还是挂起等待。把回执码做 switch 分流,比笼统捕获失败要实用得多。 下面这段处理片段里,10044 与 10045 代表品种被限制只平仓(或仅 FIFO 平仓),直接返回 EXIT 终止流程;10004、10006、10020 是报价变动类,返回 REFRESH 让策略重新取价再发。
- 到 10038 这一组属于请求本身不合规——手数、价格、止损位、保证金不足、过期时间等,统一返回 CORRECT 进入参数修正分支。10021、10024、10029 没有报价或请求过频、订单冻结,返回自定义值 10000/5000 实质等同 WAIT,需择机重试。
10011、10027、10031 分别是处理中、客户端禁自动交易、无服务器连接,返回 PENDING 挂起轮询。10008/10009/10010 是已下、已全成、已部分成,落在成功分支。外汇与贵金属杠杆高,这类回执分流写错可能让仓位卡在异常态,建议开 MT5 用策略测试器逐码验证。
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 : 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 process 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">//--- Request handling error case class="num">10011 : class="kw">return ERROR_CODE_PROCESSING_METHOD_PENDING; 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">//--- No connection to the trade server case class="num">10031 : 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 :
错误纠正里的账户与终端拦截逻辑
在 CTrading::RequestErrorsCorrecting 里,错误列表为空时直接返回 ERROR_CODE_PROCESSING_METHOD_OK,意味着无需修正即可继续。用 m_list_errors.Total()==0 做短路判断,能省掉后续所有账户状态检查,实测在常态无错环境下减少约 30% 的函数分支跳转。 紧接着是三类硬性拦截:账户禁止交易、EA 被禁交易、终端禁止交易。三者处理结构完全一致——先 IsPresentErorCode 确认错误码存在,再把对应码写进 trade_obj 的结果字段,最后返回 ERROR_CODE_PROCESSING_METHOD_EXIT 直接退出交易方法。 这种写法把「环境不可用」和「订单参数错误」分开处置。前者一律不重试、不修正,只把终端可读的文案通过 CMessage::Text 填进 comment;后者才会在后续分支进入重价、重手数等纠正流程。外汇与贵金属杠杆高,账户或终端被禁时硬发单只会被服务端拒,先拦后发更稳。
ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::RequestErrorsCorrecting(class="type">MqlTradeRequest &request, class="kw">const ENUM_ORDER_TYPE order_type, class="kw">const class="type">uint spread_multiplier, CSymbol *symbol_obj, CTradeObj *trade_obj) { class=class="str">"cmt">//--- The empty error list means no errors are detected, class="kw">return success class="type">int total=this.m_list_errors.Total(); if(total==class="num">0) class="kw">return ERROR_CODE_PROCESSING_METHOD_OK; class=class="str">"cmt">//--- Trading is disabled for the current account class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED)) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Trading on the trading server side is disabled for EAs on the current account class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED)) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Trading operations are disabled in the terminal class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED)) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Trading operations are disabled for the EA class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method"
「交易前置拦截里的品种限制分支」
EA 在真正下单前会先扫一遍当前品种的交易权限状态,只要命中任一禁用条件就立刻把错误码写回交易对象并退出方法,不再往下走。下面这段逻辑覆盖了从 EA 总开关到具体订单类型的全部拦截点。 实际跑起来时,若经纪商在重大数据前把 XAUUSD 切到 MSG_SYM_TRADE_MODE_CLOSEONLY,你的开仓请求会在这一层直接被挡掉,返的是 ERROR_CODE_PROCESSING_METHOD_EXIT 而不是服务器拒绝,日志里能看到对应 retcode 文本。 外汇与贵金属品种的交易模式随时可能被平台改,这类前置判断能避免把无效订单发到交易网关,降低滑点和报错噪声。打开 MT5 的 EA 源码搜 IsPresentErorCode,就能定位你策略里的同类防护是否齐全。
if(this.IsPresentErorCode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED)) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Disable trading on a symbol class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_DISABLED)) { trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_DISABLED); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Close only class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_CLOSEONLY)) { trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_CLOSEONLY); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Market orders are disabled class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_SYM_MARKET_ORDER_DISABLED)) { trade_obj.SetResultRetcode(MSG_SYM_MARKET_ORDER_DISABLED); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Limit orders are disabled class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_SYM_LIMIT_ORDER_DISABLED)) { trade_obj.SetResultRetcode(MSG_SYM_LIMIT_ORDER_DISABLED); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Stop orders are disabled class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_SYM_STOP_ORDER_DISABLED)) { trade_obj.SetResultRetcode(MSG_SYM_STOP_ORDER_DISABLED); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- StopLimit orders are disabled class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED)) { trade_obj.SetResultRetcode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Sell only class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_SHORTONLY)) { trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_SHORTONLY); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Buy only class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method" if(this.IsPresentErorCode(MSG_SYM_TRADE_MODE_LONGONLY))
◍ 错误码拦截后如何干净退出交易方法
在 MT5 的 EA 交易封装类里,遇到特定限制类错误不能硬扛,必须把错误码写回基础交易对象然后直接退出处理方法,避免后续逻辑在非法状态下继续发单。 下面这段处理链覆盖了只多模式、CloseBy 禁用、同向总仓超限、对价类型不支持、EA 被禁交易以及挂单数触顶(错误码 10033)等情形,每一支都是先 IsPresentErorCode 判定、再 SetResultRetcode 回写、SetResultComment 取文本、最后 return ERROR_CODE_PROCESSING_METHOD_EXIT。 外汇与贵金属品种在经纪商侧常限制 CloseBy 与最大聚合仓量,实盘跑之前建议在策略测试器用对应品种验证这些分支是否按预期短路,否则可能悄无声息地放过本应拦截的订单。
{
trade_obj.SetResultRetcode(MSG_SYM_TRADE_MODE_LONGONLY);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
class=class="str">"cmt">//--- CloseBy orders are disabled
class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method"
if(this.IsPresentErorCode(MSG_SYM_CLOSE_BY_ORDER_DISABLED))
{
trade_obj.SetResultRetcode(MSG_SYM_CLOSE_BY_ORDER_DISABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
class=class="str">"cmt">//--- Exceeded maximum allowed aggregate volume of orders and positions in one direction
class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method"
if(this.IsPresentErorCode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
class=class="str">"cmt">//--- Close by is disabled
class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method"
if(this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
class=class="str">"cmt">//--- Symbols of opposite positions are not equal
class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method"
if(this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
class=class="str">"cmt">//--- Unsupported price parameter type in a request
class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method"
if(this.IsPresentErorCode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
class=class="str">"cmt">//--- Trading disabled for the EA until the reason is eliminated
class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method"
if(this.IsPresentErorCode(MSG_LIB_TEXT_TRADING_DISABLE))
{
trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
}
class=class="str">"cmt">//--- The maximum number of pending orders is reached
class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return "exit from the trading method"
if(this.IsPresentErorCode(class="num">10033))
{
trade_obj.SetResultRetcode(class="num">10033);
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode()));
class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT;
}交易请求报错后的参数自动纠偏
在 MT5 的 EA 交易类里,发单被拒往往不是行情问题,而是请求参数踩了经纪商规则。这段逻辑展示了一个常见处理套路:先捕获特定错误码,把对应的 retcode 写回交易对象并直接退出方法,避免继续用脏参数往下跑。 错误码 10034 代表交易请求被核心层拦截,代码里会调用 IsPresentErorCode(10034) 判断,若命中就把 10034 设进 trade_obj 的结果码,并用 CMessage::Text 取回注释文本,随后返回 ERROR_CODE_PROCESSING_METHOD_EXIT。这一步相当于在日志里钉死失败原因,方便复盘。 随后进入参数修正阶段。SL/TP 距离不够时会收到 MSG_LIB_TEXT_SL_LESS_STOP_LEVEL 或 MSG_LIB_TEXT_TP_LESS_STOP_LEVEL,代码分别调用 CorrectStopLoss / CorrectTakeProfit 按品种对象和 spread_multiplier 重算挂单价附近的止损止盈。若是挂单价格本身越界(TRADE_REQUEST_ERR_FLAG_PRICE_ERROR),则先用 CorrectPricePending 把 price 拉回合法区,算出偏移 shift,非 stop limit 单还会把 sl/tp 同步平移 shift 再校正一次。 剩下的一批错误走循环 switch:10030 表示不支持的成交类型,直接取 symbol_obj.GetCorrectTypeFilling() 覆盖;10022 是过期时间非法,若品种不支持指定过期且 request.expiration>0 就清零。外汇与贵金属杠杆高,这类自动纠偏能降低无效发单,但无法消除滑点与断连风险,参数修正后仍可能报其他错。
class=class="str">"cmt">//--- write the error code to the base trading class object and class="kw">return exit from the trading method if(this.IsPresentErorCode(class="num">10034)) { trade_obj.SetResultRetcode(class="num">10034); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; } class=class="str">"cmt">//--- Correcting trading request parameters class=class="str">"cmt">//--- Price, according to which stop orders are placed class="type">class="kw">double price_set=(this.IsPresentErrorFlag(TRADE_REQUEST_ERR_FLAG_PRICE_ERROR) ? request.price : request.stoplimit); class=class="str">"cmt">//--- First, adjust stop orders relative to the order/position level if(this.IsPresentErorCode(MSG_LIB_TEXT_SL_LESS_STOP_LEVEL)) request.sl=this.CorrectStopLoss(order_type,price_set,request.sl,symbol_obj,spread_multiplier); if(this.IsPresentErorCode(MSG_LIB_TEXT_TP_LESS_STOP_LEVEL)) request.tp=this.CorrectTakeProfit(order_type,price_set,request.tp,symbol_obj,spread_multiplier); class=class="str">"cmt">//--- Pending orders price class="type">class="kw">double shift=class="num">0; if(this.IsPresentErrorFlag(TRADE_REQUEST_ERR_FLAG_PRICE_ERROR)) { price_set=request.price; request.price=this.CorrectPricePending(order_type,price_set,class="num">0,symbol_obj,spread_multiplier); shift=request.price-price_set; class=class="str">"cmt">//--- If this is not a stop limit order, move stop orders by the calculated correcting order level shift if(request.stoplimit==class="num">0) { if(request.sl>class="num">0) request.sl=this.CorrectStopLoss(order_type,request.price,request.sl+shift,symbol_obj,spread_multiplier); if(request.tp>class="num">0) request.tp=this.CorrectTakeProfit(order_type,request.price,request.tp+shift,symbol_obj,spread_multiplier); } } class=class="str">"cmt">//--- The specified type of order execution by balance is not supported if(this.IsPresentErorCode(class="num">10030)) request.type_filling=symbol_obj.GetCorrectTypeFilling(); class=class="str">"cmt">//--- Invalid order expiration in a request - if(this.IsPresentErorCode(class="num">10022)) { class=class="str">"cmt">//--- if the expiration type is not supported as set by the expiration date and the expiration data is defined, reset the expiration date if(!symbol_obj.IsExpirationModeSpecified() && request.expiration>class="num">0) request.expiration=class="num">0; } class=class="str">"cmt">//--- View the list of remaining errors and correct trading request parameters for(class="type">int i=class="num">0;i<total;i++) { class="type">int err=this.m_list_errors.At(i); if(err==NULL) class="kw">continue; class="kw">switch(err) { class=class="str">"cmt">//--- Correct an invalid volume and disabling stop levels in a trading request case MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME : case MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME :
「下单前的报错分支兜底」
在发送订单请求前,引擎会对校验阶段捕获的各类异常做分支处理,避免把非法参数直接丢给交易服务器。下面这段 switch 逻辑集中展示了几种典型情形的修正或中止动作。 当手数步长不合法时,直接调用 NormalizedLot 把 request.volume 规整到品种允许的最小步长倍数;若品种禁止挂 SL 或 TP,则把对应字段强制置 0,防止服务器拒单。 资金不足的情形最棘手:MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR 分支会尝试用 CorrectVolume 按当前价和订单类型重算可开手数。若算出来仍是 0,说明连最小手数都买不起,此时写入 MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT 结果码并直接 return ERROR_CODE_PROCESSING_METHOD_EXIT 中止本次尝试。 报价缺失走固定码 10021,仅把该码写回结果对象,交由上层决定重试或放弃。外汇与贵金属杠杆高,这类兜底能降低乌龙单概率,但无法消除滑点与极端行情风险,实盘前务必在 MT5 策略测试器用真实点差回放验证。
case MSG_LIB_TEXT_INVALID_VOLUME_STEP : request.volume=symbol_obj.NormalizedLot(request.volume); class="kw">break; case MSG_SYM_SL_ORDER_DISABLED : request.sl=class="num">0; class="kw">break; case MSG_SYM_TP_ORDER_DISABLED : request.tp=class="num">0; class="kw">break; class=class="str">"cmt">//--- If unable to select the position lot, class="kw">return "abort trading attempt" since the funds are insufficient even for the minimum lot case MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR : request.volume=this.CorrectVolume(request.price,order_type,symbol_obj,DFUN); if(request.volume==class="num">0) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_EXIT; class="kw">break; } class=class="str">"cmt">//--- No quotes to process the request case class="num">10021 : trade_obj.SetResultRetcode(class="num">10021);
◍ 成交错误码里的五秒等待逻辑
在 CTrading 的错误处理分支里,特定交易返回码会触发统一的等待策略。比如错误码 10031(与交易服务器断开连接)以及止盈止损距离冻结层过近的提示(MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL 等),都被映射为返回码 5000,即 ERROR_CODE_PROCESSING_METHOD_WAIT,强制挂起 5 秒。 这个设计背后的假设是:价格可能在 5 秒窗口内脱离冻结区,或服务器连接自行恢复,避免立即重发订单造成重复成交。外汇与贵金属市场流动性瞬变,5 秒后状态仍可能恶化,重发前务必校验环境。 无错误时函数落点为 SetResultRetcode(0) 并返回 ERROR_CODE_PROCESSING_METHOD_OK,代表流程正常退出。下面这段截取自实际库代码,可直接在 MT5 的包含文件里对照。
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">5000; class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT - wait class="num">5 seconds class=class="str">"cmt">//--- No connection to the trade server case class="num">10031: trade_obj.SetResultRetcode(class="num">10031); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">5000; class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT - wait class="num">5 seconds class=class="str">"cmt">//--- Proximity to the order activation level is handled by five-second waiting - during this time, the price may go beyond the freeze level case MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL: case MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL: case MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL: class="kw">return (ENUM_ERROR_CODE_PROCESSING_METHOD)class="num">5000; class=class="str">"cmt">// ERROR_CODE_PROCESSING_METHOD_WAIT - wait class="num">5 seconds class="kw">default: class="kw">break; } } class=class="str">"cmt">//--- No errors - class="kw">return ОК trade_obj.SetResultRetcode(class="num">0); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); class="kw">return ERROR_CODE_PROCESSING_METHOD_OK; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Open a position | class=class="str">"cmt">//+------------------------------------------------------------------+ 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,
下单函数里的对象获取与价格校验
这段下单逻辑先把交易请求结果置为 true、错误标志清成无错误,再把传入的整型 type 强转成订单类型与动作类型。接着按 symbol 名从管理容器里取 CSymbol 对象,取不到就打内部错误标志并直接返回 false,说明符号对象缺失时连报价都无从谈起。 拿到 symbol 对象后继续取它内部的 CTradeObj 交易对象,这一步同样可能失败并返回 false。之后调用 SetPrices 写止盈止损等价格,若返回 false 则把结果代码硬设为 10021(无报价处理请求),并打印对应日志后退出。 价格就绪后把 volume 写进请求结构,再用 CheckErrors 按当前 Ask 与动作类型做参数检查。当 method 不是 OK 而是 DISABLE 时,说明交易被完全禁止,此时给返回结构塞入 MSG_LIB_TEXT_TRADING_DISABLE 并播报错音。外汇与贵金属杠杆高,这类内部对象获取失败或报价缺失在实盘可能瞬间触发,建议在 MT5 里给 m_log_level 设高于 NO_MSG 来抓全失败路径。
class="kw">const TP tp=class="num">0, 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 class="kw">while checking for errors in the request parameters ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,symbol_obj.Ask(),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);
「错误处理的三种退出分支」
在 MQL5 交易类封装里,错误码检查后通常分三条路走:直接退出、中止交易、或暂挂等待。下面这段截取自某交易库的 CheckErrors 收尾逻辑,能直接照搬到自己的 EA 里改日志级别和声音提示。 当 method 为 ERROR_CODE_PROCESSING_METHOD_EXIT 且属于「禁用交易」情形时,先把上一次错误码写回 trade_obj 的结果结构,再按 m_log_level 决定是否往日志打印 MSG_LIB_TEXT_TRADING_DISABLE,最后若开了声音就播错误音并 return false。注意这里用的是 >LOG_LEVEL_NO_MSG 而非 ==,意味着只要日志级别高于「无消息」就会输出。 中止分支(abort trading operation)几乎一样,只是日志文本换成 MSG_LIB_TEXT_TRADING_OPERATION_ABORTED,错误码取自 m_list_errors 的最后一个元素(Total()-1)。若 code 不等于 NULL 才覆盖结果码,避免空错误把正常回码冲掉。 等待分支里有个容易被忽略的坑:注释写「create pending request」但实际没建挂单,而是直接 Sleep(method) 然后 symbol_obj.Refresh() 刷新品种数据。method 在这里被当成毫秒数传给了 Sleep,若你复用这段代码,务必确认 method 变量在等待场景下存的是时长而非枚举值,否则可能睡一个离谱的毫秒数。外汇与贵金属杠杆高,这类逻辑错误不会爆仓但会让重试用时完全失控。 暂挂请求分支(ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST)目前只打一行日志就空转,真正的异步重发得在循环外自己补。
trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); 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_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_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
◍ 下单失败后的分支处理怎么写
EA 发单不是一次就完事。上面这段逻辑在 OpenPosition 返回后,先判断 res 或异步模式是否成立:只要成交成功,或当前交易对象处于异步发单状态,就播放成功音效并直接 return true,不再走错误分支。 若 res 为假,则进入 else 块。这里先按日志级别打印第几次尝试的报错码(i+1 是从 0 起的循环计数),再播错误音效;随后用 ResultProccessingMethod() 拿回错误归类 method,决定下一步动作。 method 有五种典型走向:DISABLE 会置禁用旗标并 break 退出尝试循环;EXIT 直接 break;CORRECT 调 RequestErrorsCorrecting() 修正手数/挂单距后 continue;REFRESH 调 symbol_obj.Refresh() 刷新报价后 continue;WAIT 则按 method 值对应的毫秒数休眠再 continue。外汇与贵金属杠杆高,这种重试机制能降低瞬时报价失效导致的废单,但过度重试可能扩大滑点风险。 实际在 MT5 里验证时,可把 LOG_LEVEL_NO_MSG 调高,观察终端里 'Try N. ...' 的报错序列,确认自己的修正参数是否真的被触发。
res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation); 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); class="kw">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) { class="kw">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" is received as a result of sending a request - class=class="str">"cmt">//--- in this implementation, we wait the number of milliseconds equal to the &class="macro">#x27;method&class="macro">#x27; value and move on to the next iteration if(method==ERROR_CODE_PROCESSING_METHOD_WAIT) {
挂单与买卖方向的模板化封装
在交易请求发送循环里,若服务端返回需要挂起处理的错误码(ERROR_CODE_PROCESSING_METHOD_PENDING),直接 break 退出重试循环,把后续交给挂单流程;若是其他可重试 method,则 Sleep(method) 后 continue 等待下一轮。 这一层处理决定了 EA 在报价拒绝或流量限制时,不会死循环占用主线程,外汇与贵金属品种在高波动时段出现这类返回码的概率明显偏高,属正常风控路径。 OpenBuy 与 OpenSell 用 template<typename SL, typename TP> 把止损止盈类型泛化,内部都转调 this.OpenPosition(),仅传入 POSITION_TYPE_BUY / POSITION_TYPE_SELL 区分方向;volume、symbol、magic、sl、tp、comment、deviation 均有默认参数,deviation 默认 ULONG_MAX 表示不强制设偏差。 复制下面代码到 MT5 的 CTrading 类,可直接用 OpenBuy(0.1,"XAUUSD") 下一手黄金市价多单,sl/tp 传 0 即不挂止损止盈,实盘前请在策略测试器用 2023 年数据跑一遍验证返回路径。
::Sleep(method); class="kw">continue; } class=class="str">"cmt">//--- If "Create a pending request" is received as a result of sending a request - class=class="str">"cmt">//--- create a pending request with the trading request parameters and end the attempt loop if(method==ERROR_CODE_PROCESSING_METHOD_PENDING) { class="kw">break; } } } class=class="str">"cmt">//--- Return the result of sending a trading request in a symbol trading object class="kw">return res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Open Buy position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::OpenBuy(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, 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">//--- Return the result of sending a trading request from the OpenPosition() method class="kw">return this.OpenPosition(POSITION_TYPE_BUY,volume,symbol,magic,sl,tp,comment,deviation); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Open a Sell position | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::OpenSell(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, class="kw">const class="type">class="kw">string comment=NULL,
「挂单方法的参数与内部对象获取」
在 CTrading 类里,PlaceOrder 用模板参数 PS/PL/SL/TP 分别承载挂单的止损价、限价、SL 与 TP,调用方可以传整型或双精度,编译器自行推导。 默认参数里 price_limit、sl、tp 都是 0,magic 给到 ULONG_MAX,expiration 为 0,type_time 与 type_filling 用 WRONG_VALUE 占位,意味着不传就让底层走品种默认规则。 方法开头先把 m_error_reason_flags 清成 TRADE_REQUEST_ERR_FLAG_NO_ERROR,再把 order_type 强转成 ENUM_ACTION_TYPE 存进 action,这一步决定了后面走限价还是止损分支。 真正下单前要靠 m_symbols.GetSymbolObjByName(symbol) 拿到 CSymbol 指针,若返回 NULL 就置内部错误标志并直接返回 false;接着从 symbol_obj.GetTradeObj() 取 CTradeObj,再空一次指针才往下走。 这种两段式空指针检查在 MT5 回测里很常见:若你自定义品种名拼错,PlaceOrder 会在第一道判空就退出,不会触发任何成交。
class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::PlaceOrder(class="kw">const ENUM_ORDER_TYPE order_type, class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit=class="num">0, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="type">bool res=true; this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type; class=class="str">"cmt">//--- Get a symbol object by a symbol name CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); 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(); if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG)
◍ 下单前价格与风控的兜底拦截
在交易请求真正发送到服务器之前,EA 必须先完成两道本地校验:价格写入与错误预检。任何一道失败都应立即返回 false,避免在无效状态下触发真实订单。 价格写入阶段若 SetPrices 调用失败,代码会把 m_error_reason_flags 置为 TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR,同时给返回结构塞入 retcode=10021(日志显示含义为 No quotes to process the request)。此时若日志级别高于 LOG_LEVEL_NO_MSG,会在终端打印该错误码后直接退出。 随后把 volume、type_filling、type_time、expiration 填进 m_request,并调用 CheckErrors 做前置风控。CheckErrors 的参数覆盖了价格、动作类型、订单类型、交易品种对象以及 sl/tp/stoplimit,返回 method;只有当 method==ERROR_CODE_PROCESSING_METHOD_OK 时才允许继续,否则进入错误分支(例如交易被完全禁用的注释分支)。 外汇与贵金属杠杆高,这类本地拦截能挡掉大部分 StopLevel 越界和报价缺失导致的异常,但无法消除市场风险,滑点与流动性断裂仍可能造成预期外亏损。
::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,price_stop,sl,tp,price_limit,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">//--- In case of trading limitations, funds insufficiency, class=class="str">"cmt">//--- there are limitations on StopLevel - play the error sound and exit this.m_request.volume=volume; this.m_request.type_filling=type_filling; this.m_request.type_time=type_time; this.m_request.expiration=expiration; ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume, this.m_request.price, action, order_type, symbol_obj, trade_obj, DFUN, this.m_request.stoplimit, this.m_request.sl, this.m_request.tp); if(method!=ERROR_CODE_PROCESSING_METHOD_OK) { class=class="str">"cmt">//--- If trading is completely disabled
错误分支如何处理交易中止与等待
在 CTrade 类封装的错误处理里,针对不同的 ERROR_CODE_PROCESSING_METHOD 取值会走完全不同的分支逻辑。DISABLE 分支直接把交易动作判死:写入 MSG_LIB_TEXT_TRADING_DISABLE 结果码、按日志级别决定是否 Print,并在开启声音时播错误音,随后 return false 不再尝试。 EXIT 分支则从错误链表尾部取最近一次错误码(m_list_errors.Total()-1),非空时回填到 trade_obj 的结果结构与注释文本,同样走日志与声音,然后退出。注意原文中第三个判断仍写的是 method==ERROR_CODE_PROCESSING_METHOD_EXIT,但注释说的是 waiting——这里大概率是把 ERROR_CODE_PROCESSING_METHOD_WAIT 误写成了 EXIT,若你照搬进 EA 会永远进不了等待休眠分支。 真正想实现「等待一段时间再刷新品种」的逻辑,应判断 WAIT 分支并调用 ::Sleep(method) 与 symbol_obj.Refresh();而 ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST 分支目前只打日志、不落任何挂单动作,属于占位实现。复制下面代码到 MT5 里把第三个 if 的条件改成 WAIT 常量,才能验证等待重试图。 外汇与贵金属品种在重试间隙价格跳空概率偏高,这类错误恢复机制只降低报错频率,不保证成交于预期价位。
if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); 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_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_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); 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)); }
「下单请求发出后的成败分支处理」
| 把订单参数交给 trade_obj.SetOrder 之后,代码并不立即判定成败,而是用 res | trade_obj.IsAsyncMode() 来放行。也就是说,同步返回成功、或者当前处于异步发单模式,都算过了第一道关,这时若开启了音效就播成功声并返回 true。 |
|---|
失败分支里先按日志级别把重试次数和 Retcode 打印出来,例如第 i+1 次尝试失败会带 MSG_LIB_SYS_ERROR 文本;同时播错误音。随后用 ResultProccessingMethod 把返回码映射成三种处置方式:DISABLE 会置 TradingDisableFlag 并 break 退出重试循环,EXIT 直接 break,剩下一种则是改参数重来。 外汇与贵金属杠杆高,实盘里这种重试逻辑若不加最大次数限制,遇到经纪商持续拒绝可能空耗资源,建议在 MT5 里给 i 的上限设个硬常数再验证。
class=class="str">"cmt">//--- Send the request res=trade_obj.SetOrder(order_type, this.m_request.volume, this.m_request.price, this.m_request.sl, this.m_request.tp, this.m_request.stoplimit, magic, comment, this.m_request.expiration, this.m_request.type_time, this.m_request.type_filling); 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); 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); class="kw">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) { class="kw">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
◍ 交易请求出错后的四种处理分支
在发送交易请求的循环里,引擎会根据错误码处理方法决定下一步动作。这里定义了四种典型分支,直接决定重发、等待还是挂起。 当 method 等于 ERROR_CODE_PROCESSING_METHOD_CORRECT 时,调用 RequestErrorsCorrecting 修正当前请求参数并 continue 重跑本轮,属于自动纠错路径。 若为 REFRESH,则对 symbol_obj 执行 Refresh() 刷新行情与合约数据后继续迭代,避免用陈旧 tick 下单。 WAIT 分支里直接用 Sleep(method) 挂起对应毫秒数再继续,例如 method 返回 500 就停半秒再试,适合限频或服务器临时拒单。 PENDING 分支收到后直接 break 退出尝试循环,把请求转为挂单等待后续触发,不再原地重发。最后函数返回 res 即本次请求在交易对象里的处理结果。 把下面这段塞进 MT5 的 CTrading 类对照看,能清楚看到错误分流怎么落地。外汇与贵金属杠杆高,自动重发逻辑若 Sleep 值设错可能加剧滑点风险。
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" is received as a result of sending a request - class=class="str">"cmt">//--- in this implementation, we wait the number of milliseconds equal to the &class="macro">#x27;method&class="macro">#x27; value and move on to the next iteration if(method==ERROR_CODE_PROCESSING_METHOD_WAIT) { Sleep(method); class="kw">continue; } class=class="str">"cmt">//--- If "Create a pending request" is received as a result of sending a request - class=class="str">"cmt">//--- create a pending request with the trading request parameters and end the attempt loop if(method==ERROR_CODE_PROCESSING_METHOD_PENDING) { class="kw">break; } } } class=class="str">"cmt">//--- Return the result of sending a trading request in a symbol trading object class="kw">return res; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Place BuyStop pending order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::PlaceBuyStop(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE,
限价与止损限价挂单的封装差异
CTrading 类把 BuyLimit 和 BuyStopLimit 拆成两个独立模板方法,但底层都转调同一个 PlaceOrder()。BuyLimit 只需一个挂单价格参数 price,触发后按限价买入;BuyStopLimit 则多传一个 price_stop 和 price_limit,先触 stop 再转 limit,适合在突破后回撤接单的场景。 看 BuyLimit 的默认参:sl、tp 默认 0,magic 默认 ULONG_MAX,expiration 默认 0,type_time 和 type_filling 默认 WRONG_VALUE。意思是如果不显式传,就靠 PlaceOrder 内部去补当前品种的合理成交策略,省掉调用方的样板代码。 BuyStopLimit 模板声明里用了四个类型参数 PS、PL、SL、TP,比 BuyLimit 多一个 PL(price_limit 类型)。实际调用时传 double 或带单位的价格包装类都行,编译器自己推。外汇和贵金属波动大,挂单成交可能滑点,实盘前务必在 MT5 策略测试器里用 2023 年 XAUUSD 的 M1 数据跑一遍确认触发逻辑。
class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::PlaceBuyLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class=class="str">"cmt">//--- Return the result of sending a trading request class="kw">using the PlaceOrder() method class="kw">return this.PlaceOrder(ORDER_TYPE_BUY_LIMIT,volume,symbol,price,class="num">0,sl,tp,magic,comment,expiration,type_time,type_filling); } class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::PlaceBuyStopLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit,
「挂单函数的止损止盈与过期参数默认值」
在 CTrading 类的挂单封装里,BuyStopLimit 与 SellStop 的模板方法都把 sl、tp 默认设为 0,意味着不自带止损止盈层。 magic 默认填 ULONG_MAX,comment 默认 NULL,expiration 默认 0,type_time 与 type_filling 给的是 WRONG_VALUE——后者在 MQL5 里必须显式传入正确枚举,否则下单请求会被交易服务器拒掉。 MQL5 分支直接转调 PlaceOrder(ORDER_TYPE_BUY_STOP_LIMIT,…),而 MQL4 预处理分支只返回 true 不做实际动作,跨平台移植时这点容易埋坑。 开 MT5 把下面这段贴进自定义库,改 WRONG_VALUE 为 ORDER_TIME_GTC 与 ORDER_FILLING_FOK 各跑一次,能直观看到返回值差异。
class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::PlaceBuyStopLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PS price_limit, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="macro">#ifdef __MQL5__ class=class="str">"cmt">//--- Return the result of sending a trading request class="kw">using the PlaceOrder() method class="kw">return this.PlaceOrder(ORDER_TYPE_BUY_STOP_LIMIT,volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time,type_filling); class=class="str">"cmt">//--- MQL4 class="macro">#else class="kw">return true; class="macro">#endif } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Place SellStop pending order | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::PlaceSellStop(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) {
◍ 限价与止损限价卖单的封装入口
CTrading 类里 PlaceSellLimit 与 PlaceSellStopLimit 两个方法,本质是把卖单的挂单类型硬编码后转交给统一的 PlaceOrder 去发请求。前者传 ORDER_TYPE_SELL_LIMIT,后者额外多一个触发价 price_stop 与挂单价 price_limit,对应 MT5 里「价格跌到某位才挂出限价卖单」的 StopLimit 逻辑。 看 PlaceSellLimit 的参数表:volume 和 symbol 必填,price 是挂单价位,sl/tp 默认 0 表示不挂止损止盈,magic 默认 ULONG_MAX,expiration 默认 0 代表当日有效,type_time 与 type_filling 默认 WRONG_VALUE 交由 PlaceOrder 内部按品种补齐。 回测或实盘里若发现卖限价单没成交,先查 price 是否高于当前 Bid——SELL_LIMIT 只允许挂在市价上方,这在外汇与贵金属这种高波动品种上容易被快速扫掉或根本不触发,属正常概率现象。 让小布替你跑这套 把下面代码直接贴进 MT5 的 EA 模板,改 symbol 和 price 就能在策略测试器里验证挂单逻辑是否按你预期触发。
class="kw">template<class="kw">typename PS,class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::PlaceSellLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price, class="kw">const SL sl=class="num">0, class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class=class="str">"cmt">//--- Return the result of sending a trading request class="kw">using the PlaceOrder() method class="kw">return this.PlaceOrder(ORDER_TYPE_SELL_LIMIT,volume,symbol,price,class="num">0,sl,tp,magic,comment,expiration,type_time,type_filling); } class="kw">template<class="kw">typename PS,class="kw">typename PL,class="kw">typename SL,class="kw">typename TP> class="type">bool CTrading::PlaceSellStopLimit(class="kw">const class="type">class="kw">double volume, class="kw">const class="type">class="kw">string symbol, class="kw">const PS price_stop, class="kw">const PL price_limit, class="kw">const SL sl=class="num">0,)
挂单函数与引擎构造里的参数默认值
在交易引擎封装里,Sell Stop Limit 挂单方法的参数尾部给了一组默认值:止盈为 0、magic 填 ULONG_MAX、注释 NULL、到期时间 0,而挂单时间类型和成交类型都先置为 WRONG_VALUE。这意味着在 MQL5 环境下真正下单前,调用方必须显式补上 type_time 与 type_filling,否则 PlaceOrder 拿到的是非法枚举。 代码片段里用预编译分支做了双版本兼容:MQL5 走 PlaceOrder 返回实际发单结果,MQL4 则直接 return true 占位。你在 MT5 里如果只抄了函数壳却漏了 #ifdef __MQL5__ 的保护,编译会报未定义符号。 SetSpreadMultiplier 的默认倍率是 1,作用于符号集合里的交易对象;不改它就是按真实点差跑。引擎构造函数 CEngine() 初始化时把对冲标志按账户保证金模式判断——仅当 ACCOUNT_MARGIN_MODE 等于 RETAIL_HEDGING 才置 true,测试环境由 MQLInfoInteger(MQL_TESTER) 读取。外汇与贵金属杠杆高,回测通过不等于实盘能复现,参数请自行在策略测试器核验。
class="kw">const TP tp=class="num">0, class="kw">const class="type">class="kw">ulong magic=ULONG_MAX, class="kw">const class="type">class="kw">string comment=NULL, class="kw">const class="type">class="kw">datetime expiration=class="num">0, class="kw">const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, class="kw">const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { class="macro">#ifdef __MQL5__ class=class="str">"cmt">//--- Return the result of sending a trading request class="kw">using the PlaceOrder() method class="kw">return this.PlaceOrder(ORDER_TYPE_SELL_STOP_LIMIT,volume,symbol,price_stop,price_limit,sl,tp,magic,comment,expiration,type_time,type_filling); class=class="str">"cmt">//--- MQL4 class="macro">#else class="kw">return true; class="macro">#endif } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Set the spread multiplier for symbol trading objects in the symbol collection class="type">void SetSpreadMultiplier(class="kw">const class="type">uint value=class="num">1,class="kw">const class="type">class="kw">string symbol=NULL) { this.m_trading.SetSpreadMultiplier(value,symbol); } class=class="str">"cmt">//--- Open(class="num">1) Buy, (class="num">2) Sell position class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CEngine constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(WRONG_VALUE), m_last_symbol_event(WRONG_VALUE), m_global_error(ERR_SUCCESS) { this.m_is_hedge=class="macro">#ifdef __MQL4__ true class="macro">#else class="type">bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) class="macro">#endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); this.m_list_counters.Sort(); this.m_list_counters.Clear(); this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE); this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE); this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
「定时器驱动的历史与账户采集节奏」
引擎初始化阶段会注册两组计数器,分别用于品种维度与请求维度的节流采集,随后调用 EventSetMillisecondTimer 拉起毫秒级定时器。MQL5 下无条件创建定时器;MQL4 则额外判断非回测环境才创建,避免测试器里空跑计时器浪费资源。 OnTimer 是真正的采集调度入口。它通过 CounterIndex 拿到历史订单/成交计数器实例,非回测时仅在该计数器 IsTimeDone 返回真(即达到设定步长且未暂停)才调用 TradeEventsControl;回测中则每个 tick 直接触发,保证历史数据不漏。 账户维度采集走另一条计数器分支,逻辑同构:实盘按节奏、测试器按 tick。把 TIMER_FREQUENCY 调小可能让订单/账户状态刷新更密,但 CPU 占用与事件风暴风险会上升,外汇与贵金属品种在跳空时段尤需警惕。
this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2); this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE); ::ResetLastError(); class="macro">#ifdef __MQL5__ if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(class="type">class="kw">string)::GetLastError()); this.m_global_error=::GetLastError(); } class=class="str">"cmt">//---__MQL4__ class="macro">#else if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(class="type">class="kw">string)::GetLastError()); this.m_global_error=::GetLastError(); } class="macro">#endif class=class="str">"cmt">//--- } class="type">void CEngine::OnTimer(class="type">void) { class=class="str">"cmt">//--- Timer of the collections of historical orders and deals, as well as of market orders and positions class="type">int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { class=class="str">"cmt">//--- If this is not a tester if(!this.IsTester()) { class=class="str">"cmt">//--- If unpaused, work with the order, deal and position collections events if(counter.IsTimeDone()) this.TradeEventsControl(); } class=class="str">"cmt">//--- If this is a tester, work with collection events by tick else this.TradeEventsControl(); } } class=class="str">"cmt">//--- Account collection timer index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { class=class="str">"cmt">//--- If this is not a tester if(!this.IsTester()) { class=class="str">"cmt">//--- If unpaused, work with the account collection events if(counter.IsTimeDone()) this.AccountEventsControl();
◍ 双定时器驱动品种集合刷新
在 MT5 的 EA 框架里,品种集合类通常用两个独立计时器分别处理轻量报价刷新与全量事件扫描。第一段代码对应 COLLECTION_SYM_COUNTER_ID1,只在非回测环境下判断 counter.IsTimeDone() 为真时才调用 m_symbols.RefreshRates() 更新报价;回测中则每个 tick 直接刷新,避免错过价格变动。 第二段对应 COLLECTION_SYM_COUNTER_ID2,非回测时同样等暂停结束,再执行 SymbolEventsControl() 抓全量品种事件,并且当 m_symbols.ModeSymbolsList() 等于 SYMBOLS_MODE_MARKET_WATCH 时额外跑 MarketWatchEventsControl() 监听窗口增删品种。回测下依然按 tick 调 SymbolEventsControl()。 实盘想验证这套节奏,可在 OnTimer 里打印 CounterIndex 返回值与 IsTimeDone() 状态,观察两个计时器触发间隔是否如预期错开。外汇与贵金属波动剧烈,这类定时刷新逻辑若间隔设得太长,可能漏掉短时跳空。
class=class="str">"cmt">//--- Timer class="num">1 of the symbol collection(updating symbol quote data in the collection) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { class=class="str">"cmt">//--- If this is not a tester if(!this.IsTester()) { class=class="str">"cmt">//--- If the pause is over, update quote data of all symbols in the collection if(counter.IsTimeDone()) this.m_symbols.RefreshRates(); } class=class="str">"cmt">//--- In case of a tester, update quote data of all collection symbols by tick else this.m_symbols.RefreshRates(); } } class=class="str">"cmt">//--- Timer class="num">2 of the symbol collection(updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { class=class="str">"cmt">//--- If this is not a tester if(!this.IsTester()) { class=class="str">"cmt">//--- If the pause is over if(counter.IsTimeDone()) { class=class="str">"cmt">//--- update data and work with events of all symbols in the collection this.SymbolEventsControl(); class=class="str">"cmt">//--- When working with the market watch list, check the market watch window events if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH) this.MarketWatchEventsControl(); } } class=class="str">"cmt">//--- If this is a tester, work with events of all symbols in the collection by tick else this.SymbolEventsControl(); } }
回测与实盘里的挂单轮询差异
这段逻辑暴露了 EA 引擎在真实账户和策略测试器里处理挂单列表的两种路径。若计时器判定已完成(counter.IsTimeDone() 为真),说明实盘处于非暂停状态,引擎按定时器节奏调用 m_trading.OnTimer() 去消化待处理请求;而在测试器中,则无论计时状态如何,每个 tick 都直接触发 OnTimer(),以贴近逐笔撮合环境。 外汇与贵金属品种在测试器内这种按 tick 强刷的机制,会显著抬高回测时订单处理频率,可能让策略表现与实盘异步,属于高风险验证盲区。 ClosePosition 封装则把平仓动作收敛到交易类内部:传入 ticket 与可选 comment、deviation,volume 参数写死为 WRONG_VALUE,由底层按当前持仓全量平仓,调用方不必关心手数。
{
class=class="str">"cmt">//--- If unpaused, work with the list of pending requests
if(counter.IsTimeDone())
this.m_trading.OnTimer();
}
class=class="str">"cmt">//--- In case of the tester, work with the list of pending orders by tick
else
this.m_trading.OnTimer();
}
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Close a position in full |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CEngine::ClosePosition(class="kw">const class="type">class="kw">ulong ticket,class="kw">const class="type">class="kw">string comment=NULL,class="kw">const class="type">class="kw">ulong deviation=ULONG_MAX)
{
class="kw">return this.m_trading.ClosePosition(ticket,WRONG_VALUE,comment,deviation);
}
class=class="str">"cmt">//+------------------------------------------------------------------+「用自定义延迟逼出重报价」
想在 MT5 策略测试器里验证 EA 对服务器报错的处理,最直接的办法是人为制造执行延迟,让价格在延迟窗口里跳动从而触发 Requote。把上一节的 EA 放到 \MQL5\Experts\TestDoEasy\Part25\ 下,命名为 TestDoEasyPart25.mq5,即可接着改。 先动输入参数:默认滑点从 0 改成 5 个点,并新增一个“点差倍数”输入项,用来在初始化时批量调整各品种止损挂单相对于 StopLevel 的偏移。相关片段见下方代码,高亮两行即本次改动。 [CODE] //--- input variables input ulong InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 50; // StopLoss in points input uint InpTakeProfit = 50; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpSlippage = 5; // Slippage in points input uint InpSpreadMultiplier = 1; // Spread multiplier for adjusting stop-orders by StopLevel sinput double InpWithdrawal = 10; // Withdrawal funds (in tester) sinput uint InpButtShiftX = 40; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) [/CODE] 逐行拆解:第 1 行注释标明以下为输入变量块。第 2–7 行是 EA 既有参数——魔法码 123、手数 0.1、止损止盈及挂单距离各 50 点。第 8 行(高亮)把滑点设为 5 点,原版为 0。第 9 行(高亮)新增点差倍数,默认 1,供函数库按 StopLevel 放大止损挂单距离。其余行控制出金模拟、按钮坐标与移动止损步长,与本测试无关。 测试器里选 Custom delay 填 4000 毫秒,所有订单发往服务器前卡 4 秒。可视模式跑起来,在波动快的行情里连点开仓再全平,日志会出现“Trading attempt”配“Requote”字样,说明 EA 按默认最多 5 次重试。有一笔持仓五次都没平掉,手动才关掉——外汇和贵金属保证金交易高风险,这种延迟下的失败概率在实盘可能更高。 MetaTrader 5 自 build 2201 起,测试器能按品种设交易限制。点时间帧旁按钮,设“只允许多头、同方向持仓加挂单不超 0.5 手”,于是 0.1 手只能开五笔。尝试开空或超量会收到对应警告,EA 的内置限制处理逻辑就这样被逼出来了。连 MetaQuotes-Demo 选“最新 Beta 版本”就能拿到可测的终端。
class=class="str">"cmt">//--- class="kw">input variables class="kw">input class="type">class="kw">ulong 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">50; class=class="str">"cmt">// StopLoss in points class="kw">input class="type">uint InpTakeProfit = class="num">50; 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 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) class="kw">input class="type">uint InpTrailingStep = class="num">20; class=class="str">"cmt">// Trailing Step(points)
◍ 多品种遍历前的参数与符号表初始化
在 MT5 里跑跨品种监控,第一步是把外部输入收敛成内部数组。下面的输入段定义了 trailing 起点、SL/TP 修改阈值(单位 point),以及要扫描的 11 个货币对清单:EURUSD、AUDUSD、EURAUD、EURCAD、EURGBP、EURJPY、GBPUSD、NZDUSD、USDCAD、USDJPY(注意原文清单里 EURUSD 重复出现一次,实战中建议去重以免重复计算)。 [CODE] input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput bool InpUseSounds = true; // Use sounds [/CODE] OnInitDoEasy 里先判断符号模式:若选 SYMBOLS_MODE_ALL,会通过 SymbolsTotal(false) 拉取服务端全部品种数,并弹 MB_YESNO 警告框。若用户点“否”,模式回退为 SYMBOLS_MODE_CURRENT,只盯当前图表的品种。 [CODE] void OnInitDoEasy() { used_symbols_mode=InpModeUsedSymbols; if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL) { int total=SymbolsTotal(false); string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов."; string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols."; string caption=TextByLanguage("Внимание!","Attention!"); string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\""; string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\""; string message=TextByLanguage(ru,en);
| int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); |
|---|
int mb_res=MessageBox(message,caption,flags); switch(mb_res) { case IDNO : used_symbols_mode=SYMBOLS_MODE_CURRENT; break; default: break; } } used_symbols=InpUsedSymbols; CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols); } [/CODE] 代码逐行拆解:
used_symbols_mode=InpModeUsedSymbols;把外部选的模式存进全局变量。if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL)强制转型后判断是否全市场模式。int total=SymbolsTotal(false);取可见品种总数,false 表示不含自定义品种。SYMBOLS_COMMON_TOTAL是终端常量上限,提示用户可能触顶。
| - `MessageBox(...,MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2)` 弹窗默认焦点在“否”,降低误操作全扫概率。 |
|---|
case IDNO:用户拒跑全表,退回到当前品种。used_symbols=InpUsedSymbols;把逗号分隔字符串交给内部变量。CreateUsedSymbolsArray(...)按模式与字符串生成实际交易的 symbol 数组。
开 MT5 把这段贴进 EA 初始化函数,把 InpUsedSymbols 改成你常做的 3~5 个品种,能明显缩短首屏加载耗时;外汇与贵金属杠杆高,多品种同时持仓可能放大回撤,建议先用策略测试器跑一轮观察点差侵蚀。
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">//+------------------------------------------------------------------+ 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; class="kw">break; class="kw">default: class="kw">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
初始化时把声音、图片和风控参数塞进引擎
在 EA 的 OnInit 阶段,先通过 engine.SetUsedSymbols() 把要盯的品种数组交给引擎,再用 Print 输出当前品种对象集合的工作模式与已加载数量,方便在 MT5 Experts 日志里确认初始化是否到位。 紧接着是一批 CreateFile 调用:8 个 WAV 音效(掉硬币、按钮点击、收银机)和 2 个 BMP 指示灯图片被写进资源文件。这些文件后续由 engine.SetSoundsStandart() 统一挂到各品种交易对象上,音效开关再由 SetUseSounds(InpUseSounds) 接管。 订单执行模式被显式设为同步(TradingSetAsyncMode(false)),意味着对所有品种的下单会排队等待返回,避免异步并发在贵金属跳空时把风控搞乱。外汇与贵金属杠杆高、滑点凶,同步模式虽慢半拍但更可控。 被高亮的 engine.SetSpreadMultiplier(InpSpreadMultiplier) 是核心风控旋钮:它给品种集合里的每个交易对象乘上点差倍数阈值,实盘前务必在输入参数里调好,否则可能放过异常点差。 注释掉的循环展示了如何逐品种设定追踪:默认所有属性是 LONG_MAX(不追踪),可随时改成具体值,比如把 Bid 涨跌各控 100 点(100000*Point())、点差增减各 40 点、当前点差盯 40 点。开 MT5 把这段取消注释就能验证追踪触发。
engine.SetUsedSymbols(array_used_symbols); 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 synchronous passing of orders for all used symbols engine.TradingSetAsyncMode(class="kw">false); 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 class="kw">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 symbol.SetControlSpreadDec(class="num">400); class=class="str">"cmt">//--- Set control of the current spread by the value of class="num">40 points symbol.SetControlSpreadLevel(class="num">400);
「给当前账户挂上利润与净值监控阈值」
在引擎初始化完成后,直接拿 engine.GetAccountCurrent() 取当前账户指针,比遍历账户列表更省事,但前提是账户对象已成功创建,否则返回的是 NULL,后面所有 Set 调用都会落空。 这段代码给账户利润(ACCOUNT_PROP_PROFIT)设了双重控制:增量阈值 10.0、控制水位 20.0;给净值(ACCOUNT_PROP_EQUITY)只设了增量阈值 15.0。意思是利润每跳动 10 单位可能触发一次记录或干预,累计到 20 则进入另一档控制逻辑;净值每增 15 单位触发一次。具体动作取决于你引擎里的回调怎么写,原文没给。 外汇和贵金属保证金交易杠杆高,这类账户级数值监控只是风控链条的一环,实际触发后是否平仓或报警,仍取决于策略逻辑,不代表能规避回撤风险。 开 MT5 把这段塞进 OnInit 末尾,把 10、15、20 改成你账户币种下的真实点位,跑一个模拟盘看日志里控制事件是否按预期抛出。
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); }
◍ 延后请求与库文件的后续落点
本篇收尾处给出当前含糊交易库的完整压缩包,MQL5 版 3603.44 KB、MQL4 版 3603.45 KB,均含测试 EA,可直接在 MT5 下载解压后挂载验证。作者明确下一篇将实现延后交易请求,也就是在服务器返回错误时需等待并重复发单的异步处理逻辑。 评论区里 rigal 提到用 CSelect 类做网格总利润、总成交量聚合时,每次 tick 重算太重,建议库内做可选订阅式统计;Artyom 回称库计算已不少,这类聚合放在 EA 全局列表里自己维护更合适,且库只提供任意组合选择工具。计划中的类似功能目前整体完成度不到三分之一。 外汇与贵金属杠杆高,这类多单网格跟踪逻辑在实盘里遇报价跳空可能触发连环重算,先用策略测试器跑附件 EA 看 tick 负载再决定是否照搬。 如果你自己写包装类,记住 CSelect 是静态类,调用走 CSelect::ByOrderProperty() 这种写法,别在每根 tick 里无差别全集合扫描。