RSI strategy in Python

The RSI flags overbought and oversold extremes, here is an RSI mean-reversion strategy in Python, backtested with real costs on the Rust core.

What is RSI?

The Relative Strength Index (RSI) is a bounded momentum oscillator that measures the speed and size of recent price moves on a 0–100 scale. Readings above 70 are conventionally 'overbought' and below 30 'oversold', the levels traders fade or follow.

Used for mean reversion, RSI buys oversold dips and steps aside as momentum normalizes. It shines in range-bound markets and gives false signals in strong trends, where an oversold market simply keeps falling, so a stop-loss is essential.

The logic

We compute a 14-period RSI on the close, the standard lookback. It rises as up-moves dominate and falls as down-moves do, compressing price action into a single mean-reverting signal.

The rule is a graded long-only reversion: full size when RSI is deeply oversold (below 30), half size in the soft-oversold zone (below 45), and flat once momentum recovers above 45. A stop-loss caps the loss when 'oversold' just gets cheaper.

RSI in Python

Here is the full strategy in the Manifold-BT expression DSL. It imports manifoldbt, builds the signal, configures realistic execution, and runs the backtest against Rust.

rsi.py
import manifoldbt as mbt
from manifoldbt.indicators import close, rsi
from manifoldbt.helpers import time_range, Slippage, Interval

# 14-period RSI of the close
r = rsi(close, 14)

# Ladder into longs as the market gets more oversold, flat once it recovers
strategy = (
    mbt.Strategy.create("rsi_reversion")
    .signal("rsi", r)
    .size(mbt.when(r < 30.0, 1.0, mbt.when(r < 45.0, 0.5, 0.0)))
    .stop_loss(pct=5.0)
)

start, end = time_range("2021-01-01", "2026-01-01")
config = mbt.BacktestConfig(
    universe={"binance": ["ETHUSDT"]},
    time_range_start=start,
    time_range_end=end,
    bar_interval=Interval.hours(1),
    initial_capital=10_000,
    fees=mbt.FeeConfig.binance_perps(),
    slippage=Slippage.fixed_bps(2),
    warmup_bars=14,
)
store = mbt.ingest(provider="binance", symbol="ETHUSDT", symbol_id=1,
                   interval="1h", start="2021-01-01T00:00:00Z",
                   end="2026-01-01T00:00:00Z")
result = mbt.run(strategy, config, store)
print(result.summary())
mbt.plot.tearsheet(result, show=True)

Backtest it with Manifold-BT

Hourly ETH bars give the oscillator plenty of swings to fade, with Binance perpetual fees and two basis points of slippage so the reversion edge is measured against real friction.

Sweep the RSI period and the entry thresholds before trusting any single setting. A rule that only works at exactly 14/30 is overfit; one that holds across a plateau of nearby values is more likely to be real.

Backtest configuration
Universe{"binance": ["ETHUSDT"]}
Bar interval1h
FeesFeeConfig.binance_perps()
Slippagefixed 2 bps
Warmup14 bars
Sample tearsheet (illustrative, not a forecast)
Total return+31.4%
Sharpe1.12
Max drawdown-13.9%
Win rate59%
Trades486

Frequently asked questions

Is RSI a good indicator for backtesting?

RSI is a solid baseline for mean-reversion research: simple, bounded, and well understood. But its signals degrade in trends, so always backtest it with realistic costs and a stop-loss before trusting the edge.

Keep reading

Run your first backtest

Install Manifold-BT and reproduce the backtest above in seconds. The Rust core runs years of bars sub-second so you can sweep parameters instead of waiting.

$pip install manifoldbt