Modular Range-Trading Strategy
This strategy targets range-bound markets using two modules that cannot be active at the same time. The first module relies on MACD momentum confirmation with RSI and Bollinger Bands mean reversion. The second module buys or sells extremes when price bounces back inside the Bollinger Bands with RSI oversold or overbought levels. ATR-based stops and optional exits via Bollinger Bands or RSI reversals manage risk.
Details
- Entry Criteria:
- Logic 1 Long: ADX below threshold, MACD crosses above signal, RSI above its SMA, price below middle Bollinger band.
- Logic 1 Short: ADX below threshold, MACD crosses below signal, RSI below its SMA, price above middle Bollinger band.
- Logic 2 Long: ADX below threshold, price crosses back above lower band, RSI below oversold level.
- Logic 2 Short: ADX below threshold, price crosses back below upper band, RSI above overbought level.
- Long/Short: Both directions.
- Exit Criteria:
- ATR stop loss.
- Optional Bollinger or RSI signals depending on active logic.
- Stops: ATR multiples.
- Default Values: Bollinger 20/2, RSI 14, MACD 12/26/9, ATR 14, ADX 14.
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: Multiple
- Stops: Yes
- Complexity: Complex
- Timeframe: Medium-term
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>
/// Simplified modular range strategy using RSI reversion with SMA context.
/// </summary>
public class ModularRangeTradingStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _smaPeriod;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private SimpleMovingAverage _sma;
private decimal _prevRsi;
private bool _hasPrevRsi;
private int _barsFromSignal;
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// SMA period.
/// </summary>
public int SmaPeriod
{
get => _smaPeriod.Value;
set => _smaPeriod.Value = value;
}
/// <summary>
/// RSI overbought threshold.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// RSI oversold threshold.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// Minimum bars between entries.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public ModularRangeTradingStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "General");
_smaPeriod = Param(nameof(SmaPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("SMA Period", "SMA period", "General");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetDisplay("RSI Overbought", "RSI overbought threshold", "General");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetDisplay("RSI Oversold", "RSI oversold threshold", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 10)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candles timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_sma = null;
_prevRsi = 0m;
_hasPrevRsi = false;
_barsFromSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_sma = new SimpleMovingAverage { Length = SmaPeriod };
_prevRsi = 0m;
_hasPrevRsi = false;
_barsFromSignal = SignalCooldownBars;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_rsi, _sma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_rsi.IsFormed || !_sma.IsFormed)
return;
if (!_hasPrevRsi)
{
_prevRsi = rsiValue;
_hasPrevRsi = true;
return;
}
_barsFromSignal++;
var close = candle.ClosePrice;
var longSignal = _prevRsi <= RsiOversold && rsiValue > RsiOversold && close < smaValue;
var shortSignal = _prevRsi >= RsiOverbought && rsiValue < RsiOverbought && close > smaValue;
if (_barsFromSignal >= SignalCooldownBars && longSignal && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_barsFromSignal = 0;
}
else if (_barsFromSignal >= SignalCooldownBars && shortSignal && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_barsFromSignal = 0;
}
else if (Position > 0 && (rsiValue >= 55m || close >= smaValue))
{
SellMarket();
}
else if (Position < 0 && (rsiValue <= 45m || close <= smaValue))
{
BuyMarket();
}
_prevRsi = rsiValue;
}
}
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 RelativeStrengthIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class modular_range_trading_strategy(Strategy):
"""
Modular range trading: RSI reversion with SMA context.
"""
def __init__(self):
super(modular_range_trading_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14).SetDisplay("RSI Period", "RSI period", "General")
self._sma_period = self.Param("SmaPeriod", 30).SetDisplay("SMA Period", "SMA period", "General")
self._rsi_ob = self.Param("RsiOverbought", 70.0).SetDisplay("RSI OB", "RSI overbought", "General")
self._rsi_os = self.Param("RsiOversold", 30.0).SetDisplay("RSI OS", "RSI oversold", "General")
self._cooldown_bars = self.Param("SignalCooldownBars", 10).SetDisplay("Cooldown", "Min bars between entries", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Candles", "General")
self._prev_rsi = 0.0
self._has_prev = False
self._bars_from_signal = 10
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(modular_range_trading_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._has_prev = False
self._bars_from_signal = self._cooldown_bars.Value
def OnStarted2(self, time):
super(modular_range_trading_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
sma = SimpleMovingAverage()
sma.Length = self._sma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_val, sma_val):
if candle.State != CandleStates.Finished:
return
rsi = float(rsi_val)
sma = float(sma_val)
close = float(candle.ClosePrice)
if not self._has_prev:
self._prev_rsi = rsi
self._has_prev = True
return
self._bars_from_signal += 1
ob = float(self._rsi_ob.Value)
os_val = float(self._rsi_os.Value)
long_signal = self._prev_rsi <= os_val and rsi > os_val and close < sma
short_signal = self._prev_rsi >= ob and rsi < ob and close > sma
if self._bars_from_signal >= self._cooldown_bars.Value and long_signal and self.Position <= 0:
self.BuyMarket()
self._bars_from_signal = 0
elif self._bars_from_signal >= self._cooldown_bars.Value and short_signal and self.Position >= 0:
self.SellMarket()
self._bars_from_signal = 0
elif self.Position > 0 and (rsi >= 55.0 or close >= sma):
self.SellMarket()
elif self.Position < 0 and (rsi <= 45.0 or close <= sma):
self.BuyMarket()
self._prev_rsi = rsi
def CreateClone(self):
return modular_range_trading_strategy()