RSI Multi-Timeframe Analysis in MQL5: What Actually Works for Scalping

11 February 2026, 11:17
Jaume Sancho Serra
0
55
RSI Multi-Timeframe Analysis in MQL5: What Actually Works for Scalping

I've been running RSI strategies on XAUUSD for about 18 months now, and honestly, single-timeframe RSI gave me more headaches than profits. The standard RSI(14) on M15 looked great in backtests—until I went live in October and watched it get chopped to pieces during the London open.

That's when I rebuilt everything around multi-timeframe confirmation. And yeah, it's one of those "why didn't I do this sooner" moments. Here's what I learned (the hard way).


Why Single-TF RSI Fails on Fast Pairs

The problem with using RSI on just one timeframe—especially for scalping Gold or major pairs—is that you're basically flying blind. You might see RSI < 30 on M15, think "oversold, time to buy," and then watch the price drop another 50 pips because H1 and H4 are both screaming "downtrend."

I lost count of how many times this happened in November before I finally got it through my thick skull: you need context from higher timeframes.

Multi-timeframe confirmation solves this by:

  • Filtering out trades that contradict the bigger picture
  • Catching high-probability setups where multiple timeframes align
  • Reducing whipsaws during consolidation (wich Gold loves to do)

The Setup: RSI Convergence Strategy

The basic idea is simple: only take trades when RSI signals align across at least 3 timeframes.

For example, my current setup for XAUUSD buys requires:

  • M15 RSI < 30 (strongly oversold)
  • M30 RSI < 35 (approaching oversold)
  • H1 RSI < 45 (below midline, not overbought)

When all three hit, that's my "convergence zone" (trust me on this). Win rate went from 42% (single TF) to 61% (multi TF) between October and December last year. Real money, Pepperstone account. For context: that's 127 trades, profit factor of 1.8, max drawdown 4.2%.

MQL5 Implementation (How I Built It)

Let me show you the actual code I'm running. It's not fancy, but it works.

Step 1: Create RSI Handles

In MQL5, you need indicator handles for each timeframe. This took me a while to figure out because I kept trying to create them inside OnTick() (rookie mistake—massive memory leak).

// Global variables
int rsiHandleM15, rsiHandleM30, rsiHandleH1;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Create RSI handles once, not every tick
   rsiHandleM15 = iRSI(_Symbol, PERIOD_M15, 14, PRICE_CLOSE);
   rsiHandleM30 = iRSI(_Symbol, PERIOD_M30, 14, PRICE_CLOSE);
   rsiHandleH1  = iRSI(_Symbol, PERIOD_H1,  14, PRICE_CLOSE);
   
   if(rsiHandleM15 == INVALID_HANDLE || rsiHandleM30 == INVALID_HANDLE || 
      rsiHandleH1 == INVALID_HANDLE)
   {
      Print("Error creating RSI handles - check your inputs");
      return(INIT_FAILED);
   }
   
   return(INIT_SUCCEEDED);
}


Step 2: Read RSI Values

Helper function to grab RSI from any handle. I use this constantly:

//+------------------------------------------------------------------+
//| Get RSI value from handle                                        |
//| Note: shift=0 reads current (forming) bar, shift=1 reads last    |
//| closed bar. For confirmation signals, shift=1 is more reliable.  |
//+------------------------------------------------------------------+
double GetRSI(int handle, int shift = 0)
{
   double rsiBuffer[1];  // Pre-sized for 1 element
   ArraySetAsSeries(rsiBuffer, true);
   
   if(CopyBuffer(handle, 0, shift, 1, rsiBuffer) != 1)
   {
      Print("RSI buffer copy failed, error: ", GetLastError());
      return -1.0;
   }
   
   return rsiBuffer[0];
}

Pro tip: For the entry timeframe (M15), I use  shift=0  to catch the signal as it forms. But for confirmation timeframes (M30, H1), I often use  shift=1  to read the closed bar—this avoids false signals from bars still in formation. More on this below.


Step 3: Convergence Logic

This is where it gets interesting. My first version had like 8 different conditions and was a mess. Current version is way simpler:

//+------------------------------------------------------------------+
//| Check for multi-TF buy setup                                     |
//+------------------------------------------------------------------+
bool IsMultiTFBuySetup()
{
   // Entry TF: current bar (forming)
   double rsi_m15 = GetRSI(rsiHandleM15, 0);
   
   // Confirmation TFs: closed bar (more reliable)
   double rsi_m30 = GetRSI(rsiHandleM30, 1);
   double rsi_h1  = GetRSI(rsiHandleH1, 1);
   
   // Sanity check - if any RSI read failed, bail
   if(rsi_m15 < 0 || rsi_m30 < 0 || rsi_h1 < 0)
      return false;
   
   // Buy convergence: lower TFs oversold, H1 not overbought
   if(rsi_m15 < 30 &&     // M15 strongly oversold
      rsi_m30 < 35 &&     // M30 approaching oversold  
      rsi_h1 < 45)        // H1 supporting (below mid)
   {
      return true;
   }
   
   return false;
}

//+------------------------------------------------------------------+
//| Check for multi-TF sell setup                                    |
//+------------------------------------------------------------------+
bool IsMultiTFSellSetup()
{
   double rsi_m15 = GetRSI(rsiHandleM15, 0);
   double rsi_m30 = GetRSI(rsiHandleM30, 1);
   double rsi_h1  = GetRSI(rsiHandleH1, 1);
   
   if(rsi_m15 < 0 || rsi_m30 < 0 || rsi_h1 < 0)
      return false;
   
   // Sell convergence: lower TFs overbought, H1 not oversold
   if(rsi_m15 > 70 &&     // M15 strongly overbought
      rsi_m30 > 65 &&     // M30 approaching overbought  
      rsi_h1 > 55)        // H1 supporting (above mid)
   {
      return true;
   }
   
   return false;
}

Step 4: Actually Taking the Trade

Integrate into OnTick(). Pro tip: only check for signals on new bars, not every tick. Otherwise you'll spam yourself with duplicate trades (been there).

#include 

CTrade trade;  // Trade execution object

//+------------------------------------------------------------------+
//| Expert tick function                                            |
//+------------------------------------------------------------------+
void OnTick()
{
   // Only check on new M15 bar
   static datetime lastBar = 0;
   datetime currentBar = iTime(_Symbol, PERIOD_M15, 0);
   
   if(currentBar == lastBar)
      return;  // Not a new bar yet
   
   lastBar = currentBar;
   
   // Don't trade if we already have a position
   // Note: For hedging accounts, use PositionsTotal() with a loop instead
   if(PositionSelect(_Symbol))
      return;
   
   // Check convergence
   if(IsMultiTFBuySetup())
   {
      Print("Multi-TF BUY convergence detected");
      ExecuteBuy();
   }
   else if(IsMultiTFSellSetup())
   {
      Print("Multi-TF SELL convergence detected");
      ExecuteSell();
   }
}

//+------------------------------------------------------------------+
//| Execute buy order                                                |
//+------------------------------------------------------------------+
void ExecuteBuy()
{
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double sl = ask - 200 * _Point;  // 20 pips SL (adjust for your pair)
   double tp = ask + 400 * _Point;  // 40 pips TP (2:1 R:R)
   
   trade.Buy(0.1, _Symbol, ask, sl, tp, "RSI Multi-TF Buy");
}

//+------------------------------------------------------------------+
//| Execute sell order                                               |
//+------------------------------------------------------------------+
void ExecuteSell()
{
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double sl = bid + 200 * _Point;
   double tp = bid - 400 * _Point;
   
   trade.Sell(0.1, _Symbol, bid, sl, tp, "RSI Multi-TF Sell");
}

What I Learned After 3 Months Live

Okay, real talk: multi-TF RSI isn't a magic bullet. Here's what works and what doesn't.

What Works:

  • Gold during London-NY overlap (13:00-17:00 GMT) - this setup kills it. The London session itself (08:00-16:30 GMT) is good, but the overlap with NY is where the real moves happen.
  • EURUSD during NY-London overlap - decent win rate
  • Lower timeframes for entry, higher for trend context - this is key

What Doesn't Work:

  • Asian session on anything - too choppy, convergence signals are trash
  • First 30 min of news events - RSI goes haywire (lost $180 in 12 minutes during NFP in November, never again)
  • Pairs with high spreads - For XAUUSD, anything above $0.30-0.40 spread eats your edge. For forex pairs, keep it under 1 pip on majors.

Stuff I'm Still Testing:

  • Using H4 RSI as a "veto" (if H4 > 70, ignore all M15 buy signals)
  • Dynamic thresholds based on ATR (when ATR(14) on H1 spikes above $3.00, normal RSI thresholds might not apply)
  • Adding a simple MA filter to avoid ranging markets

Alternative: Weighted RSI Score

If strict thresholds feel too rigid (they do sometimes), try this approach instead. I built a second version that uses weighted scores:

//+------------------------------------------------------------------+
//| Calculate weighted RSI score across timeframes                   |
//+------------------------------------------------------------------+
double CalculateRSIScore()
{
   double score = 0.0;
   
   double rsi_m15 = GetRSI(rsiHandleM15, 0);
   double rsi_m30 = GetRSI(rsiHandleM30, 1);
   double rsi_h1  = GetRSI(rsiHandleH1, 1);
   
   // Sanity check
   if(rsi_m15 < 0 || rsi_m30 < 0 || rsi_h1 < 0)
      return 0.0;  // Neutral on error
   
   // Normalize to -50/+50 range (RSI 50 = neutral)
   // Higher TFs get more weight (they're more reliable)
   
   score += (rsi_m15 - 50) * 0.2;  // 20% weight (entry signal)
   score += (rsi_m30 - 50) * 0.3;  // 30% weight (confirmation)
   score += (rsi_h1  - 50) * 0.5;  // 50% weight (trend context)
   
   // Score > +15 = sell signal
   // Score < -15 = buy signal
   // -10 to +10 = stay out (choppy/neutral)
   
   return score;
}

This gives you more nuance than binary yes/no. A score of -25 is way more confident than -16, and you can size positions accordingly. I weight H1 at 50% because the higher timeframe trend is the most important filter—fighting it is a losing game.

I'm still forward-testing this version (started Jan 15), but early results look promising. It avoids some of the false signals that the strict threshold method hits.

Common Mistakes (I Made All of These)

Mistake #1: Waiting for ALL timeframes to be extreme
Don't require M15, M30, AND H1 to all be < 30. You'll get like 2 signals per month. Use graduated thresholds (30/35/45) instead.

Mistake #2: Ignoring time-of-day
Multi-TF RSI works during active sessions. During Asian hours or late Friday? Forget it. Add session filters.

Mistake #3: Forgetting to release handles
Memory leak city. Always clean up in OnDeinit():

void OnDeinit(const int reason)
{
   IndicatorRelease(rsiHandleM15);
   IndicatorRelease(rsiHandleM30);
   IndicatorRelease(rsiHandleH1);
   
   Print("RSI handles released");
}


Mistake #4: No ATR filter
If Gold's ATR(14) on H1 spikes above $3.00 (crazy volatility, like during FOMC), your normal RSI thresholds might not apply. I learned this the hard way. Consider widening your thresholds or staying out entirely during high-ATR periods.

Mistake #5: Using shift=0 for confirmation timeframes
When you read RSI from a higher timeframe with  shift=0 , you're reading the bar that's still forming—it can change before it closes. For true "confirmation," use  shift=1  on M30 and H1 to read the last closed bar. This reduces false signals significantly.

Mistake #6: Not accounting for hedging vs netting accounts
PositionSelect(_Symbol)  works great on netting accounts (one position per symbol). But on hedging accounts (common with Pepperstone, IC Markets), you can have multiple positions on the same symbol. Use  PositionsTotal()  with a loop to check properly.

Where to Go From Here

If you want to try this, start with just 3 timeframes (M15, M30, H1) and keep it simple. Don't overthink it.

My full EA (with all the session filters, ATR checks, and risk management) is in my GitHub portfolio: github.com/jimmer89/mql5-portfolio. The indicator version (just shows you when convergence happens) is in MQL5 CodeBase—search "RSI MultiTF Scanner."

For backtesting, use MT5's Strategy Tester in "Every tick based on real ticks" mode. Multi-timeframe EAs need proper tick data to simulate correctly—"Open prices only" will give you garbage results.

I'm also testing a version with divergence detection across timeframes, but that's still in the "does this actually work or am I just curve-fitting" phase.

Bottom Line

Multi-timeframe RSI isn't revolutionary—it's just RSI done properly. Single-TF analysis is like trying to drive by only looking at the dashboard. You need to look out the windshield too.

Does it work 100% of the time? Nope. But it filters out enough garbage trades that my win rate improved by almost 20%, and that's what matters.

If you're running single-TF RSI right now and getting chopped up, try this. Worst case, you waste a weekend coding it. Best case, you fix your strategy.

Let me know if you build something with this. Always curious to see what people come up with.

Jaume S.(WhiteChocolate on MQL5.com) - Freelance MQL5 dev, Gold scalping addict. Currently running this on 2 live accounts + 1 demo for testing new filters.