MQL5 Cookbook: 怎样在设置/修改交易参数时避免错误·综合运用
卖仓止损与追踪步长的边界处理
计算卖仓止损时,先取柱最高价加 StopLoss 点数,用 NormalizeDouble 按品种精度收敛。若算出的 level 已高于 sym_up_level,直接返回该值,不再下探。 否则改以买价 sym_ask 加同样点数重算,此时若仍不够高,就夹到 sym_up_level+sym_offset 这个最接近的可成交边界,避免报无效止损。 ModifyTrailingStop 里只有当 TrailingStop>0 且 StopLoss>0 才进场。POSITION_TYPE_SELL 的触发条件是 new_sl < pos_sl - CorrectValueBySymbolDigits(TrailingStop*sym_point),也就是新止损比旧止损低出一个追踪步长才改。 外汇与贵金属杠杆高,止损夹边后实际滑点可能让成交价偏离 sym_offset 边界,上 MT5 用品种可视化回放验证这几行更稳妥。
class="kw">return(level<sym_down_level ? level : sym_down_level-sym_offset); } } class=class="str">"cmt">//--- 计算卖出仓位的数据 if(position_type==POSITION_TYPE_SELL) { class=class="str">"cmt">// 柱的最高价加上指定的点数 level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); class=class="str">"cmt">//--- 如果计算的价位比止损价位上限高, class=class="str">"cmt">// 计算完成,返回当前止损价位 if(level>sym_up_level) class="kw">return(level); class=class="str">"cmt">//---如果不高,基于买价计算 else { level=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits); class=class="str">"cmt">//--- 如果计算的价位比上限高,返回计算的价位 class=class="str">"cmt">// 否则设为最接近的可能值 class="kw">return(level>sym_up_level ? level : sym_up_level+sym_offset); } } class=class="str">"cmt">//--- class="kw">return(class="num">0.0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 修改跟踪止损价位 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ModifyTrailingStop() { class=class="str">"cmt">//--- 如果追踪止损和止损都设置了 if(TrailingStop>class="num">0 && StopLoss>class="num">0) { class="type">class="kw">double new_sl=class="num">0.0; class=class="str">"cmt">// 计算新的止损价位 class="type">bool condition=class="kw">false; class=class="str">"cmt">// 检查修改条件 class=class="str">"cmt">//--- 获取持仓存在/缺席的标志。 pos_open=PositionSelect(_Symbol); class=class="str">"cmt">//--- 如果有持仓 if(pos_open) { class=class="str">"cmt">//--- 取得交易品种属性 GetSymbolProperties(S_ALL); class=class="str">"cmt">//--- 取得仓位属性 GetPositionProperties(P_ALL); class=class="str">"cmt">//--- 取得止损价位 new_sl=CalculateTrailingStop(pos_type); class=class="str">"cmt">//--- 根据仓位类型,检查追踪止损修改的相关条件 class="kw">switch(pos_type) { case POSITION_TYPE_BUY : class=class="str">"cmt">//--- 如果新的止损值比 class=class="str">"cmt">// 当前值加步长高 condition=new_sl>pos_sl+CorrectValueBySymbolDigits(TrailingStop*sym_point); class="kw">break; case POSITION_TYPE_SELL : class=class="str">"cmt">//--- 如果新的止损值比 class=class="str">"cmt">// 当前值减步长低 condition=new_sl<pos_sl-CorrectValueBySymbolDigits(TrailingStop*sym_point); class="kw">break; } class=class="str">"cmt">//--- 如果有止损,修改前比较数值
◍ 跟踪止损与交易模块的衔接写法
在 EA 的持仓管理段,先判断 pos_sl 是否大于 0:若已有止损且满足条件(例如新止损比当前更优),才调用 trade.PositionModify(_Symbol,new_sl,pos_tp) 去改跟踪止损;若 pos_sl==0 则直接补设止损。这两段都必须在修改失败时用 Print 把 GetLastError 和 ErrorDescription 打出来,否则 MT5 后台只会静默失败。 TradingBlock() 是信号到下单的中枢。开头先声明 signal、comment、tp、sl、lot 等变量,其中 signal 初始化为 WRONG_VALUE,这是后面过滤“无信号”的关键哨兵值。 实际跑起来时,signal=GetTradingSignal() 拿到信号后,若为 WRONG_VALUE 直接 return 退出;随后 PositionSelect(_Symbol) 查当前品种持仓,GetSymbolProperties(S_ALL) 拉满合约属性,再进 switch(signal) 分支算具体交易参数。外汇与贵金属杠杆高,修改止损的逻辑若漏了错误打印,实盘可能在该止损时没止损,风险显著。 让小布替你跑这套:把上面两段粘进 MT5 策略测试器,故意把 new_sl 设成非法值,看 Journal 里是否如预期输出“修改仓位出错”及错误码,验证你的错误处理没被优化掉。
if(pos_sl>class="num">0) { class=class="str">"cmt">//--- 如果订单修改条件符合,比如新值比当前值更低/更高 class=class="str">"cmt">// 修改仓位的跟踪止损 if(condition) { if(!trade.PositionModify(_Symbol,new_sl,pos_tp)) Print("修改仓位出错: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } class=class="str">"cmt">//--- 如果没有止损,简单设置它 if(pos_sl==class="num">0) { if(!trade.PositionModify(_Symbol,new_sl,pos_tp)) Print("修改仓位出错: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 交易模块 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void TradingBlock() { ENUM_ORDER_TYPE signal=WRONG_VALUE; class=class="str">"cmt">// 用于取得信号的变量 class="type">class="kw">string comment="hello :)"; class=class="str">"cmt">// 仓位注释 class="type">class="kw">double tp=class="num">0.0; class=class="str">"cmt">// 获利 class="type">class="kw">double sl=class="num">0.0; class=class="str">"cmt">// 止损 class="type">class="kw">double lot=class="num">0.0; class=class="str">"cmt">// 如有仓位反转用于计算仓位交易量 class="type">class="kw">double position_open_price=class="num">0.0; class=class="str">"cmt">// 仓位开盘价 ENUM_ORDER_TYPE order_type=WRONG_VALUE; class=class="str">"cmt">// 建仓的订单类型 class="type">ENUM_POSITION_TYPE opposite_position_type=WRONG_VALUE; class=class="str">"cmt">// 反向仓位类型 class=class="str">"cmt">//--- 取得信号 signal=GetTradingSignal(); class=class="str">"cmt">//--- 如果没有信号,则退出 if(signal==WRONG_VALUE) class="kw">return; class=class="str">"cmt">//--- 查找已有持仓 pos_open=PositionSelect(_Symbol); class=class="str">"cmt">//--- 取得所有交易品种属性 GetSymbolProperties(S_ALL); class=class="str">"cmt">//--- 计算交易变量值 class="kw">switch(signal) {
「开仓分支与回测环境判定」
这段逻辑把买卖信号的变量赋值和实盘开仓动作收拢在同一个 switch 之后的流程里。买入分支用 sym_ask 作为开仓价,卖出分支用 sym_bid,同时把反向持仓类型记到 opposite_position_type,供后面反向加仓判断使用。 sl 与 tp 通过 CalculateStopLoss 和 CalculateTakeProfit 按订单类型算出,不写死点数,意味着止损止盈规则跟着信号类型走。若当前无持仓(pos_open 为假),先由 CalculateLot(Lot) 调整交易量再调 trade.PositionOpen;失败就用 Print 把 GetLastError 和 ErrorDescription 打出来,方便在 MT5 专家日志里直接看错误码。 已有持仓时,脚本读取仓位类型与交易量,当信号方向与持仓相反且 Reverse 开关开启,会在原仓位量上叠加 CalculateLot(Lot) 的新量再开仓,属于逆势加仓结构。外汇与贵金属杠杆高,逆势加仓可能快速放大回撤,实盘前务必在策略测试器跑一遍。 末尾两个函数 IsTester 与 IsOptimization 分别返回 MQL5InfoInteger(MQL5_TESTER) 和 MQL5InfoInteger(MQL5_OPTIMIZATION) 的布尔值。你在 EA 里用这两个标志,可以区分实盘、回测与参数优化阶段,比如优化时跳过发邮件通知,能省掉不少无效 IO。
class=class="str">"cmt">//--- 给买入订单变量赋值 case ORDER_TYPE_BUY : position_open_price=sym_ask; order_type=ORDER_TYPE_BUY; opposite_position_type=POSITION_TYPE_SELL; class="kw">break; class=class="str">"cmt">//--- 给卖出订单变量赋值 case ORDER_TYPE_SELL : position_open_price=sym_bid; order_type=ORDER_TYPE_SELL; opposite_position_type=POSITION_TYPE_BUY; class="kw">break; } class=class="str">"cmt">//--- 计算获利和止损价位 sl=CalculateStopLoss(order_type); tp=CalculateTakeProfit(order_type); class=class="str">"cmt">//--- 如果没有持仓 if(!pos_open) { class=class="str">"cmt">//--- 调整交易量 lot=CalculateLot(Lot); class=class="str">"cmt">//--- 开启仓位 class=class="str">"cmt">// 如果开启仓位失败,打印相关信息 if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment)) { Print("开启仓位错误: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } class=class="str">"cmt">//--- 如果有持仓 else { class=class="str">"cmt">//--- 读取仓位类型 GetPositionProperties(P_TYPE); class=class="str">"cmt">//--- 如果信号和仓位方向相反并且启用了反向仓位操作 if(pos_type==opposite_position_type && Reverse) { class=class="str">"cmt">//--- 取得仓位交易量 GetPositionProperties(P_VOLUME); class=class="str">"cmt">//--- 调整交易量 lot=pos_volume+CalculateLot(Lot); class=class="str">"cmt">//--- 开启仓位. 如果开启仓位失败,打印相关信息 if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment)) { Print("开启仓位错误: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } class=class="str">"cmt">//--- class="kw">return; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回测试标志 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsTester() { class="kw">return(MQL5InfoInteger(MQL5_TESTER)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回优化标志 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsOptimization() { class="kw">return(MQL5InfoInteger(MQL5_OPTIMIZATION)); } class=class="str">"cmt">//+------------------------------------------------------------------+
EA运行时环境判定与面板刷新钩子
在 MT5 的 EA 架构里,区分「实盘实时」「策略测试器」「可视化回测」三种环境,直接决定你敢不敢在 OnInit / OnTick 里画对象、刷面板。IsVisualMode() 用 MQL5InfoInteger(MQL5_VISUAL_MODE) 拿标志位,返回 true 即当前处于可视化测试模式——这种模式下图表对象能渲染,但和真金白银的实时环境不是一回事。 IsRealtime() 把 IsTester()、IsOptimization()、IsVisualMode() 三个条件全否掉才返回 true,也就是说它只认「测试器之外、且非可视化」的纯实时环境。外汇和贵金属杠杆高、滑点跳空频繁,面板刷新逻辑若误判环境,可能在回测里正常、实盘却疯狂创建标签对象拖慢终端。
| SetInfoPanel() 只在 IsVisualMode() | IsRealtime() 时执行内部代码,相当于给 GUI 绘制加了环境闸门。OnInit 里先 CheckNewBar() 再 GetPositionProperties(P_ALL) 最后 SetInfoPanel(),确保启动瞬间面板有数据;OnTick 则每根新柱走 GetBarsData → TradingBlock → ModifyTrailingStop,老柱直接跳过交易只更新属性与面板。 |
|---|
开 MT5 按 F4 把下面代码贴进 EA,在策略测试器开/关可视化勾选框,分别挂 EURUSD 观察面板是否绘制,就能验证环境分支是否生效。
class=class="str">"cmt">//| 返回可视化测试模式标志 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsVisualMode() { class="kw">return(MQL5InfoInteger(MQL5_VISUAL_MODE)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 如果所有条件都满足,返回 | class=class="str">"cmt">//| 策略测试器之外的实时模式 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool IsRealtime() { if(!IsTester() && !IsOptimization() && !IsVisualMode()) class="kw">return(true); else class="kw">return(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 设置信息面板 | class=class="str">"cmt">//|------------------------------------------------------------------+ class="type">void SetInfoPanel() { class=class="str">"cmt">//--- 可视化或实时模式 if(IsVisualMode() || IsRealtime()) { class=class="str">"cmt">// SetInfoPanel() 函数其余的代码 class=class="str">"cmt">// ... } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 初始化新柱 CheckNewBar(); 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 OnTick() { class=class="str">"cmt">//--- 如果柱不是新的,退出 if(!CheckNewBar()) class="kw">return; class=class="str">"cmt">//--- 如果有新柱 else { 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(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 交易事件 | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用 OnTrade 钩子同步仓位面板
MT5 的 OnTrade() 是交易事件回调,每当开仓、平仓或改仓触发时由终端自动调用,不需要你手动轮询。把它接上仓位抓取与面板刷新,就能在成交瞬间把持仓数据推到自定义面板,避免用 OnTick 高频空转刷屏。 下面这段是最小可用骨架:终端触发交易事件后,先取全部仓位属性,再重绘信息面板。注意 GetPositionProperties(P_ALL) 里的 P_ALL 是自定义枚举,代表一次性拉取当前账户下所有持仓字段。 [CODE] void OnTrade() { //--- 取得仓位信息并在面板上更新它们的值 GetPositionProperties(P_ALL); //--- 更新信息面板 SetInfoPanel(); } [/CODE] 逐行拆解:第 1 行声明无返回值函数 OnTrade,由 MQL5 运行时在交易动作后调用;第 3 行注释说明目的是取仓位并更新面板;第 4 行调用 GetPositionProperties 并传入 P_ALL,把持仓的票种、手数、开价、浮盈等一次性读进来;第 6 行注释指向面板刷新;第 7 行 SetInfoPanel() 把刚读到的数值写到 GUI 控件。 实盘里若面板控件较多,建议在 SetInfoPanel 内部做脏检查,仅当字段变化才重绘,能明显降低 CPU 占用。外汇与贵金属杠杆高、滑点突变频繁,这种事件驱动写法比每秒扫一遍更稳,但仍需你在 MT5 策略测试器或模拟盘验证触发频率。
class="type">void OnTrade() { class=class="str">"cmt">//--- 取得仓位信息并在面板上更新它们的值 GetPositionProperties(P_ALL); class=class="str">"cmt">//--- 更新信息面板 SetInfoPanel(); }
「双核跑一轮参数优化要七分钟」
把 EA 丢进策略测试器做参数优化时,先确认输入变量给了足够宽的取值区间,否则搜出来的只是局部最优。MQL5 的遗传优化和穷举在步长设大后会明显拉开耗时差距。 在一台双核笔记本(Intel Core2 Duo P7350 @ 2.00GHz)上跑完一轮全参数优化,实测约 7 分钟。这个耗时对现在多核机器不算什么,但提醒你:参数维度一多,老机器上仍可能卡到十几分钟。 优化目标选「最大采收率(Maximal profit factor / recovery factor 类)」时,测试器会按该指标排序结果。图里能看到不同参数组合的恢复系数分布,挑靠前那几个去做样本外验证,别直接上实盘。外汇与贵金属杠杆高,回测漂亮不代表 live 环境能复现,务必先开 MT5 用历史数据自己跑一遍。
黄金品种下止损计算的坑
前面几节把仓位属性面板和开仓逻辑都拆完了,但评论区里一个实测细节值得单独拎出来:在 InstaTrader 平台跑附带的 PositionPropertiesPlus EA 时,外汇对大多能正常带止损开仓,可换到 GOLD 就频繁报「无效止损」,策略测试器里根本建不了仓。 根因不是市场执行模式,而是 GOLD 的符号点(point)和符号刻度(tick)不一致。EA 里 SL/TP 计算默认按 point 来,只有当 point==tick 时才准确;GOLD 不满足这个条件,必须按实际 symbol tick size 修正止损距离,否则必报 4756 类错误。 有交易者验证过:GOLD 在 InstaTrader 里即时执行,开仓时不能挂止损,只能先开仓再改 SL,这样就不报错。若你想让 EA 直接带损开 GOLD,得在算止损那一段乘上 SymbolInfoInteger(_Symbol,SYMBOL_TRADE_TICK_SIZE) 做修正。 文章用的标准库 CTrade 类有 PositionClose 方法,平仓信号触发时直接调它即可,不用自己拼交易请求。下面这段是原文里信息面板显隐的判断骨架,注意它只在可视化或实时模式才画面板。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 设置信息面板| class=class="str">"cmt">//|------------------------------------------------------------------+ class="type">void SetInfoPanel() { class=class="str">"cmt">//--- 可视化或实时模式 if(ShowInfoPanel && (IsVisualMode() || IsRealtime())) { class=class="str">"cmt">// SetInfoPanel() 函数的剩余代码 class=class="str">"cmt">// ... } }