MACD + Bollinger Bands + RSI Strategy
This composite setup looks for pullbacks against the prevailing MACD momentum that stretch beyond the Bollinger Bands. When MACD is positive yet price closes below the lower band with an oversold RSI, the strategy buys in anticipation of a trend continuation. The opposite applies for shorts.
Details
- Entry Criteria:
- Long:
MACD > 0andClose < LowerBandandRSI < 30 - Short:
MACD < 0andClose > UpperBandandRSI > 70
- Long:
- Long/Short: Both sides
- Exit Criteria: Opposite signal
- Stops: None
- Default Values:
MacdFastLength= 12MacdSlowLength= 26MacdSignalLength= 9BBLength= 20BBMultiplier= 2.0RSILength= 14
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: MACD, Bollinger Bands, RSI
- Stops: No
- Complexity: Medium
- Timeframe: Short-term
- Seasonality: No
- Neural networks: No
- Divergence: Yes
- 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>
/// MACD + Bollinger Bands + RSI Strategy.
/// Uses MACD for momentum, BB for volatility levels, RSI for confirmation.
/// Buys when MACD bullish + price near lower BB + RSI oversold.
/// Sells when MACD bearish + price near upper BB + RSI overbought.
/// </summary>
public class MacdBbRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbWidth;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _cooldownBars;
private MovingAverageConvergenceDivergence _macd;
private BollingerBands _bollinger;
private RelativeStrengthIndex _rsi;
private decimal _prevMacd;
private int _cooldownRemaining;
public MacdBbRsiStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_bbLength = Param(nameof(BBLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands");
_bbWidth = Param(nameof(BBWidth), 1.5m)
.SetDisplay("BB Width", "BB standard deviation multiplier", "Bollinger Bands");
_rsiLength = Param(nameof(RSILength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI");
_cooldownBars = Param(nameof(CooldownBars), 50)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
public int BBLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
public decimal BBWidth
{
get => _bbWidth.Value;
set => _bbWidth.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();
_macd = null;
_bollinger = null;
_rsi = null;
_prevMacd = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_macd = new MovingAverageConvergenceDivergence();
_bollinger = new BollingerBands { Length = BBLength, Width = BBWidth };
_rsi = new RelativeStrengthIndex { Length = RSILength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, OnProcess)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
// Process MACD and BB manually
var macdResult = _macd.Process(candle);
var bbResult = _bollinger.Process(candle);
if (!_macd.IsFormed || !_bollinger.IsFormed)
return;
var macdVal = macdResult.ToDecimal();
var bb = (BollingerBandsValue)bbResult;
if (bb.UpBand is not decimal upper ||
bb.LowBand is not decimal lower ||
bb.MovingAverage is not decimal middle)
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevMacd = macdVal;
return;
}
var close = candle.ClosePrice;
// Buy: price below lower BB + RSI oversold + MACD positive
if (close <= lower && rsi < 30 && Position == 0)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
// Sell: price above upper BB + RSI overbought + MACD negative
else if (close >= upper && rsi > 70 && Position == 0)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
_prevMacd = macdVal;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergence, BollingerBands, RelativeStrengthIndex, IndicatorHelper, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class macd_bb_rsi_strategy(Strategy):
"""MACD + Bollinger Bands + RSI Strategy."""
def __init__(self):
super(macd_bb_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._bb_length = self.Param("BBLength", 20) \
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands")
self._bb_width = self.Param("BBWidth", 1.5) \
.SetDisplay("BB Width", "BB standard deviation multiplier", "Bollinger Bands")
self._rsi_length = self.Param("RSILength", 14) \
.SetDisplay("RSI Length", "RSI period", "RSI")
self._cooldown_bars = self.Param("CooldownBars", 50) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._macd = None
self._bollinger = None
self._rsi = None
self._prev_macd = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_bb_rsi_strategy, self).OnReseted()
self._macd = None
self._bollinger = None
self._rsi = None
self._prev_macd = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(macd_bb_rsi_strategy, self).OnStarted2(time)
self._macd = MovingAverageConvergenceDivergence()
self._bollinger = BollingerBands()
self._bollinger.Length = int(self._bb_length.Value)
self._bollinger.Width = float(self._bb_width.Value)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._on_process).Start()
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._bollinger)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_val):
if candle.State != CandleStates.Finished:
return
# Process MACD and BB manually (need CandleIndicatorValue wrapper for Python)
civ_macd = CandleIndicatorValue(self._macd, candle)
civ_macd.IsFinal = True
macd_result = self._macd.Process(civ_macd)
civ_bb = CandleIndicatorValue(self._bollinger, candle)
civ_bb.IsFinal = True
bb_result = self._bollinger.Process(civ_bb)
if not self._macd.IsFormed or not self._bollinger.IsFormed:
return
macd_val = float(IndicatorHelper.ToDecimal(macd_result))
if bb_result.UpBand is None or bb_result.LowBand is None or bb_result.MovingAverage is None:
return
upper = float(bb_result.UpBand)
lower = float(bb_result.LowBand)
middle = float(bb_result.MovingAverage)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_macd = macd_val
return
close = float(candle.ClosePrice)
rsi = float(rsi_val)
cooldown = int(self._cooldown_bars.Value)
# Buy: price below lower BB + RSI oversold
if close <= lower and rsi < 30 and self.Position == 0:
self.BuyMarket()
self._cooldown_remaining = cooldown
# Sell: price above upper BB + RSI overbought
elif close >= upper and rsi > 70 and self.Position == 0:
self.SellMarket()
self._cooldown_remaining = cooldown
self._prev_macd = macd_val
def CreateClone(self):
return macd_bb_rsi_strategy()