RSI Mean Reversion
תוכן זה אינו זמין עדיין בשפה שלך.
Buy when RSI rebounds out of oversold territory; exit when it rebounds out of overbought. The textbook mean-reversion play.
class RSIMeanReversion(Strategy): """Buy oversold, sell overbought.""" rsi_period = Param(14, min=5, max=30) oversold = Param(30, min=15, max=40) overbought = Param(70, min=60, max=85) trade = Symbol() sizing = FixedCapital()
def execute(self): rsi = RSI(self.rsi_period, on=self.trade)
if rsi.crossed_above(self.oversold) and self.trade.is_flat: self.buy(self.trade, reason=f"RSI oversold ({rsi.value:.1f})")
if rsi.crossed_below(self.overbought) and self.trade.is_long: self.sell(self.trade, self.trade.position, reason=f"RSI overbought ({rsi.value:.1f})")Walkthrough
Why crossover, not threshold
if rsi.value < 30: would fire on every bar RSI is below 30. That would either trigger a no-op (already long) or thrash if you cleared the position elsewhere.
crossed_above(30) fires for exactly one bar — the bar where RSI transitions from ≤ 30 to > 30. That’s the “leaving oversold” moment, which is the classic mean-reversion entry.
Threshold Params
Both 30 and 70 are Param() — let the optimizer find the best pair. Defaults match the Wilder convention; lower thresholds (40/60) trade more often, higher thresholds (20/80) trade more selectively.
F-string reasons
reason=f"RSI oversold ({rsi.value:.1f})" captures the RSI value at signal time. Reasons are saved with every fill and show up in the trade log — distinct from print(...) output, which appears on the Console tab.
No stop loss
Mean-reversion strategies often skip stops — the thesis is “price will revert to the mean,” and a stop loss can lock in the trough. But a runaway loss is a runaway loss. A reasonable middle ground:
self.stop_loss(5.0) # wider than typical to give the mean-reversion thesis roomWhat to tune
| Param | Typical range | Effect |
|---|---|---|
rsi_period |
5..30 | Lower = more sensitive (more signals); higher = smoother. |
oversold |
15..40 | Lower = entries only on extreme dips. |
overbought |
60..85 | Higher = ride the trend longer. |
Common modifications
Symmetric short side
if rsi.crossed_below(self.overbought) and self.trade.is_flat: self.sell_short(self.trade, reason=f"RSI overbought ({rsi.value:.1f})")
if rsi.crossed_above(self.oversold) and self.trade.is_short: self.buy_to_cover(self.trade, self.trade.position, reason="Cover short")Smoothed RSI
If signals fire too often, smooth the RSI with an EMA:
rsi = RSI(self.rsi_period, on=self.trade)smooth = EMA(3, on=rsi)
if smooth.crossed_above(self.oversold) and self.trade.is_flat: self.buy(self.trade)Regime filter
Mean reversion fails in strong trends. Filter for trendless markets with Aroon — when neither .up nor .down is elevated, no recent extreme has printed and the market is ranging:
class RangingRSI(Strategy): """RSI mean reversion, gated on a trendless Aroon regime.""" rsi_period = Param(14, min=2, max=50) oversold = Param(30, min=10, max=45) trade = Symbol() sizing = FixedCapital()
def execute(self): rsi = RSI(self.rsi_period, on=self.trade) aroon = Aroon(14, on=self.trade)
# Only enter when no recent high/low extreme has printed (ranging market) if aroon.up < 50 and aroon.down < 50 and rsi.crossed_above(self.oversold) and self.trade.is_flat: self.buy(self.trade)