Tendency EMA + RSI Strategy
This strategy layers a fast/medium EMA crossover on top of a slower trend EMA and an RSI filter. Long trades require the fast EMA to cross above the medium EMA while both remain above the slow trend line and the candle closes bullish. Short trades mirror these rules. RSI extremes close positions, and an optional "close after X bars" feature locks in profits if price moves in the expected direction quickly.
The approach aims to participate only in pullback entries that align with the prevailing trend, using the RSI to exit when momentum becomes overstretched. It works best on intraday charts where EMA crossovers offer timely signals and multiple setups occur each session.
Details
- Entry Criteria:
- Fast EMA crosses above medium EMA, both above slow EMA, bullish candle.
- Fast EMA crosses below medium EMA, both below slow EMA, bearish candle.
- Long/Short: Long enabled, short optional.
- Exit Criteria:
- RSI > 70 closes long; RSI < 30 closes short.
- Optional: close after X bars if trade is profitable.
- Stops: None built‑in.
- Default Values:
- RSI length = 14.
- EMA A/B/C lengths = 9/21/50.
- Close after X bars = off, X = 5.
- Filters:
- Category: Trend + Momentum
- Direction: Both (long default)
- Indicators: EMA, RSI
- Stops: No
- Complexity: Moderate
- Timeframe: Short
- 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>
/// Tendency EMA + RSI Strategy.
/// Uses EMA crossover with RSI and trend filter.
/// Buys when fast EMA crosses above medium EMA while above slow EMA.
/// Exits when RSI becomes overbought/oversold.
/// </summary>
public class TendencyEmaRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaALength;
private readonly StrategyParam<int> _emaBLength;
private readonly StrategyParam<int> _emaCLength;
private readonly StrategyParam<int> _cooldownBars;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _emaA;
private ExponentialMovingAverage _emaB;
private ExponentialMovingAverage _emaC;
private decimal _prevEmaA;
private decimal _prevEmaB;
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 EmaALength
{
get => _emaALength.Value;
set => _emaALength.Value = value;
}
public int EmaBLength
{
get => _emaBLength.Value;
set => _emaBLength.Value = value;
}
public int EmaCLength
{
get => _emaCLength.Value;
set => _emaCLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public TendencyEmaRsiStrategy()
{
_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");
_emaALength = Param(nameof(EmaALength), 10)
.SetGreaterThanZero()
.SetDisplay("EMA A Length", "Fast EMA length", "Moving Averages");
_emaBLength = Param(nameof(EmaBLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA B Length", "Medium EMA length", "Moving Averages");
_emaCLength = Param(nameof(EmaCLength), 100)
.SetGreaterThanZero()
.SetDisplay("EMA C Length", "Slow/Trend 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;
_emaA = null;
_emaB = null;
_emaC = null;
_prevEmaA = 0;
_prevEmaB = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_emaA = new ExponentialMovingAverage { Length = EmaALength };
_emaB = new ExponentialMovingAverage { Length = EmaBLength };
_emaC = new ExponentialMovingAverage { Length = EmaCLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _emaA, _emaB, _emaC, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _emaA);
DrawIndicator(area, _emaB);
DrawIndicator(area, _emaC);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal rsiVal, decimal emaA, decimal emaB, decimal emaC)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_emaA.IsFormed || !_emaB.IsFormed || !_emaC.IsFormed)
{
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
if (_prevEmaA == 0 || _prevEmaB == 0)
{
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
// EMA crossovers
var emaCrossUp = emaA > emaB && _prevEmaA <= _prevEmaB;
var emaCrossDown = emaA < emaB && _prevEmaA >= _prevEmaB;
// Buy: EMA A crosses above EMA B + EMA A > EMA C (uptrend) + bullish candle
if (emaCrossUp && emaA > emaC && candle.ClosePrice > candle.OpenPrice && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: EMA A crosses below EMA B + EMA A < EMA C (downtrend) + bearish candle
else if (emaCrossDown && emaA < emaC && candle.ClosePrice < candle.OpenPrice && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: RSI overbought
else if (Position > 0 && rsiVal > 70)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: RSI oversold
else if (Position < 0 && rsiVal < 30)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevEmaA = emaA;
_prevEmaB = emaB;
}
}
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 tendency_ema_rsi_strategy(Strategy):
"""Tendency EMA + RSI Strategy."""
def __init__(self):
super(tendency_ema_rsi_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._ema_a_length = self.Param("EmaALength", 10) \
.SetDisplay("EMA A Length", "Fast EMA length", "Moving Averages")
self._ema_b_length = self.Param("EmaBLength", 20) \
.SetDisplay("EMA B Length", "Medium EMA length", "Moving Averages")
self._ema_c_length = self.Param("EmaCLength", 100) \
.SetDisplay("EMA C Length", "Slow/Trend EMA length", "Moving Averages")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._rsi = None
self._ema_a = None
self._ema_b = None
self._ema_c = None
self._prev_ema_a = 0.0
self._prev_ema_b = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(tendency_ema_rsi_strategy, self).OnReseted()
self._rsi = None
self._ema_a = None
self._ema_b = None
self._ema_c = None
self._prev_ema_a = 0.0
self._prev_ema_b = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(tendency_ema_rsi_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._ema_a = ExponentialMovingAverage()
self._ema_a.Length = int(self._ema_a_length.Value)
self._ema_b = ExponentialMovingAverage()
self._ema_b.Length = int(self._ema_b_length.Value)
self._ema_c = ExponentialMovingAverage()
self._ema_c.Length = int(self._ema_c_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._ema_a, self._ema_b, self._ema_c, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema_a)
self.DrawIndicator(area, self._ema_b)
self.DrawIndicator(area, self._ema_c)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_val, ema_a_val, ema_b_val, ema_c_val):
if candle.State != CandleStates.Finished:
return
if not self._rsi.IsFormed or not self._ema_a.IsFormed or not self._ema_b.IsFormed or not self._ema_c.IsFormed:
self._prev_ema_a = float(ema_a_val)
self._prev_ema_b = float(ema_b_val)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_ema_a = float(ema_a_val)
self._prev_ema_b = float(ema_b_val)
return
ema_a = float(ema_a_val)
ema_b = float(ema_b_val)
ema_c = float(ema_c_val)
rsi = float(rsi_val)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_ema_a = ema_a
self._prev_ema_b = ema_b
return
if self._prev_ema_a == 0.0 or self._prev_ema_b == 0.0:
self._prev_ema_a = ema_a
self._prev_ema_b = ema_b
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
cooldown = int(self._cooldown_bars.Value)
ema_cross_up = ema_a > ema_b and self._prev_ema_a <= self._prev_ema_b
ema_cross_down = ema_a < ema_b and self._prev_ema_a >= self._prev_ema_b
if ema_cross_up and ema_a > ema_c and close > open_price and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif ema_cross_down and ema_a < ema_c and close < open_price 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 > 70:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and rsi < 30:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_ema_a = ema_a
self._prev_ema_b = ema_b
def CreateClone(self):
return tendency_ema_rsi_strategy()