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

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

(3/3)·把RSI阈值入场与反向区域机制写进EA,让亏损单自动加倍反向对冲

实战向进阶 第 3/3 篇

很多交易者把RSI当作单纯的反转指标,超买超卖一碰就反向开仓,却忽略价格继续惯性推进会把账户拖进深亏。另一类人手动补仓摊平,没有量化区域边界,回撤一来就只能干瞪眼。本文把区域反转模型直接落成MQL5代码,让系统自己算上下限、自动触发反向单。

RSI 穿越阈值触发网格恢复

用 RSI 的 30/70 边界做信号源时,重点不是指标本身,而是「前一根柱刚在界内、当前柱跨出界外」这一根-bar 穿越动作。下面这段逻辑只在 bar 1 与 bar 0 的关系满足时才吐信号,能避开指标在边界附近反复抖动的假触发。

MQL5 / C++
if (rsiBuffer[class="num">1] > class="num">30 && rsiBuffer[class="num">0] <= class="num">30) { class=class="str">"cmt">//--- Check for RSI crossing below class="num">30 (oversold signal).
   Print("BUY SIGNAL"); class=class="str">"cmt">//--- Log a BUY signal.
   zoneRecovery.HandleSignal(ORDER_TYPE_BUY); class=class="str">"cmt">//--- Trigger the Zone Recovery BUY logic.
} else if (rsiBuffer[class="num">1] < class="num">70 && rsiBuffer[class="num">0] >= class="num">70) { class=class="str">"cmt">//--- Check for RSI crossing above class="num">70 (overbought signal).
   Print("SELL SIGNAL"); class=class="str">"cmt">//--- Log a SELL signal.
   zoneRecovery.HandleSignal(ORDER_TYPE_SELL); class=class="str">"cmt">//--- Trigger the Zone Recovery SELL logic.
}
}
class=class="str">"cmt">//--- Manage zone recovery logic
zoneRecovery.ManageZones(); class=class="str">"cmt">//--- Perform zone recovery logic for active positions.
class=class="str">"cmt">//--- Check and close at zone targets
zoneRecovery.CheckCloseAtTargets(); class=class="str">"cmt">//--- Evaluate and close trades when target levels are reached.
逐行拆:第 1 行 rsiBuffer[1] > 30 && rsiBuffer[0] <= 30 是确认 RSI 从超卖线上方跌穿 30,倾向视为潜在买点;Print("BUY SIGNAL") 仅往日志写字符串,不下单;zoneRecovery.HandleSignal(ORDER_TYPE_BUY) 把买类型丢给网格恢复模块。下方 else if 对称处理 70 上穿的超买卖点。 收尾三行不在 if 内:ManageZones() 管已开仓的网格层,CheckCloseAtTargets() 到分区目标价才评估平仓。外汇与贵金属杠杆高,RSI 穿越在震荡市命中率可能偏高,趋势市中连续穿界可能触发多层加仓,实盘前请在 MT5 策略测试器用 2020—2023 年 XAUUSD 的 M15 跑一遍验证层数与回撤。

MQL5 / C++
if (rsiBuffer[class="num">1] > class="num">30 && rsiBuffer[class="num">0] <= class="num">30) { class=class="str">"cmt">//--- Check for RSI crossing below class="num">30 (oversold signal).
   Print("BUY SIGNAL"); class=class="str">"cmt">//--- Log a BUY signal.
   zoneRecovery.HandleSignal(ORDER_TYPE_BUY); class=class="str">"cmt">//--- Trigger the Zone Recovery BUY logic.
} else if (rsiBuffer[class="num">1] < class="num">70 && rsiBuffer[class="num">0] >= class="num">70) { class=class="str">"cmt">//--- Check for RSI crossing above class="num">70 (overbought signal).
   Print("SELL SIGNAL"); class=class="str">"cmt">//--- Log a SELL signal.
   zoneRecovery.HandleSignal(ORDER_TYPE_SELL); class=class="str">"cmt">//--- Trigger the Zone Recovery SELL logic.
}
}
class=class="str">"cmt">//--- Manage zone recovery logic
zoneRecovery.ManageZones(); class=class="str">"cmt">//--- Perform zone recovery logic for active positions.
class=class="str">"cmt">//--- Check and close at zone targets
zoneRecovery.CheckCloseAtTargets(); class=class="str">"cmt">//--- Evaluate and close trades when target levels are reached.

「给区域反转系统加追踪止损的实测改造」

在 MT5 策略测试器里跑区域反转 RSI,选好品种、周期并打开可视化模式,能直接看历史行情中订单怎么触发。我们把初始参数定为初始手数 0.1、区域大小 700 点、目标大小 1400 点、加仓倍数 2.0,从 2024-01-01 跑一整年,原版逻辑大部分时间暴露在反转实例里,命中率并不理想。 原系统死等完整利润目标,于是我们写了 applyTrailingStop 函数,只盯第一笔仓位做追踪。关键输入是 slPoints(止损距市价点数)、magicNo(筛选标识,0 即全管)、minProfitPoints(激活前最低利润),避免过早挪止损。 [CODE] ZoneRecovery zoneRecovery(0.1, 700, 1400, 2.0, _Symbol); //--- Initialize the ZoneRecovery object with specified parameters. //+------------------------------------------------------------------+

//FUNCTION TO APPLY TRAILING STOP

//+------------------------------------------------------------------+ void applyTrailingStop(double slPoints, CTrade &trade_object, int magicNo=0, double minProfitPoints=0){ double buySl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - slPoints*_Point, _Digits); //--- Calculate the stop loss price for BUY trades double sellSl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + slPoints*_Point, _Digits); //--- Calculate the stop loss price for SELL trades for (int i = PositionsTotal() - 1; i >= 0; i--){ //--- Loop through all open positions ulong ticket = PositionGetTicket(i); //--- Get the ticket number of the current position if (ticket > 0){ //--- Check if the ticket is valid if (PositionSelectByTicket(ticket)){ //--- Select the position by its ticket number 代码逐行拆解:第 1 行用 0.1 手、700 点区域、1400 点目标、2.0 倍和当前符号构造 ZoneRecovery 对象。applyTrailingStop 函数头定义了四个参数,slPoints 为追踪距离点数,trade_object 传 CTrade 引用,magicNo 默认 0,minProfitPoints 默认 0。buySl 行取卖价减 slPoints 个 _Point 再按 _Digits 规范化,算买单新止损;sellSl 行取买价加点数算卖单新止损。for 循环从最后一仓倒序到 0,防止改仓时索引错位;PositionGetTicket 拿订单号,大于 0 有效后用 PositionSelectByTicket 选中查详情。 在 ZoneRecovery 类里加 public 布尔 isFirstPosition,开仓时若是反转仓置 false,否则置 true。OnTick 中判断 isFirstPosition 为 true 就调 applyTrailingStop(100, obj_Trade, 0, 100),即利润满 100 点后启动 100 点距追踪。 追踪损平仓后若不重置,系统残留会乱开反转单。补一段:isFirstPosition 为 true 且无任何仓位时调 zoneRecovery.Reset()。最终回测反转仓数量下降,命中率明显提升,外汇与贵金属属高风险,该改善只代表历史样本可能倾向更优,实盘仍需自测。

MQL5 / C++
ZoneRecovery zoneRecovery(<span class="number">class="num">0.1</span>, <span class="number">class="num">700</span>, <span class="number">class="num">1400</span>, <span class="number">class="num">2.0</span>, <span class="predefines">_Symbol</span>);
<span class="comment">class=class="str">"cmt">//--- Initialize the ZoneRecovery object with specified parameters.</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FUNCTION TO APPLY TRAILING STOP&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> applyTrailingStop(<span class="keyword">class="type">class="kw">double</span> slPoints, CTrade &amp;trade_object, <span class="keyword">class="type">int</span> magicNo=<span class="number">class="num">0</span>, <span class="keyword">class="type">class="kw">double</span> minProfitPoints=<span class="number">class="num">0</span>){
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> buySl = <span class="functions">NormalizeDouble</span>(<span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>, <span class="macro">SYMBOL_BID</span>) - slPoints*<span class="predefines">_Point</span>, <span class="predefines">_Digits</span>); <span class="comment">class=class="str">"cmt">//--- Calculate the stop loss price for BUY trades</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> sellSl = <span class="functions">NormalizeDouble</span>(<span class="functions">SymbolInfoDouble</span>(<span class="predefines">_Symbol</span>, <span class="macro">SYMBOL_ASK</span>) + slPoints*<span class="predefines">_Point</span>, <span class="predefines">_Digits</span>); <span class="comment">class=class="str">"cmt">//--- Calculate the stop loss price for SELL trades</span>
&nbsp;&nbsp;
&nbsp;&nbsp; <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i = <span class="functions">PositionsTotal</span>() - <span class="number">class="num">1</span>; i &gt;= <span class="number">class="num">0</span>; i--){ <span class="comment">class=class="str">"cmt">//--- Loop through all open positions</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">ulong</span> ticket = <span class="functions">PositionGetTicket</span>(i); <span class="comment">class=class="str">"cmt">//--- Get the ticket number of the current position</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span> (ticket &gt; <span class="number">class="num">0</span>){ <span class="comment">class=class="str">"cmt">//--- Check if the ticket is valid</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span> (<span class="functions">PositionSelectByTicket</span>(ticket)){ <span class="comment">class=class="str">"cmt">//--- Select the position by its ticket number</span>

◍ 多单空单分别改止损的判定逻辑

这段逻辑只处理当前图表品种且 magic 号匹配(或 magic 为 0 表示不限制)的持仓,避免 EA 误改手动单或其他策略的单子。 先取持仓开仓价与当前 SL:positionOpenPrice 和 positionSl 是后续所有比较的基准。若持仓为 BUY,用开仓价加 minProfitPoints * _Point 算出最低保本价 minProfitPrice,只有当新止损 buySl 高于保本价、高于开仓价、且优于原 SL(或原 SL 为 0)时才调用 PositionModify 把止损推到 buySl。 SELL 方向完全对称:minProfitPrice = 开仓价减 minProfitPoints 点,新止损 sellSl 必须低于保本价、低于开仓价、且比原 SL 更优才修改。这样能保证追踪止损只在已经浮盈足够点数后才生效,不会把止损拉到成本下方。 实盘里把 minProfitPoints 设成 50(对应 5 美元点值的黄金 0.5 美元)跑一遍,能在 MT5 终端的‘交易’标签里看到 SL 从 0 变成具体价位的时间点,验证逻辑是否按预期触发。外汇与贵金属杠杆高,改止损脚本若 magic 写错可能动到别的单,先在策略测试器用历史数据过一遍。

MQL5 / C++
if(PositionGetString(POSITION_SYMBOL)==_Symbol && class=class="str">"cmt">//--- Check if the position belongs to the current symbol
    (magicNo==class="num">0 || PositionGetInteger(POSITION_MAGIC)==magicNo)){ class=class="str">"cmt">//--- Check if the position matches the given magic number or if no magic number is specified

    class="type">class="kw">double positionOpenPrice=PositionGetDouble(POSITION_PRICE_OPEN); class=class="str">"cmt">//--- Get the opening price of the position
    class="type">class="kw">double positionSl=PositionGetDouble(POSITION_SL); class=class="str">"cmt">//--- Get the current stop loss of the position

    if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY){ class=class="str">"cmt">//--- Check if the position is a BUY trade
        class="type">class="kw">double minProfitPrice=NormalizeDouble(positionOpenPrice+minProfitPoints*_Point,_Digits); class=class="str">"cmt">//--- Calculate the minimum price at which profit is locked
        if(buySl>minProfitPrice &&  class=class="str">"cmt">//--- Check if the calculated stop loss is above the minimum profit price
            buySl>positionOpenPrice && class=class="str">"cmt">//--- Check if the calculated stop loss is above the opening price
            (buySl>positionSl || positionSl==class="num">0)){ class=class="str">"cmt">//--- Check if the calculated stop loss is greater than the current stop loss or if no stop loss is set
            trade_object.PositionModify(ticket,buySl,PositionGetDouble(POSITION_TP)); class=class="str">"cmt">//--- Modify the position to update the stop loss
        }
    }
    else if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL){ class=class="str">"cmt">//--- Check if the position is a SELL trade
        class="type">class="kw">double minProfitPrice=NormalizeDouble(positionOpenPrice-minProfitPoints*_Point,_Digits); class=class="str">"cmt">//--- Calculate the minimum price at which profit is locked
        if(sellSl<minProfitPrice &&  class=class="str">"cmt">//--- Check if the calculated stop loss is below the minimum profit price
            sellSl<positionOpenPrice && class=class="str">"cmt">//--- Check if the calculated stop loss is below the opening price
            (sellSl<positionSl || positionSl==class="num">0)){ class=class="str">"cmt">//--- Check if the calculated stop loss is less than the current stop loss or if no stop loss is set
            trade_object.PositionModify(ticket,sellSl,PositionGetDouble(POSITION_TP)); class=class="str">"cmt">//--- Modify the position to update the stop loss
        }
    }
}

首仓与恢复态的切换逻辑

这段 MT5 下单片段把「初始 BUY」和「恢复 BUY」用同一个入口区分开。trade.Buy 成功后,isFirstPosition 会按 isRecovery 当前值赋值:未恢复时记 true(首仓),已恢复时记 false(对冲仓),随后 isRecovery 强制置 true,意味着任何后续同方向补单都不再享受首仓逻辑。 首仓成立时,applyTrailingStop(100, obj_Trade, 0, 100) 会挂上 100 点跟踪止损,魔法码 0、最小利润门槛也是 100 点;一旦 PositionsTotal() 归零且仍标记为首仓,说明整轮区域恢复已平仓完毕,zoneRecovery.Reset() 把参数和缓存清回初始态。 实盘验证点:在 EURUSD M5 上把最小利润门槛从 100 改成 30,回测中首仓被跟踪止损扫掉的概率可能明显上升;外汇与贵金属杠杆高,区域恢复模型在单边行情中浮亏可能快速放大,仅建议用模拟盘先跑通这套状态机。

MQL5 / C++
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);
   isFirstPosition = isRecovery ? false : true;
   isRecovery = true;                              class=class="str">"cmt">//--- Set recovery state to true after the first trade.
   class="kw">return true;
}

if (zoneRecovery.isFirstPosition == true){ class=class="str">"cmt">//--- Check if this is the first position in the Zone Recovery process
   applyTrailingStop(class="num">100, obj_Trade, class="num">0, class="num">100); class=class="str">"cmt">//--- Apply a trailing stop with class="num">100 points, passing the "obj_Trade" object, a magic number of class="num">0, and a minimum profit of class="num">100 points
}

if (zoneRecovery.isFirstPosition == true && PositionsTotal() == class="num">0){ class=class="str">"cmt">//--- Check if this is the first position and if there are no open positions
   zoneRecovery.Reset(); class=class="str">"cmt">//--- Reset the Zone Recovery system, restoring initial settings and clearing previous trade data
}

「一点提醒」

把 RSI 区域反转逻辑写进 EA,核心就是信号识别、自动执行和反手仓位的追踪止损管理三件事,前面几节已经把框架拆开过了。 评论区里有人提到外汇市场区间比趋势更常见,原策略靠价格突破初始区间来填坑,遇到单边外扩可能扛出大回撤。给首单加一个日线 ATR 波动过滤器,能过滤掉低波动假突破,降低马丁式补仓的灾难性概率——外汇和贵金属杠杆高,这类策略实盘前必须自己跑历史测试。 下面这段是从附件 EA 里抽出的日线 ATR 手算片段,没调任何参数,你可以直接贴进 MT5 看数值: 别把正态当圣经 波动过滤器不是免死金牌,ATR 周期 20 只是示例,黄金和欧美对这个值敏感度不同,自己改了再验。

MQL5 / C++
<span class="macro">ENUM_TIMEFRAMES</span> _atrTimeFrame = <span class="macro">PERIOD_D1</span>;
<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _atrPeriod&nbsp;&nbsp;&nbsp;&nbsp;= <span class="number">class="num">20</span>;
<span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_atrValue&nbsp;&nbsp;&nbsp;&nbsp; = <span class="number">class="num">0</span>;
&nbsp;&nbsp; <span class="predefines">class="type">MqlRates</span> _rates[]; <span class="keyword">class="type">int</span> _ratesCopied = <span class="functions">CopyRates</span>(<span class="predefines">_Symbol</span>,_atrTimeFrame,<span class="number">class="num">0</span>,_atrPeriod+class="num">1,_rates);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="keyword">if</span> (_ratesCopied&gt;<span class="number">class="num">0</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; <span class="keyword">for</span> (<span class="keyword">class="type">int</span> i=<span class="number">class="num">1</span>; i&lt;_ratesCopied; i++) _atrValue += <span class="functions">MathMax</span>(_rates[i].high,_rates[i-<span class="number">class="num">1</span>].close)-<span class="functions">MathMin</span>(_rates[i].low,_rates[i-<span class="number">class="num">1</span>].close);
&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _atrValue /= <span class="functions">MathMax</span>(<span class="keyword"></span>_ratesCopied,class="num">1);
把反转边界交给小布盯盘算
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时RSI区域与反向触发线,你只需核对EA参数是否和盘面一致,把重复劳动交给小布,你专注决策。

常见问题

买入时区域下限设在入场价下方、区域上限对齐入场价;价格跌破下限即触发反向卖单,手数按预设乘数放大。
趋势市中连续反向单可能累积敞口,概率上回撤控制依赖乘数设定;震荡市反转胜率倾向更高,但需策略测试器验证。
小布盯盘目前内置的是区域边界与信号可视化,不直接输出MQL5源码,但可对照它的盘面标记校准你写的EA逻辑。
没有恒定答案,常见从1.2到2倍不等;外汇贵金属高风险,建议用策略测试器做多周期优化再定。
过拟合与滑点、点差扩张是主因,尤其贵金属跳空时反向单成交价可能偏离区域边界,需留容差。