在MQL5中开发马丁格尔(Martingale)区域恢复策略·综合运用
🧩

在MQL5中开发马丁格尔(Martingale)区域恢复策略·综合运用

(3/3)·从策略定义到自动化落地,老练交易者的高风险恢复系统如何跑通MT5

实战向进阶 第 3/3 篇

许多交易者在亏损区间本能平仓,却忽略了反向加仓构建恢复区可能把浮亏转平。但马丁格尔式加量若缺明确风控,保证金螺旋会迅速吞噬账户。本篇作为系列终章,把前两层概念压进可执行的MQL5代码与回测结论。

RSI 区域模型的初始化骨架

这段代码搭了一个基于 RSI 的 Zone 交易 EA 底子,先把常量和全局变量定清楚,再在 OnInit 里挂 RSI 句柄。RSI 周期写死为 14、价格源用收盘价,超买线 70、超卖线 30,属于 MT5 里最常规的默认值组合。 宏 ZONE_T_L 定义成字符串 "ZTL",用来给目标低位 Zone 线做标识;随后 #include <Trade/Trade.mqh> 并实例化 CTrade obj_trade,后续下单都走这个对象。zoneRange 与 zoneTarget 在 OnTick 中分别设为 200 点和 400 点(乘 _Point),也就是在常规品种上约 20 点和 40 点的区间带宽。 OnInit 只做一件事:rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE),返回 INIT_SUCCEEDED。OnDeinit 里用 IndicatorRelease 释放句柄、ArrayFree 清空 rsiData,避免指标句柄泄漏。开 MT5 新建 EA 把这段贴进编辑器,编译后能在初始化日志看到无报错即说明骨架可用。外汇与贵金属杠杆高,这类未完结逻辑仅作结构验证,实盘前须自测。

MQL5 / C++
class="macro">#define ZONE_T_L "ZTL"           class=class="str">"cmt">// Define a constant for the target low zone line name
class=class="str">"cmt">//--- Include trade instance class
class="macro">#include <Trade/Trade.mqh>      class=class="str">"cmt">// Include the trade class for trading functions
CTrade obj_trade;               class=class="str">"cmt">// Create an instance of the CTrade class for trading operations
class=class="str">"cmt">//--- Declare variables to hold indicator data
class="type">int rsi_handle;                 class=class="str">"cmt">// Handle for the RSI indicator
class="type">class="kw">double rsiData[];               class=class="str">"cmt">// Array to store RSI data
class="type">int totalBars = class="num">0;              class=class="str">"cmt">// Variable to keep track of the total number of bars
class="type">class="kw">double overBoughtLevel = class="num">70.0;  class=class="str">"cmt">// Overbought level for RSI
class="type">class="kw">double overSoldLevel = class="num">30.0;    class=class="str">"cmt">// Oversold level for RSI
class="type">class="kw">double zoneHigh = class="num">0;            class=class="str">"cmt">// Variable to store the high zone price
class="type">class="kw">double zoneLow = class="num">0;             class=class="str">"cmt">// Variable to store the low zone price
class="type">class="kw">double zoneTargetHigh = class="num">0;      class=class="str">"cmt">// Variable to store the target high zone price
class="type">class="kw">double zoneTargetLow = class="num">0;       class=class="str">"cmt">// Variable to store the target low zone price
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   class=class="str">"cmt">//--- Initialize the RSI indicator
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, class="num">14, PRICE_CLOSE);
   class=class="str">"cmt">//--- Return initialization result
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   class=class="str">"cmt">//--- Remove RSI indicator from memory
   IndicatorRelease(rsi_handle);
   ArrayFree(rsiData); class=class="str">"cmt">// Free the RSI data array
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
   class=class="str">"cmt">//--- Retrieve the current Ask and Bid prices
   class="type">class="kw">double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   class="type">class="kw">double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   class="type">class="kw">double zoneRange = class="num">200 * _Point;        class=class="str">"cmt">// Define the range for the zones
   class="type">class="kw">double zoneTarget = class="num">400 * _Point;       class=class="str">"cmt">// Define the target range for the zones

   class=class="str">"cmt">//--- Variables to track trading status

◍ 价格越界即清仓与加倍 recovery 的逻辑

这段控制逻辑干两件事:当 bid 跑出目标区就全平并重置,当 bid 落在 zone 内则按方向补 recovery 单。外汇与贵金属杠杆高,这类自动加倍仓位在极端跳空时可能瞬间放大回撤,务必先在策略测试器跑历史数据。 越界判定依赖 zoneTargetHigh 和 zoneTargetLow 同时大于 0。一旦 bid 大于 High 或小于 Low,先平当前符号仓位、删画图,再用 PositionTotal 倒序循环按 ticket 逐个平掉剩余持仓,最后把 zone 与方向变量全部归零。 在 zone 有效的前提下,recovery_lot 会被 NormalizeDouble 限制到 2 位小数。若 bid 上破 zoneHigh 且买未完成(或上次为空),就 Buy(lots_Rec),并把 lastDirection 置 1、recovery_lot 乘 2、isBuyDone 置真;若下破 zoneLow 且卖未完成(或上次为多)则反向操作。每次触发 recovery 手数翻倍,连续 4 次后手数可能为初始的 16 倍,这种几何增长倾向在震荡市快速吞噬保证金。 别把 zone 边界当绝对安全垫 MT5 实时tick与回测撮合不同,zoneTarget 若由前一根K线算得,遇数据缺口可能跳过平仓条件,建议手动打印 bid 与 zone 值验证触发顺序。

MQL5 / C++
class="kw">static class="type">int lastDirection = class="num">0;                      class=class="str">"cmt">// -class="num">1 = sell, class="num">1 = buy
  class="kw">static class="type">class="kw">double recovery_lot = class="num">0.0;                    class=class="str">"cmt">// Lot size for recovery trades
  class="kw">static class="type">bool isBuyDone = false, isSellDone = false;   class=class="str">"cmt">// Flags to track trade completion
  
  class=class="str">"cmt">//--- Close all positions if the bid price is outside target zones
  if (zoneTargetHigh > class="num">0 && zoneTargetLow > class="num">0) {
    if (bid > zoneTargetHigh || bid < zoneTargetLow) {
      obj_trade.PositionClose(_Symbol); class=class="str">"cmt">// Close the current position
      deleteZoneLevels();               class=class="str">"cmt">// Delete all drawn zone levels
      for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) {
        class="type">ulong ticket = PositionGetTicket(i);
        if (ticket > class="num">0) {
          if (PositionSelectByTicket(ticket)) {
            obj_trade.PositionClose(ticket); class=class="str">"cmt">// Close positions by ticket
          }
        }
      }
      class=class="str">"cmt">//--- Reset all zone and direction variables
      zoneHigh = class="num">0;
      zoneLow = class="num">0;
      zoneTargetHigh = class="num">0;
      zoneTargetLow = class="num">0;
      lastDirection = class="num">0;
      recovery_lot = class="num">0;
    }
  }
  class=class="str">"cmt">//--- Check if price is within defined zones and take action
  if (zoneHigh > class="num">0 && zoneLow > class="num">0) {  
    class="type">class="kw">double lots_Rec = NormalizeDouble(recovery_lot, class="num">2); class=class="str">"cmt">// Normalize the recovery lot size to class="num">2 decimal places
    if (bid > zoneHigh) {
      if (isBuyDone == false || lastDirection < class="num">0) {
        obj_trade.Buy(lots_Rec); class=class="str">"cmt">// Open a buy trade
        
        lastDirection = class="num">1;       class=class="str">"cmt">// Set the last direction to buy
        recovery_lot = recovery_lot * class="num">2; class=class="str">"cmt">// Double the recovery lot size
        isBuyDone = true;        class=class="str">"cmt">// Mark buy trade as done
        isSellDone = false;      class=class="str">"cmt">// Reset sell trade flag
      }
    } else if (bid < zoneLow) {
      if (isSellDone == false || lastDirection > class="num">0) {
        obj_trade.Sell(lots_Rec); class=class="str">"cmt">// Open a sell trade

「RSI 反转区的挂单区间画法」

这段逻辑跑在 EA 的 OnTick 里,先处理上一笔 recovery 的方向标记与手数翻倍:若上一次是卖,则 lastDirection 置 -1,recovery_lot 乘 2,并把 isSellDone 标 true、isBuyDone 复位。 随后用 iBars 抓当前周期 K 线数,totalBars 没变就直接 return,避免同根 bar 重复触发;PositionsTotal() 大于 0 也退出,保证同时只跑一单。 真正进场看 RSI 穿越:CopyBuffer 取缓冲 0 的最近两根(索引 1 与 0),当 rsiData[1] < overSoldLevel 且 rsiData[0] > overSoldLevel,判定超卖反弹,obj_trade.Buy(0.01) 以 0.01 手试多。 单子成交后按开价画四条 zone 线:zoneHigh 取开单价,zoneLow = zoneHigh - zoneRange,目标上下沿各加减 zoneTarget,分别用绿/红/蓝画线。lastDirection 置 1,recovery_lot 初始化为 0.02,isBuyDone 标 true。外汇与贵金属波动剧烈,这种 zone 反转策略回测胜率随参数漂移明显,实盘前务必在 MT5 用历史数据验证 overSoldLevel 与 zoneRange 的敏感性。

MQL5 / C++
lastDirection = -class="num">1;      class=class="str">"cmt">// Set the last direction to sell
recovery_lot = recovery_lot * class="num">2; class=class="str">"cmt">// Double the recovery lot size
isBuyDone = false;      class=class="str">"cmt">// Reset buy trade flag
isSellDone = true;      class=class="str">"cmt">// Mark sell trade as done
 }
  }
 }

 class=class="str">"cmt">//--- Update bars and check for new bars
 class="type">int bars = iBars(_Symbol, PERIOD_CURRENT);
 if (totalBars == bars) class="kw">return; class=class="str">"cmt">// Exit if no new bars
 totalBars = bars; class=class="str">"cmt">// Update the total number of bars

 class=class="str">"cmt">//--- Exit if there are open positions
 if (PositionsTotal() > class="num">0) class="kw">return;

 class=class="str">"cmt">//--- Copy RSI data and check for oversold/overbought conditions
 if (!CopyBuffer(rsi_handle, class="num">0, class="num">1, class="num">2, rsiData)) class="kw">return;

 class=class="str">"cmt">//--- Check for oversold condition and open a buy position
 if (rsiData[class="num">1] < overSoldLevel && rsiData[class="num">0] > overSoldLevel) {
   obj_trade.Buy(class="num">0.01); class=class="str">"cmt">// Open a buy trade with class="num">0.01 lots
   class="type">ulong pos_ticket = obj_trade.ResultOrder();
   if (pos_ticket > class="num">0) {
     if (PositionSelectByTicket(pos_ticket)) {
       class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
       zoneHigh = NormalizeDouble(openPrice, _Digits); class=class="str">"cmt">// Set the high zone price
       zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); class=class="str">"cmt">// Set the low zone price
       zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); class=class="str">"cmt">// Set the target high zone price
       zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); class=class="str">"cmt">// Set the target low zone price
       drawZoneLevel(ZONE_H, zoneHigh, clrGreen, class="num">2); class=class="str">"cmt">// Draw the high zone line
       drawZoneLevel(ZONE_L, zoneLow, clrRed, class="num">2); class=class="str">"cmt">// Draw the low zone line
       drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target high zone line
       drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target low zone line

       lastDirection = class="num">1;      class=class="str">"cmt">// Set the last direction to buy
       recovery_lot = class="num">0.01 * class="num">2; class=class="str">"cmt">// Set the initial recovery lot size
       isBuyDone = true;       class=class="str">"cmt">// Mark buy trade as done
       isSellDone = false;     class=class="str">"cmt">// Reset sell trade flag

RSI 超买反转后的卖单与区域画线

当 RSI 前一根柱线高于超买阈值、当前柱线跌破该阈值时,脚本判定为超买回落信号,直接以 0.01 手抛出空单。外汇与贵金属杠杆品种波动剧烈,这类信号仅代表概率性反转倾向,实盘须自行评估穿仓风险。 成交后代码通过 PositionSelectByTicket 抓取开盘价,并据此用 NormalizeDouble 按品种小数位推算 zoneLow、zoneHigh(加 zoneRange)、zoneTargetHigh(再加 zoneTarget)、zoneTargetLow(减 zoneTarget)四条边界。随后 drawZoneLevel 以不同颜色宽度把边界画成水平线:红绿为基准区,蓝色 3 像素为目标区。 lastDirection 置为 -1,recovery_lot 初始化为 0.02(0.01 翻倍),isSellDone 置真、isBuyDone 复位。这意味着后续若价格回到区域并触发 recovery 逻辑,加仓手数会从双倍起跳。 drawZoneLevel 本身只是薄封装:ObjectCreate 建 OBJ_HLINE,再设颜色与线宽。想改视觉只需动 clr 与 width 参数,不必碰主逻辑。

MQL5 / C++
   class=class="str">"cmt">//--- Check for overbought condition and open a sell position
   else if (rsiData[class="num">1] > overBoughtLevel && rsiData[class="num">0] < overBoughtLevel) {
      obj_trade.Sell(class="num">0.01); class=class="str">"cmt">// Open a sell trade with class="num">0.01 lots
      class="type">ulong pos_ticket = obj_trade.ResultOrder();
      if (pos_ticket > class="num">0) {
         if (PositionSelectByTicket(pos_ticket)) {
            class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            zoneLow = NormalizeDouble(openPrice, _Digits); class=class="str">"cmt">// Set the low zone price
            zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); class=class="str">"cmt">// Set the high zone price
            zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); class=class="str">"cmt">// Set the target high zone price
            zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); class=class="str">"cmt">// Set the target low zone price
            drawZoneLevel(ZONE_H, zoneHigh, clrGreen, class="num">2); class=class="str">"cmt">// Draw the high zone line
            drawZoneLevel(ZONE_L, zoneLow, clrRed, class="num">2); class=class="str">"cmt">// Draw the low zone line
            drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target high zone line
            drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target low zone line
            
            lastDirection = -class="num">1;      class=class="str">"cmt">// Set the last direction to sell
            recovery_lot = class="num">0.01 * class="num">2; class=class="str">"cmt">// Set the initial recovery lot size
            isBuyDone = false;       class=class="str">"cmt">// Reset buy trade flag
            isSellDone = true;       class=class="str">"cmt">// Mark sell trade as done
         }
      }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|      FUNCTION TO DRAW HORIZONTAL ZONE LINES                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void drawZoneLevel(class="type">class="kw">string levelName, class="type">class="kw">double price, class="type">color clr, class="type">int width) {
   ObjectCreate(class="num">0, levelName, OBJ_HLINE, class="num">0, TimeCurrent(), price); class=class="str">"cmt">// Create a horizontal line object
   ObjectSetInteger(class="num">0, levelName, OBJPROP_COLOR, clr); class=class="str">"cmt">// Set the line class="type">color
   ObjectSetInteger(class="num">0, levelName, OBJPROP_WIDTH, width); class=class="str">"cmt">// Set the line width
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|       FUNCTION TO DELETE DRAWN ZONE LINES                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void deleteZoneLevels() {

◍ 清场旧标记再画新区

在 MT5 的 EA 循环里,每次重算前必须先把上一根 K 线留下的区间线删掉,否则图表会被历史对象堆满,也干扰后续 Zone 的重新定位。上面四行就是干这件事的:ZONE_H 与 ZONE_L 是现价波动区的上下边界,ZONE_T_H 与 ZONE_T_L 则是目标延伸区的上下边界,全部挂在图表子窗口 0 上。 具体拆解:ObjectDelete(0, ZONE_H) 删除主波动区上沿;ObjectDelete(0, ZONE_L) 删除主波动区下沿;ObjectDelete(0, ZONE_T_H) 删除目标区上沿;ObjectDelete(0, ZONE_T_L) 删除目标区下沿。四个调用都不带返回值判断,属于测试代码里的粗暴清场,实盘若担心对象不存在报错可加 ObjectFind 前置。 把这段塞进 OnTick 或 OnCalculate 的开头,跑策略测试器时你会看到:每根新 K 线生成前旧线消失,图表始终只保留当前 Zone。外汇与贵金属杠杆高,这类对象管理只是防视觉污染的底活,不涉及任何方向判断。

MQL5 / C++
   ObjectDelete(class="num">0, ZONE_H); class=class="str">"cmt">// Delete the high zone line
   ObjectDelete(class="num">0, ZONE_L); class=class="str">"cmt">// Delete the low zone line
   ObjectDelete(class="num">0, ZONE_T_H); class=class="str">"cmt">// Delete the target high zone line
   ObjectDelete(class="num">0, ZONE_T_L); class=class="str">"cmt">// Delete the target low zone line
}

「画得少,看得清」

这套区间恢复马丁格尔的 MQL5 骨架已经铺完:从基础定义到 EA 主体,再到附件里 9.7 KB 的 Zone_Recovery.mq5 实例,你能直接拖进 MT5 编译跑通。它本质是一套仓位管理框架,不是独立进场策略,实盘前最好先接一个区间退出逻辑,否则裸跑恢复单在贵金属跳空时容易把浮亏拖大。 外汇和贵金属杠杆高、滑点猛,附件代码只帮你跨过「会自动补单」这道门槛,不代表任何未来收益。把示例里的步长、网格间距按你自己的品种波动率改两行,比照抄全文更有用。 剩下要做的就是把这套骨架接进你现有的信号过滤,让恢复单只在概率倾斜时才触发。代码能跑只是起点,参数空间得你自己用历史数据去碾。

把回测巡检交给小布盯盘
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到恢复区的均价与保证金占用,你只管判断区间假设是否成立。

常见问题

区域恢复以预设亏损区间为触发,反向单量按倍数扩张并对冲首单,目标是在回归时整体持平;普通网格多按固定间距无明确恢复边界,风险轮廓更松散。
可在EA全局变量设MaxRecoveryLevels,每开一层反向单计数加一,达到上限即停止加仓并告警,避免保证金失控。
小布盯盘品种页支持自定义指标与EA输出字段,把恢复区边界和层数推送到面板,就能在盯盘时看到系统所处阶段,不替代你决策。
恢复单集中在反转点附近,滑点可能使均价下降不及预期,实盘盈利概率倾向低于回测,外汇贵金属属高风险需放大容差再评估。
因持仓量指数增长带来保证金陡升,若市场长期单边,恢复区不回归则爆仓概率高,需严密计划与仓位纪律。