MQL5 Cookbook: 在EA交易中使用指标来设置交易条件·综合运用
◍ 两种执行模式下开仓函数的分叉处理
MT5 的成交机制分即时(Instant)与市场(Market)两类,OpenPosition() 必须按 symb.execution_mode 分别走逻辑,否则在部分券商服务器上会直接报 4756 类错误。 即时模式下,止损止盈可随 PositionOpen() 一次性带入;市场模式在 build 803 之前只能先以 0 止损 0 止盈开仓,再用 PositionModify() 补设,否则订单会被拒。 如果市场模式下已存在同标的仓位,函数选择不加 sl/tp 只加手数,这个分支能避免加仓时被旧止损价覆盖。外汇与贵金属杠杆高,滑点参数 Deviation 经 CorrectValueBySymbolDigits 校正后再 SetDeviationInPoints,能降低实盘意外成交概率。 下面这段是核心开仓封装,建议直接丢进 MT5 策略测试器跑一遍不同 execution_mode 的返回。
class="type">void OpenPosition(class="type">class="kw">double lot, ENUM_ORDER_TYPE order_type, class="type">class="kw">double price, class="type">class="kw">double sl, class="type">class="kw">double tp, class="type">class="kw">string comment) { class=class="str">"cmt">//--- 设置交易结构的幻数 trade.SetExpertMagicNumber(MagicNumber); class=class="str">"cmt">//--- 设置滑点点数 trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation)); class=class="str">"cmt">//--- 即时执行模式 class=class="str">"cmt">// 开启仓位时可以同时设置止损和获利 if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT) { class=class="str">"cmt">//--- 如果仓位开启失败,打印相关信息 if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment)) Print("开启仓位错误: ",GetLastError()," - ",ErrorDescription(GetLastError())); } class=class="str">"cmt">//--- 市场执行模式 class=class="str">"cmt">// 首先开启一个仓位,然后设置止损和获利价位 class=class="str">"cmt">// *** 从 build class="num">803 开始,止损和获利可以在仓位开启时设置 *** if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET) { class=class="str">"cmt">//--- 如果没有仓位,首先开启仓位,然后设置止损和获利 if(!pos.exists) { class=class="str">"cmt">//--- 如果仓位开启失败,打印相关信息 if(!trade.PositionOpen(_Symbol,order_type,lot,price,class="num">0,class="num">0,comment)) Print("开启仓位错误: ",GetLastError()," - ",ErrorDescription(GetLastError())); class=class="str">"cmt">//--- 取得有无仓位的标志 pos.exists=PositionSelect(_Symbol); class=class="str">"cmt">//--- 如果有持仓 if(pos.exists) { class=class="str">"cmt">//--- 设置止损和获利 if(!trade.PositionModify(_Symbol,sl,tp)) Print("修改仓位出错: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } class=class="str">"cmt">//--- 如果仓位存在,增加交易量,不改变止损和获利价位 else { class=class="str">"cmt">//--- 如果仓位开启失败,打印相关信息 if(!trade.PositionOpen(_Symbol,order_type,lot,price,sl,tp,comment)) Print("开启仓位错误: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } }
EA生命周期里的清理与触发节奏
MT5 的 EA 在退出图表时需要显式释放资源,否则指标句柄会残留在内存里。OnDeinit 里先打印去初始化原因文本,当 reason 等于 REASON_REMOVE 时,除了删掉信息面板对象,还必须调用 IndicatorRelease(indicator_handle) 把指标句柄交还系统。 OnTick 的入口先做新柱判断:CheckNewBar() 返回 false 时,若处于可视化模式或实时环境,仅刷新持仓属性与面板数值就退出,不重复跑策略逻辑。这样能避免同一根柱内被报价 tick 反复触发交易检查。 只有当 CheckNewBar() 为真且 CheckTradingPermission() 返回 0,才会执行 GetIndicatorsData()、GetBarsData()、TradingBlock() 与 ModifyTrailingStop()。外汇与贵金属杠杆高,权限校验不通过时强行下单可能触发即时风控拒绝,实盘前应在策略测试器里用不同品种验证该分支。 无论新柱与否,函数末尾都会再跑一次 GetPositionProperties(P_ALL) 和 SetInfoPanel(),保证面板上的浮盈、点差等字段和当前持仓同步。
class=class="str">"cmt">//--- 取得属性 GetPositionProperties(P_ALL); class=class="str">"cmt">//--- 设置信息面板 SetInfoPanel(); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA去初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- 在日志中打印去初始化原因 Print(GetDeinitReasonText(reason)); class=class="str">"cmt">//--- 当从图表上移除时 if(reason==REASON_REMOVE) { class=class="str">"cmt">//--- 从图表上删除所有有关信息面板的对象 DeleteInfoPanel(); class=class="str">"cmt">//--- 删除指标句柄 IndicatorRelease(indicator_handle); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA订单函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- 如果柱不是新的,退出 if(!CheckNewBar()) { if(IsVisualMode() || IsRealtime()) { class=class="str">"cmt">//--- 取得属性并更新信息面板上的数值 GetPositionProperties(P_ALL); class=class="str">"cmt">//--- 设置/更新信息面板 SetInfoPanel(); } class="kw">return; } class=class="str">"cmt">//--- 如果有新柱 else { class=class="str">"cmt">//--- 如果允许交易 if(CheckTradingPermission()==class="num">0) { if(!GetIndicatorsData()) class="kw">return; GetBarsData(); class=class="str">"cmt">// 取得柱数据 TradingBlock(); class=class="str">"cmt">// 检查条件和交易 ModifyTrailingStop(); class=class="str">"cmt">// 修改跟踪止损价位 } } class=class="str">"cmt">//--- 取得属性 GetPositionProperties(P_ALL); class=class="str">"cmt">//--- 更新信息面板 SetInfoPanel(); }
「用策略测试器跑通参数优化」
在 MT5 策略测试器里先把品种、周期、模型(建议用每笔 tick 或 1 分钟 OHLC)和日期范围设好,再加载待优化的 EA。附件里的 *.set 文件可直接导入,省去手动填参;双核处理器上完整优化一轮大约耗 40 分钟,机器弱就别同时开别的重负载。 优化图表里按利润区域着色,颜色越亮代表该组参数组合净盈利越高、回撤相对可控,一眼能筛掉大面积暗区代表的劣质参数带。 以最大采收率为目标跑出的结果,会给出该目标下排名靠前的参数组合;但外汇和贵金属属高风险品种,历史最大采收率不代表未来能复现,只说明该组合在样本内曾达到过的提取效率上限。
◍ 把附件EA跑起来的目录与坑
想复现这套基于 MultiRange_PCH 指标的条件测试 EA,先得把目录放对:解压后把 TestIndicatorConditions 文件夹整体丢进 MT5 终端的 MQL5\Experts 路径下;与此同时 MultiRange_PCH 指标文件必须单独放到 MQL5\Indicators,否则 EA 初始化阶段就会因找不到指标而直接罢工。 附件里实际给了三个文件:testindicatorconditions.set(0.89 KB,预设参数)、multirange_pch.mq5(12.06 KB,核心指标源码)、testindicatorconditions.zip(19.92 KB,整合包)。有用户反馈在某一台电脑解压报损坏,换终端环境后正常,所以下载完先校验大小再解压能省掉一半排查时间。 策略测试器里还有个容易被忽略的现象:在 Alpari UK 模拟环境跑 EA 时,TP 和 SL 常常不会在开仓瞬间出现,只有开了 TrailingStop 才看到 SL 被改写上去;但同逻辑在调试模式直连模拟账户时,SL/TP 在 TradingBlock() 首笔建仓就挂上了。这更可能是经纪商成交回执与测试器事件序列的差异,而不是代码漏设,外汇与贵金属品种本身高杠杆高风险,验证时务必用小额模拟先跑通。 下面这段脚本是当年用来抓 EURUSD 符号属性的最小例子,经纪商 A 和 B 同样标称 INSTANT 成交,但填充模式一个返回 1、一个返回 3,点差模式也分别是浮动与固定——说明同类型账户下底层字段仍可能分裂。代码逐行拆:宏 SymInt 包了 SymbolInfoInteger,SymS 包了 SymbolInfoString;OnStart 里先取 SYMBOL_DIGITS 拿小数位(EURUSD 通常为 4),再取 SYMBOL_SPREAD_FLOAT 判是否浮动点差,随后把 SYMBOL_TRADE_EXEMODE 强转为枚举字符串看成交模式,最后读 SYMBOL_FILLING_MODE 拿订单填充方式。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|test_symbol.mq5 class=class="str">"cmt">//| class="num">2013 年 MetaQuotes 软件公司版权所有。 class=class="str">"cmt">//|http://www.mql5.com | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2013, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#define SymInt SymbolInfoInteger class="macro">#define SymS SymbolInfoString class="type">class="kw">string _symbol="EURUSD"; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 脚本程序启动功能| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- class="num">1) SymbolInfoInteger() class="type">long symb_digits=SymInt(_symbol,SYMBOL_DIGITS); PrintFormat("小数位数:%u",symb_digits); class="type">bool symb_spread_float=SymInt(_symbol,SYMBOL_SPREAD_FLOAT); PrintFormat("浮动点差:%d",symb_spread_float); class="type">class="kw">string trade_mode=EnumToString((ENUM_SYMBOL_TRADE_EXECUTION)SymInt(_symbol,SYMBOL_TRADE_EXEMODE)); PrintFormat("交易模式:%s",trade_mode); class="type">long fil_mode=SymInt(_symbol,SYMBOL_FILLING_MODE); PrintFormat("读取器填充模式:%u",fil_mode); class=class="str">"cmt">//--- class="num">2) SymbolInfoString()
画得少,看得清
把 SymS 取到的 SYMBOL_PATH 直接 PrintFormat 打出来,就能在专家日志里看到该交易品种在字符树里的真实路径,不用再翻文档猜层级。 这种只取一个字段、打印一行路径的写法,省掉了画多余连线和标签的功夫,MT5 日志里反而一眼能定位问题品种。 外汇与贵金属行情跳变快、点差滑点风险高,用最小代码验证路径后再接复杂逻辑,比堆界面更稳。
class="type">class="kw">string path=SymS(_symbol,SYMBOL_PATH); PrintFormat("字符树路径:%s",path); }