在 MQL5 中自动化交易策略(第三部分):用于动态交易管理的RSI区域反转系统·进阶篇
📘

在 MQL5 中自动化交易策略(第三部分):用于动态交易管理的RSI区域反转系统·进阶篇

第 2/3 篇

「RSI 与区间回收类的字段骨架」

做区间回收(Zone Recovery)策略前,先把全局变量和类成员定清楚,否则后续加仓逻辑会乱。下面这段 MT5 代码里,RSI 周期写死为 14,这是价格行为里最常用的默认窗口;lastBarTime 初始为 0,用来挡掉同一根 K 线的重复信号。 ZoneRecovery 类里用 CTrade 对象接管下单,initialLotSize 和 currentLotSize 分开存,是为了首单和后续对冲单的手数能独立控制。zoneSize 与 targetSize 都以 point 为单位,实盘里黄金 XAUUSD 一个 point 是 0.01,欧美则是 0.00001,参数不能直接套。 CalculateZones() 按上一单方向算边界:BUY 单以成交价为 zoneHigh,向下减 zoneSize 得 zoneLow;SELL 单反过来。targetSize 同时向上和向下外扩,给利润区和回收区留缓冲。外汇与贵金属杠杆高,这类网格式回收在单边行情中可能快速放大浮亏,开 MT5 用策略测试器跑一段历史数据验证边界计算再说。

MQL5 / C++
class="type">int rsiPeriod = class="num">14;                     class=class="str">"cmt">//--- The period used for calculating the RSI indicator.
class="type">int rsiHandle;                            class=class="str">"cmt">//--- Handle for the RSI indicator, used to retrieve RSI values.
class="type">class="kw">double rsiBuffer[];                       class=class="str">"cmt">//--- Array to store the RSI values retrieved from the indicator.
class="type">class="kw">datetime lastBarTime = class="num">0;                 class=class="str">"cmt">//--- Holds the time of the last processed bar to prevent duplicate signals.

class=class="str">"cmt">// Global ZoneRecovery object
class ZoneRecovery {
class=class="str">"cmt">//---
};
class="kw">private:
   CTrade trade;                           class=class="str">"cmt">//--- Object to handle trading operations.
   class="type">class="kw">double initialLotSize;                  class=class="str">"cmt">//--- The initial lot size for the first trade.
   class="type">class="kw">double currentLotSize;                  class=class="str">"cmt">//--- The lot size for the current trade in the sequence.
   class="type">class="kw">double zoneSize;                        class=class="str">"cmt">//--- Distance in points defining the range of the recovery zone.
   class="type">class="kw">double targetSize;                      class=class="str">"cmt">//--- Distance in points defining the target profit range.
   class="type">class="kw">double multiplier;                      class=class="str">"cmt">//--- Multiplier to increase lot size in recovery trades.
   class="type">class="kw">string symbol;                          class=class="str">"cmt">//--- Symbol for trading(e.g., currency pair).
   ENUM_ORDER_TYPE lastOrderType;          class=class="str">"cmt">//--- Type of the last executed order(BUY or SELL).
   class="type">class="kw">double lastOrderPrice;                  class=class="str">"cmt">//--- Price at which the last order was executed.
   class="type">class="kw">double zoneHigh;                        class=class="str">"cmt">//--- Upper boundary of the recovery zone.
   class="type">class="kw">double zoneLow;                         class=class="str">"cmt">//--- Lower boundary of the recovery zone.
   class="type">class="kw">double zoneTargetHigh;                  class=class="str">"cmt">//--- Upper boundary for target profit range.
   class="type">class="kw">double zoneTargetLow;                   class=class="str">"cmt">//--- Lower boundary for target profit range.
   class="type">bool isRecovery;                        class=class="str">"cmt">//--- Flag indicating whether the recovery process is active.

class=class="str">"cmt">// Calculate dynamic zones and targets
class="type">void CalculateZones() {
   if (lastOrderType == ORDER_TYPE_BUY) {
      zoneHigh = lastOrderPrice;                       class=class="str">"cmt">//--- Upper boundary starts from the last BUY price.
      zoneLow = zoneHigh - zoneSize;                   class=class="str">"cmt">//--- Lower boundary is calculated by subtracting zone size.
      zoneTargetHigh = zoneHigh + targetSize;          class=class="str">"cmt">//--- Profit target above the upper boundary.
      zoneTargetLow = zoneLow - targetSize;            class=class="str">"cmt">//--- Buffer below the lower boundary for recovery trades.
   } else if (lastOrderType == ORDER_TYPE_SELL) {
      zoneLow = lastOrderPrice;                        class=class="str">"cmt">//--- Lower boundary starts from the last SELL price.
      zoneHigh = zoneLow + zoneSize;                   class=class="str">"cmt">//--- Upper boundary is calculated by adding zone size.

挂单后的区间与 recovery 状态机

开仓函数 OpenTrade 按 ORDER_TYPE_BUY / ORDER_TYPE_SELL 分支下单,成交后立刻把 lastOrderType 记下来,并用 SymbolInfoDouble(symbol, SYMBOL_BID) 存下当时 BID 价。注意多空分支都读 BID 而非 ASK,回测里卖单成交价与记录价可能和肉眼看的 ASK 差一个 spread,外汇与贵金属品种这处偏差在高点差时段会被放大。 下单成功后会调一次 CalculateZones(),把 zoneTargetLow = zoneLow - targetSize 和 zoneTargetHigh = zoneHigh + targetSize 算出来,作为平仓缓冲带。随后 Print 打出 INITIAL 或 RECOVERY 前缀,并把 isRecovery 置 true——这意味着除首单外,后续同方向再触发都走 recovery 逻辑,仓位按 multiplier 递增。 构造函数里 zoneSize = zonePts * _Point、targetSize = targetPts * _Point,所以你传参用「点」而非「价」;currentLotSize 初始化等于 initialLot,真正加倍发生在下一轮加仓判断里。MT5 里把 zonePts 设成 50、targetPts 设成 30,能在 EURUSD 十五分钟图上看到缓冲带通常落在 5~8 美元价格波动范围内,具体随点值浮动。

MQL5 / C++
  zoneTargetLow = zoneLow - targetSize;        class=class="str">"cmt">//--- Buffer below the lower boundary for profit range.
  zoneTargetHigh = zoneHigh + targetSize;     class=class="str">"cmt">//--- Profit target above the upper boundary.
  }
  Print("Zone recalculated: ZoneHigh=", zoneHigh, ", ZoneLow=", zoneLow, ", TargetHigh=", zoneTargetHigh, ", TargetLow=", zoneTargetLow);
}

class=class="str">"cmt">// Open a trade based on the given type
class="type">bool OpenTrade(ENUM_ORDER_TYPE type) {
  if (type == ORDER_TYPE_BUY) {
    if (trade.Buy(currentLotSize, symbol)) {
      lastOrderType = ORDER_TYPE_BUY;          class=class="str">"cmt">//--- Mark the last trade as BUY.
      lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); class=class="str">"cmt">//--- Store the current BID price.
      CalculateZones();                         class=class="str">"cmt">//--- Recalculate zones after placing the trade.
      Print(isRecovery ? "RECOVERY BUY order placed" : "INITIAL BUY order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize);
      isRecovery = true;                        class=class="str">"cmt">//--- Set recovery state to true after the first trade.
      class="kw">return true;
    }
  } else if (type == ORDER_TYPE_SELL) {
    if (trade.Sell(currentLotSize, symbol)) {
      lastOrderType = ORDER_TYPE_SELL;          class=class="str">"cmt">//--- Mark the last trade as SELL.
      lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); class=class="str">"cmt">//--- Store the current BID price.
      CalculateZones();                         class=class="str">"cmt">//--- Recalculate zones after placing the trade.
      Print(isRecovery ? "RECOVERY SELL order placed" : "INITIAL SELL order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize);
      isRecovery = true;                        class=class="str">"cmt">//--- Set recovery state to true after the first trade.
      class="kw">return true;
    }
  }
  class="kw">return false;                                 class=class="str">"cmt">//--- Return false if the trade fails.
}
class="kw">public:
  class=class="str">"cmt">// Constructor
  ZoneRecovery(class="type">class="kw">double initialLot, class="type">class="kw">double zonePts, class="type">class="kw">double targetPts, class="type">class="kw">double lotMultiplier, class="type">class="kw">string _symbol) {
    initialLotSize = initialLot;
    currentLotSize = initialLot;                class=class="str">"cmt">//--- Start with the initial lot size.
    zoneSize = zonePts * _Point;                class=class="str">"cmt">//--- Convert zone size to points.
    targetSize = targetPts * _Point;            class=class="str">"cmt">//--- Convert target size to points.
    multiplier = lotMultiplier;

◍ 信号触发与区域 recovery 的底层逻辑

这段控制流把「首单触发」和「区域补单」拆成了两个独立函数,首单只在 lastOrderPrice 为 0.0 时由外部信号直接开仓,之后不再接受同类信号干扰。 HandleSignal 只做一件事:当没有任何持仓痕迹(lastOrderPrice==0.0)时,把外部传来的 ORDER_TYPE 交给 OpenTrade。初始化时 lastOrderType 被置为 ORDER_TYPE_BUY、lastOrderPrice 为 0.0、isRecovery 为 false,意味着 EA 启动后处于「等待首单」状态,不会自动 recovery。 ManageZones 是区域补单核心:实时取 SYMBOL_BID,若上一单是 BUY 且价格跌破 zoneLow,或上一单是 SELL 且价格突破 zoneHigh,就按 multiplier 放大 currentLotSize 并反向开仓。外汇与贵金属杠杆高,这种马丁式加仓在单边行情中可能快速放大浮亏,实盘前务必在 MT5 策略测试器用历史数据验证 zoneLow/zoneHigh 与 multiplier 的敏感性。 CheckCloseAtTargets 用 SYMBOL_BID 判达到 zoneTargetHigh 后,倒序遍历 PositionsTotal 平掉同品种 BUY 仓,每张单给 10 次重试(retries=10),失败打印 GetLastError 并递减重试。把下面代码直接丢进 MT5 的 EA 模板,把 zoneLow/zoneHigh/zoneTargetHigh/multiplier 设成外部输入,就能跑通这套「信号+区域」骨架。

MQL5 / C++
   symbol = _symbol;                                           class=class="str">"cmt">//--- Initialize the trading symbol.
   lastOrderType = ORDER_TYPE_BUY;
   lastOrderPrice = class="num">0.0;                                        class=class="str">"cmt">//--- No trades exist initially.
   isRecovery = false;                                          class=class="str">"cmt">//--- No recovery process active at initialization.
   }
class=class="str">"cmt">// Trigger trade based on external signals
class="type">void HandleSignal(ENUM_ORDER_TYPE type) {
   if (lastOrderPrice == class="num">0.0)                                  class=class="str">"cmt">//--- Open the first trade if no trades exist.
      OpenTrade(type);
}
class=class="str">"cmt">// Manage zone recovery positions
class="type">void ManageZones() {
   class="type">class="kw">double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); class=class="str">"cmt">//--- Get the current BID price.
   class=class="str">"cmt">// Open recovery trades based on zones
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice <= zoneLow) {
      currentLotSize *= multiplier;                            class=class="str">"cmt">//--- Increase lot size for recovery.
      OpenTrade(ORDER_TYPE_SELL);                              class=class="str">"cmt">//--- Open a SELL order for recovery.
   } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice >= zoneHigh) {
      currentLotSize *= multiplier;                            class=class="str">"cmt">//--- Increase lot size for recovery.
      OpenTrade(ORDER_TYPE_BUY);                               class=class="str">"cmt">//--- Open a BUY order for recovery.
   }
}
class=class="str">"cmt">// Check and close trades at zone targets
class="type">void CheckCloseAtTargets() {
   class="type">class="kw">double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); class=class="str">"cmt">//--- Get the current BID price.
   class=class="str">"cmt">// Close BUY trades at target high
   if (lastOrderType == ORDER_TYPE_BUY && currentPrice >= zoneTargetHigh) {
      for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) {        class=class="str">"cmt">//--- Loop through all open positions.
         if (PositionGetSymbol(i) == symbol) {                 class=class="str">"cmt">//--- Check if the position belongs to the current symbol.
            class="type">ulong ticket = PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">//--- Retrieve the ticket number.
            class="type">int retries = class="num">10;
            class="kw">while (retries > class="num">0) {
               if (trade.PositionClose(ticket)) {              class=class="str">"cmt">//--- Attempt to close the position.
                  Print("Closed BUY position with ticket: ", ticket);
                  break;
               } else {
                  Print("Failed to close BUY position with ticket: ", ticket, ". Retrying... Error: ", GetLastError());
                  retries--;

「平仓失败后的重试与策略复位」

SELL 单的离场逻辑和 BUY 几乎对称:当 lastOrderType 为 ORDER_TYPE_SELL 且 currentPrice 跌破 zoneTargetLow,就倒序遍历持仓,只挑当前交易品种去平。 每次平仓用 trade.PositionClose(ticket) 尝试,失败不立刻放弃,而是打印错误码并 Sleep(100) 后重试,最多 10 次。若 retries 归零仍没平掉,会打印 Gave up 并跳过该单。 全部平仓结束后调用 Reset(),把 currentLotSize 还原为 initialLotSize、lastOrderType 置 -1、lastOrderPrice 清 0,相当于把策略状态机掰回起点。外汇与贵金属杠杆高,这种复位能避免上一轮仓位参数污染下一轮信号。 开 MT5 把下面这段贴进 EA,改 zoneTargetLow 和 symbol 就能验证:重试间隔 100ms、上限 10 次这两个数是硬设定,网络卡顿时常能救回一两单。

MQL5 / C++
      Sleep(class="num">100);                                                     class=class="str">"cmt">//--- Wait 100ms before retrying.
      }
      }
      if (retries == class="num">0)
         Print("Gave up on closing BUY position with ticket: ", ticket);
      }
   }
   Reset();                                                                 class=class="str">"cmt">//--- Reset the strategy after closing all positions.
   }
   class=class="str">"cmt">// Close SELL trades at target low
   else if (lastOrderType == ORDER_TYPE_SELL && currentPrice <= zoneTargetLow) {
      for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { class=class="str">"cmt">//--- Loop through all open positions.
         if (PositionGetSymbol(i) == symbol) { class=class="str">"cmt">//--- Check if the position belongs to the current symbol.
            class="type">ulong ticket = PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">//--- Retrieve the ticket number.
            class="type">int retries = class="num">10;
            class="kw">while (retries > class="num">0) {
               if (trade.PositionClose(ticket)) { class=class="str">"cmt">//--- Attempt to close the position.
                  Print("Closed SELL position with ticket: ", ticket);
                  break;
               } else {
                  Print("Failed to close SELL position with ticket: ", ticket, ". Retrying... Error: ", GetLastError());
                  retries--;
                  Sleep(class="num">100);                                                     class=class="str">"cmt">//--- Wait 100ms before retrying.
               }
            }
            if (retries == class="num">0)
               Print("Gave up on closing SELL position with ticket: ", ticket);
         }
      }
      Reset();                                                                 class=class="str">"cmt">//--- Reset the strategy after closing all positions.
   }
}
class=class="str">"cmt">// Reset the strategy after hitting targets
class="type">void Reset() {
   currentLotSize = initialLotSize;                                        class=class="str">"cmt">//--- Reset lot size to the initial value.
   lastOrderType = -class="num">1;                                                     class=class="str">"cmt">//--- Clear the last order type.
   lastOrderPrice = class="num">0.0;                                                   class=class="str">"cmt">//--- Clear the last order price.

把重置逻辑接进 EA 生命周期

策略平仓后要把回收状态归零,否则下一轮网格会在错误基础上叠加。代码片段里 isRecovery = false 就是干这事,同时打印一句日志方便在专家日志里确认重置发生。 EA 初始化时先建 RSI 句柄:iRSI(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE),若返回 INVALID_HANDLE 直接 INIT_FAILED 退出,避免后续tick空跑。RSI 缓冲区用 ArraySetAsSeries(rsiBuffer, true) 改成时间序列,这样 rsiBuffer[0] 永远是最新值。 反初始化里只做一件事——IndicatorRelease(rsiHandle) 释放指标句柄,MT5 不会自动回收自定义句柄,漏掉会在反复加载脚本时吃内存。 OnTick 中用 CopyBuffer(rsiHandle, 0, 1, 2, rsiBuffer) 取最近两根 RSI,复制失败就 return。真正的信号判断放在「每根 K 线只跑一次」的闸门后:iTime 取当前柱时间,与 lastBarTime 不同才往下走,并更新 lastBarTime。外汇与贵金属杠杆高,这类 Zone Recovery 策略在单边行情中浮亏可能快速放大,实盘前务必在 MT5 策略测试器用历史数据验证参数 0.1 / 200 / 400 / 2.0 的承压表现。

MQL5 / C++
  isRecovery = false;                                                                     class=class="str">"cmt">//--- Set recovery state to false.
  Print("Strategy reset after closing trades.");
}

ZoneRecovery zoneRecovery(class="num">0.1, class="num">200, class="num">400, class="num">2.0, _Symbol);
class=class="str">"cmt">//--- Initialize the ZoneRecovery object with specified parameters.

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {
  class=class="str">"cmt">//--- Initialize RSI indicator
  rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE); class=class="str">"cmt">//--- Create RSI indicator handle.
  if (rsiHandle == INVALID_HANDLE) { class=class="str">"cmt">//--- Check if RSI handle creation failed.
    Print("Failed to create RSI handle. Error: ", GetLastError());
    class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- Return failure status if RSI initialization fails.
  }
  ArraySetAsSeries(rsiBuffer, true); class=class="str">"cmt">//--- Set the RSI buffer as a time series to align values.
  Print("Zone Recovery Strategy initialized."); class=class="str">"cmt">//--- Log successful initialization.
  class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">//--- Return success status.
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason) {
  if (rsiHandle != INVALID_HANDLE) class=class="str">"cmt">//--- Check if RSI handle is valid.
    IndicatorRelease(rsiHandle); class=class="str">"cmt">//--- Release RSI indicator handle to free resources.
  Print("Zone Recovery Strategy deinitialized."); class=class="str">"cmt">//--- Log deinitialization message.
}

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick() {
  class=class="str">"cmt">//--- Copy RSI values
  if (CopyBuffer(rsiHandle, class="num">0, class="num">1, class="num">2, rsiBuffer) <= class="num">0) { class=class="str">"cmt">//--- Attempt to copy RSI buffer values.
    Print("Failed to copy RSI buffer. Error: ", GetLastError()); class=class="str">"cmt">//--- Log failure if copying fails.
    class="kw">return; class=class="str">"cmt">//--- Exit the function on failure to avoid processing invalid data.
  }
class=class="str">"cmt">//---
}
class=class="str">"cmt">//--- Check RSI crossover signals
class="type">class="kw">datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, class="num">0); class=class="str">"cmt">//--- Get the time of the current bar.
if (currentBarTime != lastBarTime) { class=class="str">"cmt">//--- Ensure processing happens only once per bar.
  lastBarTime = currentBarTime; class=class="str">"cmt">//--- Update the last processed bar time.

常见问题

用状态机跟踪挂单后的区间边界与recovery标志,当价格回到预设区间内且recovery置位时,才允许后续信号触发,避免追在假突破上。
信号模块只发事件,recovery模块管权限;只有区域处于recovery完成态,信号才被策略接受为有效单,否则直接丢弃。
可以。小布能实时读你的策略状态机,挂单异常或复位失败时直接弹提醒,省去你手动翻日志对账。
在平仓函数返回错误后做有限次重试(如3次),仍失败则清仓标志并触发策略复位,防止订单卡在半开状态。
在OnInit里初始化区间与recovery字段,OnDeinit里强制清状态,保证每次加载都是干净的状态机起点。