BB + RSI Strategy
This strategy buys when the price closes below the lower Bollinger Band while the RSI is below the buy level. The position is closed when the RSI rises above the exit level or when the price drops from the peak by the specified trailing step percentage.
Parameters
- Candle Type
- Bollinger Bands period
- Bollinger Bands deviation
- RSI period
- RSI buy level
- RSI exit level
- Trailing step percent
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bollinger Bands and RSI strategy with trailing exit.
/// </summary>
public class BbRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bbPeriod;
private readonly StrategyParam<decimal> _bbDeviation;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiBuyLevel;
private readonly StrategyParam<decimal> _rsiExitLevel;
private readonly StrategyParam<decimal> _trailingStep;
private BollingerBands _bollingerBands;
private RelativeStrengthIndex _rsi;
private bool _inTrade;
private decimal _peakPrice;
/// <summary>
/// Candle type for calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BbPeriod
{
get => _bbPeriod.Value;
set => _bbPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands deviation.
/// </summary>
public decimal BbDeviation
{
get => _bbDeviation.Value;
set => _bbDeviation.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI level to enter long.
/// </summary>
public decimal RsiBuyLevel
{
get => _rsiBuyLevel.Value;
set => _rsiBuyLevel.Value = value;
}
/// <summary>
/// RSI level to exit long.
/// </summary>
public decimal RsiExitLevel
{
get => _rsiExitLevel.Value;
set => _rsiExitLevel.Value = value;
}
/// <summary>
/// Trailing step percentage.
/// </summary>
public decimal TrailingStep
{
get => _trailingStep.Value;
set => _trailingStep.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BbRsiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_bbPeriod = Param(nameof(BbPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Bollinger Bands")
.SetOptimize(10, 50, 5);
_bbDeviation = Param(nameof(BbDeviation), 1.5m)
.SetRange(0.5m, 5m)
.SetDisplay("BB Deviation", "Bollinger Bands deviation", "Bollinger Bands")
.SetOptimize(1m, 4m, 0.5m);
_rsiPeriod = Param(nameof(RsiPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI calculation period", "RSI")
.SetOptimize(7, 21, 2);
_rsiBuyLevel = Param(nameof(RsiBuyLevel), 48m)
.SetRange(0m, 100m)
.SetDisplay("RSI Buy Level", "RSI threshold to enter long", "RSI")
.SetOptimize(20m, 40m, 5m);
_rsiExitLevel = Param(nameof(RsiExitLevel), 52m)
.SetRange(0m, 100m)
.SetDisplay("RSI Exit Level", "RSI threshold to exit long", "RSI")
.SetOptimize(60m, 80m, 5m);
_trailingStep = Param(nameof(TrailingStep), 2m)
.SetRange(0.1m, 20m)
.SetDisplay("Trailing Step %", "Trailing stop step percent", "Risk")
.SetOptimize(0.5m, 5m, 0.5m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_inTrade = default;
_peakPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bollingerBands = new BollingerBands { Length = BbPeriod, Width = BbDeviation };
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx([_bollingerBands, _rsi], ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollingerBands);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished)
return;
if (values[0] is not BollingerBandsValue bbValue ||
bbValue.UpBand is not decimal upperBand ||
bbValue.LowBand is not decimal lowerBand ||
!values[1].IsFormed)
return;
var rsiValue = values[1].GetValue<decimal>();
var closePrice = candle.ClosePrice;
if (Position == 0)
{
// Long entry: close below lower BB and RSI oversold
if (closePrice < lowerBand && rsiValue < RsiBuyLevel)
{
BuyMarket();
_peakPrice = closePrice;
_inTrade = true;
}
// Short entry: close above upper BB and RSI overbought
else if (closePrice > upperBand && rsiValue > RsiExitLevel)
{
SellMarket();
_peakPrice = closePrice;
_inTrade = true;
}
}
else if (Position > 0 && _inTrade)
{
if (closePrice > _peakPrice)
_peakPrice = closePrice;
var trailingDrop = _peakPrice * (1m - TrailingStep / 100m);
if (closePrice <= trailingDrop || rsiValue > RsiExitLevel)
{
SellMarket();
_inTrade = false;
}
}
else if (Position < 0 && _inTrade)
{
if (closePrice < _peakPrice)
_peakPrice = closePrice;
var trailingRise = _peakPrice * (1m + TrailingStep / 100m);
if (closePrice >= trailingRise || rsiValue < RsiBuyLevel)
{
BuyMarket();
_inTrade = false;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class bb_rsi_strategy(Strategy):
def __init__(self):
super(bb_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._bb_period = self.Param("BbPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("BB Period", "Bollinger Bands period", "Bollinger Bands")
self._bb_deviation = self.Param("BbDeviation", 1.5) \
.SetDisplay("BB Deviation", "Bollinger Bands deviation", "Bollinger Bands")
self._rsi_period = self.Param("RsiPeriod", 13) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "RSI calculation period", "RSI")
self._rsi_buy_level = self.Param("RsiBuyLevel", 48.0) \
.SetDisplay("RSI Buy Level", "RSI threshold to enter long", "RSI")
self._rsi_exit_level = self.Param("RsiExitLevel", 52.0) \
.SetDisplay("RSI Exit Level", "RSI threshold to exit long", "RSI")
self._trailing_step = self.Param("TrailingStep", 2.0) \
.SetDisplay("Trailing Step %", "Trailing stop step percent", "Risk")
self._in_trade = False
self._peak_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(bb_rsi_strategy, self).OnReseted()
self._in_trade = False
self._peak_price = 0.0
def OnStarted2(self, time):
super(bb_rsi_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length = self._bb_period.Value
bb.Width = self._bb_deviation.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bb, rsi, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bb)
self.DrawOwnTrades(area)
def OnProcess(self, candle, bb_value, rsi_value):
if candle.State != CandleStates.Finished:
return
bb = bb_value
upper = bb.UpBand
lower = bb.LowBand
if upper is None or lower is None:
return
if not rsi_value.IsFormed:
return
upper_v = float(upper)
lower_v = float(lower)
rsi_v = float(rsi_value)
close = float(candle.ClosePrice)
buy_level = float(self._rsi_buy_level.Value)
exit_level = float(self._rsi_exit_level.Value)
trail_step = float(self._trailing_step.Value)
if self.Position == 0:
if close < lower_v and rsi_v < buy_level:
self.BuyMarket()
self._peak_price = close
self._in_trade = True
elif close > upper_v and rsi_v > exit_level:
self.SellMarket()
self._peak_price = close
self._in_trade = True
elif self.Position > 0 and self._in_trade:
if close > self._peak_price:
self._peak_price = close
trailing_drop = self._peak_price * (1.0 - trail_step / 100.0)
if close <= trailing_drop or rsi_v > exit_level:
self.SellMarket()
self._in_trade = False
elif self.Position < 0 and self._in_trade:
if close < self._peak_price:
self._peak_price = close
trailing_rise = self._peak_price * (1.0 + trail_step / 100.0)
if close >= trailing_rise or rsi_v < buy_level:
self.BuyMarket()
self._in_trade = False
def CreateClone(self):
return bb_rsi_strategy()