BBands Stop Strategy
This strategy uses the BBands Stop indicator derived from Bollinger Bands to follow market trends. When the stop line flips upward, it closes any short position and opens a long one. A downward flip closes long positions and opens short. Parameters control Bollinger period, deviation, risk offset and permissions for entering or exiting longs and shorts.
Details
- Entry Criteria:
- Long: Uptrend stop line is active.
- Short: Downtrend stop line is active.
- Long/Short: Both.
- Exit Criteria:
- Opposite stop signal.
- Stops: Trailing stop from Bollinger Bands.
- Filters: None.
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>
/// Bollinger Bands stop based trend following strategy.
/// </summary>
public class BBandsStopStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _deviation;
private readonly StrategyParam<decimal> _moneyRisk;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private int _trend;
private decimal _smax1;
private decimal _smin1;
private decimal _bsmax1;
private decimal _bsmin1;
private bool _initialized;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int Length { get => _length.Value; set => _length.Value = value; }
public decimal Deviation { get => _deviation.Value; set => _deviation.Value = value; }
public decimal MoneyRisk { get => _moneyRisk.Value; set => _moneyRisk.Value = value; }
public bool BuyPosOpen { get => _buyPosOpen.Value; set => _buyPosOpen.Value = value; }
public bool SellPosOpen { get => _sellPosOpen.Value; set => _sellPosOpen.Value = value; }
public bool BuyPosClose { get => _buyPosClose.Value; set => _buyPosClose.Value = value; }
public bool SellPosClose { get => _sellPosClose.Value; set => _sellPosClose.Value = value; }
public BBandsStopStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_length = Param(nameof(Length), 20)
.SetDisplay("Length", "Bollinger period", "Indicator");
_deviation = Param(nameof(Deviation), 1.5m)
.SetDisplay("Deviation", "Bollinger deviation", "Indicator");
_moneyRisk = Param(nameof(MoneyRisk), 1m)
.SetDisplay("Money Risk", "Offset factor", "Indicator");
_buyPosOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Buy Open", "Allow to enter long", "Trading");
_sellPosOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Sell Open", "Allow to enter short", "Trading");
_buyPosClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Buy Close", "Allow to exit long", "Trading");
_sellPosClose = Param(nameof(SellPosClose), true)
.SetDisplay("Sell Close", "Allow to exit short", "Trading");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trend = 0;
_smax1 = 0;
_smin1 = 0;
_bsmax1 = 0;
_bsmin1 = 0;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bb = new BollingerBands
{
Length = Length,
Width = Deviation
};
SubscribeCandles(CandleType).BindEx(bb, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
var bbVal = (BollingerBandsValue)value;
if (bbVal.UpBand is not decimal upper ||
bbVal.LowBand is not decimal lower ||
bbVal.MovingAverage is not decimal middle)
return;
var mRisk = 0.5m * (MoneyRisk - 1m);
var smax0 = upper;
var smin0 = lower;
if (!_initialized)
{
_initialized = true;
_smax1 = smax0;
_smin1 = smin0;
var firstOffset = mRisk * (smax0 - smin0);
_bsmax1 = smax0 + firstOffset;
_bsmin1 = smin0 - firstOffset;
return;
}
var prevTrend = _trend;
if (candle.ClosePrice > _smax1)
_trend = 1;
else if (candle.ClosePrice < _smin1)
_trend = -1;
if (_trend > 0 && smin0 < _smin1)
smin0 = _smin1;
else if (_trend < 0 && smax0 > _smax1)
smax0 = _smax1;
var dsize = mRisk * (smax0 - smin0);
var bsmax0 = smax0 + dsize;
var bsmin0 = smin0 - dsize;
if (_trend > 0 && bsmin0 < _bsmin1)
bsmin0 = _bsmin1;
else if (_trend < 0 && bsmax0 > _bsmax1)
bsmax0 = _bsmax1;
if (_trend > 0 && prevTrend <= 0)
{
if (SellPosClose && Position < 0)
BuyMarket();
if (BuyPosOpen && Position <= 0)
BuyMarket();
}
else if (_trend < 0 && prevTrend >= 0)
{
if (BuyPosClose && Position > 0)
SellMarket();
if (SellPosOpen && Position >= 0)
SellMarket();
}
_smax1 = smax0;
_smin1 = smin0;
_bsmax1 = bsmax0;
_bsmin1 = bsmin0;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class b_bands_stop_strategy(Strategy):
def __init__(self):
super(b_bands_stop_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._length = self.Param("Length", 20) \
.SetDisplay("Length", "Bollinger period", "Indicator")
self._deviation = self.Param("Deviation", 1.5) \
.SetDisplay("Deviation", "Bollinger deviation", "Indicator")
self._money_risk = self.Param("MoneyRisk", 1.0) \
.SetDisplay("Money Risk", "Offset factor", "Indicator")
self._buy_pos_open = self.Param("BuyPosOpen", True) \
.SetDisplay("Buy Open", "Allow to enter long", "Trading")
self._sell_pos_open = self.Param("SellPosOpen", True) \
.SetDisplay("Sell Open", "Allow to enter short", "Trading")
self._buy_pos_close = self.Param("BuyPosClose", True) \
.SetDisplay("Buy Close", "Allow to exit long", "Trading")
self._sell_pos_close = self.Param("SellPosClose", True) \
.SetDisplay("Sell Close", "Allow to exit short", "Trading")
self._trend = 0
self._smax1 = 0.0
self._smin1 = 0.0
self._bsmax1 = 0.0
self._bsmin1 = 0.0
self._initialized = False
@property
def candle_type(self):
return self._candle_type.Value
@property
def length(self):
return self._length.Value
@property
def deviation(self):
return self._deviation.Value
@property
def money_risk(self):
return self._money_risk.Value
@property
def buy_pos_open(self):
return self._buy_pos_open.Value
@property
def sell_pos_open(self):
return self._sell_pos_open.Value
@property
def buy_pos_close(self):
return self._buy_pos_close.Value
@property
def sell_pos_close(self):
return self._sell_pos_close.Value
def OnReseted(self):
super(b_bands_stop_strategy, self).OnReseted()
self._trend = 0
self._smax1 = 0.0
self._smin1 = 0.0
self._bsmax1 = 0.0
self._bsmin1 = 0.0
self._initialized = False
def OnStarted2(self, time):
super(b_bands_stop_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length = self.length
bb.Width = self.deviation
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bb, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bb)
self.DrawOwnTrades(area)
def on_process(self, candle, value):
if candle.State != CandleStates.Finished:
return
upper = float(value.UpBand)
lower = float(value.LowBand)
middle = float(value.MovingAverage)
if upper == 0 or lower == 0:
return
m_risk = 0.5 * (self.money_risk - 1.0)
smax0 = upper
smin0 = lower
if not self._initialized:
self._initialized = True
self._smax1 = smax0
self._smin1 = smin0
first_offset = m_risk * (smax0 - smin0)
self._bsmax1 = smax0 + first_offset
self._bsmin1 = smin0 - first_offset
return
prev_trend = self._trend
close = float(candle.ClosePrice)
if close > self._smax1:
self._trend = 1
elif close < self._smin1:
self._trend = -1
if self._trend > 0 and smin0 < self._smin1:
smin0 = self._smin1
elif self._trend < 0 and smax0 > self._smax1:
smax0 = self._smax1
dsize = m_risk * (smax0 - smin0)
bsmax0 = smax0 + dsize
bsmin0 = smin0 - dsize
if self._trend > 0 and bsmin0 < self._bsmin1:
bsmin0 = self._bsmin1
elif self._trend < 0 and bsmax0 > self._bsmax1:
bsmax0 = self._bsmax1
if self._trend > 0 and prev_trend <= 0:
if self.sell_pos_close and self.Position < 0:
self.BuyMarket()
if self.buy_pos_open and self.Position <= 0:
self.BuyMarket()
elif self._trend < 0 and prev_trend >= 0:
if self.buy_pos_close and self.Position > 0:
self.SellMarket()
if self.sell_pos_open and self.Position >= 0:
self.SellMarket()
self._smax1 = smax0
self._smin1 = smin0
self._bsmax1 = bsmax0
self._bsmin1 = bsmin0
def CreateClone(self):
return b_bands_stop_strategy()