JBrainUltraRSI Strategy
This sample strategy combines the Relative Strength Index (RSI) and the Stochastic oscillator to generate trading signals. The idea is derived from the original MetaTrader Expert Advisor that used the JBrainTrendSig1 and UltraRSI indicators. In this adaptation the Stochastic oscillator acts as a trend filter while RSI provides entry signals.
How It Works
- Indicators
- RSI: Measures momentum by comparing recent gains and losses. A cross above the 50 level indicates bullish momentum, while a cross below 50 indicates bearish momentum.
- Stochastic Oscillator: Evaluates the position of the close relative to the recent range. Crosses of %K and %D lines confirm trend direction.
- Modes
- JBrainSig1Filter – RSI generates signals and the Stochastic oscillator confirms direction.
- UltraRsiFilter – Stochastic oscillator provides signals filtered by RSI.
- Composition – Signals are taken only when both indicators agree on direction.
- Trading Rules
- A long position opens when a buy signal appears and short position is absent or closed.
- A short position opens when a sell signal appears and long position is absent or closed.
- Reverse signals close existing positions if allowed.
Parameters
| Parameter | Description |
|---|---|
RsiPeriod |
RSI calculation period. |
StochLength |
%K period for the Stochastic oscillator. |
SignalLength |
%D period for the Stochastic oscillator. |
Mode |
Mode of combining indicators. |
AllowLongEntry / AllowShortEntry |
Permissions to open long or short positions. |
AllowLongExit / AllowShortExit |
Permissions to close long or short positions. |
CandleType |
Candle timeframe used by the strategy. |
Notes
- The strategy uses StockSharp high level API with
Bind/BindExfor indicator processing. - Stops and targets can be configured with the built-in protection mechanism
StartProtection(). - Example visualisation draws candles, indicators and own trades if a chart area is available.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy combining RSI and Stochastic oscillator signals.
/// </summary>
public class JBrainUltraRsiStrategy : Strategy
{
/// <summary>
/// Combination modes for indicators.
/// </summary>
public enum AlgorithmModes
{
JBrainSig1Filter,
UltraRsiFilter,
Composition
}
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _stochLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<AlgorithmModes> _mode;
private readonly StrategyParam<bool> _allowLongEntry;
private readonly StrategyParam<bool> _allowShortEntry;
private readonly StrategyParam<bool> _allowLongExit;
private readonly StrategyParam<bool> _allowShortExit;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private StochasticOscillator _stochastic;
private decimal? _prevRsi;
private decimal? _prevK;
private decimal? _prevD;
/// <summary>
/// RSI calculation period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Stochastic %K period.
/// </summary>
public int StochLength
{
get => _stochLength.Value;
set => _stochLength.Value = value;
}
/// <summary>
/// Stochastic %D period.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// Mode combining indicators.
/// </summary>
public AlgorithmModes Mode
{
get => _mode.Value;
set => _mode.Value = value;
}
/// <summary>
/// Permission to open long positions.
/// </summary>
public bool AllowLongEntry
{
get => _allowLongEntry.Value;
set => _allowLongEntry.Value = value;
}
/// <summary>
/// Permission to open short positions.
/// </summary>
public bool AllowShortEntry
{
get => _allowShortEntry.Value;
set => _allowShortEntry.Value = value;
}
/// <summary>
/// Permission to close long positions.
/// </summary>
public bool AllowLongExit
{
get => _allowLongExit.Value;
set => _allowLongExit.Value = value;
}
/// <summary>
/// Permission to close short positions.
/// </summary>
public bool AllowShortExit
{
get => _allowShortExit.Value;
set => _allowShortExit.Value = value;
}
/// <summary>
/// Candle type to process.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="JBrainUltraRsiStrategy"/>.
/// </summary>
public JBrainUltraRsiStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI calculation period", "Indicators")
;
_stochLength = Param(nameof(StochLength), 9)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "Period for %K line", "Indicators")
;
_signalLength = Param(nameof(SignalLength), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %D", "Period for %D line", "Indicators")
;
_mode = Param(nameof(Mode), AlgorithmModes.Composition)
.SetDisplay("Mode", "Algorithm to enter the market", "General");
_allowLongEntry = Param(nameof(AllowLongEntry), true)
.SetDisplay("Allow Long Entry", "Permission to open long positions", "Trading");
_allowShortEntry = Param(nameof(AllowShortEntry), true)
.SetDisplay("Allow Short Entry", "Permission to open short positions", "Trading");
_allowLongExit = Param(nameof(AllowLongExit), true)
.SetDisplay("Allow Long Exit", "Permission to close long positions", "Trading");
_allowShortExit = Param(nameof(AllowShortExit), true)
.SetDisplay("Allow Short Exit", "Permission to close short positions", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = default;
_stochastic = default;
_prevRsi = default;
_prevK = default;
_prevD = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_stochastic = new StochasticOscillator
{
K = { Length = StochLength },
D = { Length = SignalLength },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_stochastic, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawIndicator(area, _stochastic);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
// process RSI manually
var rsiResult = _rsi.Process(candle.ClosePrice, candle.OpenTime, true);
if (!rsiResult.IsFormed)
return;
var rsi = rsiResult.ToDecimal();
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is not decimal k || stoch.D is not decimal d)
{
_prevRsi = rsi;
return;
}
var rsiUp = _prevRsi is decimal pr && pr <= 50m && rsi > 50m;
var rsiDown = _prevRsi is decimal pr2 && pr2 >= 50m && rsi < 50m;
var stochUp = _prevK is decimal pk && _prevD is decimal pd && pk <= pd && k > d;
var stochDown = _prevK is decimal pk2 && _prevD is decimal pd2 && pk2 >= pd2 && k < d;
var buySignal = false;
var sellSignal = false;
switch (Mode)
{
case AlgorithmModes.JBrainSig1Filter:
buySignal = rsiUp && k > d;
sellSignal = rsiDown && k < d;
break;
case AlgorithmModes.UltraRsiFilter:
buySignal = stochUp && rsi > 50m;
sellSignal = stochDown && rsi < 50m;
break;
case AlgorithmModes.Composition:
buySignal = rsiUp && stochUp;
sellSignal = rsiDown && stochDown;
break;
}
if (buySignal)
{
if (Position < 0 && AllowShortExit)
BuyMarket();
if (AllowLongEntry && Position <= 0)
BuyMarket();
}
else if (sellSignal)
{
if (Position > 0 && AllowLongExit)
SellMarket();
if (AllowShortEntry && Position >= 0)
SellMarket();
}
_prevRsi = rsi;
_prevK = k;
_prevD = d;
}
}
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, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class j_brain_ultra_rsi_strategy(Strategy):
# Algorithm modes
JBRAIN_SIG1_FILTER = 0
ULTRA_RSI_FILTER = 1
COMPOSITION = 2
def __init__(self):
super(j_brain_ultra_rsi_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 13) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "RSI calculation period", "Indicators")
self._stoch_length = self.Param("StochLength", 9) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic %K", "Period for %K line", "Indicators")
self._signal_length = self.Param("SignalLength", 3) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic %D", "Period for %D line", "Indicators")
self._mode = self.Param("Mode", 2) \
.SetDisplay("Mode", "Algorithm to enter the market", "General")
self._allow_long_entry = self.Param("AllowLongEntry", True) \
.SetDisplay("Allow Long Entry", "Permission to open long positions", "Trading")
self._allow_short_entry = self.Param("AllowShortEntry", True) \
.SetDisplay("Allow Short Entry", "Permission to open short positions", "Trading")
self._allow_long_exit = self.Param("AllowLongExit", True) \
.SetDisplay("Allow Long Exit", "Permission to close long positions", "Trading")
self._allow_short_exit = self.Param("AllowShortExit", True) \
.SetDisplay("Allow Short Exit", "Permission to close short positions", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._rsi = None
self._prev_rsi = None
self._prev_k = None
self._prev_d = None
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def stoch_length(self):
return self._stoch_length.Value
@property
def signal_length(self):
return self._signal_length.Value
@property
def mode(self):
return self._mode.Value
@property
def allow_long_entry(self):
return self._allow_long_entry.Value
@property
def allow_short_entry(self):
return self._allow_short_entry.Value
@property
def allow_long_exit(self):
return self._allow_long_exit.Value
@property
def allow_short_exit(self):
return self._allow_short_exit.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(j_brain_ultra_rsi_strategy, self).OnReseted()
self._rsi = None
self._prev_rsi = None
self._prev_k = None
self._prev_d = None
def OnStarted2(self, time):
super(j_brain_ultra_rsi_strategy, self).OnStarted2(time)
self.StartProtection(None, None)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.rsi_period
stochastic = StochasticOscillator()
stochastic.K.Length = self.stoch_length
stochastic.D.Length = self.signal_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(stochastic, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._rsi)
self.DrawIndicator(area, stochastic)
self.DrawOwnTrades(area)
def process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
# process RSI manually
rsi_result = process_float(self._rsi, candle.ClosePrice, candle.OpenTime, True)
if not rsi_result.IsFormed:
return
rsi = float(rsi_result)
k_val = stoch_value.K
d_val = stoch_value.D
if k_val is None or d_val is None:
self._prev_rsi = rsi
return
k = float(k_val)
d = float(d_val)
rsi_up = self._prev_rsi is not None and self._prev_rsi <= 50.0 and rsi > 50.0
rsi_down = self._prev_rsi is not None and self._prev_rsi >= 50.0 and rsi < 50.0
stoch_up = self._prev_k is not None and self._prev_d is not None and self._prev_k <= self._prev_d and k > d
stoch_down = self._prev_k is not None and self._prev_d is not None and self._prev_k >= self._prev_d and k < d
buy_signal = False
sell_signal = False
m = self.mode
if m == self.JBRAIN_SIG1_FILTER:
buy_signal = rsi_up and k > d
sell_signal = rsi_down and k < d
elif m == self.ULTRA_RSI_FILTER:
buy_signal = stoch_up and rsi > 50.0
sell_signal = stoch_down and rsi < 50.0
elif m == self.COMPOSITION:
buy_signal = rsi_up and stoch_up
sell_signal = rsi_down and stoch_down
if buy_signal:
if self.Position < 0 and self.allow_short_exit:
self.BuyMarket()
if self.allow_long_entry and self.Position <= 0:
self.BuyMarket()
elif sell_signal:
if self.Position > 0 and self.allow_long_exit:
self.SellMarket()
if self.allow_short_entry and self.Position >= 0:
self.SellMarket()
self._prev_rsi = rsi
self._prev_k = k
self._prev_d = d
def CreateClone(self):
return j_brain_ultra_rsi_strategy()