实现 Deus EA:使用 MQL5 中的 RSI 和移动平均线进行自动交易·进阶篇
(2/3)· 从指标参数到订单管理的完整拼图,手把手把 Deus EA 的买卖规则写进 MT5
很多交易者把 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 跑之前先确认指标句柄参数没写错。
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 跑一遍验证胜率与回撤。
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,改参数前先确认品种精度。
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 策略测试器用历史数据跑一遍。
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 观察成交频率变化,是验证该逻辑最直接的做法。
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()); } } }