Rsi Stochastic Strategy
Strategy that combines RSI and Stochastic Oscillator for double confirmation of oversold and overbought conditions.
Testing indicates an average annual return of about 181%. It performs best in the crypto market.
RSI provides a broader momentum view, while Stochastic gives faster signals near extremes. Trades flip as the oscillator crosses levels within the RSI context.
Ideal for nimble traders who favor oscillator setups. The strategy relies on an ATR stop to contain risk.
Details
- Entry Criteria:
- Long:
RSI < RsiOversold && StochK < StochOversold - Short:
RSI > RsiOverbought && StochK > StochOverbought
- Long:
- Long/Short: Both
- Exit Criteria:
- Long:
RSI > 50 - Short:
RSI < 50
- Long:
- Stops: Percent-based at
StopLossPercent - Default Values:
RsiPeriod= 14RsiOversold= 30mRsiOverbought= 70mStochPeriod= 14StochK= 3StochD= 3StochOversold= 20mStochOverbought= 80mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: RSI, Stochastic Oscillator
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- 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>
/// Strategy combining RSI with EMA trend filter for oversold/overbought trading.
/// </summary>
public class RsiStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _cooldownBars;
private decimal _emaValue;
private int _cooldown;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// EMA period for trend filter.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Strategy constructor.
/// </summary>
public RsiStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetRange(7, 21)
.SetDisplay("RSI Period", "Period of the RSI indicator", "Indicators");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetDisplay("RSI Oversold", "RSI oversold level", "Indicators");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetDisplay("RSI Overbought", "RSI overbought level", "Indicators");
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetRange(10, 50)
.SetDisplay("EMA Period", "EMA period for trend filter", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_emaValue = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
// Bind EMA to capture value
subscription.Bind(ema, OnEma);
// Bind RSI for main logic
subscription
.Bind(rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
var rsiArea = CreateChartArea();
if (rsiArea != null)
DrawIndicator(rsiArea, rsi);
}
}
private void OnEma(ICandleMessage candle, decimal ema)
{
_emaValue = ema;
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_emaValue == 0)
return;
var close = candle.ClosePrice;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Long: RSI oversold
if (rsiValue < RsiOversold && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Short: RSI overbought
else if (rsiValue > RsiOverbought && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit long: RSI returns to neutral
if (Position > 0 && rsiValue > 50)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short: RSI returns to neutral
else if (Position < 0 && rsiValue < 50)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class rsi_stochastic_strategy(Strategy):
"""
Strategy combining RSI with EMA trend filter for oversold/overbought trading.
"""
def __init__(self):
super(rsi_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetRange(7, 21) \
.SetDisplay("RSI Period", "Period of the RSI indicator", "Indicators")
self._rsi_oversold = self.Param("RsiOversold", 30.0) \
.SetDisplay("RSI Oversold", "RSI oversold level", "Indicators")
self._rsi_overbought = self.Param("RsiOverbought", 70.0) \
.SetDisplay("RSI Overbought", "RSI overbought level", "Indicators")
self._ema_period = self.Param("EmaPeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("EMA Period", "EMA period for trend filter", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 100) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General") \
.SetRange(5, 500)
self._ema_value = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def rsi_oversold(self):
return self._rsi_oversold.Value
@property
def rsi_overbought(self):
return self._rsi_overbought.Value
@property
def ema_period(self):
return self._ema_period.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnStarted2(self, time):
super(rsi_stochastic_strategy, self).OnStarted2(time)
self._ema_value = 0.0
self._cooldown = 0
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_period
subscription = self.SubscribeCandles(self.candle_type)
# Bind EMA to capture value
subscription.Bind(ema, self.OnEma)
# Bind RSI for main logic
subscription.Bind(rsi, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
rsi_area = self.CreateChartArea()
if rsi_area is not None:
self.DrawIndicator(rsi_area, rsi)
def OnEma(self, candle, ema_val):
self._ema_value = float(ema_val)
def ProcessCandle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if self._ema_value == 0:
return
if self._cooldown > 0:
self._cooldown -= 1
return
rv = float(rsi_value)
# Long: RSI oversold
if rv < self.rsi_oversold and self.Position == 0:
self.BuyMarket()
self._cooldown = self.cooldown_bars
# Short: RSI overbought
elif rv > self.rsi_overbought and self.Position == 0:
self.SellMarket()
self._cooldown = self.cooldown_bars
# Exit long: RSI returns to neutral
if self.Position > 0 and rv > 50:
self.SellMarket()
self._cooldown = self.cooldown_bars
# Exit short: RSI returns to neutral
elif self.Position < 0 and rv < 50:
self.BuyMarket()
self._cooldown = self.cooldown_bars
def OnReseted(self):
super(rsi_stochastic_strategy, self).OnReseted()
self._ema_value = 0.0
self._cooldown = 0
def CreateClone(self):
return rsi_stochastic_strategy()