MQL5 Cookbook: 在EA交易中使用指标来设置交易条件·综合运用
📘

MQL5 Cookbook: 在EA交易中使用指标来设置交易条件·综合运用

第 3/3 篇

◍ 两种执行模式下开仓函数的分叉处理

MT5 的成交机制分即时(Instant)与市场(Market)两类,OpenPosition() 必须按 symb.execution_mode 分别走逻辑,否则在部分券商服务器上会直接报 4756 类错误。 即时模式下,止损止盈可随 PositionOpen() 一次性带入;市场模式在 build 803 之前只能先以 0 止损 0 止盈开仓,再用 PositionModify() 补设,否则订单会被拒。 如果市场模式下已存在同标的仓位,函数选择不加 sl/tp 只加手数,这个分支能避免加仓时被旧止损价覆盖。外汇与贵金属杠杆高,滑点参数 Deviation 经 CorrectValueBySymbolDigits 校正后再 SetDeviationInPoints,能降低实盘意外成交概率。 下面这段是核心开仓封装,建议直接丢进 MT5 策略测试器跑一遍不同 execution_mode 的返回。

MQL5 / C++
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(),保证面板上的浮盈、点差等字段和当前持仓同步。

MQL5 / C++
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 拿订单填充方式。

MQL5 / C++
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 日志里反而一眼能定位问题品种。 外汇与贵金属行情跳变快、点差滑点风险高,用最小代码验证路径后再接复杂逻辑,比堆界面更稳。

MQL5 / C++
  class="type">class="kw">string path=SymS(_symbol,SYMBOL_PATH);
  PrintFormat("字符树路径:%s",path);
}

常见问题

实时模式要检查点差和库存费,回测模式只需验证信号,分叉处理能避免实盘误单。把两种逻辑用条件分支隔离即可。
多半是OnDeinit里没清掉挂单或定时器。EA卸载前应在清理函数里释放资源、删除未成交挂单,节奏才稳。
可以。小布能读取你的品种页和指标信号,提示开仓条件冲突或参数过拟合风险,省去手动核对。
常是遗传算法步长太大或样本区间含极端行情。缩小步长、剔除异常段再跑通常能继续。
检查是否放在对应品种的Experts子目录,且文件编译无报错。重开终端或刷新导航树一般能出来。