实现 Deus EA:使用 MQL5 中的 RSI 和移动平均线进行自动交易·综合运用
📘

实现 Deus EA:使用 MQL5 中的 RSI 和移动平均线进行自动交易·综合运用

第 3/3 篇

◍ EA 骨架与参数预设

这套均线+RSI 的 EA 把超买超卖阈值压得很低:RSI_Overbought 设 35.0、RSI_Oversold 设 15.0,跟常见的 70/30 完全两码事,倾向在弱势震荡里抓反转。MA_Period 取 25、用 SMA 收 PRICE_CLOSE,属于慢均线过滤噪音的思路。 初始化段只打一行日志就返回 INIT_SUCCEEDED,没做句柄缓存;反初始化把 reason 码直接 Print 出来,方便你查 EA 被卸掉的诱因。 OnTick 里先抓 Ask/Bid,再用 ArrayResize 把 Close 写死成 2 个元素,分别存当根和前一根收盘价。RSI 用 iRSI 取 0 号缓冲,若返回 WRONG_VALUE 就报错退出——外汇和贵金属杠杆高,这类指标调用失败若不停 tick,可能连发错单。 下面这段是可直接贴进 MT5 看的声明与三个回调函数骨架,参数和变量名都保留原样,方便你改完立刻编译。

MQL5 / C++
input class="type">class="kw">double RSI_Overbought = class="num">35.0;      class=class="str">"cmt">// RSI overbought level
input class="type">class="kw">double RSI_Oversold = class="num">15.0;          class=class="str">"cmt">// RSI oversold level
class=class="str">"cmt">//--- Moving Average parameters
input class="type">int MA_Period = class="num">25;                  class=class="str">"cmt">// Moving Average period
input ENUM_MA_METHOD MA_Method = MODE_SMA; class=class="str">"cmt">// Moving Average method
input ENUM_APPLIED_PRICE MA_Price = PRICE_CLOSE; class=class="str">"cmt">// Applied price for MA
class=class="str">"cmt">//--- Variables
class="type">class="kw">double rsiValue;  class=class="str">"cmt">// RSI value
class="type">class="kw">double maValue;  class=class="str">"cmt">// Moving Average value
class="type">class="kw">double Ask;
class="type">class="kw">double Bid;
class="type">class="kw">double Close[class="num">2]; class=class="str">"cmt">// Initializing Close array with two elements
class=class="str">"cmt">//--- Function prototypes
class="type">void ApplyTrailingStop();
class="type">void OpenPosition(CTrade &trade, class="type">int orderType); class=class="str">"cmt">// Pass CTrade by reference
class="type">void ClosePositions(class="type">int orderType);             class=class="str">"cmt">// pass orderType directly
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   Print("Deus  EA initialized successfully.");
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
   Print("Deus  EA deinitialized. Reason: ", reason);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
  class=class="str">"cmt">//--- Update current prices
    Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    ArrayResize(Close, class="num">2);
    Close[class="num">0] = iClose(_Symbol, Period(), class="num">0);
    Close[class="num">1] = iClose(_Symbol, Period(), class="num">1);

    class=class="str">"cmt">//--- Calculate RSI value
    rsiValue = iRSI(_Symbol, _Period, RSI_Period, PRICE_CLOSE, class="num">0);
    if (rsiValue == WRONG_VALUE)
     {
      Print("Error calculating RSI");
      class="kw">return;
     }
    class=class="str">"cmt">//--- Calculate Moving Average value

「RSI 碰均线后的开平与止损逻辑」

这段逻辑把 RSI 与移动平均线拧成一条交易触发链:先取当前均线值,若返回 WRONG_VALUE 直接打印错误并退出,避免拿脏数据下单。 买侧判定为 rsiValue < RSI_Oversold 且前一根收盘价 Close[1] 站上 maValue;此时若账户无持仓,先平掉可能的卖单再开买单。卖侧对称:rsiValue > RSI_Overbought 且 Close[1] 跌破 maValue,无持仓时平买开卖。 TrailingStop 大于 0 才调用尾随止损函数,参数设 0 就完全不跟。外汇与贵金属杠杆高,这种反转信号在震荡市可能连续假突破,实盘前用 MT5 策略测试器跑至少 3 个月 tick 数据。 OpenPosition 里止损止盈用 StopLoss、TakeProfit 乘 _Point 算距离:买单 sl=price - StopLoss*_Point,tp=price + TakeProfit*_Point;卖单反向。成交通过 trade.PositionOpen 并打日志,失败用 GetLastError 查码。 ClosePositions 倒序遍历持仓,只平掉与传入 orderType 同方向的仓,不会误伤反向单。

MQL5 / C++
maValue = iMA(_Symbol, _Period, MA_Period, class="num">0, MA_Method, MA_Price, class="num">0);
if (maValue == WRONG_VALUE)
  {
    Print("Error calculating Moving Average");
    class="kw">return;
  }
class=class="str">"cmt">//--- Check for Buy Signal
if(rsiValue < RSI_Oversold && Close[class="num">1] > maValue)
  {
    if(PositionsTotal() == class="num">0)
      {
        ClosePositions(ORDER_TYPE_SELL);
        OpenPosition(ORDER_TYPE_BUY);
      }
  }

class=class="str">"cmt">//--- Check for Sell Signal
if(rsiValue > RSI_Overbought && Close[class="num">1] < maValue)
  {
    if(PositionsTotal() == class="num">0)
      {
        ClosePositions(ORDER_TYPE_BUY);
        OpenPosition(ORDER_TYPE_SELL);
      }
  }

class=class="str">"cmt">//--- Apply trailing stop if specified
if(TrailingStop > class="num">0)
  {
    ApplyTrailingStop();
  }
}

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to open a position                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OpenPosition(class="type">int orderType)
  {
  class=class="str">"cmt">//--- Determine price stop loss, and take profit levels 
  class="type">class="kw">double price = (orderType == ORDER_TYPE_BUY) ? Ask : Bid;
  class="type">class="kw">double sl = (orderType == ORDER_TYPE_BUY) ? price - StopLoss * _Point : price + StopLoss * _Point;
  class="type">class="kw">double tp = (orderType == ORDER_TYPE_BUY) ? price + TakeProfit * _Point : price - TakeProfit * _Point;


  class="type">bool result = trade.PositionOpen(_Symbol, orderType, Lots, price, sl, tp, "Deus  EA");

  if(result)
    {
      Print("Order opened successfully. Type: ", orderType, ", Price: ", price);
    }
  else
    {
      Print("Failed to open order. Error code: ", GetLastError());
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to close positions                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ClosePositions(class="type">int orderType)
  {
  for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
    {
      if(PositionSelectByIndex(i))
        {
          class=class="str">"cmt">//--- Check if the positions type matches the order type to be closed
          if(PositionGetInteger(POSITION_TYPE) == orderType)
            {

用 PositionModify 把止损拖着走

这段逻辑干的事很直接:遍历当前品种的所有持仓,按市价 Bid/Ask 反推一条 trailing stop 距离,再调用 CTrade 的 PositionModify 把止损往有利方向挪。 先取持仓票据:ulong ticket = PositionGetInteger(POSITION_TICKET); 若 trade.PositionClose(ticket) 返回 false,就 Print 出 GetLastError() 的错误码,方便在 MT5 专家日志里定位是成交拒绝还是上下文锁问题。 ApplyTrailingStop() 里用 for(int i = PositionsTotal()-1; i >= 0; i--) 倒序遍历,PositionSelectByIndex(i) 且 PositionGetSymbol()==_Symbol 才处理。买仓以 Bid - TrailingStop*_Point 为新 SL,卖仓以 Ask + TrailingStop*_Point 为新 SL;仅当原 SL 落后于新 SL(买仓 POSITION_SL < sl,卖仓 POSITION_SL > sl)才修改,避免无谓发单。 外汇与贵金属杠杆高,trailing 间距若设得过小(比如 10 点内),在 EURUSD 正常毛刺里可能频繁触发修改请求并被经纪商限流;建议先在策略测试器用真实点差回放验证发单频率。

MQL5 / C++
class="type">class="kw">ulong ticket = PositionGetInteger(POSITION_TICKET);
if(!trade.PositionClose(ticket)
  {
  Print("Failed to close position. Error code: ", GetLastError());
  }
 }
 }
 }
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function to apply trailing stop                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void ApplyTrailingStop()
  {
  for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
    {
    if (PositionSelectByIndex(i) && PositionGetSymbol() == _Symbol)
      {
      class="type">class="kw">double price = (PositionGetInteger(POSITION_TYPE) == ORDER_TYPE_BUY) ? Bid : Ask;
      class="type">class="kw">double sl = (PositionGetInteger(POSITION_TYPE) == ORDER_TYPE_BUY) ? price - TrailingStop * _Point : price + TrailingStop * _Point;
      
      class=class="str">"cmt">//--- Trailing stop logic for buy positions 
      if (PositionGetInteger(POSITION_TYPE) == ORDER_TYPE_BUY && PositionGetDouble(POSITION_SL) < sl)
        {
        if (!trade.PositionModify(PositionGetInteger(POSITION_TICKET), sl, PositionGetDouble(POSITION_TP)))
          {
          Print("Failed to modify position. Error code: ", GetLastError());
          }
        }
      
      class=class="str">"cmt">//--- Trailing stop logic for sell positions
      else if (PositionGetInteger(POSITION_TYPE) == ORDER_TYPE_SELL && PositionGetDouble(POSITION_SL) > sl)
        {
        if (!trade.PositionModify(PositionGetInteger(POSITION_TICKET), sl, PositionGetDouble(POSITION_TP)))
          {
          Print("Failed to modify position. Error code: ", GetLastError());
          }
        }
      }
    }
    }

◍ 把这条线请下神坛

Deus EA 以均线叠加 RSI 生成信号,逻辑上把决策自动化,能压住手工交易里的情绪漂移,但社区回测讨论已经戳破一层窗户纸:有用户指出用 15 作超卖、35 作超买,在单月样本里像刻意调参,换成 85 才对称;另有三人实测编译报错、缺括号、函数签名错。这类现象说明,回测漂亮不等于实盘能打。 外汇与贵金属杠杆高、滑点跳空频发,EA 在小样本优化的低回撤,很可能只是过拟合的影子。部署前拿 EURUSD、XAUUSD 各跑至少半年 tick 数据,比对 15/35 与 15/85 两版 RSI 边界的胜率和最大回撤,比信原文结论更实在。 定期拉参数、看成交日志,才是让自动化系统活过不同市况的唯一笨办法。线画得再神,也只是一段 await 里的判断分支。

常见问题

RSI 从超卖区上穿且价格站上移动平均线时倾向做多,反向则做空;具体阈值在参数面板预设,可开图表核对信号触发点。
检查 PositionModify 拖止损逻辑是否生效,让止损随盈利偏移而非固定;外汇贵金属波动大,建议先用模拟盘验证参数。
小布盯盘的 AIGC 已内置常见指标诊断,打开对应品种页即可看到 RSI 与均线交叉的状态提示,省去手动盯盘。
可能过度拟合或忽略点差滑点;把均线周期和 RSI 周期调粗一点,减少交易频率再观察概率表现。
无统一答案,按账户权益 1% 风险反推 lot,sl 参考近期平均波动;贵金属高风险,先用小仓试。