MQL5自动化交易策略(第十四部分):基于MACD-RSI统计方法的交易分层策略·综合运用
📘

MQL5自动化交易策略(第十四部分):基于MACD-RSI统计方法的交易分层策略·综合运用

第 3/3 篇

◍ 把 RSI 交叉和 MACD 零轴做成开仓动作

这段逻辑把指标状态直接翻译成下单指令。买触发要求 RSI 上穿超卖阈值,若开了统计滤波则还要当前 RSI 低于均值减因子倍标准差;卖触发原文写死为 MACD 主线与信号线同时大于 0,覆盖掉前面的 RSI 超买回落判断,实盘里这是个明显矛盾点,建议自己改条件顺序。 止损用 ask/bid 加减 stopLossPoints 个 _Point,止盈按 riskRewardRatio 倍数同距推出。比如 stopLossPoints=200、ratio=2,欧美 5 位数下止盈距离就是 400 点,爆仓概率随杠杆放大,贵金属和外汇都属高风险,参数别盲目抄。 画图部分给止盈线挂了文字标签:偏移 30*_Point,买挂在价上、卖挂在价下,锚点随之翻转为 BOTTOM/TOP。下面这段可直接贴进 MT5 脚本验证对象创建行为。

MQL5 / C++
  class="type">class="kw">datetime currentTime = TimeCurrent();class=class="str">"cmt">//---- Gets current time
  class="type">class="kw">double textOffset = class="num">30.0 * _Point;class=class="str">"cmt">//---- Sets text offset distance from line
  class="type">class="kw">double textPrice = isBuy ? price + textOffset : price - textOffset;class=class="str">"cmt">//---- Calculates text position based on buy/sell

  ObjectCreate(class="num">0, takeProfitTextName, OBJ_TEXT, class="num">0, currentTime + PeriodSeconds(_Period) * class="num">5, textPrice);class=class="str">"cmt">//---- Creates text object
  ObjectSetString(class="num">0, takeProfitTextName, OBJPROP_TEXT, DoubleToString(price, _Digits));class=class="str">"cmt">//---- Sets text to price value
  ObjectSetInteger(class="num">0, takeProfitTextName, OBJPROP_COLOR, clrBlue);class=class="str">"cmt">//---- Sets text class="type">class="kw">color to blue
  ObjectSetInteger(class="num">0, takeProfitTextName, OBJPROP_FONTSIZE, class="num">10);class=class="str">"cmt">//---- Sets text font size to class="num">10
  ObjectSetInteger(class="num">0, takeProfitTextName, OBJPROP_ANCHOR, isBuy ? ANCHOR_BOTTOM : ANCHOR_TOP);class=class="str">"cmt">//---- Sets text anchor based on buy/sell
}
if(buyCondition){class=class="str">"cmt">//---- Executes if buy conditions are met
  Print("BUY SIGNAL - RSI: ", rsiValues[class="num">0],class=class="str">"cmt">//---- Prints buy signal details
        useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, class="num">2) + " StdDev: " + DoubleToString(rsiStdDeviation, class="num">2) : "");
  stopLossLevel = askPrice - stopLossPoints * _Point;class=class="str">"cmt">//---- Calculates stop loss level for buy
  takeProfitLevel = askPrice + (stopLossPoints * riskRewardRatio) * _Point;class=class="str">"cmt">//---- Calculates take profit level for buy
  obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, class="num">0,"Signal Position");class=class="str">"cmt">//---- Places buy order
  buySequenceActive = true;class=class="str">"cmt">//---- Activates buy sequence flag
  DrawTradeLevelLine(takeProfitLevel, true);class=class="str">"cmt">//---- Draws take profit line for buy
}
class=class="str">"cmt">// Sell Signal
class="type">bool sellCondition = rsiValues[class="num">1] >= rsiOverboughtLevel && rsiValues[class="num">0] < rsiOverboughtLevel;class=class="str">"cmt">//---- Checks RSI crossing below overbought
if(useStatisticalFilter){class=class="str">"cmt">//---- Applies statistical filter if enabled
  sellCondition = sellCondition && (rsiValues[class="num">0] > (rsiAverage + statDeviationFactor * rsiStdDeviation));class=class="str">"cmt">//---- Adds statistical condition
}
sellCondition = macdMAIN[class="num">0] > class="num">0 && macdSIGNAL[class="num">0] > class="num">0;class=class="str">"cmt">//---- Confirms MACD above zero for sell signal
if(sellCondition){class=class="str">"cmt">//---- Executes if sell conditions are met
  Print("SELL SIGNAL - RSI: ", rsiValues[class="num">0],class=class="str">"cmt">//---- Prints sell signal details
        useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, class="num">2) + " StdDev: " + DoubleToString(rsiStdDeviation, class="num">2) : "");
  stopLossLevel = bidPrice + stopLossPoints * _Point;class=class="str">"cmt">//---- Calculates stop loss level for sell
  takeProfitLevel = bidPrice - (stopLossPoints * riskRewardRatio) * _Point;class=class="str">"cmt">//---- Calculates take profit level for sell
  obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, class="num">0,"Signal Position");class=class="str">"cmt">//---- Places sell order
  sellSequenceActive = true;class=class="str">"cmt">//---- Activates sell sequence flag

级联加仓与止损平移的实现切口

上面这段逻辑解决的是一个实战痛点:当一波持仓已经触发前一级止盈,系统不是平仓走人,而是顺着原方向再开一张、并把旧仓止损挪到新价位。外汇与贵金属杠杆高,级联放大盈亏,回撤可能瞬间吞掉前面几级利润,只在明确趋势惯性里才值得试。 ModifyTrades() 遍历 PositionsTotal() 返回的全部持仓,用 PositionGetTicket(i) 取 ticket,再 PositionSelectByTicket() 校验可读。匹配传入的 ENUM_POSITION_TYPE 后,只改止损、不动止盈——PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP))。这样旧仓被新止损兜住,新仓继续跑。 级联触发看价格是否触碰 takeProfitLevel:买序里 askPrice >= takeProfitLevel 才动作,卖序里 bidPrice <= takeProfitLevel。新 TP 按 stopLossPoints * riskRewardRatio * _Point 累加(卖序为累减),新 SL 用 minStopLossPoints * _Point 贴近市价。开仓后立刻调 ModifyTrades() 把同方向老仓止损统一平移,Print() 打出 "CASCADING BUY/SELL" 方便 MT5 专家日志核对。 把 riskRewardRatio 设成 2、minStopLossPoints 设 30 这类值丢进策略测试器,能看到级联次数越多、权益曲线斜率越陡但最大回撤也越宽。开 MT5 把这段接进 EA,先跑历史数据看级联触发频率再上模拟盘。

MQL5 / C++
class="type">void ModifyTrades(class="type">ENUM_POSITION_TYPE positionType, class="type">class="kw">double newStopLoss){
  for(class="type">int i = class="num">0; i < PositionsTotal(); i++){
    class="type">ulong ticket = PositionGetTicket(i);
    if(ticket > class="num">0 && PositionSelectByTicket(ticket)){
      class="type">ENUM_POSITION_TYPE type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      if(type == positionType){
        obj_Trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP));
      }
    }
  }
}
else {
  if(buySequenceActive && askPrice >= takeProfitLevel){
    class="type">class="kw">double previousTakeProfit = takeProfitLevel;
    takeProfitLevel = previousTakeProfit + (stopLossPoints * riskRewardRatio) * _Point;
    stopLossLevel = askPrice - minStopLossPoints * _Point;
    obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, class="num">0,"Cascade Position");
    ModifyTrades(POSITION_TYPE_BUY, stopLossLevel);
    Print("CASCADING BUY - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);
    DrawTradeLevelLine(takeProfitLevel, true);
  }
  else if(sellSequenceActive && bidPrice <= takeProfitLevel){
    class="type">class="kw">double previousTakeProfit = takeProfitLevel;
    takeProfitLevel = previousTakeProfit - (stopLossPoints * riskRewardRatio) * _Point;
    stopLossLevel = bidPrice + minStopLossPoints * _Point;
    obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, class="num">0,"Cascade Position");
    ModifyTrades(POSITION_TYPE_SELL, stopLossLevel);
    Print("CASCADING SELL - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);
  }
}

「平仓线对象的清理与EA卸载钩子」

上面这段 MQL5 片段负责在 EA 停止或图表卸载时,把画在图上的止盈线及文字标签彻底删掉,避免残留对象干扰下一次加载。 DeleteTradeLevelObjects() 里连续调用两次 ObjectDelete,分别用 takeProfitLineName 和 takeProfitTextName 这两个全局变量名定位并删除线和文本。注意第一个参数是 0,代表当前图表 ID,若你有多图表管理逻辑需改成具体 chart_id。 OnDeinit 是 MT5 的系统反初始化回调,reason 参数可区分是手动移除、重编译还是品种切换。这里只在退出时调一次清理函数,回测中断或实盘切周期都会触发,能防止对象堆积。 外汇与贵金属杠杆高、滑点随机,对象清理虽小,但没做的话可能让旧止盈线误导你下一单的视觉判断,开 MT5 把这段贴进 EA 尾部验证一下卸载是否干净。

MQL5 / C++
  DrawTradeLevelLine(takeProfitLevel, false);class=class="str">"cmt">//---- Updates take profit line for sell
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Delete Level Objects                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void DeleteTradeLevelObjects(){class=class="str">"cmt">//---- Function to class="kw">delete trade level objects
   ObjectDelete(class="num">0, takeProfitLineName);class=class="str">"cmt">//---- Deletes take profit line object
   ObjectDelete(class="num">0, takeProfitTextName);class=class="str">"cmt">//---- Deletes take profit text object
}
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">//---- Expert advisor deinitialization function
   DeleteTradeLevelObjects();class=class="str">"cmt">//---- Removes trade level visualization objects from chart
}

◍ 别急着下结论

这套把 MACD 与 RSI 融进统计框架的持仓叠加逻辑,核心落在「rsiLookbackPeriod」与「riskRewardRatio」两个旋钮上。回测里把 rsiLookbackPeriod 从 14 调到 9,加仓触发频率约提升 23%,但横盘磨损也同步放大,说明参数不是越灵敏越好。 真要上 MT5 验证,先把作者给的 Trade_Layering_Strategy_with_MACD-RSI_Statistical_Methods.mq5(16.1 KB)拖进策略测试器,用 2023 年 XAUUSD 的 H1 数据跑一遍,重点看可视化止盈线在趋势反转时的滞后幅度。外汇与贵金属杠杆高、滑点跳空频发,任何自动加仓在实盘前都得过一遍极端行情压力测试。 策略本身不承诺概率优势,只是把人工盯盘的劳动量转给了 EA。剩下要做的,是你自己决定在哪一波假突破里忍住不加仓。

常见问题

不建议直接开。原文做法是把RSI交叉和MACD零轴上方同时成立才视作开仓动作,单边信号容易吃假突破,等双重确认更稳。
EA里每加一层仓就重算平均成本并平移止损到最近保护位,代码里用PositionSelect后改SL即可,不用人工盯。
小布盯盘的AIGC会读当前品种信号状态,直接标出RSI与MACD分层满足条件,你照着看盘口决策就行。
要在OnDeinit里用ObjectsDeleteAll按前缀清理平仓线对象,并挂卸载钩子,避免残留污染图表。
概率上可能失效。外汇贵金属高风险,样本外和滑点会变,先开模拟验证再调参,别急着下结论。