Reverse Strategy is a mean-reversion trading system that combines Bollinger Bands and the Relative Strength Index (RSI) to identify exhausted moves. The strategy looks for price reversals near the Bollinger envelopes while simultaneously requiring the RSI to cross back from an oversold or overbought zone. Once both conditions are satisfied, the strategy enters against the previous move and manages trades with fixed band-based stops and targets.
Trading Logic
Subscribe to the configured candle series (default 5-minute candles).
Calculate Bollinger Bands using a simple moving average with the configured period and deviation multiplier.
Calculate RSI using the configured lookback period.
Track the previous finished candle to detect crossovers:
Long setup: the previous close is below the previous lower band and RSI is below the oversold threshold. The current close must move back above the lower band while RSI rises above the oversold level.
Short setup: the previous close is above the previous upper band and RSI is above the overbought threshold. The current close must fall back below the upper band while RSI drops below the overbought level.
When a long setup triggers, buy at market, set a protective stop one standard deviation below the entry close, and a take profit two standard deviations above it.
When a short setup triggers, sell at market, set a protective stop one standard deviation above the entry close, and a take profit two standard deviations below it.
Manage open positions:
Close long trades if price touches the upper band, hits the stop, or reaches the take-profit target.
Close short trades if price touches the lower band, hits the stop, or reaches the take-profit target.
Parameters
Name
Description
Default
CandleType
Time frame for the candle subscription.
5-minute time frame
BollingerPeriod
Number of bars used for the Bollinger moving average and standard deviation.
20
BollingerWidth
Standard deviation multiplier applied to Bollinger Bands.
2.0
RsiPeriod
Number of bars used to calculate the RSI.
14
RsiOverbought
RSI threshold signaling overbought conditions for short entries.
70
RsiOversold
RSI threshold signaling oversold conditions for long entries.
30
All parameters support optimization via the StockSharp Designer or Runner. Adjusting the oversold/overbought levels changes how aggressive the reversal detection is, while the Bollinger width controls how far price must stretch before signals are considered.
Usage Notes
The strategy uses the high-level StockSharp API with automatic candle subscriptions and indicator binding.
All trading operations rely on market orders (BuyMarket/SellMarket). Stop-loss and take-profit levels are handled in code rather than as pending orders.
The default configuration targets major reversals on intraday charts but can be adapted to higher time frames by changing CandleType.
Consider combining the strategy with additional filters (trend, volatility, session time) when running in live environments.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "Reverse" MetaTrader expert.
/// Uses Bollinger Band touches with RSI confirmation for mean-reversion entries.
/// Enters long when price crosses above lower band with RSI oversold,
/// enters short when price crosses below upper band with RSI overbought.
/// </summary>
public class ReverseStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerWidth;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private ExponentialMovingAverage _ema;
private RelativeStrengthIndex _rsi;
private decimal _prevClose;
private decimal _prevRsi;
private decimal _prevLower;
private decimal _prevUpper;
private bool _hasPrev;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
public ReverseStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signals", "General");
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "MA length for Bollinger Bands", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 1m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetDisplay("RSI Overbought", "Upper threshold for short signals", "Signals");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetDisplay("RSI Oversold", "Lower threshold for long signals", "Signals");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ema = new ExponentialMovingAverage { Length = BollingerPeriod };
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_hasPrev = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ema, _rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ema.IsFormed || !_rsi.IsFormed)
return;
var close = candle.ClosePrice;
var bandOffset = emaValue * (BollingerWidth / 100m);
var upperBand = emaValue + bandOffset;
var lowerBand = emaValue - bandOffset;
if (!_hasPrev)
{
_prevClose = close;
_prevRsi = rsiValue;
_prevLower = lowerBand;
_prevUpper = upperBand;
_hasPrev = true;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Long: price crosses up from below lower band + RSI was oversold
var longSignal = _prevClose < _prevLower && close >= lowerBand && _prevRsi < RsiOversold;
// Short: price crosses down from above upper band + RSI was overbought
var shortSignal = _prevClose > _prevUpper && close <= upperBand && _prevRsi > RsiOverbought;
if (longSignal)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (shortSignal)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
// Exit long at upper band
if (Position > 0 && close >= upperBand)
SellMarket(Position);
// Exit short at lower band
if (Position < 0 && close <= lowerBand)
BuyMarket(Math.Abs(Position));
_prevClose = close;
_prevRsi = rsiValue;
_prevLower = lowerBand;
_prevUpper = upperBand;
}
/// <inheritdoc />
protected override void OnReseted()
{
_ema = null;
_rsi = null;
_prevClose = 0;
_prevRsi = 0;
_prevLower = 0;
_prevUpper = 0;
_hasPrev = false;
base.OnReseted();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class reverse_strategy(Strategy):
def __init__(self):
super(reverse_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._bollinger_period = self.Param("BollingerPeriod", 20)
self._bollinger_width = self.Param("BollingerWidth", 1.0)
self._rsi_period = self.Param("RsiPeriod", 14)
self._rsi_overbought = self.Param("RsiOverbought", 70.0)
self._rsi_oversold = self.Param("RsiOversold", 30.0)
self._prev_close = 0.0
self._prev_rsi = 0.0
self._prev_lower = 0.0
self._prev_upper = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@BollingerPeriod.setter
def BollingerPeriod(self, value):
self._bollinger_period.Value = value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
@BollingerWidth.setter
def BollingerWidth(self, value):
self._bollinger_width.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiOverbought(self):
return self._rsi_overbought.Value
@RsiOverbought.setter
def RsiOverbought(self, value):
self._rsi_overbought.Value = value
@property
def RsiOversold(self):
return self._rsi_oversold.Value
@RsiOversold.setter
def RsiOversold(self, value):
self._rsi_oversold.Value = value
def OnReseted(self):
super(reverse_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_rsi = 0.0
self._prev_lower = 0.0
self._prev_upper = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(reverse_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_rsi = 0.0
self._prev_lower = 0.0
self._prev_upper = 0.0
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self.BollingerPeriod
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, rsi, self._process_candle).Start()
def _process_candle(self, candle, ema_value, rsi_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
rsi_val = float(rsi_value)
close = float(candle.ClosePrice)
band_offset = ema_val * (float(self.BollingerWidth) / 100.0)
upper_band = ema_val + band_offset
lower_band = ema_val - band_offset
if not self._has_prev:
self._prev_close = close
self._prev_rsi = rsi_val
self._prev_lower = lower_band
self._prev_upper = upper_band
self._has_prev = True
return
# Long: price crosses up from below lower band + RSI was oversold
long_signal = (self._prev_close < self._prev_lower and close >= lower_band and
self._prev_rsi < float(self.RsiOversold))
# Short: price crosses down from above upper band + RSI was overbought
short_signal = (self._prev_close > self._prev_upper and close <= upper_band and
self._prev_rsi > float(self.RsiOverbought))
if long_signal:
if self.Position <= 0:
self.BuyMarket()
elif short_signal:
if self.Position >= 0:
self.SellMarket()
# Exit long at upper band
if self.Position > 0 and close >= upper_band:
self.SellMarket()
# Exit short at lower band
if self.Position < 0 and close <= lower_band:
self.BuyMarket()
self._prev_close = close
self._prev_rsi = rsi_val
self._prev_lower = lower_band
self._prev_upper = upper_band
def CreateClone(self):
return reverse_strategy()