实现 Deus EA:使用 MQL5 中的 RSI 和移动平均线进行自动交易·进阶篇
🤖

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

(2/3)· 从指标参数到订单管理的完整拼图,手把手把 Deus EA 的买卖规则写进 MT5

新手友好 第 2/3 篇

很多交易者把 RSI 和均线只是挂在图表上肉眼看,却从没想过让 EA 替自己按固定阈值执行。Deus EA 把 7 期 RSI 与 25 期 SMA 的组合写成代码,省去人工盯盘的迟疑和情绪干扰。

EA 骨架与行情快照的初始化

这段 MQL5 片段搭起了一个名为 Deus 的 EA 骨架:输入参数先锁定均线应用价为收盘价,随后声明 RSI、均线、买卖价与收盘价数组等变量,并前置了移动止损与开平仓函数的原型。OnInit 里只做了一件事——向日志打印初始化成功并返回 INIT_SUCCEEDED,说明真正的策略逻辑不在这里落地。 OnDeinit 接收 reason 参数,把 EA 卸载原因打到日志,方便你事后在终端查为什么退出。注意原文里 OnInit 和 OnDeinit 各自重复写了一遍函数体,MT5 编译会直接报重定义错误,复制时务必删掉多余块。 在 tick 处理中,代码用 SymbolInfoDouble 取当前 Ask/Bid,并用 ArrayResize 把 Close 数组定为 2 个元素,分别装入 0 号与 1 号 K 线的收盘价——也就是最近两根蜡烛的收价。接着 iRSI 取周期 RSI_Period 的 0 号值,iMA 取 MA_Period 均线值,任一是 WRONG_VALUE 就打印错误并 return。外汇与贵金属杠杆高,这类基础取值若失败会直接让信号失效,上 MT5 跑之前先确认指标句柄参数没写错。

MQL5 / C++
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=class="str">"cmt">// Initializing Close array with two elements
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="type">void ApplyTrailingStop();
class="type">void OpenPosition(CTrade trade, class="type">int orderType);
class="type">void ClosePositions(class="type">int orderType);
class=class="str">"cmt">//--- Function prototypes
class="type">void ApplyTrailingStop();
class="type">void OpenPosition(CTrade trade, class="type">int orderType);
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);
  }
   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="type">void OnDeinit(const class="type">int reason)
  {
   Print("Deus  EA deinitialized. Reason: ", reason);
  }
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
   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

◍ RSI 与均线共振的开仓判定

这段逻辑把信号触发点全部收口在 OnTick 里:先抓 Ask/Bid 与最近两根收盘价,再算 RSI(0) 与 MA(0),任何指标返回 WRONG_VALUE 就直接 Print 并 return,避免脏数据下单。 买侧条件是 rsiValue < RSI_Oversold 且上一根收盘价 Close[1] 大于 maValue;卖侧反过来,rsiValue > RSI_Overbought 且 Close[1] < maValue。注意它看的是 1 号偏移的收盘,不是当前根,等于用已定型 K 线确认,过滤掉未收盘毛刺。 仓位门控只用 PositionsTotal()==0 一把锁:无持仓才先 ClosePositions 反方向再 OpenPosition 新方向。也就是说同品种同向重复开仓被禁,但跨品种或子账户其他魔术号持仓它看不见。 TrailingStop>0 时才调 ApplyTrailingStop(),等于把移动止损做成可选开关,默认 0 就是不跟。外汇与贵金属杠杆高,这类共振信号在震荡市可能连续假突破,实盘前请在 MT5 策略测试器用 2020—2023 年 XAUUSD M15 跑一遍验证胜率与回撤。

MQL5 / C++
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">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
    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
    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

「开仓与平仓的函数骨架」

这段逻辑把 trailing stop 判定和仓位操作拆成了独立函数,核心是先判断 TrailingStop>0 才挂追踪,再走 OpenPosition 下单。外汇与贵金属杠杆高,实盘前务必在 MT5 策略测试器用历史数据跑一遍,确认滑点和点值计算符合预期。 OpenPosition 里根据 orderType 区分买/卖:买价取 Ask,卖价取 Bid;止损用 price ± StopLoss*_Point,止盈用 price ± TakeProfit*_Point。下单后 bool result 接收回执,成功打印类型和价格,失败用 GetLastError 输错误码,方便排查。 平仓侧用 for 从 PositionsTotal()-1 倒序遍历,PositionSelectByIndex 选中后比对 POSITION_TYPE,匹配才调 trade.PositionClose。倒序遍历能避免删除仓位后索引错位,这是 MT5 多仓管理的常见坑。 把 StopLoss、TakeProfit 当成外部参数传入,就能在 EA 面板随时调点。黄金 XAUUSD 的 _Point 通常是 0.01,欧美 EURUSD 也是 0.00001,改参数前先确认品种精度。

MQL5 / C++
if (TrailingStop > class="num">0)
  {
    ApplyTrailingStop();
  }
class="type">void OpenPosition(class="type">int orderType)
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 open a position                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OpenPosition(class="type">int orderType)
  {
  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="type">void OpenPosition(class="type">int orderType)
for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
    {
      if(PositionSelectByIndex(i))
        {
         if(PositionGetInteger(POSITION_TYPE) == orderType)
          {
            if(!trade.PositionClose(ticket)
              {
               Print("Failed to close position. Error code: ", GetLastError());
              }
          }
     }
  }
if(PositionGetInteger(POSITION_TYPE) == orderType)
if(!trade.PositionClose(ticket)
              {
               Print("Failed to close position. Error code: ", GetLastError());
              }
          }
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

平仓与追踪止损的函数骨架

EA 里把‘撤单’和‘保利润’拆成两个独立函数,逻辑上更干净,也方便在 OnTick 里按需调用。下面这段是平仓函数 ClosePositions 的雏形:按持仓索引倒序遍历,只挑出与指定 orderType 相同的仓位去关。 遍历必须用 PositionsTotal()-1 降到 0,因为平仓后总持仓数会变,正序删会漏单。PositionSelectByIndex 选中后,PositionGetInteger(POSITION_TYPE) 拿到的是 ORDER_TYPE_BUY 或 ORDER_TYPE_SELL 枚举值,和入参比对才动手。 追踪止损那段更讲究:先卡死 _Symbol 避免动到别的品种,买仓用 Bid - TrailingStop*_Point 算新 SL,卖仓用 Ask + TrailingStop*_Point。只有当新 SL 比原 SL 更优(买仓原 SL 更小、卖仓原 SL 更大)才改,防止回退。 外汇和贵金属杠杆高,追踪止损只是降低回撤概率,遇跳空仍可能滑到更差价位,实盘前务必在 MT5 策略测试器用历史数据跑一遍。

MQL5 / C++
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))
        {
         if(PositionGetInteger(POSITION_TYPE) == orderType)
           {
            if(!trade.PositionClose(ticket)
              {
               Print("Failed to close position. Error code: ", GetLastError());
              }
           }
        }
     }
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;
     
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());
              }
          }
          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());
              }
          }
        }
class="type">void ApplyTrailingStop()
  {
   for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
     {

◍ 遍历持仓改写移动止损的逻辑

这段代码片段演示了如何按索引遍历当前图表品种的持仓,并针对多空方向分别计算追踪止损价。核心判断是:仅当新算出的止损比原有 POSITION_SL 更优时才去改,避免无意义的反复挂改单。 多单用 Bid 作基准,空单用 Ask;TrailingStop 以 _Point 为单位的输入参数(示例里是 25 点)决定回撤距离。若 PositionModify 返回 false,会打印错误码,方便在 MT5 专家日志里定位是成交价偏移还是权限问题。 外汇与贵金属杠杆高,追踪止损只是控制回撤概率的工具,不保证规避黑天鹅跳空。直接把下面代码贴进 EA 的 OnTick 遍历段,把 TrailingStop 从 25 调到 15 观察成交频率变化,是验证该逻辑最直接的做法。

MQL5 / C++
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;
   
   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());
        }
     }
   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());
        }
     }
  }
把信号执行交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 RSI 与均线交叉的实时状态,你只需核对 EA 逻辑是否和盘面一致。

常见问题

较短的 7 期 RSI 对价格变动更敏感,配合 15/35 的极端阈值能更快捕捉超卖与超买反转,但信号噪声也更高,需靠均线过滤。
一次仅持一仓的设计倾向降低回撤与复杂度,趋势延续时会在平仓后按新信号重新介入,可能牺牲部分连续性换取清晰风控。
贵金属波动大,25 点追踪可能频繁扫损;实盘前应按品种波动率重设参数,外汇与贵金属均属高风险,需先模拟验证。
目前小布提供指标状态与信号诊断,代码编写仍需在 MT5 编辑器中完成,但可把小布看到的阈值背离作为 EA 调参参考。
固定点数在跳空时可能滑点成交,建议对重要数据时段禁用 EA 或改用 ATR 动态点数,概率上更适配非常规波动。