返回

EURUSD RSI 1H交易
MQL4
EA · 智能交易系统
v1.0
98video
👁 28 浏览
⬇ 10 下载
📅 2026-07-13 03:32:58
更新时间: 2026-07-13 03:56:05
详情
源代码
MQL41//+------------------------------------------------------------------+
//| 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);
}