Stochastic RSI Crossover Strategy
This method converts the classic Relative Strength Index into a Stochastic RSI, then smooths the result into %K and %D lines. When %K crosses %D inside carefully chosen zones, the move implies a short term shift in momentum. The algorithm trades only when a three layer EMA structure confirms the direction of the broader trend, helping to filter whipsaws.
Once a crossover appears, the close price must also sit above or below the fast EMA depending on the signal. This protects against acting on oscillations that occur against the prevailing trend and keeps attention on moments when momentum aligns with direction. Traders can adjust smoothing periods and RSI lengths to tune how sensitive the system reacts to volatility spikes.
Risk is referenced through an Average True Range reading. Multipliers of the current ATR propose stop‑loss and profit targets, providing a dynamic level that expands in volatile markets and contracts when activity calms. Although the script does not automatically send protective orders, these calculated levels aid manual management or can be tied into additional risk modules.
Details
- Entry Criteria:
- Long:
%Kcrosses above%D,%Kin[10,60], EMAs aligned bullishly, price above EMA1. - Short:
%Kcrosses below%D,%Kin[40,95], EMAs aligned bearishly, price below EMA1.
- Long:
- Long/Short: Both sides.
- Exit Criteria: None built-in.
- Stops: ATR multiples suggested but not automatically placed.
- Default Values:
SmoothK= 3,SmoothD= 3.RsiLength= 14,StochLength= 14.Ema1Length= 20,Ema2Length= 50,Ema3Length= 100.AtrLength= 14,AtrLossMultiplier= 1.5,AtrProfitMultiplier= 2.0.
- Filters:
- Category: Momentum
- Direction: Both
- Indicators: Multiple
- Stops: Optional
- Complexity: Moderate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: Yes
- Risk level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Stoch RSI Crossover Strategy.
/// RSI is turned into a Stochastic RSI over StochLength bars and smoothed into %K (SmoothK) and %D (SmoothD).
/// A long opens when %K crosses above %D with %K in [10, 60], EMA1 > EMA2 > EMA3 and the close above EMA1.
/// A short opens when %K crosses below %D with %K in [40, 95], EMA1 < EMA2 < EMA3 and the close below EMA1.
/// There is no built-in exit: an opposite signal reverses the position. The ATR multipliers only describe suggested levels.
/// </summary>
public class StochRsiCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _smoothK;
private readonly StrategyParam<int> _smoothD;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _stochLength;
private readonly StrategyParam<int> _ema1Length;
private readonly StrategyParam<int> _ema2Length;
private readonly StrategyParam<int> _ema3Length;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrLossMultiplier;
private readonly StrategyParam<decimal> _atrProfitMultiplier;
private readonly StrategyParam<DataType> _candleType;
private Highest _rsiHigh;
private Lowest _rsiLow;
private SimpleMovingAverage _kMa;
private SimpleMovingAverage _dMa;
private decimal? _prevK;
private decimal? _prevD;
/// <summary>
/// %K smoothing.
/// </summary>
public int SmoothK
{
get => _smoothK.Value;
set => _smoothK.Value = value;
}
/// <summary>
/// %D smoothing.
/// </summary>
public int SmoothD
{
get => _smoothD.Value;
set => _smoothD.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Stochastic period applied to RSI.
/// </summary>
public int StochLength
{
get => _stochLength.Value;
set => _stochLength.Value = value;
}
/// <summary>
/// Fast EMA period.
/// </summary>
public int Ema1Length
{
get => _ema1Length.Value;
set => _ema1Length.Value = value;
}
/// <summary>
/// Medium EMA period.
/// </summary>
public int Ema2Length
{
get => _ema2Length.Value;
set => _ema2Length.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int Ema3Length
{
get => _ema3Length.Value;
set => _ema3Length.Value = value;
}
/// <summary>
/// ATR period of the suggested levels.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiplier of the suggested stop loss.
/// </summary>
public decimal AtrLossMultiplier
{
get => _atrLossMultiplier.Value;
set => _atrLossMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier of the suggested profit target.
/// </summary>
public decimal AtrProfitMultiplier
{
get => _atrProfitMultiplier.Value;
set => _atrProfitMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public StochRsiCrossoverStrategy()
{
_smoothK = Param(nameof(SmoothK), 3)
.SetGreaterThanZero()
.SetDisplay("Smooth K", "%K smoothing", "Stoch RSI");
_smoothD = Param(nameof(SmoothD), 3)
.SetGreaterThanZero()
.SetDisplay("Smooth D", "%D smoothing", "Stoch RSI");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Stoch RSI");
_stochLength = Param(nameof(StochLength), 14)
.SetGreaterThanZero()
.SetDisplay("Stoch Length", "Stochastic period applied to RSI", "Stoch RSI");
_ema1Length = Param(nameof(Ema1Length), 20)
.SetGreaterThanZero()
.SetDisplay("EMA1 Length", "Fast EMA period", "Trend");
_ema2Length = Param(nameof(Ema2Length), 50)
.SetGreaterThanZero()
.SetDisplay("EMA2 Length", "Medium EMA period", "Trend");
_ema3Length = Param(nameof(Ema3Length), 100)
.SetGreaterThanZero()
.SetDisplay("EMA3 Length", "Slow EMA period", "Trend");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period of the suggested levels", "Risk");
_atrLossMultiplier = Param(nameof(AtrLossMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Loss Multiplier", "ATR multiplier of the suggested stop loss", "Risk");
_atrProfitMultiplier = Param(nameof(AtrProfitMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("ATR Profit Multiplier", "ATR multiplier of the suggested profit target", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevK = null;
_prevD = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevK = null;
_prevD = null;
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var ema1 = new ExponentialMovingAverage { Length = Ema1Length };
var ema2 = new ExponentialMovingAverage { Length = Ema2Length };
var ema3 = new ExponentialMovingAverage { Length = Ema3Length };
_rsiHigh = new Highest { Length = StochLength };
_rsiLow = new Lowest { Length = StochLength };
_kMa = new SimpleMovingAverage { Length = SmoothK };
_dMa = new SimpleMovingAverage { Length = SmoothD };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(rsi, ema1, ema2, ema3, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema1);
DrawIndicator(area, ema2);
DrawIndicator(area, ema3);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rsiValue, IIndicatorValue ema1Value, IIndicatorValue ema2Value, IIndicatorValue ema3Value)
{
if (candle.State != CandleStates.Finished)
return;
if (!rsiValue.IsFormed)
return;
var rsi = rsiValue.GetValue<decimal>();
var high = _rsiHigh.Process(rsi, candle.OpenTime, true).ToDecimal();
var low = _rsiLow.Process(rsi, candle.OpenTime, true).ToDecimal();
if (!_rsiHigh.IsFormed || !_rsiLow.IsFormed)
return;
var stoch = high - low > 0m ? 100m * (rsi - low) / (high - low) : 0m;
var kValue = _kMa.Process(stoch, candle.OpenTime, true);
if (!_kMa.IsFormed)
return;
var k = kValue.ToDecimal();
var dValue = _dMa.Process(k, candle.OpenTime, true);
if (!_dMa.IsFormed)
return;
var d = dValue.ToDecimal();
var prevK = _prevK;
var prevD = _prevD;
_prevK = k;
_prevD = d;
if (prevK is not decimal pk || prevD is not decimal pd)
return;
if (!ema1Value.IsFormed || !ema2Value.IsFormed || !ema3Value.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ema1 = ema1Value.GetValue<decimal>();
var ema2 = ema2Value.GetValue<decimal>();
var ema3 = ema3Value.GetValue<decimal>();
var close = candle.ClosePrice;
var crossUp = pk <= pd && k > d;
var crossDown = pk >= pd && k < d;
var longSignal = crossUp && k >= 10m && k <= 60m && ema1 > ema2 && ema2 > ema3 && close > ema1;
var shortSignal = crossDown && k >= 40m && k <= 95m && ema1 < ema2 && ema2 < ema3 && close < ema1;
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(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, SimpleMovingAverage, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class stoch_rsi_crossover_strategy(Strategy):
"""
Stoch RSI Crossover Strategy.
RSI is turned into a Stochastic RSI over StochLength bars and smoothed into %K (SmoothK) and %D (SmoothD).
A long opens when %K crosses above %D with %K in [10, 60], EMA1 > EMA2 > EMA3 and the close above EMA1.
A short opens when %K crosses below %D with %K in [40, 95], EMA1 < EMA2 < EMA3 and the close below EMA1.
There is no built-in exit: an opposite signal reverses the position. The ATR multipliers only describe suggested levels.
"""
def __init__(self):
super(stoch_rsi_crossover_strategy, self).__init__()
self._smooth_k = self.Param("SmoothK", 3).SetGreaterThanZero().SetDisplay("Smooth K", "%K smoothing", "Stoch RSI")
self._smooth_d = self.Param("SmoothD", 3).SetGreaterThanZero().SetDisplay("Smooth D", "%D smoothing", "Stoch RSI")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI period", "Stoch RSI")
self._stoch_length = self.Param("StochLength", 14).SetGreaterThanZero().SetDisplay("Stoch Length", "Stochastic period applied to RSI", "Stoch RSI")
self._ema1_length = self.Param("Ema1Length", 20).SetGreaterThanZero().SetDisplay("EMA1 Length", "Fast EMA period", "Trend")
self._ema2_length = self.Param("Ema2Length", 50).SetGreaterThanZero().SetDisplay("EMA2 Length", "Medium EMA period", "Trend")
self._ema3_length = self.Param("Ema3Length", 100).SetGreaterThanZero().SetDisplay("EMA3 Length", "Slow EMA period", "Trend")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period of the suggested levels", "Risk")
self._atr_loss_multiplier = self.Param("AtrLossMultiplier", 1.5).SetGreaterThanZero().SetDisplay("ATR Loss Multiplier", "ATR multiplier of the suggested stop loss", "Risk")
self._atr_profit_multiplier = self.Param("AtrProfitMultiplier", 2.0).SetGreaterThanZero().SetDisplay("ATR Profit Multiplier", "ATR multiplier of the suggested profit target", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi_high = None
self._rsi_low = None
self._k_ma = None
self._d_ma = None
self._prev_k = None
self._prev_d = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(stoch_rsi_crossover_strategy, self).OnReseted()
self._prev_k = None
self._prev_d = None
def OnStarted2(self, time):
super(stoch_rsi_crossover_strategy, self).OnStarted2(time)
self._prev_k = None
self._prev_d = None
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
ema1 = ExponentialMovingAverage()
ema1.Length = self._ema1_length.Value
ema2 = ExponentialMovingAverage()
ema2.Length = self._ema2_length.Value
ema3 = ExponentialMovingAverage()
ema3.Length = self._ema3_length.Value
self._rsi_high = Highest()
self._rsi_high.Length = self._stoch_length.Value
self._rsi_low = Lowest()
self._rsi_low.Length = self._stoch_length.Value
self._k_ma = SimpleMovingAverage()
self._k_ma.Length = self._smooth_k.Value
self._d_ma = SimpleMovingAverage()
self._d_ma.Length = self._smooth_d.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(rsi, ema1, ema2, ema3, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema1)
self.DrawIndicator(area, ema2)
self.DrawIndicator(area, ema3)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_value, ema1_value, ema2_value, ema3_value):
if candle.State != CandleStates.Finished:
return
if not rsi_value.IsFormed:
return
rsi_dec = rsi_value.GetValue[Decimal](None)
rsi = float(rsi_dec)
high = float(process_float(self._rsi_high, rsi_dec, candle.OpenTime, True).GetValue[Decimal](None))
low = float(process_float(self._rsi_low, rsi_dec, candle.OpenTime, True).GetValue[Decimal](None))
if not self._rsi_high.IsFormed or not self._rsi_low.IsFormed:
return
stoch = 100.0 * (rsi - low) / (high - low) if high - low > 0 else 0.0
k_value = process_float(self._k_ma, stoch, candle.OpenTime, True)
if not self._k_ma.IsFormed:
return
k_dec = k_value.GetValue[Decimal](None)
k = float(k_dec)
d_value = process_float(self._d_ma, k_dec, candle.OpenTime, True)
if not self._d_ma.IsFormed:
return
d = float(d_value.GetValue[Decimal](None))
prev_k = self._prev_k
prev_d = self._prev_d
self._prev_k = k
self._prev_d = d
if prev_k is None or prev_d is None:
return
if not ema1_value.IsFormed or not ema2_value.IsFormed or not ema3_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ema1 = float(ema1_value.GetValue[Decimal](None))
ema2 = float(ema2_value.GetValue[Decimal](None))
ema3 = float(ema3_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
cross_up = prev_k <= prev_d and k > d
cross_down = prev_k >= prev_d and k < d
long_signal = cross_up and 10.0 <= k <= 60.0 and ema1 > ema2 and ema2 > ema3 and close > ema1
short_signal = cross_down and 40.0 <= k <= 95.0 and ema1 < ema2 and ema2 < ema3 and close < ema1
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return stoch_rsi_crossover_strategy()