RSI + EMA Trend Strategy
This system pairs a classic Relative Strength Index (RSI) oscillator with a dual moving-average trend filter. The RSI provides short-term overbought and oversold readings while the two exponential moving averages (EMAs) define the broader trend. The strategy only takes trades in the direction of the fast EMA relative to the slow EMA, helping avoid counter‑trend setups during strong directional moves.
When price momentum pushes RSI below the oversold threshold and the fast EMA is above the slow EMA, the market is assumed to be in an uptrend and a long position is opened. Conversely, if RSI rises above the overbought level while the fast EMA still exceeds the slow EMA, the strategy initiates a short trade, expecting a short‑term pullback inside the larger trend channel.
Positions are exited when RSI leaves the extreme zone on the opposite side, signalling that the mean reversion move has likely exhausted. The method is simple yet effective for capturing brief momentum swings in trending environments. It works well on liquid instruments where RSI extremes occur frequently but trend direction remains intact.
Details
- Entry Criteria:
- Long:
RSI < oversold and EMA1 > EMA2.
- Short:
RSI > overbought and EMA1 > EMA2.
- Long/Short: Both sides.
- Exit Criteria:
- Long:
RSI > overbought.
- Short:
RSI < oversold.
- Stops: None built-in.
- Default Values:
RSI Length = 14.
Overbought/Oversold = 70 / 30.
EMA Lengths = 150 / 600.
- Filters:
- Category: Momentum
- Direction: Both
- Indicators: Multiple
- Stops: No
- Complexity: Basic
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- 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>
/// RSI + EMA Strategy.
/// Uses RSI oversold/overbought levels with dual EMA trend filter.
/// Buys when RSI is oversold and fast EMA > slow EMA.
/// Sells when RSI is overbought and fast EMA > slow EMA.
/// </summary>
public class RsiEmaStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _rsiOverbought;
private readonly StrategyParam<int> _rsiOversold;
private readonly StrategyParam<int> _ma1Length;
private readonly StrategyParam<int> _ma2Length;
private readonly StrategyParam<int> _cooldownBars;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ma1;
private ExponentialMovingAverage _ma2;
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 RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
public int RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
public int Ma1Length
{
get => _ma1Length.Value;
set => _ma1Length.Value = value;
}
public int Ma2Length
{
get => _ma2Length.Value;
set => _ma2Length.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public RsiEmaStrategy()
{
_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 calculation length", "RSI");
_rsiOverbought = Param(nameof(RsiOverbought), 70)
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI");
_rsiOversold = Param(nameof(RsiOversold), 30)
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI");
_ma1Length = Param(nameof(Ma1Length), 20)
.SetGreaterThanZero()
.SetDisplay("MA1 Length", "Fast EMA length", "Moving Averages");
_ma2Length = Param(nameof(Ma2Length), 50)
.SetGreaterThanZero()
.SetDisplay("MA2 Length", "Slow EMA length", "Moving Averages");
_cooldownBars = Param(nameof(CooldownBars), 10)
.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;
_ma1 = null;
_ma2 = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_ma1 = new ExponentialMovingAverage { Length = Ma1Length };
_ma2 = new ExponentialMovingAverage { Length = Ma2Length };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _ma1, _ma2, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma1);
DrawIndicator(area, _ma2);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal rsiVal, decimal ma1Val, decimal ma2Val)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_ma1.IsFormed || !_ma2.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
var uptrend = ma1Val > ma2Val;
var downtrend = ma1Val < ma2Val;
// Buy: RSI oversold in uptrend
if (rsiVal < RsiOversold && uptrend && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: RSI overbought in downtrend
else if (rsiVal > RsiOverbought && downtrend && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: RSI overbought
else if (Position > 0 && rsiVal > RsiOverbought)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: RSI oversold
else if (Position < 0 && rsiVal < RsiOversold)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = 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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class rsi_ema_strategy(Strategy):
"""RSI + EMA Strategy."""
def __init__(self):
super(rsi_ema_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 calculation length", "RSI")
self._rsi_overbought = self.Param("RsiOverbought", 70) \
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI")
self._rsi_oversold = self.Param("RsiOversold", 30) \
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI")
self._ma1_length = self.Param("Ma1Length", 20) \
.SetDisplay("MA1 Length", "Fast EMA length", "Moving Averages")
self._ma2_length = self.Param("Ma2Length", 50) \
.SetDisplay("MA2 Length", "Slow EMA length", "Moving Averages")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._rsi = None
self._ma1 = None
self._ma2 = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(rsi_ema_strategy, self).OnReseted()
self._rsi = None
self._ma1 = None
self._ma2 = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(rsi_ema_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._ma1 = ExponentialMovingAverage()
self._ma1.Length = int(self._ma1_length.Value)
self._ma2 = ExponentialMovingAverage()
self._ma2.Length = int(self._ma2_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._ma1, self._ma2, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ma1)
self.DrawIndicator(area, self._ma2)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_val, ma1_val, ma2_val):
if candle.State != CandleStates.Finished:
return
if not self._rsi.IsFormed or not self._ma1.IsFormed or not self._ma2.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
rsi = float(rsi_val)
ma1 = float(ma1_val)
ma2 = float(ma2_val)
rsi_ob = int(self._rsi_overbought.Value)
rsi_os = int(self._rsi_oversold.Value)
cooldown = int(self._cooldown_bars.Value)
uptrend = ma1 > ma2
downtrend = ma1 < ma2
if rsi < rsi_os and uptrend and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif rsi > rsi_ob and downtrend 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:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and rsi < rsi_os:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
def CreateClone(self):
return rsi_ema_strategy()