返回

EURUSD RSI 1H交易

MQL4 EA · 智能交易系统 v1.0
98video 👁 28 浏览 ⬇ 10 下载 📅 2026-07-13 03:32:58 更新时间: 2026-07-13 03:56:05
下载源码

详情

源代码

MQL4
1//+------------------------------------------------------------------+
//|                                              RSI_EURUSD.mq4      |
//|                        基于RSI指标的反转交易EA(带每方向冷却)      |
//+------------------------------------------------------------------+
#property copyright "Your Name"
#property link      ""
#property version   "1.10"
#property strict

//--- 输入参数(EA可配置的交易参数)
input int      RSI_Period     = 14;       // RSI周期
input double   RSI_Overbought = 70.0;     // 超买阈值(大于此值触发卖出)
input double   RSI_Oversold   = 30.0;     // 超卖阈值(小于此值触发买入)
input double   LotSize        = 0.1;      // 下单手数
input double   TP_Percent     = 0.1;      // 止盈百分比(相对入场价)
input double   SL_Percent     = 0.1;      // 止损百分比(相对入场价)
input int      MagicNumber    = 20260712; // EA魔术号(用于标识本报单)
input int      Slippage       = 3;        // 最大允许滑点(点)
input int      CooldownHours  = 6;        // 每方向冷却小时数(同方向最近开仓后多少小时内不再开新仓)

//+------------------------------------------------------------------+
//| 函数功能:EA初始化函数,校验市场数据与手数合法性后初始化日志        |
//+------------------------------------------------------------------+
int OnInit()
{
   // 检查市场BID报价是否有效,无效则告警并退出初始化
   if(MarketInfo(Symbol(), MODE_BID) <= 0)
   {
      Alert(Symbol(), " market data invalid, EA cannot run.");
      return(INIT_FAILED);
   }
   // 读取当前品种的最小/最大手数与步长,用于校验手数参数
   double minLot  = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot  = MarketInfo(Symbol(), MODE_MAXLOT);
   double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
   // 若用户设置的手数不在允许范围内,弹出告警并退出初始化
   if(LotSize < minLot || LotSize > maxLot)
   {
      Alert("LotSize ", LotSize, " out of range [", minLot, ", ", maxLot, "]");
      return(INIT_FAILED);
   }
   // 输出初始化日志,便于在终端确认EA已正常启动
   Print("RSI_EURUSD initialized on ", Symbol(), " magic=", MagicNumber,
         " lot=", LotSize, " TP%=", TP_Percent, " SL%=", SL_Percent);
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| 函数功能:EA卸载函数,清除图表左上角的Comment信息                   |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // 清空图表注释,避免EA关闭后残留显示
   Comment("");
}

//+------------------------------------------------------------------+
//| 函数功能:新K线检测器,首次调用时记录当前K线时间,后续仅在新K线     |
//|           出现时返回true,避免同一根K线内重复触发交易逻辑             |
//+------------------------------------------------------------------+
bool IsNewBar()
{
   // 静态变量保存上一次检测到的K线开盘时间
   static datetime last_bar_time = 0;
   // 首次调用时记录当前K线时间并返回false,防止初次即触发下单
   if(last_bar_time == 0)
   {
      last_bar_time = Time[0];
      return false;
   }
   // 当前K线时间与上次相同,说明未换K线,返回false
   if(Time[0] == last_bar_time) return false;
   // 时间已变化,更新记录并返回true(新K线)
   last_bar_time = Time[0];
   return true;
}

//+------------------------------------------------------------------+
//| 函数功能:EA主入口函数,每个新K线执行一次RSI信号判断与下单流程        |
//+------------------------------------------------------------------+
void OnTick()
{
   // 新K线门控:仅在新K线出现时才进入交易逻辑,避免重复下单
   if(!IsNewBar()) return;

   // 读取已完成的上一根K线对应的RSI指标值(参数1表示取前一根已收K线)
   double rsi = iRSI(Symbol(), 0, RSI_Period, PRICE_CLOSE, 1);
   // 当RSI返回0.0时通常表示数据尚未准备好,此时跳过本根K线
   if(rsi == 0.0)
   {
      Print("RSI value unavailable, skipping bar.");
      return;
   }

   // 初始化本根K线的信号状态,NONE表示未触发任何交易信号
   string signal = "NONE";

   // 判断RSI是否进入超卖区域,若进入则尝试触发买入信号
   if(rsi < RSI_Oversold)
   {
      // 检查买入方向是否处于冷却期(已有同向持仓或距上次同向开仓未超过冷却时间)
      if(IsCooldownActive(OP_BUY))
      {
         // 冷却中,输出日志说明本次买入被阻止
         Print("BUY blocked by cooldown, rsi=", DoubleToString(rsi, 2));
      }
      else
      {
         // 冷却结束,执行买入下单并更新信号状态
         OpenBuy(rsi);
         signal = "BUY";
      }
   }
   // 否则判断RSI是否进入超买区域,若进入则尝试触发卖出信号
   else if(rsi > RSI_Overbought)
   {
      // 检查卖出方向是否处于冷却期
      if(IsCooldownActive(OP_SELL))
      {
         // 冷却中,输出日志说明本次卖出被阻止
         Print("SELL blocked by cooldown, rsi=", DoubleToString(rsi, 2));
      }
      else
      {
         // 冷却结束,执行卖出下单并更新信号状态
         OpenSell(rsi);
         signal = "SELL";
      }
   }

   // 读取两个方向的最近开仓时间(基于历史订单),用于显示剩余冷却时间
   datetime lastBuyOpen  = GetLastOpenTime(OP_BUY);
   datetime lastSellOpen = GetLastOpenTime(OP_SELL);

   // 统计当前仍处于持仓状态的同EA订单数量
   int posCount = 0;
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      // 从后向前遍历所有持仓订单,筛选本EA的持仓
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
         if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol())
            posCount++;
   }

   // 在图表左上角显示EA状态信息(含信号、持仓与冷却时间)
   Comment("=== RSI_EURUSD ===\n",
           "RSI[1]: ", DoubleToString(rsi, 2), "\n",
           "Signal: ", signal, "\n",
           "Open positions: ", posCount, "\n",
           "Cooldown BUY: ",  FormatRemaining(lastBuyOpen),  "\n",
           "Cooldown SELL: ", FormatRemaining(lastSellOpen));
}

//+------------------------------------------------------------------+
//| 函数功能:以ASK价格开立买入市价单,止损与止盈按入场价±百分比计算     |
//+------------------------------------------------------------------+
void OpenBuy(double rsi)
{
   // 取当前ASK报价与品种小数位数,用于价格精度处理
   double ask   = MarketInfo(Symbol(), MODE_ASK);
   int    digits = (int)MarketInfo(Symbol(), MODE_DIGITS);

   // 买入止损价 = 入场价*(1-止损%);止盈价 = 入场价*(1+止盈%)
   double sl = NormalizeDouble(ask * (1.0 - SL_Percent / 100.0), digits);
   double tp = NormalizeDouble(ask * (1.0 + TP_Percent / 100.0), digits);
   double price = NormalizeDouble(ask, digits);   // 入场价即为当前ASK

   // 发送买入市价单:箭头颜色蓝色,注释和魔术号用于识别
   int ticket = OrderSend(Symbol(), OP_BUY, LotSize, price, Slippage,
                          sl, tp, "RSI_EURUSD", MagicNumber, 0, clrBlue);
   // 下单失败时打印错误码与价格信息,便于排查
   if(ticket < 0)
      Print("OpenBuy FAILED error=", GetLastError(),
            " ask=", ask, " sl=", sl, " tp=", tp);
   // 下单成功时打印成交回执与触发时的RSI值
   else
      Print("OpenBUY ticket=", ticket, " price=", price, " sl=", sl, " tp=", tp,
            " rsi=", DoubleToString(rsi, 2));
}

//+------------------------------------------------------------------+
//| 函数功能:以BID价格开立卖入市价单,止损与止盈按入场价±百分比计算     |
//+------------------------------------------------------------------+
void OpenSell(double rsi)
{
   // 取当前BID报价与品种小数位数
   double bid   = MarketInfo(Symbol(), MODE_BID);
   int    digits = (int)MarketInfo(Symbol(), MODE_DIGITS);

   // 卖出止损价 = 入场价*(1+止损%);止盈价 = 入场价*(1-止盈%)
   double sl = NormalizeDouble(bid * (1.0 + SL_Percent / 100.0), digits);
   double tp = NormalizeDouble(bid * (1.0 - TP_Percent / 100.0), digits);
   double price = NormalizeDouble(bid, digits);   // 入场价即为当前BID

   // 发送卖入市价单:箭头颜色红色,注释和魔术号用于识别
   int ticket = OrderSend(Symbol(), OP_SELL, LotSize, price, Slippage,
                          sl, tp, "RSI_EURUSD", MagicNumber, 0, clrRed);
   // 下单失败时打印错误码与价格信息
   if(ticket < 0)
      Print("OpenSell FAILED error=", GetLastError(),
            " bid=", bid, " sl=", sl, " tp=", tp);
   // 下单成功时打印成交回执与触发时的RSI值
   else
      Print("OpenSELL ticket=", ticket, " price=", price, " sl=", sl, " tp=", tp,
            " rsi=", DoubleToString(rsi, 2));
}

//+------------------------------------------------------------------+
//| 函数功能:判断指定方向是否处于冷却期(同向持仓或距最近开仓未超冷却)|
//+------------------------------------------------------------------+
bool IsCooldownActive(int op)
{
   // 先遍历当前持仓,若发现同方向持仓则冷却期生效,立即返回true
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         // 筛选条件:本EA魔术号 + 当前品种 + 同方向
         if(OrderMagicNumber() == MagicNumber
            && OrderSymbol() == Symbol()
            && OrderType() == op)
            return(true);
      }
   }

   // 读取该方向最近一次开仓时间(通过历史订单查找)
   datetime lastOpen = GetLastOpenTime(op);
   // 若该方向从未有过成交记录,不冷却
   if(lastOpen == 0) return(false);
   // 若距上次同向开仓时间未超过设定的冷却小时数,则冷却期仍生效
   if(TimeCurrent() - lastOpen < CooldownHours * 3600) return(true);
   // 冷却期已过,返回false允许下单
   return(false);
}

//+------------------------------------------------------------------+
//| 函数功能:将剩余冷却时间格式化为 "Xh YYm" 字符串,无冷却时返回"--"   |
//+------------------------------------------------------------------+
string FormatRemaining(datetime lastOpen)
{
   // 若没有该方向的开仓记录(值为0),直接返回占位符 "--"
   if(lastOpen == 0) return("--");
   // 计算已过去秒数与剩余秒数
   int elapsed = (int)(TimeCurrent() - lastOpen);
   int remaining = CooldownHours * 3600 - elapsed;
   // 剩余时间不大于0说明冷却已结束,返回占位符 "--"
   if(remaining <= 0) return("--");
   // 将剩余秒数转换为小时与分钟两部分并拼接为可读字符串
   int h = remaining / 3600;
   int m = (remaining % 3600) / 60;
   return(IntegerToString(h) + "h " + IntegerToString(m) + "m");
}

//+------------------------------------------------------------------+
//| 函数功能:从历史订单中查找指定方向最近一次成交的开仓时间             |
//+------------------------------------------------------------------+
datetime GetLastOpenTime(int op)
{
   // 从最新的历史订单开始向前遍历,找到第一笔匹配方向即返回其开仓时间
   for(int i = OrdersHistoryTotal() - 1; i >= 0; i--)
   {
      if(!OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) continue;
      // 仅处理本EA、本品种的订单
      if(OrderMagicNumber() != MagicNumber) continue;
      if(OrderSymbol() != Symbol()) continue;
      // 找到同方向第一笔即返回(历史按时间倒序,第一笔即最新)
      if(OrderType() == op)
         return(OrderOpenTime());
   }
   // 该方向无历史成交记录,返回0表示无冷却
   return(0);
}