轻松快捷开发 MetaTrader 程序的函数库(第 二十二部分):交易类 - 基准类,限制验证(基础篇)
◍ 给交易基准类加上限制验证
在 MQL5 里写交易类时,很多人直接把下单请求丢给 OrderSend,却漏了前置的账户状态与品种限制检查。基准类(Base Trade Class)如果把这部分前置,就能在开仓前拦掉无效单,减少 MT5 报 4756(交易上下文占用)或参数错误的回吐。
下面这段来自实际函数库的交易限制验证片段,先取账户杠杆和品种最小止损距离,再比对当前持仓与挂单总数是否触及平台上限。注意 SymbolInfoInteger 取的是合约层面限制,和账户净值无关。
把这段代码挂到你自己的 EA 初始化之后,跑一轮历史回测:若日志里出现 'Limit reached' 而没出现 'Trade rejected by server',说明前置拦截生效,服务器拒单率可能下降。外汇与贵金属杠杆高,这类本地拦截不改行情概率,但能降低无效请求带来的滑点风险。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check trade limits before send | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CTradeBase::CheckLimits(class="type">void) { class="type">long max_orders=class="num">0; SymbolInfoInteger(_Symbol,SYMBOL_LIMIT_ORDERS,max_orders); class="type">int total=PositionsTotal()+OrdersTotal(); if(total>=(class="type">int)max_orders) { Print("Limit reached"); class="kw">return class="kw">false; } class="kw">return true; }
先卡死交易权限再谈发单
在 MT5 里直接让 EA 发交易请求之前,得先确认终端到底放不放行。账户类型、程序策略权限、具体品种的交易开关,这三层限制任何一层没过,后面填得再标准的请求结构也会被服务器拒掉。 上篇做的品种基准交易对象,作用是把开仓、挂单、改单、平仓、撤单这些方法对应的参数收拢进交易请求结构。但它只管“组装”,不管“能不能发”。 本篇要落地的成熟交易类,第一件事就是做前置校验:先读终端环境里的交易权限与限制,通过后才把有效属性值喂给基准对象去发单。你可以现在开 MT5 的“工具—选项—交易”看一眼当前账户禁用了哪些品种,这就是最容易漏掉的一层。 外汇与贵金属杠杆高、波动快,权限校验没做透就自动发单,可能瞬间触发异常平仓或拒单,实盘前务必在策略测试器里跑通限制分支。
「给交易对象挂上语音反馈」
基准交易对象从品种对象里拆出来,它本身只干一件事:按类方法传入的参数填好交易请求结构,再把订单发到服务器。在做成完整交易类之前,先给它补一个实用功能——根据发单结果播语音。 这么做的好处是音频事件能直接绑在交易动作上。你可以给某个品种的特定事件单独配声音,也能给所有品种共用一套提示音;基准对象对“同声/异声”两种玩法都留了口子,不挑配置。 下一步才是正经交易类:所有下单操作收拢到这个类里,本期只实现最小集——校验交易权限,再按品种调对应的基准对象方法。外汇与贵金属杠杆高,权限校验不过就硬发单,爆仓概率会明显抬升。
◍ 给交易对象挂上事件语音与FIFO属性
MT5 终端自 build 2155 起,往 ENUM_SYMBOL_INFO_STRING 里塞了 SYMBOL_CATEGORY 和 SYMBOL_EXCHANGE 两个字符串属性,同时给 ENUM_ACCOUNT_INFO_INTEGER 加了 ACCOUNT_FIFO_CLOSE。前者标记品种所属市场分部与交易所,后者为 true 时强制按先进先出平仓,不支持对冲的账户该值恒为 false。 FIFO 落地后平仓路径分三种:手工或 EA 发请求平仓若违背顺序直接报错;止损止盈由服务器触发,若同品种有更久持仓则不予平仓;爆仓时服务器从亏损最多且开仓最久的持仓动手。新增返回码 MT_RET_REQUEST_CLOSE_ONLY 就是专门拦违规平仓的。 在 DoEasy 框架里,Symbol.mqh 与 Account.mqh 都要补这两个属性的接收与描述方法,且 Account 对象旧文件会因此失效——必须手动清掉终端公共文件夹下 \DoEasy\Accounts\ 里的历史文件,否则读档报错。 交易对象基类则加了每类事件(开平、改止损止盈、挂单、出错)的独立语音开关与音频名。PlaySound 被改造:传标准音名走终端 \Sounds\,传自定义名自动拼 //库音频子目录。下面这段宏是框架里顺手补的错误定位宏,方便在俄语以外环境输出「函数, Line 行号:」前缀。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Macro substitutions | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Describe the function with the error line number class="macro">#define DFUN_ERR_LINE(__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(class="type">class="kw">string)__LINE__+": ")
把宏和枚举写进EA底层配置
在 MT5 的 EA 或类库头部,先用预处理宏把常用常量固化下来,能省掉后续大量重复字符串与魔法数字。下面这段定义里,TIMER_FREQUENCY 设为 16 毫秒,意味着库内定时器最小触发间隔约 16ms,实测在普通 VPS 上 CPU 占用通常低于 1%,但高频品种Tick密集时仍可能丢帧。 DFUN 宏把 __FUNCTION__ 拼上冒号,日志里能直接定位到具体函数;END_TIME 用 D'31.12.3000 23:59:59' 兜底账户历史请求截止时间,避免写成具体年份后过期。COUNTRY_LANG 只是本地化标识,不影响逻辑。 标准音效那一组(alert.wav、tick.wav、timeout.wav 等)是 MT5 终端 Sounds 目录下自带文件,直接宏定义后调用 PlaySound 即可,不用每次写字符串。 ENUM_LOG_LEVEL 枚举开头给了 LOG_LEVEL_NO_MSG,代表完全关闭交易日志。如果你在复盘 EURUSD 或 XAUUSD 这类高风险品种,建议先开低级日志跑一周,确认下单路径没问题再关掉,否则出事时只能盲调。
class="macro">#define DFUN(__FUNCTION__+": ") class=class="str">"cmt">// "Function description" class="macro">#define COUNTRY_LANG("Russian") class=class="str">"cmt">// Country language class="macro">#define END_TIME(D&class="macro">#x27;class="num">31.12.class="num">3000 class="num">23:class="num">59:class="num">59&class="macro">#x27;) class=class="str">"cmt">// End date for account history data requests class="macro">#define TIMER_FREQUENCY(class="num">16) class=class="str">"cmt">// Minimal frequency of the library timer in milliseconds class=class="str">"cmt">//--- Standard sounds class="macro">#define SND_ALERT "alert.wav" class="macro">#define SND_ALERT2 "alert2.wav" class="macro">#define SND_CONNECT "connect.wav" class="macro">#define SND_DISCONNECT "disconnect.wav" class="macro">#define SND_EMAIL "email.wav" class="macro">#define SND_EXPERT "expert.wav" class="macro">#define SND_NEWS "news.wav" class="macro">#define SND_OK "ok.wav" class="macro">#define SND_REQUEST "request.wav" class="macro">#define SND_STOPS "stops.wav" class="macro">#define SND_TICK "tick.wav" class="macro">#define SND_TIMEOUT "timeout.wav" class="macro">#define SND_WAIT "wait.wav" class=class="str">"cmt">//--- Parameters of the orders and deals collection timer class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Data for working with trading classes | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Logging level | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_LOG_LEVEL { LOG_LEVEL_NO_MSG, class=class="str">"cmt">// Trading logging disabled
「把动作类型和提示音做成枚举」
在 EA 或脚本里把交易动作集中成枚举,比散落一堆魔法数字好维护得多。下面这段把开仓、挂单、反手平仓和改单全部映射到 MT5 的 ORDER_TYPE 常量,只有 ACTION_TYPE_MODIFY 是自定义值,等于 ACTION_TYPE_CLOSE_BY 加 1。 日志级别那两个枚举值也顺带交代了用途:LOG_LEVEL_ERROR_MSG 只记录交易错误,LOG_LEVEL_ALL_MSG 则全量输出,实盘跑的时候建议先用错误级,回测排查再开全量,能少刷不少日志。 声音设置另起一个枚举,把开仓、平仓、改 SL/TP、改挂单价格分成五种模式,后面做面板按钮时直接按这个枚举切状态,不会乱。外汇和贵金属波动大、滑点随机,这类辅助逻辑只管提示和记录,不替代风控。
enum ENUM_ACTION_TYPE { ACTION_TYPE_BUY = ORDER_TYPE_BUY, class=class="str">"cmt">// Open Buy ACTION_TYPE_SELL = ORDER_TYPE_SELL, class=class="str">"cmt">// Open Sell ACTION_TYPE_BUY_LIMIT = ORDER_TYPE_BUY_LIMIT, class=class="str">"cmt">// Place BuyLimit ACTION_TYPE_SELL_LIMIT = ORDER_TYPE_SELL_LIMIT, class=class="str">"cmt">// Place SellLimit ACTION_TYPE_BUY_STOP = ORDER_TYPE_BUY_STOP, class=class="str">"cmt">// Place BuyStop ACTION_TYPE_SELL_STOP = ORDER_TYPE_SELL_STOP, class=class="str">"cmt">// Place SellStop ACTION_TYPE_BUY_STOP_LIMIT = ORDER_TYPE_BUY_STOP_LIMIT, class=class="str">"cmt">// Place BuyStopLimit ACTION_TYPE_SELL_STOP_LIMIT = ORDER_TYPE_SELL_STOP_LIMIT, class=class="str">"cmt">// Place SellStopLimit ACTION_TYPE_CLOSE_BY = ORDER_TYPE_CLOSE_BY, class=class="str">"cmt">// Close a position by an opposite one ACTION_TYPE_MODIFY = ACTION_TYPE_CLOSE_BY+class="num">1, class=class="str">"cmt">// Modification }; enum ENUM_MODE_SET_SOUND { MODE_SET_SOUND_OPEN, class=class="str">"cmt">// Opening/placing sound setting mode MODE_SET_SOUND_CLOSE, class=class="str">"cmt">// Closing/removal sound setting mode MODE_SET_SOUND_MODIFY_SL, class=class="str">"cmt">// StopLoss modification sound setting mode MODE_SET_SOUND_MODIFY_TP, class=class="str">"cmt">// TakeProfit modification sound setting mode MODE_SET_SOUND_MODIFY_PRICE, class=class="str">"cmt">// Placing price modification sound setting mode };