RSI Slope Mean Reversion
The RSI Slope Mean Reversion strategy focuses on extreme readings of the RSI to exploit reversion. Wide departures from the average level rarely last.
Trades trigger when the indicator swings far from its mean and then begins to reverse. Both long and short setups include a protective stop.
Suited for swing traders expecting oscillations, the strategy closes out once the RSI returns toward balance. Starting parameter RsiPeriod = 14.
Details
- Entry Criteria: Indicator crosses back toward mean.
- Long/Short: Both directions.
- Exit Criteria: Indicator reverts to average.
- Stops: Yes.
- Default Values:
RsiPeriod= 14SlopeLookback= 20ThresholdMultiplier= 2mStopLossPercent= 2mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Mean Reversion
- Direction: Both
- Indicators: RSI
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Short-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// RSI slope mean reversion strategy.
/// Trades reversions from extreme RSI slopes and exits when the slope returns to its recent average.
/// </summary>
public class RsiSlopeMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _slopeLookback;
private readonly StrategyParam<decimal> _thresholdMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _longRsiLevel;
private readonly StrategyParam<decimal> _shortRsiLevel;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private decimal _previousRsiValue;
private decimal[] _slopeHistory;
private int _currentIndex;
private int _filledCount;
private int _cooldown;
private bool _isInitialized;
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Period for slope statistics.
/// </summary>
public int SlopeLookback
{
get => _slopeLookback.Value;
set => _slopeLookback.Value = value;
}
/// <summary>
/// Standard deviation multiplier for entry threshold.
/// </summary>
public decimal ThresholdMultiplier
{
get => _thresholdMultiplier.Value;
set => _thresholdMultiplier.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Cooldown bars between orders.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Maximum RSI level for long entries.
/// </summary>
public decimal LongRsiLevel
{
get => _longRsiLevel.Value;
set => _longRsiLevel.Value = value;
}
/// <summary>
/// Minimum RSI level for short entries.
/// </summary>
public decimal ShortRsiLevel
{
get => _shortRsiLevel.Value;
set => _shortRsiLevel.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="RsiSlopeMeanReversionStrategy"/>.
/// </summary>
public RsiSlopeMeanReversionStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Relative Strength Index period", "RSI Settings")
.SetOptimize(5, 30, 5);
_slopeLookback = Param(nameof(SlopeLookback), 20)
.SetGreaterThanZero()
.SetDisplay("Slope Lookback", "Period for slope statistics", "Slope Settings")
.SetOptimize(10, 50, 5);
_thresholdMultiplier = Param(nameof(ThresholdMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Threshold Multiplier", "Standard deviation multiplier for entry threshold", "Slope Settings")
.SetOptimize(1m, 3m, 0.5m);
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Risk Management");
_cooldownBars = Param(nameof(CooldownBars), 1200)
.SetRange(1, 5000)
.SetDisplay("Cooldown Bars", "Bars to wait between orders", "Risk Management");
_longRsiLevel = Param(nameof(LongRsiLevel), 40m)
.SetRange(1m, 100m)
.SetDisplay("Long RSI Level", "Maximum RSI level for long entries", "Signal Filters");
_shortRsiLevel = Param(nameof(ShortRsiLevel), 60m)
.SetRange(1m, 100m)
.SetDisplay("Short RSI Level", "Minimum RSI level for short entries", "Signal Filters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_previousRsiValue = default;
_slopeHistory = new decimal[SlopeLookback];
_currentIndex = default;
_filledCount = default;
_cooldown = default;
_isInitialized = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_slopeHistory = new decimal[SlopeLookback];
_currentIndex = 0;
_filledCount = 0;
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
StartProtection(new(), new Unit(StopLossPercent, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed)
return;
if (!_isInitialized)
{
_previousRsiValue = rsiValue;
_isInitialized = true;
return;
}
var slope = rsiValue - _previousRsiValue;
_previousRsiValue = rsiValue;
_slopeHistory[_currentIndex] = slope;
_currentIndex = (_currentIndex + 1) % SlopeLookback;
if (_filledCount < SlopeLookback)
_filledCount++;
if (_filledCount < SlopeLookback)
return;
var averageSlope = 0m;
var sumSq = 0m;
for (var i = 0; i < SlopeLookback; i++)
averageSlope += _slopeHistory[i];
averageSlope /= SlopeLookback;
for (var i = 0; i < SlopeLookback; i++)
{
var diff = _slopeHistory[i] - averageSlope;
sumSq += diff * diff;
}
var slopeStdDev = (decimal)Math.Sqrt((double)(sumSq / SlopeLookback));
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var lowerThreshold = averageSlope - ThresholdMultiplier * slopeStdDev;
var upperThreshold = averageSlope + ThresholdMultiplier * slopeStdDev;
if (Position == 0)
{
if (slope < lowerThreshold && rsiValue <= LongRsiLevel)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (slope > upperThreshold && rsiValue >= ShortRsiLevel)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position > 0 && slope >= averageSlope)
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (Position < 0 && slope <= averageSlope)
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Math
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class rsi_slope_mean_reversion_strategy(Strategy):
"""
RSI slope mean reversion strategy.
Trades reversions from extreme RSI slopes and exits when the slope returns to its recent average.
"""
def __init__(self):
super(rsi_slope_mean_reversion_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "Relative Strength Index period", "RSI Settings")
self._slope_lookback = self.Param("SlopeLookback", 20) \
.SetGreaterThanZero() \
.SetDisplay("Slope Lookback", "Period for slope statistics", "Slope Settings")
self._threshold_multiplier = self.Param("ThresholdMultiplier", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Threshold Multiplier", "Standard deviation multiplier for entry threshold", "Slope Settings")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 1200) \
.SetDisplay("Cooldown Bars", "Bars to wait between orders", "Risk Management")
self._long_rsi_level = self.Param("LongRsiLevel", 40.0) \
.SetDisplay("Long RSI Level", "Maximum RSI level for long entries", "Signal Filters")
self._short_rsi_level = self.Param("ShortRsiLevel", 60.0) \
.SetDisplay("Short RSI Level", "Minimum RSI level for short entries", "Signal Filters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi = None
self._previous_rsi_value = 0.0
self._slope_history = None
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._is_initialized = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(rsi_slope_mean_reversion_strategy, self).OnReseted()
self._rsi = None
self._previous_rsi_value = 0.0
lb = int(self._slope_lookback.Value)
self._slope_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._is_initialized = False
def OnStarted2(self, time):
super(rsi_slope_mean_reversion_strategy, self).OnStarted2(time)
lb = int(self._slope_lookback.Value)
self._slope_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._process_candle).Start()
self.StartProtection(Unit(), Unit(self._stop_loss_percent.Value, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._rsi)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if not self._rsi.IsFormed:
return
rv = float(rsi_value)
if not self._is_initialized:
self._previous_rsi_value = rv
self._is_initialized = True
return
slope = rv - self._previous_rsi_value
self._previous_rsi_value = rv
lb = int(self._slope_lookback.Value)
self._slope_history[self._current_index] = slope
self._current_index = (self._current_index + 1) % lb
if self._filled_count < lb:
self._filled_count += 1
if self._filled_count < lb:
return
avg_slope = 0.0
for i in range(lb):
avg_slope += self._slope_history[i]
avg_slope /= float(lb)
sum_sq = 0.0
for i in range(lb):
diff = self._slope_history[i] - avg_slope
sum_sq += diff * diff
std_slope = math.sqrt(sum_sq / float(lb))
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown > 0:
self._cooldown -= 1
return
tm = float(self._threshold_multiplier.Value)
lower_threshold = avg_slope - tm * std_slope
upper_threshold = avg_slope + tm * std_slope
long_rsi = float(self._long_rsi_level.Value)
short_rsi = float(self._short_rsi_level.Value)
if self.Position == 0:
if slope < lower_threshold and rv <= long_rsi:
self.BuyMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif slope > upper_threshold and rv >= short_rsi:
self.SellMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position > 0 and slope >= avg_slope:
self.SellMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position < 0 and slope <= avg_slope:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
def CreateClone(self):
return rsi_slope_mean_reversion_strategy()