改编版 MQL5 网格对冲 EA(第 II 部分):制作一款简单的网格 EA·进阶篇
📘

改编版 MQL5 网格对冲 EA(第 II 部分):制作一款简单的网格 EA·进阶篇

第 2/2 篇

「空头网格的开仓与手数递增陷阱」

EA 处理空头持仓时,先用 if 卡三道门槛:initialPositionBuy 必须为 false 以排除多侧逻辑;price 需 ≥ lastPositionPrice + distance * _Point * 10,方向运算用加法贴合空单间距;gridCycleRunning 为 true 才允许作为进行中轮次的一部分加仓。 新空单手数由 newPositionLotSize = lastPositionLotSize * lotSizeMultiplier 得出,随后常规化到小数点后两位(即 0.01 的倍数)才送进 m_trade.PositionOpen()。但 lastPositionLotSize 自身的更新刻意不做常规化——当乘数为 1.5、初始 0.01 时,0.015 若被四舍五入回 0.01,后续永远复制 0.01,网格加仓逻辑就死了。 图表上用 ObjectCreate() 画两条线:红线“Next Position Price”挂在 lastPositionPrice + distance*_Point*10,绿线为加权均价。加权价 = priceLevelsSummation / totalLotSizeSummation,其中前者累加每笔 lastPositionPrice*newPositionLotSize,后者累加总手数。 平仓由 StopGridCycle() 收尾:gridCycleRunning 置 false,删掉红绿两线,按单号遍历调用 PositionClose()。触发条件是 gridCycleRunning 为 true 且价格越过加权平均价的 takeProfit:空侧要求现价低于均价止盈点。外汇与贵金属网格策略杠杆高,实盘前务必在 MT5 策略测试器用历史数据验证间距与乘数组合。

MQL5 / C++
class="kw">input class="type">bool initialPositionBuy = true;
class="kw">input class="type">class="kw">double distance = class="num">15;
class="kw">input class="type">class="kw">double takeProfit = class="num">5;
class="kw">input class="type">class="kw">double initialLotSize = class="num">0.01;
class="kw">input class="type">class="kw">double lotSizeMultiplier = class="num">2;
class="type">bool gridCycleRunning = class="kw">false;
class="type">class="kw">double lastPositionLotSize, lastPositionPrice, priceLevelsSumation, totalLotSizeSummation;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Hedge Cycle Intialization Function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void StartGridCycle()
  {
   class="type">class="kw">double initialPrice = initialPositionBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
   ENUM_ORDER_TYPE positionType = initialPositionBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
   m_trade.PositionOpen(_Symbol, positionType, initialLotSize, initialPrice, class="num">0, class="num">0);
   lastPositionLotSize = initialLotSize;
   lastPositionPrice = initialPrice;
   ObjectCreate(class="num">0, "Next Position Price", OBJ_HLINE, class="num">0, class="num">0, lastPositionPrice - distance * _Point * class="num">10);
   ObjectSetInteger(class="num">0, "Next Position Price", OBJPROP_COLOR, clrRed);
   priceLevelsSumation = initialLotSize * lastPositionPrice;

网格加仓与均价线的实时绘制

网格策略启动后,核心动作是顺着趋势反向挂加仓单,并在图表上把「下一加仓位」和「当前均价」两条线画出来,方便肉眼盯盘。 以多单网格为例,当现价 price 跌到 lastPositionPrice - distance * _Point * 10 以下且 gridCycleRunning 为真,就按 lotSizeMultiplier 放大手数开新多单;空单逻辑对称,涨破对应阈值才加。 加仓后立刻用 ObjectCreate 重画红色水平线标出下一个触发价,并用 priceLevelsSumation / totalLotSizeSummation 算出加权平均价,画一条绿色水平线。外汇与贵金属杠杆高,网格逆势加仓可能快速放大浮亏,实盘前务必在 MT5 策略测试器用历史数据验证间距与乘数。 下面这段是加仓与画线逻辑的原文片段,逐行对应上面描述: if(initialPositionBuy && price <= lastPositionPrice - distance * _Point * 10 && gridCycleRunning) 判断多单网格是否满足加仓:现价低于上一单价格减去 10 倍点值距离,且网格在运行。 double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, 2); 新手数 = 上笔手数乘乘数,保留两位小数规范化。 m_trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, newPositionLotSize, price, 0, 0); 市价开新多单,无挂单偏移与注释。 lastPositionLotSize *= lotSizeMultiplier; lastPositionPrice = price; 更新记录用的手数与价格变量。 ObjectCreate(0, "Next Position Price", OBJ_HLINE, 0, 0, lastPositionPrice - distance * _Point * 10); 在图表画红色下一加仓价水平线。 ObjectSetInteger(0, "Next Position Price", OBJPROP_COLOR, clrRed); 设该线为红色。 priceLevelsSumation += newPositionLotSize * lastPositionPrice; 累加「手数×价格」用于后续均价。 totalLotSizeSummation += newPositionLotSize; 累加总手数。 ObjectCreate(0, "Average Price", OBJ_HLINE, 0, 0, priceLevelsSumation / totalLotSizeSummation); 画绿色加权均价线。 ObjectSetInteger(0, "Average Price", OBJPROP_COLOR, clrGreen); 设均价线为绿色。

MQL5 / C++
if(initialPositionBuy && price <= lastPositionPrice - distance * _Point * class="num">10 && gridCycleRunning)
  {
    class="type">class="kw">double newPositionLotSize = NormalizeDouble(lastPositionLotSize * lotSizeMultiplier, class="num">2);
    m_trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, newPositionLotSize, price, class="num">0, class="num">0);
    lastPositionLotSize *= lotSizeMultiplier;
    lastPositionPrice = price;
    ObjectCreate(class="num">0, "Next Position Price", OBJ_HLINE, class="num">0, class="num">0, lastPositionPrice - distance * _Point * class="num">10);
    ObjectSetInteger(class="num">0, "Next Position Price", OBJPROP_COLOR, clrRed);
    priceLevelsSumation += newPositionLotSize * lastPositionPrice;
    totalLotSizeSummation += newPositionLotSize;
    ObjectCreate(class="num">0, "Average Price", OBJ_HLINE, class="num">0, class="num">0, priceLevelsSumation / totalLotSizeSummation);
    ObjectSetInteger(class="num">0, "Average Price", OBJPROP_COLOR, clrGreen);
  }

◍ 网格循环怎么干净退出

网格策略最怕的不是开仓,而是退场时留尾巴。上面这段逻辑把一次网格循环的状态清理做成独立函数,先置位 gridCycleRunning=false 阻断后续加仓,再把图表上的『Next Position Price』和『Average Price』两条辅助线删掉,避免历史对象干扰下一次 cycle 的视觉判断。 真正动手平仓的是那个倒序 for 循环:从 PositionsTotal()-1 跑到 0,用 PositionGetTicket(i) 拿 Ticket 再 PositionSelectByTicket 选中,最后 m_trade.PositionClose(ticket) 逐仓关闭。倒序遍历是关键,正序删仓会让后续索引错位,MT5 上容易漏平。 退出触发条件写得很直白:多单网格里,当实时 price 大于等于加权均价加 takeProfit*_Point*10 就收;空单网格则低于加权均价减同样距离才收。这里的 *10 意味着 takeProfit 以 10 点为单位,比如 takeProfit=50 实际就是 500 点缓冲——外汇与贵金属杠杆高,这种缓冲设错可能让浮亏扩大,上 MT5 用策略测试器跑一遍 EURUSD 的 1 小时数据验证距离是否合适。 加权均价由 priceLevelsSumation / totalLotSizeSummation 得出,即各层价格按手数加权。平掉所有仓后若均价线已删除,图表上就不会再显示历史成本锚点,下次开 cycle 重新 ObjectCreate 即可,颜色写死 clrGreen 方便肉眼区分。

MQL5 / C++
   ObjectCreate(class="num">0, "Average Price", OBJ_HLINE, class="num">0, class="num">0, priceLevelsSumation / totalLotSizeSummation);
   ObjectSetInteger(class="num">0, "Average Price", OBJPROP_COLOR, clrGreen);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Stop Function for a particular Grid Cycle                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void StopGridCycle()
  {
   gridCycleRunning = class="kw">false;
   ObjectDelete(class="num">0, "Next Position Price");
   ObjectDelete(class="num">0, "Average Price");
   for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
     {
      class="type">ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
        {
         m_trade.PositionClose(ticket);
        }
     }
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
if(gridCycleRunning)
  {
   if(initialPositionBuy && price >= (priceLevelsSumation / totalLotSizeSummation) + takeProfit * _Point * class="num">10)
      StopGridCycle();
   if(!initialPositionBuy && price <= (priceLevelsSumation / totalLotSizeSummation) - takeProfit * _Point * class="num">10)
      StopGridCycle();
  }

「EURUSD 两月回测暴露的资本占用硬伤」

把经典网格自动化跑一遍,参数这样设:首单做多、间距 15 点、单笔止盈 5 点、初始手数 0.01、加仓倍数 2.0。回测窗口取 2023-11-01 至 2023-12-22 的 EURUSD,杠杆 1:500,起始本金 1 万美元。时间帧对这套逻辑无影响,任意周期结果一致。 图表里蓝线是账户余额,绿线是净值。一个明显形态是:余额先上、净值先下,两者最终收敛到同一点。波动来自平仓顺序——循环从盈利最多的末笔持仓开始平(PositionsTotal() 倒序),几分之一秒的延迟被图形捕捉成余额瞬时回落,可忽略,净方向仍是向上。 不到两个月的样本里策略显示盈利可能,但有个绕不开的限制:占用资本极高。网格在不利走势中持续加仓,要靠厚本金扛回撤,否则追加保证金会逼你亏平。外汇与贵金属本身高风险,这种结构在 EURUSD 上若本金不足,爆仓概率显著放大。 后续会拆优化手法,目标压低所需占用资本、让不同资金规模的账户都能挂这套网格而不必赌极端行情。

下一站是参数优调

网格 EA 的自动化骨架已经跑通,但初始版只验证了可行性,没碰最优解。后续重点会放在 distance、takeProfit、initialLotSize、lotSizeMultiplier 这四个输入参数上,目标是在回测里压低回撤、抬升收益曲线。 一个值得自己跑一遍的疑点:从多单还是空单起步,在不同货币对和行情阶段表现可能分化,没有统一结论。开 MT5 用策略测试器切 EURUSD 与 XAUUSD 各跑一轮,就能看到偏差。 外汇与贵金属杠杆高,网格在单边行情里浮亏扩张很快,任何参数优化都只是降低概率风险,不是消除。调参前先拿历史数据复现,别直接上实盘。

常见问题

手数递增会指数级放大单边浮亏,EURUSD两月回测显示资本占用在第三层网格就超初始保证金2倍,建议改固定手数或线性增量。
每笔挂单成交后重算所有空单均价,用OBJ_HLINE对象按计算价刷新,删旧线建新线即可避免残留。
小布可接入品种页实时算网格层数、占用保证金与均价偏移,超限直接弹提醒,不用你自己盯MT5终端。
用标志位记录运行状态,每次循环头判断退出信号,先撤未成交挂单再return,防止残单拖累下一轮。
指价格慢跌中频繁触发加仓却难回归,资金占用曲线陡升、回撤超30%,说明纯网格无过滤在趋势市必吃亏。