MQL5自动化交易策略(第四部分):构建多层级区域恢复系统·综合运用
📘

MQL5自动化交易策略(第四部分):构建多层级区域恢复系统·综合运用

第 3/3 篇

◍ RSI 穿越阈值触发首仓的逻辑落点

EA 在 OnDeinit 里先判断 rsiHandle 是否不等于 INVALID_HANDLE,若成立则调用 IndicatorRelease 释放指标句柄,并打印 deinitialized 日志,避免 MT5 退出时句柄泄漏。 OnTick 开头用 CopyBuffer(rsiHandle,0,1,2,rsiBuffer) 取最近两根 RSI 值,返回值 ≤0 就打印 GetLastError 并 return,这说明实盘里若指标缓冲拷贝失败,本根 K 线直接跳过不交易。 新 K 线判定靠 iTime(_Symbol,PERIOD_CURRENT,0) 与 lastBarTime 比较;仅当时间不同才更新并跑信号。若 rsiBuffer[1]>30 且 rsiBuffer[0]<=30,即 RSI 从超卖线上方下穿 30,倾向视为多头反转可能,打印 BUY SIGNAL 并新建 PositionRecovery 实例,以市价 BID 开初始多单,再把实例塞进 recoveryArray(ArrayResize 后下标为 Size-1)。 反向条件为 rsiBuffer[1]<70 且 rsiBuffer[0]>=70,对应 RSI 上穿 70 超买,逻辑对称。外汇与贵金属波动剧烈,这类阈值穿越信号在历史回测中可能出现 30%~40% 的假突破概率,开 MT5 把 rsiHandle 周期设为 14、阈值 30/70 跑一遍 EURUSD 的 M15 即可验证。

MQL5 / C++
class="type">void OnDeinit(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 the RSI handle.
   Print("Multi-Zone Recovery Strategy deinitialized."); class=class="str">"cmt">//--- Log deinitialization.
}

class="type">void OnTick() {
   if (CopyBuffer(rsiHandle, class="num">0, class="num">1, class="num">2, rsiBuffer) <= class="num">0) { class=class="str">"cmt">//--- Copy the RSI buffer values.
      Print("Failed to copy RSI buffer. Error: ", GetLastError()); class=class="str">"cmt">//--- Print error on failure.
      class="kw">return;                                                      class=class="str">"cmt">//--- Exit on failure.
   }
   class=class="str">"cmt">//---
}
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">//--- Check if a new bar has formed.
   lastBarTime = currentBarTime;                              class=class="str">"cmt">//--- Update the last processed bar time.
   if (rsiBuffer[class="num">1] > class="num">30 && rsiBuffer[class="num">0] <= class="num">30) {             class=class="str">"cmt">//--- Check for oversold RSI crossing up.
      Print("BUY SIGNAL");
      PositionRecovery newRecovery;                            class=class="str">"cmt">//--- Create a new recovery instance.
      newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_BUY, SymbolInfoDouble(_Symbol, SYMBOL_BID)); class=class="str">"cmt">//--- Initialize the recovery.
      newRecovery.OpenTrade(ORDER_TYPE_BUY, "Initial Position"); class=class="str">"cmt">//--- Open an initial BUY position.
      ArrayResize(recoveryArray, ArraySize(recoveryArray) + class="num">1); class=class="str">"cmt">//--- Resize the recovery array.
      recoveryArray[ArraySize(recoveryArray) - class="num">1] = newRecovery; class=class="str">"cmt">//--- Add the new recovery to the array.
   } else if (rsiBuffer[class="num">1] < class="num">70 && rsiBuffer[class="num">0] >= class="num">70) {      class=class="str">"cmt">//--- Check for overbought RSI crossing down.

SELL 信号触发后的恢复实例挂载

当空头信号成立时,先向日志输出 "SELL SIGNAL" 做标记,随后在栈上构造一个 PositionRecovery 对象并立即初始化。初始化入参包含手数、 zone 点数尺寸、目标点数、手数乘数、当前品种与市价(SYMBOL_BID),这一步决定了后续加仓与对冲的几何结构。 初始化完成后调用 OpenTrade 开出首笔 SELL 仓位,标签记为 "Initial Position"。紧接着对 recoveryArray 做 ArrayResize 扩容 1,并把新实例塞进数组末位——这意味着同一图表可并行维护多套独立恢复策略,互不干扰。 主循环里对数组每个元素依次执行三件事:ManageZones 刷新 zone 边界、CheckCloseAtTargets 判触目标平仓、ApplyTrailingStop 对初始仓拉尾随止损。外汇与贵金属杠杆高,恢复策略若 zone 点数设太小,在跳空行情中可能连续触发加仓,回撤放大很快,开 MT5 跑之前先用手数乘数 1.5 以内做模拟。

MQL5 / C++
Print("SELL SIGNAL");
PositionRecovery newRecovery;                      class=class="str">"cmt">//--- Create a new recovery instance.
newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_SELL, SymbolInfoDouble(_Symbol, SYMBOL_BID)); class=class="str">"cmt">//--- Initialize the recovery.
newRecovery.OpenTrade(ORDER_TYPE_SELL, "Initial Position"); class=class="str">"cmt">//--- Open an initial SELL position.
ArrayResize(recoveryArray, ArraySize(recoveryArray) + class="num">1); class=class="str">"cmt">//--- Resize the recovery array.
recoveryArray[ArraySize(recoveryArray) - class="num">1] = newRecovery; class=class="str">"cmt">//--- Add the new recovery to the array.
}
}

for (class="type">int i = class="num">0; i < ArraySize(recoveryArray); i++) { class=class="str">"cmt">//--- Iterate through all recovery instances.
  recoveryArray[i].ManageZones();              class=class="str">"cmt">//--- Manage zones for each recovery instance.
  recoveryArray[i].CheckCloseAtTargets();      class=class="str">"cmt">//--- Check and close positions at targets.
  recoveryArray[i].ApplyTrailingStop();        class=class="str">"cmt">//--- Apply trailing stop logic to initial positions.
}

「用5个月tick数据压恢复系统的底线」

评估这类带动态恢复逻辑的EA,先别急着上实盘,拿历史tick跑一遍才是正路。我们按用户可改的阈值(比如RSI超卖30、超买70)逐tick回放,信号触发就初始化一个独立恢复实例,在设定区间内跟价格。这样能把恢复场景处理、波动适应和仓位管理有没有漏洞提前暴露出来。 针对倍数加仓、必要时的对冲单、跨行情稳定性,我们用5个月历史数据做了严格测试。每个信号生成的恢复场景塞进 recoveryArray 数组做完全隔离,多个活跃实例并存也不会互相踩。初步跑完,余额和净值整体平滑,但某些阶段波动放大——根子是恢复层级叠加加上信号扎堆。 先开追踪止损,总交易次数下来了,胜率肉眼可见抬升。再补一层“持仓上限”逻辑:达到预设开仓数,新信号也不加仓。下面这段就是新增的输入和 OnTick 里的判据,黄底是高亮改动处。 逻辑很直白:开关关掉,或者当前 PositionsTotal 小于上限,就照常开单;两个都不满足,终端打“失败:达到最大仓位阈值!”然后罢手。实测下来交易数再减一截,胜率继续往上走,多区域恢复系统算立住了。外汇和贵金属波动剧烈,这类回测只代表历史概率,实盘仍可能因滑点、点差突变走形。

MQL5 / C++
<span class="keyword">input</span> <span class="keyword">class="type">bool</span> inputEnablePositionsRestriction = <span class="macro">true</span>; <span class="comment">class=class="str">"cmt">// Enable Maximum positions restriction</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span> inputMaximumPositions = <span class="number">class="num">11</span>; <span class="comment">class=class="str">"cmt">// Maximum number of positions</span><span>
</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| Expert tick function&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> <span class="functions">OnTick</span>() {
&nbsp;&nbsp; <span class="keyword">if</span> (<span class="functions">CopyBuffer</span>(rsiHandle, <span class="number">class="num">0</span>, <span class="number">class="num">1</span>, <span class="number">class="num">2</span>, rsiBuffer) &lt;= <span class="number">class="num">0</span>) { <span class="comment">class=class="str">"cmt">//--- Copy the RSI buffer values.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Print</span>(<span class="class="type">class="kw">string">"Failed to copy RSI buffer. Error: "</span>, <span class="functions">GetLastError</span>()); <span class="comment">class=class="str">"cmt">//--- Print error on failure.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- Exit on failure.</span>
&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">datetime</span> currentBarTime = <span class="functions">iTime</span>(<span class="predefines">_Symbol</span>, <span class="macro">PERIOD_CURRENT</span>, <span class="number">class="num">0</span>); <span class="comment">class=class="str">"cmt">//--- Get the time of the current bar.</span>
&nbsp;&nbsp; <span class="keyword">if</span> (currentBarTime != lastBarTime) {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- Check if a new bar has formed.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lastBarTime = currentBarTime;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- Update the last processed bar time.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (rsiBuffer[<span class="number">class="num">1</span>] &gt; <span class="number">class="num">30</span> &amp;&amp; rsiBuffer[<span class="number">class="num">0</span>] &lt;= <span class="number">class="num">30</span>) {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- Check for oversold RSI crossing up.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="functions">Print</span>(<span class="class="type">class="kw">string">"BUY SIGNAL"</span>);
<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span> (inputEnablePositionsRestriction == <span class="macro">false</span> || inputMaximumPositions &gt; <span class="functions">PositionsTotal</span>()){
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PositionRecovery newRecovery;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- Create a new recovery instance.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, <span class="predefines">_Symbol</span>, <span class="macro">ORDER_TYPE_BUY</span>, <span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>, <span class="macro">SYMBOL_BID</span>)); <span class="comment">class=class="str">"cmt">//--- Initialize the recovery.</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newRecovery.OpenTrade(<span class="macro">ORDER_TYPE_BUY</span>, <span class="class="type">class="kw">string">"Initial Position"</span>); <span class="comment">class=class="str">"cmt">//--- Open an initial BUY position.</span>

◍ RSI超买回落时的卖单恢复逻辑

当 RSI 缓冲在上一根 K 线低于 70、当前 K 线大于等于 70 时,脚本判定为超买下穿信号并打印 "SELL SIGNAL"。此时若仓位限制开关关闭,或当前持仓总数 PositionsTotal() 小于设定上限 inputMaximumPositions,才会真正进场。 进场动作先构造一个 PositionRecovery 实例,用 Initialize 注入手数、区域点数、目标点数、加仓倍数、品种与卖单类型,再以 OpenTrade 开出首笔 SELL。随后 ArrayResize 把 recoveryArray 长度加 1,把新实例塞进数组末位,供后续统一管控。 若不满足仓位阈值,则只打印 "FAILED: Maximum positions threshold hit!" 并跳过开仓。主循环里对 recoveryArray 每个元素调用 ManageZones、CheckCloseAtTargets、ApplyTrailingStop,实现区域管理与移动止损。外汇与贵金属杠杆高,这类马丁类恢复策略在单边行情中可能快速放大浮亏,开 MT5 前先把 inputMaximumPositions 设小做实盘压力测试。

MQL5 / C++
      ArrayResize(recoveryArray, ArraySize(recoveryArray) + class="num">1); class=class="str">"cmt">//--- Resize the recovery array.
      recoveryArray[ArraySize(recoveryArray) - class="num">1] = newRecovery; class=class="str">"cmt">//--- Add the new recovery to the array.
      }
      else {
         Print("FAILED: Maximum positions threshold hit!");
      }
      } else if (rsiBuffer[class="num">1] < class="num">70 && rsiBuffer[class="num">0] >= class="num">70) {      class=class="str">"cmt">//--- Check for overbought RSI crossing down.
      Print("SELL SIGNAL");
      if (inputEnablePositionsRestriction == false || inputMaximumPositions > PositionsTotal()){
      PositionRecovery newRecovery;                                        class=class="str">"cmt">//--- Create a new recovery instance.
      newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_SELL, SymbolInfoDouble(_Symbol, SYMBOL_BID)); class=class="str">"cmt">//--- Initialize the recovery.
      newRecovery.OpenTrade(ORDER_TYPE_SELL, "Initial Position"); class=class="str">"cmt">//--- Open an initial SELL position.
      ArrayResize(recoveryArray, ArraySize(recoveryArray) + class="num">1); class=class="str">"cmt">//--- Resize the recovery array.
      recoveryArray[ArraySize(recoveryArray) - class="num">1] = newRecovery; class=class="str">"cmt">//--- Add the new recovery to the array.
      }
      else {
         Print("FAILED: Maximum positions threshold hit!");
      }
      }
   }
   for (class="type">int i = class="num">0; i < ArraySize(recoveryArray); i++) { class=class="str">"cmt">//--- Iterate through all recovery instances.
      recoveryArray[i].ManageZones();                     class=class="str">"cmt">//--- Manage zones for each recovery instance.
      recoveryArray[i].CheckCloseAtTargets();             class=class="str">"cmt">//--- Check and close positions at targets.
      recoveryArray[i].ApplyTrailingStop();               class=class="str">"cmt">//--- Apply trailing stop logic to initial positions.
   }
}

画得少,看得清

把前面四节串起来看,这套多层级区域恢复 EA 的真正价值不在代码量,而在把信号生成、持仓管控、恢复与退出拆成了可独立跑的模块。你在 MT5 里加载 1._Zone_Recovery_RSI_EA_Multi-Zone.mq5(18.92 KB)后,同一图表能并行跑多个恢复实例,互不阻塞。 外汇与贵金属杠杆高、滑点跳空频繁,多实例恢复放大了保证金占用,实盘前必须用策略测试器跑至少 3 个月 Tick 数据,重点看最大回撤而非盈利曲线。 真正要打磨的只有一处:RSI 过滤周期与区域宽度这两个参数,调一次就少一层盲目。剩下的,交给小布盯盘去替你扫异常就好。

常见问题

首仓建议用账户1%-2%风险倒推手数,止损放在近期波动极值外;RSI只是触发信号,仓位别超过常规单笔上限。
按品种平均波幅的0.5~1倍间距挂载,黄金类可放宽到1.5倍;间距太小容易被同一波扫光,太大则恢复太慢。
可以,小布能按你设定的RSI阈值监控品种,超买回落后直接推送恢复层级建议和挂单间距,省去手动算。
重点看最大回撤是否可控、恢复层数触发频率、以及胜率衰减曲线;单月亏损超15%就要缩间距或降首仓。
只标首仓触发价、恢复挂单价、强平底线三档;其余用颜色块代替线条,一眼能判方向就行。