Relative Strength Index(RSI)
The ratio of average gains to average losses over a lookback, scaled to 0–100.
rsi(prices, period: int = 14)RSI compares the size of recent gains to the size of recent losses and expresses the result on a fixed 0–100 scale. Because the scale is bounded, it is often read as an overbought/oversold gauge: above 70 the instrument has been gaining more than losing, below 30 the reverse. That reading is a convention, not a rule, and it is the source of most of the trouble people have with it.
The bounded scale is genuinely useful for a different reason: it makes the indicator comparable across instruments and across time. A 14-period RSI of 68 means the same structural thing on a small-cap equity as on a currency pair, which is not true of a raw price or a moving-average distance. That comparability is why RSI survives as a cross-sectional ranking input long after it has stopped working as a standalone timing signal.
QuantIDE's implementation returns a DataFrame with the same columns as the input rather than vectorbt's multi-index columns. That sounds like a detail until you try to combine an RSI signal with a moving-average signal across forty tickers and spend an afternoon on shape errors instead of on the strategy.
Where it misleads
- In a strong trend RSI pins above 70 or below 30 for weeks. Trading the threshold means fading a trend repeatedly, which is how the indicator earns its reputation for losing money.
- The 70/30 levels are conventions from a 1978 book, not properties of markets. Optimising them per-instrument is one of the fastest ways to overfit a backtest.
- RSI is computed on close prices, so an intraday spike that reverses before the close leaves no trace in the signal.
In code
from quantide import fetch_data, rsi, prepare_signals
data = fetch_data("SPY", "2015-01-01", "2025-01-01")
r = rsi(data["Close"], period=14)
entries = r < 30
exits = r > 70
entries, exits = prepare_signals(data["Close"], entries, exits)