MQL5中的自动化交易策略(第七部分):构建具备仓位动态调整功能的网格交易EA·综合运用
◍ 网格加仓后的保本价怎么算
当账户跑的是网格类策略、同方向挂着多单或多空时,靠肉眼估保本价基本会错。下面这段逻辑直接遍历所有持仓,按成交量加权算出统一进场成本,比单看首仓价格实用。 外汇与贵金属网格策略本身杠杆高、回撤快,保本价只是风控参考,不代表价格触及就必然反转,实际滑点可能让平仓价偏离计算值十几点。 核心函数是 CalculateWeightedBreakevenPrice:先取第一张单的方向,再累加同方向每张单的「开仓价×手数」和总手数,最后相除得出加权均价。若总手数为 0 则返回 0,避免除零报错。 配套的 CloseAllPositions 用倒序循环(从 PositionsTotal()-1 到 0)逐个按 ticket 平仓,Print 出成功或失败的单号,方便在 MT5 Experts 日志里核对哪张单没平掉。
class="type">void CloseAllPositions(){ for(class="type">int i = PositionsTotal()-class="num">1; i >= class="num">0; i--){ class="type">ulong posTkt = PositionGetTicket(i); if(PositionSelectByTicket(posTkt)){ if(obj_Trade.PositionClose(posTkt)) Print("Closed position ticket: ", posTkt); else Print("Failed to close position ticket: ", posTkt); } } } if(closureMode == CLOSE_BY_POINTS && PositionsTotal() > class="num">1) CheckBreakevenClose(ask, bid); class="type">class="kw">double CalculateWeightedBreakevenPrice(){ class="type">class="kw">double totalCost = class="num">0; class="type">class="kw">double totalVolume = class="num">0; class="type">int posType = -class="num">1; for(class="type">int i = class="num">0; i < PositionsTotal(); i++){ class="type">ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ posType = (class="type">int)PositionGetInteger(POSITION_TYPE); class="kw">break; } } for(class="type">int i = class="num">0; i < PositionsTotal(); i++){ class="type">ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ if(PositionGetInteger(POSITION_TYPE) == posType){ class="type">class="kw">double price = PositionGetDouble(POSITION_PRICE_OPEN); class="type">class="kw">double volume = PositionGetDouble(POSITION_VOLUME); totalCost += price * volume; totalVolume += volume; } } } if(totalVolume > class="num">0) class="kw">return(totalCost / totalVolume); else class="kw">return(class="num">0); }
网格持仓的加权保本平仓判定
在 CLOSE_BY_POINTS 模式下,若账户里挂着多笔同方向仓位(典型网格加仓),就不能用单笔开仓价当保本线。下面这段逻辑先算整体加权保本价,再比对实时报价是否越过阈值,触发则全平。 代码里第一步就卡了 PositionsTotal() <= 1 直接 return,说明这套判定只服务于多仓共存场景;单仓时由别的逻辑管。随后用 CalculateWeightedBreakevenPrice() 拿加权价,并随便挑一个仓位读出 POSITION_TYPE 决定多空方向。
class=class="str">"cmt">//--- Function: CheckBreakevenClose class=class="str">"cmt">//--- Description: When class="kw">using CLOSE_BY_POINTS and multiple positions exist, class=class="str">"cmt">//--- calculates the weighted breakeven price and checks if the class=class="str">"cmt">//--- current price has moved the specified points in a profitable class=class="str">"cmt">//--- direction relative to breakeven. If so, closes all positions. class=class="str">"cmt">//+-----------------------------------------------------------------------------+ class="type">void CheckBreakevenClose(class="type">class="kw">double ask, class="type">class="kw">double bid){ class=class="str">"cmt">//--- Ensure we have more than one position(grid positions) if(PositionsTotal() <= class="num">1) class="kw">return; class="type">class="kw">double weightedBreakeven = CalculateWeightedBreakevenPrice(); class="type">int posType = -class="num">1; class=class="str">"cmt">//--- Determine the trade type from one of the positions for(class="type">int i = class="num">0; i < PositionsTotal(); i++){ class="type">ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ posType = (class="type">int)PositionGetInteger(POSITION_TYPE); class="kw">break; } } if(posType == -class="num">1) class="kw">return; class=class="str">"cmt">//--- For BUY positions, profit when Bid >= breakeven + threshold if(posType == POSITION_TYPE_BUY){ if(bid >= weightedBreakeven + breakevenPoints){ Print("Closing BUY positions: Bid(", bid, ") >= Breakeven(", weightedBreakeven, ") + ", breakevenPoints); CloseAllPositions(); } } class=class="str">"cmt">//--- For SELL positions, profit when Ask <= breakeven - threshold else if(posType == POSITION_TYPE_SELL){ if(ask <= weightedBreakeven - breakevenPoints){ Print("Closing SELL positions: Ask(", ask, ") <= Breakeven(", weightedBreakeven, ") - ", breakevenPoints); CloseAllPositions(); } } }
class=class="str">"cmt">//--- Function: CheckBreakevenClose class=class="str">"cmt">//--- Description: When class="kw">using CLOSE_BY_POINTS and multiple positions exist, class=class="str">"cmt">//--- calculates the weighted breakeven price and checks if the class=class="str">"cmt">//--- current price has moved the specified points in a profitable class=class="str">"cmt">//--- direction relative to breakeven. If so, closes all positions. class=class="str">"cmt">//+-----------------------------------------------------------------------------+ class="type">void CheckBreakevenClose(class="type">class="kw">double ask, class="type">class="kw">double bid){ class=class="str">"cmt">//--- Ensure we have more than one position(grid positions) if(PositionsTotal() <= class="num">1) class="kw">return; class="type">class="kw">double weightedBreakeven = CalculateWeightedBreakevenPrice(); class="type">int posType = -class="num">1; class=class="str">"cmt">//--- Determine the trade type from one of the positions for(class="type">int i = class="num">0; i < PositionsTotal(); i++){ class="type">ulong ticket = PositionGetTicket(i); if(PositionSelectByTicket(ticket)){ posType = (class="type">int)PositionGetInteger(POSITION_TYPE); class="kw">break; } } if(posType == -class="num">1) class="kw">return; class=class="str">"cmt">//--- For BUY positions, profit when Bid >= breakeven + threshold if(posType == POSITION_TYPE_BUY){ if(bid >= weightedBreakeven + breakevenPoints){ Print("Closing BUY positions: Bid(", bid, ") >= Breakeven(", weightedBreakeven, ") + ", breakevenPoints); CloseAllPositions(); } } class=class="str">"cmt">//--- For SELL positions, profit when Ask <= breakeven - threshold else if(posType == POSITION_TYPE_SELL){ if(ask <= weightedBreakeven - breakevenPoints){ Print("Closing SELL positions: Ask(", ask, ") <= Breakeven(", weightedBreakeven, ") - ", breakevenPoints); CloseAllPositions(); } } }
「把工具请下神坛」
前面两节把动态网格 EA 的订单铺设、仓位随波动缩放、盈利与保本切换都拆开了,这一节落到实盘前最后一句提醒:任何结构化网格系统都只是把人的风控逻辑写成代码,不替你消灭外汇与贵金属的高风险。 那套 Grid_EA_With_Dynamic_Lot-Sizing.mq5 在原文附档里是 13.97 KB,回测前先把动态手数上限和网格步长两个参数钉死,不然波动放大时浮亏扩张速度会超出多数账户承受概率。 把它当校验自己风控想法的仪器,而不是印钞模块;跑通了再谈调优,跑崩了先看日志里哪一层保本触发失效。