Exp Rj SlidingRangeRj Digit System Tm Plus Strategy
Overview
This StockSharp strategy is a port of the MetaTrader expert advisor Exp_Rj_SlidingRangeRj_Digit_System_Tm_Plus. It recreates the original trading logic based on the custom Rj_SlidingRangeRj_Digit channel indicator and preserves the configurable trade management options. The strategy monitors finished candles on a configurable timeframe, detects breakouts beyond the channel, and reacts to those events with delayed entries, optional timed exits, and price-based stop/target management.
Indicator logic
The Rj_SlidingRangeRj_Digit indicator builds an adaptive price channel using a multi-step averaging process:
For the upper band, the highest high within UpCalcPeriodRange bars is calculated for each of the last UpCalcPeriodRange sliding windows, shifted by UpCalcPeriodShift bars. The average of these maxima is rounded to the precision specified by UpDigit.
The lower band repeats the same logic on lows using DnCalcPeriodRange, DnCalcPeriodShift, and DnDigit.
A candle is labelled as a breakout when its close price is above the upper band (colors 2 / 3) or below the lower band (colors 0 / 1). Candles inside the channel produce a neutral color (4).
The strategy streams finished candles, rebuilds the bands on each update, and stores the most recent color codes to mimic the CopyBuffer/SignalBar behaviour from the MQL implementation.
Trading rules
Entry delay: Signals are evaluated on the bar defined by SignalBar (default one bar ago). The strategy waits until a breakout color appears and the previous bar did not have the same breakout color. This reproduces the original one-bar delay before taking a trade.
Long entries: Enabled by EnableBuyEntries. A bullish breakout (color 2 or 3) triggers a market buy when no long position is open (short exposure is netted out automatically).
Short entries: Enabled by EnableSellEntries. A bearish breakout (color 0 or 1) triggers a market sell when no short position is open.
Exit signals:
Longs close on bearish breakout colors if EnableBuyExits is true.
Shorts close on bullish breakout colors if EnableSellExits is true.
Optional time-based exit (UseTimeExit) closes any open position once it has been held longer than ExitMinutes.
Optional stop-loss and take-profit levels expressed in points (StopLossPoints, TakeProfitPoints) are converted into price offsets using the instrument PriceStep.
All actions use BuyMarket / SellMarket so the strategy automatically reverses positions when necessary.
Parameters
Parameter
Description
Default
CandleType
Candle type (timeframe) used for signal detection.
8-hour candles
EnableBuyEntries / EnableSellEntries
Allow long/short breakout entries.
true
EnableBuyExits / EnableSellExits
Allow indicator-based exits for longs/shorts.
true
UseTimeExit
Close trades after a fixed holding time.
true
ExitMinutes
Holding time limit in minutes.
1920
UpCalcPeriodRange, UpCalcPeriodShift, UpDigit
Parameters of the upper channel band.
5, 0, 2
DnCalcPeriodRange, DnCalcPeriodShift, DnDigit
Parameters of the lower channel band.
5, 0, 2
SignalBar
Bar offset used for evaluating breakout signals.
1
StopLossPoints, TakeProfitPoints
Stop-loss / take-profit in price points (converted with PriceStep).
1000, 2000
Set the strategy Volume property to control position sizing. The stop-loss and take-profit parameters are optional; set them to 0 to disable either protection level.
Notes
The strategy expects sufficient history to form the sliding channel (roughly max(shift + 2 × range) candles). It automatically manages the internal buffers and ignores signals until enough data is available.
Price rounding is performed using decimal digits, mirroring the MQL indicator rounding behaviour.
Python implementation is intentionally omitted as per the project instructions; only the C# version is provided.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Sliding range breakout strategy using Highest/Lowest channel.
/// Enters on breakout above/below the channel, exits on opposite breakout.
/// </summary>
public class ExpRjSlidingRangeRjDigitSystemTmPlusStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private decimal? _prevUpper;
private decimal? _prevLower;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
public ExpRjSlidingRangeRjDigitSystemTmPlusStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_period = Param(nameof(Period), 10)
.SetGreaterThanZero()
.SetDisplay("Period", "Channel lookback", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevUpper = null;
_prevLower = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevUpper = null;
_prevLower = null;
var highest = new Highest { Length = Period };
var lowest = new Lowest { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, highest);
DrawIndicator(area, lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal upper, decimal lower)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevUpper = upper;
_prevLower = lower;
return;
}
var close = candle.ClosePrice;
if (_prevUpper == null || _prevLower == null)
{
_prevUpper = upper;
_prevLower = lower;
return;
}
// Breakout above previous upper → buy
if (close > _prevUpper.Value && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Breakdown below previous lower → sell
else if (close < _prevLower.Value && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevUpper = upper;
_prevLower = lower;
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class exp_rj_sliding_range_rj_digit_system_tm_plus_strategy(Strategy):
def __init__(self):
super(exp_rj_sliding_range_rj_digit_system_tm_plus_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._period = self.Param("Period", 10) \
.SetDisplay("Period", "Channel lookback", "Indicators")
self._prev_upper = None
self._prev_lower = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def Period(self):
return self._period.Value
def OnReseted(self):
super(exp_rj_sliding_range_rj_digit_system_tm_plus_strategy, self).OnReseted()
self._prev_upper = None
self._prev_lower = None
def OnStarted2(self, time):
super(exp_rj_sliding_range_rj_digit_system_tm_plus_strategy, self).OnStarted2(time)
self._prev_upper = None
self._prev_lower = None
highest = Highest()
highest.Length = self.Period
lowest = Lowest()
lowest.Length = self.Period
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, highest)
self.DrawIndicator(area, lowest)
self.DrawOwnTrades(area)
def _on_process(self, candle, upper_value, lower_value):
if candle.State != CandleStates.Finished:
return
uv = float(upper_value)
lv = float(lower_value)
close = float(candle.ClosePrice)
if self._prev_upper is None or self._prev_lower is None:
self._prev_upper = uv
self._prev_lower = lv
return
# Breakout above previous upper
if close > self._prev_upper and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Breakdown below previous lower
elif close < self._prev_lower and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_upper = uv
self._prev_lower = lv
def CreateClone(self):
return exp_rj_sliding_range_rj_digit_system_tm_plus_strategy()