MQL5 简介:如何编写简单的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) 确认。
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 永远为真,挂单不会被撤。
|| 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 重加载后残留内存引用导致初始化失败。
{
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 定义与循环入口,比反复看文档省时间。
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;