Multi-timeframe EMA + BB + RSI Strategy
Combines two exponential moving averages, Bollinger Bands and RSI to trade bounces. Long trades occur when price closes above the fast EMA after touching the lower band. Short trades trigger when price closes below the fast EMA after piercing the upper band and RSI is above 50.
Optional profit-taking closes the position after a user-defined number of bars if price moves favorably. The system is flexible enough for swing or intraday trading and supports enabling or disabling long and short sides independently.
Details
- Entry Criteria:
- Long: Close above fast EMA with a low piercing the lower Bollinger Band.
- Short: Close below fast EMA with a high piercing the upper band and RSI > 50.
- Exit Criteria:
- Long: RSI rises above the oversold level.
- Short: Price closes below the lower band.
- Indicators:
- Two EMAs (periods 10 and 55)
- Bollinger Bands (length 20, multiplier 2)
- RSI (length 14, oversold 71)
- Stops: Optional profit target after X bars; no fixed stop-loss.
- Default Values:
Ma1Period= 10Ma2Period= 55BBLength= 20BBMultiplier= 2.0RSILength= 14RSIOversold= 71XBars= 12
- Filters:
- Mean reversion with trend filter
- Timeframe: configurable
- Indicators: EMA, Bollinger Bands, RSI
- Stops: optional
- Complexity: Moderate
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>
/// Multi EMA + Bollinger Bands + RSI strategy.
/// Goes long when the close is above the fast EMA on a candle whose low pierced the lower band, and short when the
/// close is below the fast EMA on a candle whose high pierced the upper band while RSI is above 50. A long closes when
/// RSI rises above RSIOversold and a short when the close drops below the lower band. After XBars bars a position that
/// is in profit is closed as well. Each side can be switched off. The slow EMA is plotted as a trend reference.
/// </summary>
public class MemaBbRsiStrategy : Strategy
{
private readonly StrategyParam<int> _ma1Period;
private readonly StrategyParam<int> _ma2Period;
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<int> _xBars;
private readonly StrategyParam<bool> _enableLong;
private readonly StrategyParam<bool> _enableShort;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private int _barsInPosition;
/// <summary>
/// Fast EMA period.
/// </summary>
public int Ma1Period
{
get => _ma1Period.Value;
set => _ma1Period.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int Ma2Period
{
get => _ma2Period.Value;
set => _ma2Period.Value = value;
}
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BBLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
/// <summary>
/// Bollinger Bands standard deviation multiplier.
/// </summary>
public decimal BBMultiplier
{
get => _bbMultiplier.Value;
set => _bbMultiplier.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RSILength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// RSI level above which a long is closed.
/// </summary>
public decimal RSIOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// Bars after which a profitable position is closed. 0 disables the time exit.
/// </summary>
public int XBars
{
get => _xBars.Value;
set => _xBars.Value = value;
}
/// <summary>
/// Allow long trades.
/// </summary>
public bool EnableLong
{
get => _enableLong.Value;
set => _enableLong.Value = value;
}
/// <summary>
/// Allow short trades.
/// </summary>
public bool EnableShort
{
get => _enableShort.Value;
set => _enableShort.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MemaBbRsiStrategy()
{
_ma1Period = Param(nameof(Ma1Period), 10)
.SetGreaterThanZero()
.SetDisplay("MA1 Period", "Fast EMA period", "Moving Average");
_ma2Period = Param(nameof(Ma2Period), 55)
.SetGreaterThanZero()
.SetDisplay("MA2 Period", "Slow EMA period", "Moving Average");
_bbLength = Param(nameof(BBLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands");
_bbMultiplier = Param(nameof(BBMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("BB Multiplier", "Bollinger Bands standard deviation multiplier", "Bollinger Bands");
_rsiLength = Param(nameof(RSILength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_rsiOversold = Param(nameof(RSIOversold), 71m)
.SetDisplay("RSI Exit Level", "RSI level above which a long is closed", "RSI");
_xBars = Param(nameof(XBars), 12)
.SetNotNegative()
.SetDisplay("X Bars", "Bars after which a profitable position is closed, 0 disables", "Exit");
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow long trades", "Trading");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow short trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_barsInPosition = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0m;
_barsInPosition = 0;
var ma1 = new ExponentialMovingAverage { Length = Ma1Period };
var ma2 = new ExponentialMovingAverage { Length = Ma2Period };
var bollinger = new BollingerBands { Length = BBLength, Width = BBMultiplier };
var rsi = new RelativeStrengthIndex { Length = RSILength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ma1, ma2, bollinger, rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma1);
DrawIndicator(area, ma2);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
var rsiArea = CreateChartArea();
if (rsiArea != null)
DrawIndicator(rsiArea, rsi);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue ma1Value, IIndicatorValue ma2Value, IIndicatorValue bollingerValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (Position != 0)
_barsInPosition++;
if (!ma1Value.IsFormed || !bollingerValue.IsFormed || !rsiValue.IsFormed)
return;
var bb = (BollingerBandsValue)bollingerValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ma1 = ma1Value.ToDecimal();
var rsi = rsiValue.ToDecimal();
var close = candle.ClosePrice;
var longSignal = EnableLong && close > ma1 && candle.LowPrice < lower;
var shortSignal = EnableShort && close < ma1 && candle.HighPrice > upper && rsi > 50m;
var timeUp = XBars > 0 && _barsInPosition >= XBars;
if (longSignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_entryPrice = close;
_barsInPosition = 0;
}
else if (shortSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_entryPrice = close;
_barsInPosition = 0;
}
else if (Position > 0 && (rsi > RSIOversold || (timeUp && close > _entryPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (close < lower || (timeUp && close < _entryPrice)))
{
BuyMarket(-Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, BollingerBands, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class mema_bb_rsi_strategy(Strategy):
"""
Multi EMA + Bollinger Bands + RSI strategy.
Goes long when the close is above the fast EMA on a candle whose low pierced the lower band, and short when the
close is below the fast EMA on a candle whose high pierced the upper band while RSI is above 50. A long closes when
RSI rises above RSIOversold and a short when the close drops below the lower band. After XBars bars a position that
is in profit is closed as well. Each side can be switched off. The slow EMA is plotted as a trend reference.
"""
def __init__(self):
super(mema_bb_rsi_strategy, self).__init__()
self._ma1_period = self.Param("Ma1Period", 10) \
.SetGreaterThanZero() \
.SetDisplay("MA1 Period", "Fast EMA period", "Moving Average")
self._ma2_period = self.Param("Ma2Period", 55) \
.SetGreaterThanZero() \
.SetDisplay("MA2 Period", "Slow EMA period", "Moving Average")
self._bb_length = self.Param("BBLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands")
self._bb_multiplier = self.Param("BBMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("BB Multiplier", "Bollinger Bands standard deviation multiplier", "Bollinger Bands")
self._rsi_length = self.Param("RSILength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI period", "RSI")
self._rsi_oversold = self.Param("RSIOversold", 71.0) \
.SetDisplay("RSI Exit Level", "RSI level above which a long is closed", "RSI")
self._x_bars = self.Param("XBars", 12) \
.SetNotNegative() \
.SetDisplay("X Bars", "Bars after which a profitable position is closed, 0 disables", "Exit")
self._enable_long = self.Param("EnableLong", True) \
.SetDisplay("Enable Long", "Allow long trades", "Trading")
self._enable_short = self.Param("EnableShort", True) \
.SetDisplay("Enable Short", "Allow short trades", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle type", "Candle type for strategy calculation", "General")
self._entry_price = 0.0
self._bars_in_position = 0
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(mema_bb_rsi_strategy, self).OnReseted()
self._entry_price = 0.0
self._bars_in_position = 0
def OnStarted2(self, time):
super(mema_bb_rsi_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._bars_in_position = 0
ma1 = ExponentialMovingAverage()
ma1.Length = self._ma1_period.Value
ma2 = ExponentialMovingAverage()
ma2.Length = self._ma2_period.Value
bollinger = BollingerBands()
bollinger.Length = self._bb_length.Value
bollinger.Width = Decimal(self._bb_multiplier.Value)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(ma1, ma2, bollinger, rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma1)
self.DrawIndicator(area, ma2)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
rsi_area = self.CreateChartArea()
if rsi_area is not None:
self.DrawIndicator(rsi_area, rsi)
def _process_candle(self, candle, ma1_value, ma2_value, bollinger_value, rsi_value):
if candle.State != CandleStates.Finished:
return
if self.Position != 0:
self._bars_in_position += 1
if not ma1_value.IsFormed or not bollinger_value.IsFormed or not rsi_value.IsFormed:
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
upper = float(bollinger_value.UpBand)
lower = float(bollinger_value.LowBand)
ma1 = float(ma1_value.GetValue[Decimal](None))
rsi = float(rsi_value.GetValue[Decimal](None))
close = float(candle.ClosePrice)
long_signal = self._enable_long.Value and close > ma1 and float(candle.LowPrice) < lower
short_signal = self._enable_short.Value and close < ma1 and float(candle.HighPrice) > upper and rsi > 50.0
x_bars = int(self._x_bars.Value)
time_up = x_bars > 0 and self._bars_in_position >= x_bars
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._entry_price = close
self._bars_in_position = 0
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._entry_price = close
self._bars_in_position = 0
elif self.Position > 0 and (rsi > float(self._rsi_oversold.Value) or (time_up and close > self._entry_price)):
self.SellMarket(self.Position)
elif self.Position < 0 and (close < lower or (time_up and close < self._entry_price)):
self.BuyMarket(-self.Position)
def CreateClone(self):
return mema_bb_rsi_strategy()