RSI Strategy for MetaTrader 5:
Working MQL5 Code + Backtest
This Expert Advisor (EA) monitors the RSI indicator and opens buy positions when RSI enters oversold territory, then closes when RSI reaches overbought levels. Unlike PineScript strategies that run on chart data, MQL5 EAs execute real trades through your broker. This EA includes proper order management, lot sizing, and magic number identification to avoid conflicts with other EAs running on the same account.
What is Relative Strength Index?
The Relative Strength Index (RSI) is a momentum oscillator that measures the speed and magnitude of recent price changes. Developed by J. Welles Wilder in 1978, RSI oscillates between 0 and 100 and is traditionally used to identify overbought and oversold conditions in a market. When RSI rises above 70, the asset is considered overbought and may be due for a pullback. When it drops below 30, it is considered oversold and may be due for a bounce.
| Parameter | Value |
|---|---|
| overbought | 70 |
| oversold | 30 |
| midline | 50 |
RSI Expert Advisor: The Setup
Entry Rules
- ▶Open a buy order when RSI(14) drops below 30
- ▶Fixed lot size of 0.1 (configurable via input)
- ▶Only one trade open at a time — checks for existing positions before entering
Exit Rules
- ■Close the buy position when RSI(14) rises above 70
- ■Optional stop-loss parameter (default: 0 = disabled)
- ■Optional take-profit parameter (default: 0 = disabled)
Working MQL5 Code
This MQL5 Expert Advisor uses the standard iRSI() function to create an indicator handle in OnInit(). On every tick, it copies the latest RSI values into a buffer and checks for crossover conditions. Position management uses MagicNumber to identify only positions opened by this EA, preventing conflicts with manual trades or other EAs. The entry uses TRADE_ACTION_DEAL for instant execution with optional stop-loss and take-profit levels. The exit loop iterates through all positions to find and close the matching one.
//+------------------------------------------------------------------+
//| RSI Mean Reversion EA.mq5 |
//| Copyright 2026, SpendDock |
//+------------------------------------------------------------------+
#property copyright "SpendDock"
#property version "1.00"
// === INPUT PARAMETERS ===
input int RSI_Period = 14; // RSI Period
input int Overbought = 70; // Overbought Level
input int Oversold = 30; // Oversold Level
input double LotSize = 0.1; // Lot Size
input int StopLoss = 0; // Stop Loss in points (0=disabled)
input int TakeProfit = 0; // Take Profit in points (0=disabled)
input int MagicNumber = 12345; // Magic Number
// === GLOBAL VARIABLES ===
int rsiHandle;
double rsiBuffer[];
//+------------------------------------------------------------------+
int OnInit()
{
rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
if(rsiHandle == INVALID_HANDLE)
{
Print("Failed to create RSI indicator handle");
return INIT_FAILED;
}
ArraySetAsSeries(rsiBuffer, true);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(rsiHandle != INVALID_HANDLE)
IndicatorRelease(rsiHandle);
}
//+------------------------------------------------------------------+
void OnTick()
{
// Copy RSI values
if(CopyBuffer(rsiHandle, 0, 0, 3, rsiBuffer) < 3)
return;
double rsiCurrent = rsiBuffer[0];
double rsiPrevious = rsiBuffer[1];
// Check for existing position
bool hasPosition = false;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetSymbol(i) == _Symbol)
{
if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
{
hasPosition = true;
break;
}
}
}
// Entry: RSI crosses below oversold
if(!hasPosition && rsiPrevious >= Oversold && rsiCurrent < Oversold)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = (StopLoss > 0) ? ask - StopLoss * _Point : 0;
double tp = (TakeProfit > 0) ? ask + TakeProfit * _Point : 0;
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = LotSize;
request.type = ORDER_TYPE_BUY;
request.price = ask;
request.sl = sl;
request.tp = tp;
request.magic = MagicNumber;
request.comment = "RSI Oversold Entry";
request.deviation = 10;
if(!OrderSend(request, result))
Print("OrderSend failed: ", GetLastError());
}
// Exit: RSI crosses above overbought
if(hasPosition && rsiPrevious <= Overbought && rsiCurrent > Overbought)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetSymbol(i) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
{
ulong ticket = PositionGetInteger(POSITION_TICKET);
MqlTradeRequest request = {};
MqlTradeResult result = {};
request.action = TRADE_ACTION_DEAL;
request.symbol = _Symbol;
request.volume = PositionGetDouble(POSITION_VOLUME);
request.type = ORDER_TYPE_SELL;
request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
request.position = ticket;
request.magic = MagicNumber;
request.comment = "RSI Overbought Exit";
request.deviation = 10;
if(!OrderSend(request, result))
Print("Close order failed: ", GetLastError());
}
}
}
}Backtest Results: EURUSD (1D, 2019-2025)
Total Return
+22.4%
Annualized
+3.4%
Win Rate
57%
Max Drawdown
-11.8%
Sharpe Ratio
0.62
Total Trades
63
Profit Factor
1.42
Past performance does not guarantee future results. Backtest results may not reflect actual trading conditions including slippage, commissions, and liquidity constraints.
Optimization Tips
Add a spread filter to avoid entering during high-spread periods. Check `SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)` before placing orders and skip if spread exceeds a threshold.
Implement a time filter to only trade during the London and New York sessions when forex liquidity is highest. Avoid Asian session entries for major pairs.
Use the ATR (Average True Range) for dynamic stop-loss sizing instead of fixed points. This adapts to current volatility conditions.
Add a trend filter using a 200-period moving average. Only take RSI oversold longs when price is above the 200 MA.
Test on multiple currency pairs. RSI mean reversion tends to work better on range-bound pairs like EURCHF or AUDNZD than trending pairs like USDJPY.
Common Mistakes with RSI on MetaTrader 5
Forgetting to set the MagicNumber, causing the EA to interfere with manual trades or other EAs on the same account.
Backtesting with the default spread setting in MT5 Strategy Tester. Always use 'Every tick based on real ticks' mode and verify spread settings match your broker's typical spreads.
Not handling the TRADE_RETCODE responses from OrderSend(). In live trading, orders can fail due to requotes, insufficient margin, or connection issues. Always check result.retcode.
Running the EA on a timeframe different from what it was backtested on. If backtested on D1, running it on H1 will generate completely different (and untested) signals.
Build This Strategy in 60 Seconds with SpendDock
Instead of copying and debugging this code, describe your strategy in plain English. SpendDock generates the MQL5 code, backtests it against real market data, and lets you iterate until it is right.
Try SpendDock Free