MQL5 简介:如何编写简单的EA 交易和自定义指标·综合运用
🛠️

MQL5 简介:如何编写简单的EA 交易和自定义指标·综合运用

(3/3)· 把前两篇的语法与结构拼成能跑的 EA 和指标,避开新手最常卡在的编译坑

案例拆解新手友好 第 3/3 篇
很多人照着向导生成模板就直接塞逻辑,结果编译一堆报错却找不到是哪行少了分号或类型不对。还有人写完 EA 不先调试就挂真仓,把语法阶段的小错变成实盘里的滑点损失。先把模板跑通再谈策略,是这最后一环的底线。

挂单改价与撤单的分支逻辑

这段逻辑只做一件事:在交易时段内,把偏离当前波动区间的止损挂单拉回通道,过了时间窗口或价格不可移动就直接删单。外汇与贵金属波动剧烈,这类自动改单若 StopLevel 校验不严,可能触发 4756 报错(无效止损距离)。 核心判断在 dt.hour>=StartHour && dt.hour<EndHour && lev_h>atr_h[0],即只在 StartHour 到 EndHour 之间、且上轨 lev_h 高于 ATR 上轨时才允许修改买单。改单前用 NormalizeDouble(lev_h-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>0 确认新开仓价距离卖价减去最小止损距离后仍为正,避免挂单贴着点差被拒。 卖单一侧对称处理:lev_l>atr_l[0] 且原开仓价高于新下轨 lev_l 时才改,否则进入 TRADE_ACTION_REMOVE 分支。代码里 bord=true 是在处理完止损买挂单后标记状态,供后续 OnTick 其他模块读取。 别把 NormalizeDouble 当万能精度药:它只截断小数位,不验证经纪商报价小数规则,XAUUSD 在部分平台 _Digits=2 与 3 混用,复制前先 Print(_Digits) 确认。

MQL5 / C++
class=class="str">"cmt">// 开仓价位是否接近当前价位?
&& NormalizeDouble(lev_h-SymbolInfoDouble(Symbol(),SYMBOL_ASK)-StopLevel,_Digits)>class="num">0)
      {
         class=class="str">"cmt">// 将要修改挂单的参数
         request.action=TRADE_ACTION_MODIFY;
         class=class="str">"cmt">// 填写结构中的订单编号
         request.order=ticket;
         class=class="str">"cmt">// 填写结构中新的开仓价位
         request.price=NormalizeDouble(lev_h,_Digits);
         class=class="str">"cmt">// 填写结构中新的止损价位
         request.sl=NormalizeDouble(lev_l,_Digits);
         class=class="str">"cmt">// 给交易服务器发请求
         OrderSend(request,result);
         class=class="str">"cmt">// 退出OnTick()函数
         class="kw">return;
      }
            class=class="str">"cmt">// 如果不是交易时间或者已经过了平均交易范围
            else
            {
             class=class="str">"cmt">// 我们会删除此挂单
             request.action=TRADE_ACTION_REMOVE;
             class=class="str">"cmt">// 填写结构中的订单编号
             request.order=ticket;
             class=class="str">"cmt">// 给交易服务器发请求
             OrderSend(request,result);
             class=class="str">"cmt">// 退出OnTick()函数
             class="kw">return;
            }
            class=class="str">"cmt">// 设置标志,指出存在止损买单
            bord=true;
         }
      class=class="str">"cmt">// 处理所有止损卖单
      if(OrderGetInteger(ORDER_TYPE)==ORDER_TYPE_SELL_STOP)
       {
         class=class="str">"cmt">// 检查是否是交易时间,价格能否移动
         if(dt.hour>=StartHour && dt.hour<EndHour && lev_l>atr_l[class="num">0])
          {
           class=class="str">"cmt">// 如果开仓价位比所需高
           if((NormalizeDouble(OrderGetDouble(ORDER_PRICE_OPEN)-lev_l,_Digits)>class="num">0
            class=class="str">"cmt">// 如果止损没有被定义或者比所需低

「挂单改价与撤单的触发逻辑」

这段逻辑处理的是已挂止损单的动态调整:当市场还在允许交易时段、且挂单开仓价离当前 BID 仍有大于 StopLevel 的距离时,EA 会用 TRADE_ACTION_MODIFY 把挂单价和止损价改写成 lev_l 与 lev_h,再走一次 OrderSend。 若不在交易时间,或价格已经越过平均波动范围(atr_h[0] / atr_l[0] 构成的边界),代码分支直接切到 TRADE_ACTION_REMOVE,按 ticket 删单并 return,避免无效挂单占用保证金。 新单投放那一段也卡得很死:仅在 StartHour~EndHour 内,bord 为假且 lev_h 低于 atr_h[0] 才下 BUY_STOP;sord 为假且 lev_l 高于 atr_l[0] 才下 SELL_STOP。外汇与贵金属杠杆高,这种按波动带收口的挂单方式可能降低追单滑点,但极端跳空时改单请求仍可能拒单。 开 MT5 把这段塞进 OnTick 跑一遍,重点看 StopLevel 在你经纪商那是不是 0——很多 ECN 账户返回 0,那时 NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-lev_l-StopLevel,_Digits)>0 永远为真,挂单不会被撤。

MQL5 / C++
|| OrderGetDouble(ORDER_SL)==class="num">0 || NormalizeDouble(lev_h-OrderGetDouble(ORDER_SL),_Digits)>class="num">0)
class=class="str">"cmt">// 开仓价位接近当前价位吗?
&& NormalizeDouble(SymbolInfoDouble(Symbol(),SYMBOL_BID)-lev_l-StopLevel,_Digits)>class="num">0)
 {
 class=class="str">"cmt">// 挂单参数将要被修改
 request.action=TRADE_ACTION_MODIFY;
 class=class="str">"cmt">// 填写结构中的订单编号
 request.order=ticket;
 class=class="str">"cmt">// 填写结构中新的开仓价位
 request.price=NormalizeDouble(lev_l,_Digits);
 class=class="str">"cmt">// 填写结构中新的止损价位
 request.sl=NormalizeDouble(lev_h,_Digits);
 class=class="str">"cmt">// 给交易服务器发请求
 OrderSend(request,result);
 class=class="str">"cmt">// 退出OnTick()函数
 class="kw">return;
 }
 }
 class=class="str">"cmt">// 如果不是交易时间或者已经过了平均交易范围
 else
 {
 class=class="str">"cmt">// 我们会删除此挂单
 request.action=TRADE_ACTION_REMOVE;
 class=class="str">"cmt">// 填写结构中的订单编号
 request.order=ticket;
 class=class="str">"cmt">// 给交易服务器发请求
 OrderSend(request,result);
 class=class="str">"cmt">// exiting from the OnTick() function
 class="kw">return;
 }
 class=class="str">"cmt">// 设置标志,指出已经存在止损卖单
sord=true;
 }
 }
 }
 request.action=TRADE_ACTION_PENDING;
 if(dt.hour>=StartHour && dt.hour<EndHour)
 {
 if(bord==false && lev_h<atr_h[class="num">0])
 {
 request.price=NormalizeDouble(lev_h,_Digits);
 request.sl=NormalizeDouble(lev_l,_Digits);
 request.type=ORDER_TYPE_BUY_STOP;
 OrderSend(request,result);
 }
 if(sord==false && lev_l>atr_l[class="num">0])

◍ 挂单价格与止损的精度归一

这段代码负责把计算好的支撑下沿 lev_l 与阻力上沿 lev_h 下发给卖出止损单,核心在 NormalizeDouble 对报价的位数收敛。 request.price=NormalizeDouble(lev_l,_Digits) 把挂单价按当前品种小数位截断,避免服务器因价格精度不符拒单;request.sl=NormalizeDouble(lev_h,_Digits) 同步把止损设在阻力侧,符合跌破支撑反手做空的逻辑。 request.type=ORDER_TYPE_SELL_STOP 指明是跌破挂单,OrderSend(request,result) 发出后由交易环境返回结果。外汇与贵金属杠杆高,挂单未成交前也可能因跳空直接穿透,实盘前应在 MT5 策略测试器用 _Digits 对照品种规格核对。 OnDeinit 里 IndicatorRelease(hCI) 与 IndicatorRelease(hMA) 在指标卸载时释放句柄,防止 EA 重加载后残留内存引用导致初始化失败。

MQL5 / C++
   {
      request.price=NormalizeDouble(lev_l,_Digits);
      request.sl=NormalizeDouble(lev_h,_Digits);
      request.type=ORDER_TYPE_SELL_STOP;
      OrderSend(request,result);
      }
   }
}
class="type">void OnDeinit(const class="type">int reason)
  {
   IndicatorRelease(hCI);
   IndicatorRelease(hMA);
  }

把EA挂上图表并打通调试断点

在MT5里让EA跑起来,第一步不是在代码里改逻辑,而是先把它挂到图表上。Navigator窗口的Expert Advisors组里右键点EA,选Attach to chart,弹出的参数框里可以临时改输入项,按OK后图表右上角出现EA图标才算真正加载。快捷键Ctrl+N能随时开关Navigator,Insert菜单的Experts子菜单是另一条挂载路径。 光挂上不算能交易。Tools→Options→Expert Advisors里必须勾Allow AutoTrading,否则EA只观察不下单;如果代码用了DLL,还得同步开Allow DLL imports。每个EA实例也能单独授权或禁交易、禁DLL,不用全局放开。 指标和脚本的启动方式跟EA一致:内置指标展开Indicators树、自定义指标展开Custom Indicators树,右键附加即可。EA调用的指标默认不画到图表,需要肉眼核对时得手动补挂。 运行时出了什么连接断开、持仓变动、报错,去Toolbox的Journal看系统事件,EA/指标/脚本自己print的消息在Experts选项卡。 真要查逻辑错,用MetaEditor内建调试器。F5启动调试,程序在独立图表按MetaEditor Options里Debugging选项卡指定的品种和周期跑。F9或左键双击设断点,跑到断点前停住,Toolbox的Debug选项卡左边是调用栈(文件、函数、行号),右边Watch面板看变量,右键Add或Insert键能把变量塞进去盯值。 单步节奏靠三个键分:F11 Step Into进到被调函数内部,F10 Step Over跨过函数调用,Shift+F11 Step Out跑完当前函数回到上层。代码左侧绿箭头指的就是下一行要执行的语句,对照Watch里的实时数值,基本能把算错的点揪出来。外汇和贵金属杠杆高,EA哪怕调试通了也只是逻辑无错,实盘仍可能因滑点和跳空亏损。

「记住这一条就够了」

示例 EA 与指标仅作语言入门演示,原文作者明确不对实盘使用负责,外汇与贵金属杠杆交易本身高风险,直接挂上真仓可能很快爆掉。 代码里 day_n 初值设为 0 后靠 day_n < day_t 比较再赋值,但社区反馈有初学者在 MT5 调试时看到 day_n 未初始化竟是 2076449103,导致比较永远为假、指标画不出线。 你打开附带 expert.mq5 和 indicator_tp.mq5 跑一遍策略测试器,若 EA 不出单或指标空白,先查 day_n 定义与循环入口,比反复看文档省时间。

MQL5 / C++
if(day_n < day_t){
            day_n = day_t;
            h_day = high[i];
            l_day = low[i]
}
class="type">int day_n = class="num">0, day_t;
把重复编译检查交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到编译错误模式提示,你只需专注把交易逻辑写对。

常见问题

不影响执行逻辑,但参数会在加载时向用户弹出便于调整,作者信息仅作元数据记录。
大概率是指标缓冲区没在 OnInit 里绑定或计算循环越界,检查 SetIndexBuffer 与数组大小。
可以,小布盯盘的品种页会汇总常见编译异常与调试线索,省去你反复切编辑器查报错。
EA 可用策略测试器跑历史,脚本多靠单次手动触发配合 Print 输出来定位。
持仓与订单模型分离,旧版 OrderSend 逻辑要重写成交易类函数,不能直接套用。