MQL5自动化交易策略(第四部分):构建多层级区域恢复系统·综合运用
◍ 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 即可验证。
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 以内做模拟。
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 小于上限,就照常开单;两个都不满足,终端打“失败:达到最大仓位阈值!”然后罢手。实测下来交易数再减一截,胜率继续往上走,多区域恢复系统算立住了。外汇和贵金属波动剧烈,这类回测只代表历史概率,实盘仍可能因滑点、点差突变走形。
<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 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> <span class="functions">OnTick</span>() { <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) <= <span class="number">class="num">0</span>) { <span class="comment">class=class="str">"cmt">//--- Copy the RSI buffer values.</span> <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> <span class="keyword">class="kw">return</span>; <span class="comment">class=class="str">"cmt">//--- Exit on failure.</span> } <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> <span class="keyword">if</span> (currentBarTime != lastBarTime) { <span class="comment">class=class="str">"cmt">//--- Check if a new bar has formed.</span> lastBarTime = currentBarTime; <span class="comment">class=class="str">"cmt">//--- Update the last processed bar time.</span> <span class="keyword">if</span> (rsiBuffer[<span class="number">class="num">1</span>] > <span class="number">class="num">30</span> && rsiBuffer[<span class="number">class="num">0</span>] <= <span class="number">class="num">30</span>) { <span class="comment">class=class="str">"cmt">//--- Check for oversold RSI crossing up.</span> <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);"> <span class="keyword">if</span> (inputEnablePositionsRestriction == <span class="macro">false</span> || inputMaximumPositions > <span class="functions">PositionsTotal</span>()){ </span> PositionRecovery newRecovery; <span class="comment">class=class="str">"cmt">//--- Create a new recovery instance.</span> 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> 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 设小做实盘压力测试。
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 过滤周期与区域宽度这两个参数,调一次就少一层盲目。剩下的,交给小布盯盘去替你扫异常就好。