轻松快捷开发 MetaTrader 程序的函数库(第 二十四部分):基准交易类 - 无效参数自动纠正·进阶篇
(2/3)· 当参数出错就中断交易太浪费,这层纠正逻辑决定 EA 能否在错误中继续下单
「EA 报错处理的枚举与标志位」
在 MT5 的 EA 交易库里,报错信息先用一组文本常量做双语映射,再交给行为枚举决定下一步动作。下面这段枚举定义了三种应对方式:直接中断、修正参数、或挂起请求等待重发。 enum ENUM_ERROR_HANDLING_BEHAVIOR { ERROR_HANDLING_BEHAVIOR_BREAK, // 中止本次交易尝试 ERROR_HANDLING_BEHAVIOR_CORRECT, // 修正无效参数后重试 ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST, // 创建挂起请求 }; 真正区分错误严重程度的是 ENUM_TRADE_REQUEST_ERR_FLAGS,用位标记组合。TRADE_REQUEST_ERR_FLAG_NO_ERROR 值为 0 表示正常;FATAL_ERROR 为 1 会直接禁掉 EA 交易权限;INTERNAL_ERR 为 2 是库内部错误也退出;ERROR_IN_LIST 为 4 才进入可处理的错误清单。 enum ENUM_TRADE_REQUEST_ERR_FLAGS { TRADE_REQUEST_ERR_FLAG_NO_ERROR = 0, // 无错误 TRADE_REQUEST_ERR_FLAG_FATAL_ERROR = 1, // 致命错误:禁用EA并退出 TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR = 2, // 库内部错误:退出 TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST = 4, // 列表内错误:按方法处理 }; 开 MT5 把这两个枚举抄进你的 include 头文件,用按位与判断 (flags & TRADE_REQUEST_ERR_FLAG_FATAL_ERROR) 就能在实盘前拦掉会爆仓的致命路径。外汇与贵金属杠杆高,错误分支漏判可能让仓位失控,建议先在策略测试器跑一遍报错注入。
enum ENUM_ERROR_HANDLING_BEHAVIOR { ERROR_HANDLING_BEHAVIOR_BREAK, class=class="str">"cmt">// Abort trading attempt ERROR_HANDLING_BEHAVIOR_CORRECT, class=class="str">"cmt">// Correct invalid parameters ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST, class=class="str">"cmt">// Create a pending request }; enum ENUM_TRADE_REQUEST_ERR_FLAGS { TRADE_REQUEST_ERR_FLAG_NO_ERROR = class="num">0, class=class="str">"cmt">// No error TRADE_REQUEST_ERR_FLAG_FATAL_ERROR = class="num">1, class=class="str">"cmt">// Disable trading for an EA(critical error) - exit TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR = class="num">2, class=class="str">"cmt">// Library internal error - exit TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST = class="num">4, class=class="str">"cmt">// Error in the list - handle(ENUM_ERROR_CODE_PROCESSING_METHOD) };
EA 内部错误分流与核心结构体的落地定义
写 EA 最怕报错后全靠人工盯盘重启。把错误处理方式先枚举出来,后面逻辑才好分叉:正常放行、禁交易、退出方法、刷新重跑、等待重跑、挂单待处理,共 6 种状态对应不同分支。 下面这段是 EA 类里直接落地的成员声明,决定了后续取价、发单、算保证金走哪套接口。注意 m_chart_mode 控制 K 线用买价还是卖价拼,黄金和美系货币对常因点差跳变导致两者偏差超 0.5 点,回测和实盘要分开验。 结构体里 MqlTradeRequest 和 MqlTradeResult 是 OrderSendAsync 的标配,m_magic 建议按品种+周期编码,避免多 EA 同账户抢单。外汇与贵金属杠杆高,保证金模式 m_margin_mode 取错会直接算爆仓线。
enum ENUM_ERROR_CODE_PROCESSING_METHOD { ERROR_CODE_PROCESSING_METHOD_OK, class=class="str">"cmt">// No errors ERROR_CODE_PROCESSING_METHOD_DISABLE, class=class="str">"cmt">// Disable trading for the EA ERROR_CODE_PROCESSING_METHOD_EXIT, class=class="str">"cmt">// Exit the trading method ERROR_CODE_PROCESSING_METHOD_REFRESH, class=class="str">"cmt">// Update data and repeat ERROR_CODE_PROCESSING_METHOD_WAIT, class=class="str">"cmt">// Wait and repeat ERROR_CODE_PROCESSING_METHOD_PENDING, class=class="str">"cmt">// Create a pending request }; class=class="str">"cmt">//+------------------------------------------------------------------+ SActions m_datas; class="type">MqlTick m_tick; class=class="str">"cmt">// Tick structure for receiving prices class="type">MqlTradeRequest m_request; class=class="str">"cmt">// Trade request structure class="type">MqlTradeResult m_result; class=class="str">"cmt">// trade request execution result ENUM_SYMBOL_CHART_MODE m_chart_mode; class=class="str">"cmt">// Price type for constructing bars ENUM_ACCOUNT_MARGIN_MODE m_margin_mode; class=class="str">"cmt">// Margin calculation mode ENUM_ORDER_TYPE_FILLING m_type_filling; class=class="str">"cmt">// Filling policy ENUM_ORDER_TYPE_TIME m_type_expiration; class=class="str">"cmt">// Order expiration type class="type">int m_symbol_expiration_flags; class=class="str">"cmt">// Flags of order expiration modes for a trading object symbol class="type">ulong m_magic; class=class="str">"cmt">// Magic number
◍ 交易对象里的私有字段与价差乘数接口
在封装交易对象的类里,一组私有成员决定了发单时的基本行为。m_symbol 存交易品种,m_volume 是手数,m_deviation 以点为单位的滑点容忍,m_expiration 仅对指定时间的挂单生效。 m_async_mode 控制是否异步发送交易请求,开 true 时下单不阻塞 EA 主线程;m_log_level 管日志粒度,m_use_sound 决定是否播放交易事件音效。 m_multiplier 是相对经纪商 StopLevel 调整挂单距离的价差乘数,源码里标黄的那行 uint m_multiplier 即为声明。它和下面标黄的 SetSpreadMultiplier 方法配套:传入 0 时自动归为 1,避免除零或失效。 实盘里若 broker 的 StopLevel 是 10 点、m_multiplier 设为 3,则挂单距离倾向拉到 30 点以外。外汇与贵金属杠杆高,参数误设可能直接报『离市价过近』拒单,建议在 MT5 策略测试器里先改这个值看 return 的 placement 距离。
class="type">class="kw">string m_symbol; class=class="str">"cmt">// Symbol class="type">class="kw">string m_comment; class=class="str">"cmt">// Comment class="type">ulong m_deviation; class=class="str">"cmt">// Slippage in points class="type">class="kw">double m_volume; class=class="str">"cmt">// Volume class="type">class="kw">datetime m_expiration; class=class="str">"cmt">// Order expiration time(for ORDER_TIME_SPECIFIED type order) class="type">bool m_async_mode; class=class="str">"cmt">// Flag of asynchronous sending of a trade request ENUM_LOG_LEVEL m_log_level; class=class="str">"cmt">// Logging level class="type">int m_stop_limit; class=class="str">"cmt">// Distance of placing a StopLimit order in points class="type">bool m_use_sound; class=class="str">"cmt">// The flag of using sounds of the object trading events class="type">uint m_multiplier; class=class="str">"cmt">// The spread multiplier to adjust levels relative to StopLevel class="kw">public: class=class="str">"cmt">//--- Constructor CTradeObj(); class=class="str">"cmt">//--- Set/class="kw">return the spread multiplier class="type">void SetSpreadMultiplier(const class="type">uint value) { this.m_multiplier=(value==class="num">0 ? class="num">1 : value); }
「交易对象的初始化与默认参数」
CTradeObj 的构造函数把一批下单相关的默认值一次性固化,省得每次发单前重复赋值。magic 默认 0、偏差点差 5、止损限价 0、过期时间 0、异步模式关、填充方式 ORDER_FILLING_FOK、有效期 ORDER_TIME_GTC,这些直接写进成员初始化列表。 保证金模式在构造时按平台分支判定:MQL5 下读 ACCOUNT_MARGIN_MODE 整数,MQL4 则硬编码为 ACCOUNT_MARGIN_MODE_RETAIL_HEDGING。点差乘数 m_multiplier 固定为 1,注释里标了 Spread multiplier,后续若要做滑点放大测试改这一行即可。 SetResultRetcode 与 SetResultComment 是两个极简 setter,只把外部回传的 retcode 和字符串写进 m_result,方便在自定义交易类里追踪最近一次请求结果。外汇与贵金属杠杆高,这类封装若误设 FOK 填充,在流动性薄时可能直接拒单。 下面这段是构造函数的核心骨架,成员初始化和平台宏判断都在其中。
class="type">uint SpreadMultiplier(class="type">void) const { class="kw">return this.m_multiplier; } class=class="str">"cmt">//--- Set class="kw">default values class=class="str">"cmt">//--- Set the error code in the last request result class="type">void SetResultRetcode(const class="type">uint retcode) { this.m_result.retcode=retcode; } class="type">void SetResultComment(const class="type">class="kw">string comment) { this.m_result.comment=comment; } class=class="str">"cmt">//--- Data on the last request result: CTradeObj::CTradeObj(class="type">void) : m_magic(class="num">0), m_deviation(class="num">5), m_stop_limit(class="num">0), m_expiration(class="num">0), m_async_mode(false), m_type_filling(ORDER_FILLING_FOK), m_type_expiration(ORDER_TIME_GTC), m_comment(::MQLInfoString(MQL_PROGRAM_NAME)+" by DoEasy"), m_log_level(LOG_LEVEL_ERROR_MSG) { class=class="str">"cmt">//--- Margin calculation mode this.m_margin_mode= ( class="macro">#ifdef __MQL5__(ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE) class="macro">#else class=class="str">"cmt">/* MQL4 */ ACCOUNT_MARGIN_MODE_RETAIL_HEDGING class="macro">#endif ); class=class="str">"cmt">//--- Spread multiplier this.m_multiplier=class="num">1; class=class="str">"cmt">//--- Set class="kw">default sounds and flags of using sounds this.m_use_sound=false; this.InitSounds(); }
交易对象初始化与开仓接口的内部绑定
CTradeObj 的 Init 把交易所需的符号、魔术码、手数、滑点容差、挂单有效期等十来个参数一次性收口到实例成员里。注意最后三行并不是简单赋值:它实时去查 SymbolInfoInteger 拿 SYMBOL_EXPIRATION_MODE 标记,又把 m_volume 强制重置成 SymbolInfoDouble 返回的 SYMBOL_VOLUME_MIN,这意味着你传进来的 volume 参数在 Init 里会被最小交易量覆盖掉,真正下单手数得靠后续 SetVolume 再改。 MQL5 环境下 m_chart_mode 通过预编译宏走 SymbolInfoInteger(SYMBOL_CHART_MODE),而 MQL4 分支直接写死 SYMBOL_CHART_MODE_BID。这种写法保证跨版本编译不报错,但在回测里如果图表模式取不到,MQL4 会默认按买价画图,可能与你肉眼看的卖价线错位。 OpenPosition 只暴露 type、volume、sl、tp、magic、comment 几个常用项,sl/tp 默认 0 表示不挂止损止盈。想在 MT5 验证的话,把这段代码塞进 EA 的 CTradeObj 类,Init 后打印 m_volume,你会看到它等于该品种 SYMBOL_VOLUME_MIN(比如 XAUUSD 常见 0.01),而不是你传的 volume。外汇与贵金属杠杆高,最小手数约束和滑点容差会直接吃掉窄止损策略的空间,参数需按实盘品种重校。
class="type">void CTradeObj::Init(const class="type">class="kw">string symbol, const class="type">ulong magic, const class="type">class="kw">double volume, const class="type">ulong deviation, const class="type">int stoplimit, const class="type">class="kw">datetime expiration, const class="type">bool async_mode, const ENUM_ORDER_TYPE_FILLING type_filling, const ENUM_ORDER_TYPE_TIME type_expiration, ENUM_LOG_LEVEL log_level) { this.SetSymbol(symbol); this.SetMagic(magic); this.SetDeviation(deviation); this.SetVolume(volume); this.SetExpiration(expiration); this.SetTypeFilling(type_filling); this.SetTypeExpiration(type_expiration); this.SetAsyncMode(async_mode); this.SetLogLevel(log_level); this.m_symbol_expiration_flags=(class="type">int)::SymbolInfoInteger(this.m_symbol,SYMBOL_EXPIRATION_MODE); this.m_volume=::SymbolInfoDouble(this.m_symbol,SYMBOL_VOLUME_MIN); this.m_chart_mode=class="macro">#ifdef __MQL5__(ENUM_SYMBOL_CHART_MODE)::SymbolInfoInteger(this.m_symbol,SYMBOL_CHART_MODE) class="macro">#else SYMBOL_CHART_MODE_BID class="macro">#endif ; } class="type">bool CTradeObj::OpenPosition(const class="type">ENUM_POSITION_TYPE type, const class="type">class="kw">double volume, const class="type">class="kw">double sl=class="num">0, const class="type">class="kw">double tp=class="num">0, const class="type">ulong magic=ULONG_MAX, const class="type">class="kw">string comment=NULL,
◍ 下单请求封装里的同步异步分叉
这段代码把 MT5 市价单的发送逻辑收口到一个方法里:先抢一次 SymbolInfoTick 拿实时报价,失败就写错误码并退回 false,避免拿不到价格就盲发请求。 请求结构体逐字段填充,price 的取值按持仓方向和多窗口模式切换——买用 ask,卖在 SYMBOL_CHART_MODE_BID 时取 bid 否则取 last,这种细节在交叉盘和衍生品上容易踩坑。 #ifdef __MQL5__ 分支下,根据 m_async_mode 决定走 OrderSend 还是 OrderSendAsync:同步等回执,异步丢给交易池。外汇与贵金属杠杆高,异步发单若不做后续回执轮询,滑点可能超出你预设的 deviation(默认 ULONG_MAX 会回落到实例级偏差值)。 #else 分支是 MQL4 兼容写法,用老式多参 OrderSend 拿 ticket,再手动 SymbolInfoTick 回填 ask/bid 和 deal,注意这里 price 回退用了 OrderSelect 查开盘价,查不到才用请求价。
const class="type">ulong deviation=ULONG_MAX) { ::ResetLastError(); class=class="str">"cmt">//--- If failed to get the current prices, write the error code and description, send the message to the journal and class="kw">return &class="macro">#x27;false&class="macro">#x27; if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); class="kw">return false; } class=class="str">"cmt">//--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); class=class="str">"cmt">//--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.magic = (magic==ULONG_MAX ? this.m_magic : magic); this.m_request.type = OrderTypeByPositionType(type); this.m_request.price = (type==POSITION_TYPE_BUY ? this.m_tick.ask : (this.m_chart_mode==SYMBOL_CHART_MODE_BID ? this.m_tick.bid : this.m_tick.last)); this.m_request.volume = volume; this.m_request.sl = sl; this.m_request.tp = tp; this.m_request.deviation= (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); class=class="str">"cmt">//--- Return the result of sending a request to the server class="macro">#ifdef __MQL5__ class="kw">return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); class="macro">#else ::ResetLastError(); class="type">int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(class="type">int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(class="type">int)m_request.magic,m_request.expiration,clrNONE); if(ticket!=WRONG_VALUE) { ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.deal=ticket; this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price);