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>
/// Stochastic RSI Crossover Strategy with EMA trend filter.
/// Uses RSI crossovers with triple EMA alignment for trend confirmation.
/// Buys when RSI crosses above oversold in bullish EMA alignment.
/// Sells when RSI crosses below overbought in bearish EMA alignment.
/// </summary>
public class StochRsiCrossoverStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _rsiOversold;
private readonly StrategyParam<int> _rsiOverbought;
private readonly StrategyParam<int> _ema1Length;
private readonly StrategyParam<int> _ema2Length;
private readonly StrategyParam<int> _ema3Length;
private readonly StrategyParam<int> _cooldownBars;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ema1;
private ExponentialMovingAverage _ema2;
private ExponentialMovingAverage _ema3;
private decimal _prevRsi;
private int _cooldownRemaining;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
public int RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
public int Ema1Length
{
get => _ema1Length.Value;
set => _ema1Length.Value = value;
}
public int Ema2Length
{
get => _ema2Length.Value;
set => _ema2Length.Value = value;
}
public int Ema3Length
{
get => _ema3Length.Value;
set => _ema3Length.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public StochRsiCrossoverStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_rsiOversold = Param(nameof(RsiOversold), 40)
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI");
_rsiOverbought = Param(nameof(RsiOverbought), 60)
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI");
_ema1Length = Param(nameof(Ema1Length), 8)
.SetGreaterThanZero()
.SetDisplay("EMA 1 Length", "Fast EMA length", "Moving Averages");
_ema2Length = Param(nameof(Ema2Length), 14)
.SetGreaterThanZero()
.SetDisplay("EMA 2 Length", "Medium EMA length", "Moving Averages");
_ema3Length = Param(nameof(Ema3Length), 50)
.SetGreaterThanZero()
.SetDisplay("EMA 3 Length", "Slow EMA length", "Moving Averages");
_cooldownBars = Param(nameof(CooldownBars), 15)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_ema1 = null;
_ema2 = null;
_ema3 = null;
_prevRsi = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_ema1 = new ExponentialMovingAverage { Length = Ema1Length };
_ema2 = new ExponentialMovingAverage { Length = Ema2Length };
_ema3 = new ExponentialMovingAverage { Length = Ema3Length };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _ema1, _ema2, _ema3, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema1);
DrawIndicator(area, _ema2);
DrawIndicator(area, _ema3);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal rsiVal, decimal ema1Val, decimal ema2Val, decimal ema3Val)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_ema1.IsFormed || !_ema2.IsFormed || !_ema3.IsFormed)
{
_prevRsi = rsiVal;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevRsi = rsiVal;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevRsi = rsiVal;
return;
}
if (_prevRsi == 0)
{
_prevRsi = rsiVal;
return;
}
// EMA alignment (relaxed - only fast vs slow)
var bullishEma = ema1Val > ema3Val;
var bearishEma = ema1Val < ema3Val;
// RSI crossovers
var rsiCrossUpOversold = rsiVal > RsiOversold && _prevRsi <= RsiOversold;
var rsiCrossDownOverbought = rsiVal < RsiOverbought && _prevRsi >= RsiOverbought;
// Buy: RSI crosses above oversold + bullish EMA
if (rsiCrossUpOversold && bullishEma && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: RSI crosses below overbought + bearish EMA
else if (rsiCrossDownOverbought && bearishEma && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: RSI overbought or EMA bearish cross
else if (Position > 0 && (rsiVal > RsiOverbought || ema1Val < ema2Val))
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: RSI oversold or EMA bullish cross
else if (Position < 0 && (rsiVal < RsiOversold || ema1Val > ema2Val))
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevRsi = rsiVal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class stoch_rsi_crossover_strategy(Strategy):
"""Stochastic RSI Crossover Strategy."""
def __init__(self):
super(stoch_rsi_crossover_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period", "RSI")
self._rsi_oversold = self.Param("RsiOversold", 40) \
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI")
self._rsi_overbought = self.Param("RsiOverbought", 60) \
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI")
self._ema1_length = self.Param("Ema1Length", 8) \
.SetDisplay("EMA 1 Length", "Fast EMA length", "Moving Averages")
self._ema2_length = self.Param("Ema2Length", 14) \
.SetDisplay("EMA 2 Length", "Medium EMA length", "Moving Averages")
self._ema3_length = self.Param("Ema3Length", 50) \
.SetDisplay("EMA 3 Length", "Slow EMA length", "Moving Averages")
self._cooldown_bars = self.Param("CooldownBars", 15) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._rsi = None
self._ema1 = None
self._ema2 = None
self._ema3 = None
self._prev_rsi = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(stoch_rsi_crossover_strategy, self).OnReseted()
self._rsi = None
self._ema1 = None
self._ema2 = None
self._ema3 = None
self._prev_rsi = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(stoch_rsi_crossover_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._ema1 = ExponentialMovingAverage()
self._ema1.Length = int(self._ema1_length.Value)
self._ema2 = ExponentialMovingAverage()
self._ema2.Length = int(self._ema2_length.Value)
self._ema3 = ExponentialMovingAverage()
self._ema3.Length = int(self._ema3_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._ema1, self._ema2, self._ema3, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema1)
self.DrawIndicator(area, self._ema2)
self.DrawIndicator(area, self._ema3)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_val, ema1_val, ema2_val, ema3_val):
if candle.State != CandleStates.Finished:
return
if not self._rsi.IsFormed or not self._ema1.IsFormed or not self._ema2.IsFormed or not self._ema3.IsFormed:
self._prev_rsi = float(rsi_val)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = float(rsi_val)
return
rsi = float(rsi_val)
ema1 = float(ema1_val)
ema2 = float(ema2_val)
ema3 = float(ema3_val)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_rsi = rsi
return
if self._prev_rsi == 0.0:
self._prev_rsi = rsi
return
rsi_os = int(self._rsi_oversold.Value)
rsi_ob = int(self._rsi_overbought.Value)
cooldown = int(self._cooldown_bars.Value)
bullish_ema = ema1 > ema3
bearish_ema = ema1 < ema3
rsi_cross_up_oversold = rsi > rsi_os and self._prev_rsi <= rsi_os
rsi_cross_down_overbought = rsi < rsi_ob and self._prev_rsi >= rsi_ob
if rsi_cross_up_oversold and bullish_ema and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif rsi_cross_down_overbought and bearish_ema and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and (rsi > rsi_ob or ema1 < ema2):
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and (rsi < rsi_os or ema1 > ema2):
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_rsi = rsi
def CreateClone(self):
return stoch_rsi_crossover_strategy()