Delta-RSI Oscillator
This strategy uses the Delta-RSI oscillator, defined as the change of RSI smoothed with an EMA. Signals are triggered when the delta crosses zero, crosses its signal line or changes direction. Exits mirror the selected condition.
Details
- Entry Criteria: Based on
BuyCondition(zero-crossing, signal line crossing or direction change) on Delta-RSI. - Long/Short: Both, controlled by
UseLongandUseShort. - Exit Criteria: Based on
ExitConditionon Delta-RSI. - Stops: None.
- Default Values:
RsiLength= 21SignalLength= 9CandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Momentum
- Direction: Both
- Indicators: RSI, EMA
- Stops: No
- Complexity: Intermediate
- Timeframe: Intraday
- 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>
/// Delta-RSI oscillator strategy.
/// Delta-RSI is the bar-to-bar change of RSI and its signal line is an EMA of it.
/// Entries follow BuyCondition: Delta-RSI crossing zero, crossing its signal line or changing direction;
/// a bullish event opens a long, a bearish event a short (each side enabled by UseLong and UseShort), reversing an opposite position.
/// Otherwise positions are closed by the opposite event of ExitCondition.
/// </summary>
public class DeltaRsiOscillatorStrategy : Strategy
{
/// <summary>
/// Delta-RSI events that can trigger entries or exits.
/// </summary>
public enum DeltaRsiConditions
{
/// <summary>
/// Delta-RSI crosses zero.
/// </summary>
ZeroCrossing,
/// <summary>
/// Delta-RSI crosses its signal line.
/// </summary>
SignalLineCrossing,
/// <summary>
/// Delta-RSI changes direction.
/// </summary>
DirectionChange,
}
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<DeltaRsiConditions> _buyCondition;
private readonly StrategyParam<DeltaRsiConditions> _exitCondition;
private readonly StrategyParam<bool> _useLong;
private readonly StrategyParam<bool> _useShort;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _signal;
private decimal? _prevRsi;
private decimal? _prevDelta;
private decimal? _prevPrevDelta;
private decimal? _prevSignal;
/// <summary>
/// RSI length.
/// </summary>
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
/// <summary>
/// EMA length of the signal line.
/// </summary>
public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
/// <summary>
/// Event that opens positions.
/// </summary>
public DeltaRsiConditions BuyCondition { get => _buyCondition.Value; set => _buyCondition.Value = value; }
/// <summary>
/// Event that closes positions.
/// </summary>
public DeltaRsiConditions ExitCondition { get => _exitCondition.Value; set => _exitCondition.Value = value; }
/// <summary>
/// Allow long trades.
/// </summary>
public bool UseLong { get => _useLong.Value; set => _useLong.Value = value; }
/// <summary>
/// Allow short trades.
/// </summary>
public bool UseShort { get => _useShort.Value; set => _useShort.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Constructor.
/// </summary>
public DeltaRsiOscillatorStrategy()
{
_rsiLength = Param(nameof(RsiLength), 21)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI length", "Indicators");
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "EMA length of the signal line", "Indicators");
_buyCondition = Param(nameof(BuyCondition), DeltaRsiConditions.ZeroCrossing)
.SetDisplay("Entry Condition", "Event that opens positions", "Signals");
_exitCondition = Param(nameof(ExitCondition), DeltaRsiConditions.ZeroCrossing)
.SetDisplay("Exit Condition", "Event that closes positions", "Signals");
_useLong = Param(nameof(UseLong), true)
.SetDisplay("Use Long", "Allow long trades", "Signals");
_useShort = Param(nameof(UseShort), true)
.SetDisplay("Use Short", "Allow short trades", "Signals");
_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();
ResetState();
}
private void ResetState()
{
_prevRsi = null;
_prevDelta = null;
_prevPrevDelta = null;
_prevSignal = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var rsi = new RelativeStrengthIndex { Length = RsiLength };
_signal = new ExponentialMovingAverage { Length = SignalLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!rsiValue.IsFormed)
return;
var rsi = rsiValue.GetValue<decimal>();
var prevRsi = _prevRsi;
_prevRsi = rsi;
if (prevRsi is not decimal pr)
return;
var delta = rsi - pr;
var signalValue = _signal.Process(delta, candle.ServerTime, true);
var signal = signalValue.GetValue<decimal>();
var prevDelta = _prevDelta;
var prevPrevDelta = _prevPrevDelta;
var prevSignal = _prevSignal;
_prevPrevDelta = _prevDelta;
_prevDelta = delta;
_prevSignal = signalValue.IsFormed ? signal : null;
if (!signalValue.IsFormed || prevDelta is not decimal pd || prevPrevDelta is not decimal ppd || prevSignal is not decimal ps)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
bool IsBullish(DeltaRsiConditions condition) => condition switch
{
DeltaRsiConditions.ZeroCrossing => pd <= 0m && delta > 0m,
DeltaRsiConditions.SignalLineCrossing => pd <= ps && delta > signal,
_ => pd <= ppd && delta > pd,
};
bool IsBearish(DeltaRsiConditions condition) => condition switch
{
DeltaRsiConditions.ZeroCrossing => pd >= 0m && delta < 0m,
DeltaRsiConditions.SignalLineCrossing => pd >= ps && delta < signal,
_ => pd >= ppd && delta < pd,
};
if (UseLong && IsBullish(BuyCondition) && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (UseShort && IsBearish(BuyCondition) && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && IsBearish(ExitCondition))
SellMarket(Position);
else if (Position < 0 && IsBullish(ExitCondition))
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
# Delta-RSI events, matching the C# DeltaRsiConditions enum.
ZERO_CROSSING = 0
SIGNAL_LINE_CROSSING = 1
DIRECTION_CHANGE = 2
class delta_rsi_oscillator_strategy(Strategy):
"""
Delta-RSI oscillator strategy.
Delta-RSI is the bar-to-bar change of RSI and its signal line is an EMA of it.
Entries follow BuyCondition: Delta-RSI crossing zero, crossing its signal line or changing direction;
a bullish event opens a long, a bearish event a short (each side enabled by UseLong and UseShort), reversing an opposite position.
Otherwise positions are closed by the opposite event of ExitCondition.
"""
def __init__(self):
super(delta_rsi_oscillator_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 21).SetGreaterThanZero().SetDisplay("RSI Length", "RSI length", "Indicators")
self._signal_length = self.Param("SignalLength", 9).SetGreaterThanZero().SetDisplay("Signal Length", "EMA length of the signal line", "Indicators")
self._buy_condition = self.Param("BuyCondition", ZERO_CROSSING).SetDisplay("Entry Condition", "Event that opens positions: 0 zero crossing, 1 signal line crossing, 2 direction change", "Signals")
self._exit_condition = self.Param("ExitCondition", ZERO_CROSSING).SetDisplay("Exit Condition", "Event that closes positions: 0 zero crossing, 1 signal line crossing, 2 direction change", "Signals")
self._use_long = self.Param("UseLong", True).SetDisplay("Use Long", "Allow long trades", "Signals")
self._use_short = self.Param("UseShort", True).SetDisplay("Use Short", "Allow short trades", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._signal = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def _reset_state(self):
self._prev_rsi = None
self._prev_delta = None
self._prev_prev_delta = None
self._prev_signal = None
def OnReseted(self):
super(delta_rsi_oscillator_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(delta_rsi_oscillator_strategy, self).OnStarted2(time)
self._reset_state()
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
self._signal = ExponentialMovingAverage()
self._signal.Length = self._signal_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if not rsi_value.IsFormed:
return
rsi = float(rsi_value.GetValue[Decimal](None))
prev_rsi = self._prev_rsi
self._prev_rsi = rsi
if prev_rsi is None:
return
delta = rsi - prev_rsi
signal_value = process_float(self._signal, delta, candle.ServerTime, True)
signal = float(signal_value.GetValue[Decimal](None))
pd = self._prev_delta
ppd = self._prev_prev_delta
ps = self._prev_signal
self._prev_prev_delta = self._prev_delta
self._prev_delta = delta
self._prev_signal = signal if signal_value.IsFormed else None
if not signal_value.IsFormed or pd is None or ppd is None or ps is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
def is_bullish(condition):
if condition == ZERO_CROSSING:
return pd <= 0 and delta > 0
if condition == SIGNAL_LINE_CROSSING:
return pd <= ps and delta > signal
return pd <= ppd and delta > pd
def is_bearish(condition):
if condition == ZERO_CROSSING:
return pd >= 0 and delta < 0
if condition == SIGNAL_LINE_CROSSING:
return pd >= ps and delta < signal
return pd >= ppd and delta < pd
buy_condition = int(self._buy_condition.Value)
exit_condition = int(self._exit_condition.Value)
if self._use_long.Value and is_bullish(buy_condition) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif self._use_short.Value and is_bearish(buy_condition) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and is_bearish(exit_condition):
self.SellMarket(self.Position)
elif self.Position < 0 and is_bullish(exit_condition):
self.BuyMarket(-self.Position)
def CreateClone(self):
return delta_rsi_oscillator_strategy()