Bollinger RSI Countertrend SOL Strategy
Countertrend system for SOL that buys when price crosses above the lower Bollinger Band with low RSI and sells when price crosses below the upper band with high RSI. Weekdays only.
Details
- Entry Criteria:
- Long: Price crosses above lower band and
RSI<Long RSIon weekdays. - Short: Price crosses below upper band and
RSI>Short RSIon weekdays.
- Long: Price crosses above lower band and
- Long/Short: Both directions.
- Exit Criteria:
- Long: price crosses above upper band or stop loss under recent lows.
- Short: price crosses above middle band or reaches profit target.
- Stops: Long stop below recent lows.
- Default Values:
Bollinger Period= 20Bollinger Width= 2RSI Length= 14Long RSI= 25Short RSI= 79Short Profit %= 3.5
- Filters:
- Category: Mean Reversion
- Direction: Both
- Indicators: Multiple
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: Yes (Weekdays)
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Linq;
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>
/// Countertrend strategy for SOL using Bollinger Bands and RSI.
/// Buys when price crosses above the lower band with low RSI and
/// sells when price crosses below the upper band with high RSI.
/// </summary>
public class BollingerRsiCountertrendSolStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerWidth;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _longRsi;
private readonly StrategyParam<decimal> _shortRsi;
private readonly StrategyParam<decimal> _shortProfitPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevUpper;
private decimal _prevLower;
private decimal _prevBasis;
private decimal _prevLow;
private decimal? _longSlLevel;
private decimal? _shortEntryPrice;
/// <summary>
/// Bollinger period.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Bollinger width multiplier.
/// </summary>
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
/// <summary>
/// RSI period length.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// RSI threshold for long entries.
/// </summary>
public decimal LongRsi
{
get => _longRsi.Value;
set => _longRsi.Value = value;
}
/// <summary>
/// RSI threshold for short entries.
/// </summary>
public decimal ShortRsi
{
get => _shortRsi.Value;
set => _shortRsi.Value = value;
}
/// <summary>
/// Profit target for shorts in percent.
/// </summary>
public decimal ShortProfitPercent
{
get => _shortProfitPercent.Value;
set => _shortProfitPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="BollingerRsiCountertrendSolStrategy"/>.
/// </summary>
public BollingerRsiCountertrendSolStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Bollinger period", "Parameters");
_bollingerWidth = Param(nameof(BollingerWidth), 2m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Width", "Bollinger width", "Parameters");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Parameters");
_longRsi = Param(nameof(LongRsi), 25m)
.SetDisplay("Long RSI", "RSI threshold for longs", "Parameters");
_shortRsi = Param(nameof(ShortRsi), 79m)
.SetDisplay("Short RSI", "RSI threshold for shorts", "Parameters");
_shortProfitPercent = Param(nameof(ShortProfitPercent), 3.5m)
.SetDisplay("Short Profit %", "Short profit percent", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0m;
_prevUpper = 0m;
_prevLower = 0m;
_prevBasis = 0m;
_prevLow = 0m;
_longSlLevel = null;
_shortEntryPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerWidth };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(bollinger, rsi, ProcessCandle).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 ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue, IIndicatorValue rsiVal)
{
if (candle.State != CandleStates.Finished)
return;
var bb = (BollingerBandsValue)bbValue;
if (bb.UpBand is not decimal upper ||
bb.LowBand is not decimal lower ||
bb.MovingAverage is not decimal middle)
return;
var rsiValue = rsiVal.IsFormed ? rsiVal.GetValue<decimal>() : 50m;
var longEntry = _prevClose != 0 && _prevClose < _prevLower && candle.ClosePrice > lower && rsiValue < LongRsi;
var shortEntry = _prevClose != 0 && _prevClose > _prevUpper && candle.ClosePrice < upper && rsiValue > ShortRsi;
var longTp = Position > 0 && _prevClose <= _prevUpper && candle.ClosePrice > upper;
var shortTp1 = Position < 0 && _prevClose <= _prevBasis && candle.ClosePrice > middle;
var shortTp2 = Position < 0 && _shortEntryPrice.HasValue &&
(_shortEntryPrice.Value - candle.ClosePrice) / _shortEntryPrice.Value >= ShortProfitPercent / 100m;
var longSl = Position > 0 && _longSlLevel.HasValue && candle.ClosePrice < _longSlLevel.Value;
if (longEntry && Position == 0)
{
BuyMarket();
}
else if (shortEntry && Position == 0)
{
SellMarket();
}
_prevClose = candle.ClosePrice;
_prevUpper = upper;
_prevLower = lower;
_prevBasis = middle;
_prevLow = candle.LowPrice;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import BollingerBands, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class bollinger_rsi_countertrend_sol_strategy(Strategy):
def __init__(self):
super(bollinger_rsi_countertrend_sol_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetDisplay("Bollinger Period", "Bollinger period", "Parameters")
self._bollinger_width = self.Param("BollingerWidth", 2.0) \
.SetDisplay("Bollinger Width", "Bollinger width", "Parameters")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period", "Parameters")
self._long_rsi = self.Param("LongRsi", 25.0) \
.SetDisplay("Long RSI", "RSI threshold for longs", "Parameters")
self._short_rsi = self.Param("ShortRsi", 79.0) \
.SetDisplay("Short RSI", "RSI threshold for shorts", "Parameters")
self._short_profit_percent = self.Param("ShortProfitPercent", 3.5) \
.SetDisplay("Short Profit %", "Short profit percent", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_close = 0.0
self._prev_upper = 0.0
self._prev_lower = 0.0
self._prev_basis = 0.0
self._prev_low = 0.0
self._long_sl_level = None
self._short_entry_price = None
@property
def bollinger_period(self):
return self._bollinger_period.Value
@property
def bollinger_width(self):
return self._bollinger_width.Value
@property
def rsi_length(self):
return self._rsi_length.Value
@property
def long_rsi(self):
return self._long_rsi.Value
@property
def short_rsi(self):
return self._short_rsi.Value
@property
def short_profit_percent(self):
return self._short_profit_percent.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(bollinger_rsi_countertrend_sol_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_upper = 0.0
self._prev_lower = 0.0
self._prev_basis = 0.0
self._prev_low = 0.0
self._long_sl_level = None
self._short_entry_price = None
def OnStarted2(self, time):
super(bollinger_rsi_countertrend_sol_strategy, self).OnStarted2(time)
bollinger = BollingerBands()
bollinger.Length = self.bollinger_period
bollinger.Width = self.bollinger_width
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_length
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, rsi, self.on_process).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
def on_process(self, candle, bb_value, rsi_val):
if candle.State != CandleStates.Finished:
return
upper = float(bb_value.UpBand)
lower = float(bb_value.LowBand)
middle = float(bb_value.MovingAverage)
if upper == 0 or lower == 0:
return
rsi_value = float(rsi_val) if rsi_val.IsFormed else 50.0
close = float(candle.ClosePrice)
long_entry = self._prev_close != 0 and self._prev_close < self._prev_lower and close > lower and rsi_value < self.long_rsi
short_entry = self._prev_close != 0 and self._prev_close > self._prev_upper and close < upper and rsi_value > self.short_rsi
if long_entry and self.Position == 0:
self.BuyMarket()
elif short_entry and self.Position == 0:
self.SellMarket()
self._prev_close = close
self._prev_upper = upper
self._prev_lower = lower
self._prev_basis = middle
self._prev_low = float(candle.LowPrice)
def CreateClone(self):
return bollinger_rsi_countertrend_sol_strategy()