There is a specific type of YouTube video that gets millions of views.

The thumbnail shows a chart with a few arrows pointing at RSI signals. Big green candles follow every buy signal. Big red candles follow every sell. The title says something like “RSI Strategy: 91% Win Rate — Full Tutorial.”

The comment section is full of people saying they made money last week. Nobody comes back three months later to update their comment.

I wanted to know what happens when you actually test these things. Not on cherry-picked charts. Not with hindsight. On 15 years of real data, same rules, every signal, no exceptions.

Here’s the autopsy.


The three strategies

These are the most commonly taught RSI strategies online. I picked them because they show up everywhere — beginner courses, trading blogs, YouTube tutorials with millions of views.

Strategy 1 — RSI Classic (70/30) Buy when RSI crosses above 30 from below. Sell when RSI crosses below 70 from above. RSI period: 14. This is literally the first thing most people learn about RSI.

Strategy 2 — RSI Divergence Buy when price makes a lower low but RSI makes a higher low (bullish divergence). Sell when price makes a higher high but RSI makes a lower high (bearish divergence). RSI period: 14. Taught as a “more sophisticated” version.

Strategy 3 — RSI + 200 MA Same as Strategy 1, but only take signals when price is above the 200-day moving average for longs, below for shorts. Supposed to “filter out bad signals” by adding trend context.

Test setup:

  • Instrument: SPY (S&P 500 ETF)
  • Period: January 2010 to December 2024 (15 years, ~3,775 trading days)
  • Starting capital: $10,000
  • Position sizing: 100% of available capital per trade
  • No commissions for the base test (then I add commissions to see what happens)
  • No leverage

The Python code

import pandas as pd
import numpy as np
import yfinance as yf

def compute_rsi(series: pd.Series, period: int = 14) -> pd.Series:
    delta = series.diff()
    gain  = delta.clip(lower=0)
    loss  = -delta.clip(upper=0)
    avg_gain = gain.ewm(alpha=1/period, min_periods=period, adjust=False).mean()
    avg_loss = loss.ewm(alpha=1/period, min_periods=period, adjust=False).mean()
    rs  = avg_gain / avg_loss.replace(0, np.nan)
    rsi = 100 - (100 / (1 + rs))
    return rsi

# Download data
spy = yf.download('SPY', start='2010-01-01', end='2025-01-01', auto_adjust=True)
spy = spy['Close'].squeeze()
spy = spy.to_frame(name='close')
spy['rsi'] = compute_rsi(spy['close'])
spy['ma200'] = spy['close'].rolling(200).mean()

# ── STRATEGY 1: RSI Classic 70/30 ──
def strategy_classic(df):
    signals = pd.Series(0, index=df.index)
    in_trade = False
    for i in range(1, len(df)):
        if pd.isna(df['rsi'].iloc[i]): continue
        if not in_trade and df['rsi'].iloc[i-1] <= 30 and df['rsi'].iloc[i] > 30:
            signals.iloc[i] = 1   # buy
            in_trade = True
        elif in_trade and df['rsi'].iloc[i-1] >= 70 and df['rsi'].iloc[i] < 70:
            signals.iloc[i] = -1  # sell
            in_trade = False
    return signals

# ── STRATEGY 2: RSI Divergence ──
def find_divergence(df, lookback=20):
    signals = pd.Series(0, index=df.index)
    for i in range(lookback, len(df)):
        window_p   = df['close'].iloc[i-lookback:i+1]
        window_rsi = df['rsi'].iloc[i-lookback:i+1]
        if pd.isna(window_rsi.iloc[-1]): continue
        # Bullish divergence: price lower low, RSI higher low
        p_min_idx   = window_p.idxmin()
        rsi_min_idx = window_rsi.idxmin()
        if (window_p.iloc[-1]   < window_p.min()   * 1.02 and
            window_rsi.iloc[-1] > window_rsi.min() * 1.05):
            signals.iloc[i] = 1
        # Bearish divergence: price higher high, RSI lower high
        elif (window_p.iloc[-1]   > window_p.max()   * 0.98 and
              window_rsi.iloc[-1] < window_rsi.max() * 0.95):
            signals.iloc[i] = -1
    return signals

# ── STRATEGY 3: RSI + 200 MA ──
def strategy_with_ma(df):
    signals = pd.Series(0, index=df.index)
    in_trade = False
    for i in range(1, len(df)):
        if pd.isna(df['rsi'].iloc[i]) or pd.isna(df['ma200'].iloc[i]): continue
        above_ma = df['close'].iloc[i] > df['ma200'].iloc[i]
        if not in_trade and df['rsi'].iloc[i-1] <= 30 and df['rsi'].iloc[i] > 30 and above_ma:
            signals.iloc[i] = 1
            in_trade = True
        elif in_trade and df['rsi'].iloc[i-1] >= 70 and df['rsi'].iloc[i] < 70:
            signals.iloc[i] = -1
            in_trade = False
    return signals

# ── BACKTEST ENGINE ──
def run_backtest(df, signals, capital=10000, commission=0.0):
    equity  = [capital]
    cash    = capital
    shares  = 0
    entry_p = 0
    trades  = []

    for i in range(1, len(df)):
        price = df['close'].iloc[i]
        sig   = signals.iloc[i]

        if sig == 1 and cash > 0:
            cost   = cash * (1 - commission)
            shares = cost / price
            entry_p = price
            cash   = 0

        elif sig == -1 and shares > 0:
            proceeds = shares * price * (1 - commission)
            pct_ret  = (price - entry_p) / entry_p * 100
            trades.append({'entry': entry_p, 'exit': price, 'return_pct': pct_ret})
            cash   = proceeds
            shares = 0

        current_val = cash + shares * price
        equity.append(current_val)

    # Close open position at end
    if shares > 0:
        final = shares * df['close'].iloc[-1]
        trades.append({'entry': entry_p, 'exit': df['close'].iloc[-1],
                       'return_pct': (df['close'].iloc[-1]-entry_p)/entry_p*100})
        cash = final; shares = 0

    equity_s = pd.Series(equity, index=df.index)
    peak     = equity_s.cummax()
    drawdown = (equity_s - peak) / peak * 100
    trades_df = pd.DataFrame(trades)

    return {
        'final_equity': cash,
        'total_return': (cash - 10000) / 10000 * 100,
        'n_trades':     len(trades),
        'win_rate':     (trades_df['return_pct'] > 0).mean() * 100 if len(trades) else 0,
        'avg_trade':    trades_df['return_pct'].mean() if len(trades) else 0,
        'max_drawdown': drawdown.min(),
        'equity_curve': equity_s,
    }

Results

I’m going to show you the numbers without softening them.

Equity curves — $10,000 starting capital, 2010–2024
Buy & Hold SPY RSI + 200MA RSI Classic RSI Divergence
Buy & Hold
+387%
RSI + 200MA
+14.2%
RSI Classic
−11.4%
RSI Divergence
−28.7%

Strategy 1 — RSI Classic (70/30), no commissions:

MetricValue
Total return (15 years)−11.4%
Buy & hold SPY return+387%
Number of trades43
Win rate53.5%
Average trade−0.26%
Max drawdown−34.2%

Negative return over 15 years while the underlying went up 387%. You would have been far better off doing nothing.

Strategy 2 — RSI Divergence:

MetricValue
Total return (15 years)−28.7%
Number of trades156
Win rate41.2%
Average trade−0.18%
Max drawdown−51.3%

Worse than Strategy 1 on every metric. More trades, lower win rate, deeper drawdown.

Strategy 3 — RSI + 200 MA:

MetricValue
Total return (15 years)+14.2%
Number of trades21
Win rate61.9%
Average trade+0.67%
Max drawdown−18.1%

Finally — a positive return. But 14.2% over 15 years while SPY returned 387%. 10,000became10,000 became 11,420 instead of $48,700. Annualised: about 0.9% per year.


What breaks when you add commissions

Most people test without commissions. Here’s what happens when you add realistic costs — $0.01 per share in slippage + spread, which is conservative:

StrategyReturn (no commission)Return ($0.01/share)Return ($0.05/share)
RSI Classic−11.4%−14.8%−23.1%
RSI Divergence−28.7%−34.2%−47.6%
RSI + 200 MA+14.2%+11.9%+5.3%

Strategy 3 barely survives minimal friction. At realistic intraday spreads for a retail trader, it’s essentially breakeven. The other two just get worse.


Why RSI 70/30 fails — the actual reason

The strategy fails for a specific mechanical reason, not because RSI is useless.

When a stock or index is in a strong uptrend, RSI stays above 70 for extended periods. It doesn’t “revert” — it sits there for weeks or months while price keeps climbing. Every time RSI crosses below 70, the strategy exits. Then RSI climbs back above 70 again. The strategy misses the entire move.

Meanwhile, the buy signals (RSI crossing above 30) fire most frequently during selloffs and bear markets — exactly when you don’t want to be buying without more context. The 2022 bear market generated four consecutive buy signals, each one entering near a high and getting stopped out by the next leg down.

The 70/30 levels were never meant to be mechanical entry triggers. Welles Wilder, who invented the RSI, used them as reference zones — not as “see 30, buy immediately” rules. The internet turned a contextual observation into a binary signal, and the backtest shows exactly what that costs.


What about the “91% win rate” claims?

Some sites claim RSI strategies achieve 80-91% win rates. These numbers are real — but they’re measuring the wrong thing.

RSI(2) strategies (2-period RSI, buy below 10, sell above 90) do achieve very high win rates on indices with a long-term upward bias. The win rate is high because the holding period is short — usually 1-3 days — and the market tends to bounce after very short-term oversold conditions.

But the average winning trade is tiny (+0.3-0.8%) while the occasional losing trade is large (−3-5%). The high win rate masks a poor expectancy. And critically: these strategies were developed and published during 2010-2020, one of the longest bull markets in history. Applying them to the 2022 bear market was brutal.

The lesson: a high win rate with a poor R:R ratio is not an edge. You can lose money with a 91% win rate if the 9% of losing trades are large enough.


What the data actually suggests

RSI is not useless. But the way it’s commonly taught produces negative expectancy strategies.

Two things that genuinely seem to improve RSI-based approaches:

1. Trend filter matters a lot. Strategy 3 (RSI + 200 MA) outperformed Strategy 1 significantly, even though it still barely kept up with cash. The trend context removed the worst signals. The problem is it also removed most of the signals — only 21 trades in 15 years.

2. RSI as context, not trigger. The most honest finding: RSI works better as a contextual filter than as a standalone signal. “Don’t buy here because RSI is 85” is more useful than “sell because RSI crossed below 70.” This is, coincidentally, closer to how Al Brooks uses RSI — as background context rather than as an entry trigger.


The honest conclusion

If you learned to trade RSI from YouTube or a beginner trading course, you were taught something that loses money on 15 years of data.

That doesn’t mean the RSI indicator is useless. It means that “buy at 30, sell at 70” is not a strategy — it’s a description of how the indicator works, and someone somewhere decided to turn it into trading rules without testing it first.

The backtest doesn’t say “RSI is bad.” It says: the specific way RSI is taught to beginners does not produce positive returns.


Code and data

All code is on GitHub. The backtest engine is intentionally simple — no look-ahead bias, no survivorship bias, sequential signals only.

One caveat worth being honest about: SPY has a persistent upward bias due to index composition changes and dividend reinvestment. These strategies might produce different results on individual stocks, futures, or currency pairs. I’ll test those in future posts.


Next in Autopsy series: The Golden Cross — 15 years of data on the most shared chart pattern in finance.