How to Code an RSI EA in MQL5 (Full Example + Real Backtest)
The Relative Strength Index is one of the first indicators most traders reach for, and "how do I turn my RSI rule into an EA?" is a question that comes up constantly. The idea is simple — buy when RSI comes back up out of oversold, sell when it drops out of overbought — but the MQL5 details trip people up: RSI in MQL5 is a *handle*, not a value, you read it with CopyBuffer, and you only want to act once per bar. Below is a complete, compiling Expert Advisor that does it correctly, followed by a real backtest of the exact code.
The strategy
A classic RSI(14) mean-reversion rule on the H1 chart:
- Long when RSI crosses *up* through 30 (leaving oversold).
- Short when RSI crosses *down* through 70 (leaving overbought).
- Exit when RSI returns to the 50 midline, with a fixed stop loss and take profit as a safety net.
The key word is *crosses*. You compare the last two closed bars — you do not fire every time RSI happens to be below 30, or you'll open a trade on every tick while it sits there.
The MQL5 details that matter
Three things separate MQL5 from old MQL4 code and from most AI-generated snippets:
- RSI is a handle. Call
iRSI(_Symbol, _Period, 14, PRICE_CLOSE)once inOnInit()and store the returned handle. You read its value later withCopyBuffer, not by callingiRSIagain on every tick. - Read the buffer as a series.
ArraySetAsSeries(buffer, true)makes index0the current (still-forming) bar,1the last *closed* bar,2the one before it. That's what lets you detect a cross between two completed bars. - Act on new bars only. Check
SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE)and skip the tick if the bar hasn't changed. Signals should be evaluated on closed candles, not mid-candle noise.
The full EA
#property version "1.00"
#property strict
#include <Trade/Trade.mqh>
input int InpRsiPeriod = 14;
input double InpOversold = 30.0;
input double InpOverbought = 70.0;
input double InpExitLevel = 50.0;
input double InpLots = 0.10;
input int InpStopLossPts = 500; // stop loss in points
input int InpTakeProfPts = 900; // take profit in points
input int InpMagic = 528491;
CTrade c_Trade;
int h_RSI = INVALID_HANDLE;
double g_rsi[];
datetime g_lastBar = 0;
int OnInit()
{
h_RSI = iRSI(_Symbol, _Period, InpRsiPeriod, PRICE_CLOSE);
if(h_RSI == INVALID_HANDLE) { Print("iRSI failed, err=", GetLastError()); return(INIT_FAILED); }
ArraySetAsSeries(g_rsi, true);
c_Trade.SetExpertMagicNumber(InpMagic);
c_Trade.SetTypeFillingBySymbol(_Symbol);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
if(h_RSI != INVALID_HANDLE) IndicatorRelease(h_RSI);
}
bool IsNewBar()
{
datetime t = (datetime)SeriesInfoInteger(_Symbol, _Period, SERIES_LASTBAR_DATE);
if(t == g_lastBar) return(false);
g_lastBar = t;
return(true);
}
bool HasPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetTicket(i) == 0) continue;
if(PositionGetString(POSITION_SYMBOL) == _Symbol &&
PositionGetInteger(POSITION_MAGIC) == InpMagic) return(true);
}
return(false);
}
void OnTick()
{
if(!IsNewBar()) return;
if(CopyBuffer(h_RSI, 0, 0, 3, g_rsi) < 3) return;
double rsiPrev = g_rsi[2]; // bar before last closed
double rsiNow = g_rsi[1]; // last closed bar
if(HasPosition())
{
long type = PositionGetInteger(POSITION_TYPE);
if(type == POSITION_TYPE_BUY && rsiNow >= InpExitLevel) c_Trade.PositionClose(_Symbol);
if(type == POSITION_TYPE_SELL && rsiNow <= InpExitLevel) c_Trade.PositionClose(_Symbol);
return;
}
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double pt = _Point;
if(rsiPrev <= InpOversold && rsiNow > InpOversold) // cross up out of oversold -> long
{
double sl = InpStopLossPts > 0 ? NormalizeDouble(ask - InpStopLossPts * pt, _Digits) : 0;
double tp = InpTakeProfPts > 0 ? NormalizeDouble(ask + InpTakeProfPts * pt, _Digits) : 0;
c_Trade.Buy(InpLots, _Symbol, ask, sl, tp);
}
else if(rsiPrev >= InpOverbought && rsiNow < InpOverbought) // cross down out of overbought -> short
{
double sl = InpStopLossPts > 0 ? NormalizeDouble(bid + InpStopLossPts * pt, _Digits) : 0;
double tp = InpTakeProfPts > 0 ? NormalizeDouble(bid - InpTakeProfPts * pt, _Digits) : 0;
c_Trade.Sell(InpLots, _Symbol, bid, sl, tp);
}
}
double OnTester() { return TesterStatistics(STAT_PROFIT); }
A few things worth calling out: every position loop is filtered by both symbol and magic number, so this EA never touches trades it didn't open; stops and targets are normalized to the symbol's digit count; and the handle is released in OnDeinit(). Those are the details that keep an EA correct on a real 5-digit broker.
A real backtest of this exact code
Paste that into MetaEditor, compile (it builds with 0 errors, 0 warnings), and run it in the Strategy Tester. Here is the result of running this exact source on EURUSD, H1, full-year 2024 with the default inputs above and a $10,000 starting balance:
- Net profit: +$340
- Win rate: 66%
- Profit factor: 1.27
- Max drawdown: 2.8%
- Trades: 122
That's a modest, low-drawdown result on unoptimized defaults — exactly what you'd expect from a plain RSI rule with no filters. It's a starting point, not a finished system. Past performance does not guarantee future results, and you should always test across multiple symbols and market regimes before trusting any EA.
If you'd rather describe it than code it
Writing, compiling, and backtesting an EA by hand is the reliable way to learn, but it's slow to iterate. Ranar Algo lets you type the strategy in plain English — "RSI 14 on H1, buy when it crosses up through 30, sell when it crosses down through 70, exit at 50, 50-pip stop" — and it generates the MQL5, compiles it, and runs a real MetaTrader 5 backtest, returning the same kind of numbers you see above. You keep full ownership of the generated code — it's your IP to take into MetaEditor and modify. It's still improving and feedback is genuinely welcome; the goal is to shorten the loop between an idea and seeing whether it holds up.
Wrapping up
An RSI EA is a great first Expert Advisor because the logic is easy to reason about, which makes the MQL5 mechanics — handles, CopyBuffer, series indexing, new-bar timing — easy to see in isolation. Get those right and you can swap in any indicator. The code above compiles as-is and the backtest numbers are from that exact source, so you have a clean, honest baseline to build on.