EMA/SMA + RSI Crossover Strategy
This strategy tracks three exponential moving averages (fast, medium, and slow) along with an RSI filter to participate in emerging trends. A trade is triggered when the fast average crosses the medium one in the direction of the prevailing slow average, indicating that momentum is accelerating. Only candles that close in the direction of the crossover are considered to avoid whipsaws.
A protective exit can optionally close positions after a user-defined number of bars if they remain profitable. The RSI acts as an overbought/oversold guard to exit when momentum becomes stretched.
Backtests show the technique works best on liquid crypto pairs during trending phases where moving averages offer clear separation.
Details
- Entry Criteria:
- Long:
EMA_fast > EMA_mediumandEMA_fast(t-1) <= EMA_medium(t-1)andClose > EMA_slowandClose > Open - Short:
EMA_fast < EMA_mediumandEMA_fast(t-1) >= EMA_medium(t-1)andClose < EMA_slowandClose < Open
- Long:
- Long/Short: Both sides.
- Exit Criteria:
- Long:
RSI > 70orX bars in profit and Close > entry - Short:
RSI < 30orX bars in profit and Close < entry
- Long:
- Stops: None.
- Default Values:
EMA_fast= 10EMA_medium= 20EMA_slow= 100RSI_length= 14X bars= 24
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: EMA, RSI
- Stops: Optional time-based
- Complexity: Medium
- Timeframe: Short-term
- 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>
/// EMA/SMA + RSI Strategy.
/// Uses three EMAs for trend and crossover, with RSI for exit signals.
/// Buy on fast EMA crossing above medium EMA when both above slow EMA.
/// Sell on fast EMA crossing below medium EMA when both below slow EMA.
/// </summary>
public class EmaSmaRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _emaALength;
private readonly StrategyParam<int> _emaBLength;
private readonly StrategyParam<int> _emaCLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _cooldownBars;
private ExponentialMovingAverage _emaA;
private ExponentialMovingAverage _emaB;
private ExponentialMovingAverage _emaC;
private RelativeStrengthIndex _rsi;
private decimal _prevEmaA;
private decimal _prevEmaB;
private int _cooldownRemaining;
public EmaSmaRsiStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_emaALength = Param(nameof(EmaALength), 10)
.SetGreaterThanZero()
.SetDisplay("EMA A Length", "Fast EMA period", "Moving Averages");
_emaBLength = Param(nameof(EmaBLength), 20)
.SetGreaterThanZero()
.SetDisplay("EMA B Length", "Medium EMA period", "Moving Averages");
_emaCLength = Param(nameof(EmaCLength), 50)
.SetGreaterThanZero()
.SetDisplay("EMA C Length", "Slow EMA period", "Moving Averages");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.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 RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_emaA = null;
_emaB = null;
_emaC = null;
_rsi = null;
_prevEmaA = 0;
_prevEmaB = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_emaA = new ExponentialMovingAverage { Length = EmaALength };
_emaB = new ExponentialMovingAverage { Length = EmaBLength };
_emaC = new ExponentialMovingAverage { Length = EmaCLength };
_rsi = new RelativeStrengthIndex { Length = RsiLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_emaA, _emaB, _emaC, _rsi, 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 emaA, decimal emaB, decimal emaC, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
if (!_emaA.IsFormed || !_emaB.IsFormed || !_emaC.IsFormed || !_rsi.IsFormed)
{
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevEmaA = emaA;
_prevEmaB = emaB;
return;
}
// Crossover detection
var bullishCross = emaA > emaB && _prevEmaA <= _prevEmaB && _prevEmaA > 0;
var bearishCross = emaA < emaB && _prevEmaA >= _prevEmaB && _prevEmaA > 0;
// Exit long on RSI overbought
if (Position > 0 && rsi > 70)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short on RSI oversold
else if (Position < 0 && rsi < 30)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Buy: fast crosses above medium, both above slow
else if (bullishCross && emaA > emaC && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: fast crosses below medium, both below slow
else if (bearishCross && emaA < emaC && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_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 ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class ema_sma_rsi_strategy(Strategy):
"""EMA/SMA + RSI Strategy.
Uses three EMAs for trend and crossover, with RSI for exit signals."""
def __init__(self):
super(ema_sma_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._ema_a_length = self.Param("EmaALength", 10) \
.SetDisplay("EMA A Length", "Fast EMA period", "Moving Averages")
self._ema_b_length = self.Param("EmaBLength", 20) \
.SetDisplay("EMA B Length", "Medium EMA period", "Moving Averages")
self._ema_c_length = self.Param("EmaCLength", 50) \
.SetDisplay("EMA C Length", "Slow EMA period", "Moving Averages")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period", "RSI")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._ema_a = None
self._ema_b = None
self._ema_c = None
self._rsi = 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(ema_sma_rsi_strategy, self).OnReseted()
self._ema_a = None
self._ema_b = None
self._ema_c = None
self._rsi = None
self._prev_ema_a = 0.0
self._prev_ema_b = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ema_sma_rsi_strategy, self).OnStarted2(time)
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)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema_a, self._ema_b, self._ema_c, self._rsi, 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, ema_a_val, ema_b_val, ema_c_val, rsi_val):
if candle.State != CandleStates.Finished:
return
if not self._ema_a.IsFormed or not self._ema_b.IsFormed or not self._ema_c.IsFormed or not self._rsi.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
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_ema_a = float(ema_a_val)
self._prev_ema_b = float(ema_b_val)
return
ea = float(ema_a_val)
eb = float(ema_b_val)
ec = float(ema_c_val)
rsi = float(rsi_val)
cooldown = int(self._cooldown_bars.Value)
bullish_cross = ea > eb and self._prev_ema_a <= self._prev_ema_b and self._prev_ema_a > 0
bearish_cross = ea < eb and self._prev_ema_a >= self._prev_ema_b and self._prev_ema_a > 0
if 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
elif bullish_cross and ea > ec and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif bearish_cross and ea < ec and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
self._prev_ema_a = ea
self._prev_ema_b = eb
def CreateClone(self):
return ema_sma_rsi_strategy()