Bollinger Cci Strategy
Implementation of strategy - Bollinger Bands + CCI. Buy when price is below lower Bollinger Band and CCI is below -100 (oversold). Sell when price is above upper Bollinger Band and CCI is above 100 (overbought).
Testing indicates an average annual return of about 73%. It performs best in the crypto market.
Bollinger bands map volatility limits, and CCI measures the distance from the mean. Breaks beyond a band with CCI confirmation trigger trades.
Suitable for volatile markets where trends extend quickly. ATR stops are applied for safety.
Details
- Entry Criteria:
- Long:
Close < LowerBand && CCI < CciOversold - Short:
Close > UpperBand && CCI > CciOverbought
- Long:
- Long/Short: Both
- Exit Criteria: Price returns to middle band
- Stops: ATR-based using
StopLossAtr - Default Values:
BollingerPeriod= 20BollingerDeviation= 2.0mCciPeriod= 20CciOversold= -100mCciOverbought= 100mStopLossAtr= 2AtrPeriod= 14CandleType= TimeSpan.FromMinutes(5).TimeFrame() The stop lies StopLossAtr ATRs (AtrPeriod) from the entry close and is checked on candle closes. An entry signal against an open position reverses it.
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: Bollinger Bands, CCI
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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 CCI strategy.
/// A close below the lower band with CCI below CciOversold goes long and a close above the upper band with CCI above CciOverbought goes short,
/// reversing an opposite position. The position closes when price returns to the middle band. The stop lies StopLossAtr ATR from the entry
/// close and is checked on candle closes.
/// </summary>
public class BollingerCciStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _cciOversold;
private readonly StrategyParam<decimal> _cciOverbought;
private readonly StrategyParam<decimal> _stopLossAtr;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _stopPrice;
/// <summary>
/// Period of the Bollinger Bands.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier of the bands.
/// </summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>
/// Period of CCI.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// CCI level for longs.
/// </summary>
public decimal CciOversold
{
get => _cciOversold.Value;
set => _cciOversold.Value = value;
}
/// <summary>
/// CCI level for shorts.
/// </summary>
public decimal CciOverbought
{
get => _cciOverbought.Value;
set => _cciOverbought.Value = value;
}
/// <summary>
/// Stop distance from the entry in ATRs.
/// </summary>
public decimal StopLossAtr
{
get => _stopLossAtr.Value;
set => _stopLossAtr.Value = value;
}
/// <summary>
/// Period of the stop ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BollingerCciStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Period of the Bollinger Bands", "Indicators");
_bollingerDeviation = Param(nameof(BollingerDeviation), 2m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Standard deviation multiplier of the bands", "Indicators");
_cciPeriod = Param(nameof(CciPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Period of CCI", "Indicators");
_cciOversold = Param(nameof(CciOversold), -100m)
.SetDisplay("CCI Oversold", "CCI level for longs", "Indicators");
_cciOverbought = Param(nameof(CciOverbought), 100m)
.SetDisplay("CCI Overbought", "CCI level for shorts", "Indicators");
_stopLossAtr = Param(nameof(StopLossAtr), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss ATR", "Stop distance from the entry in ATRs", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the stop ATR", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stopPrice = default;
var bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerDeviation };
var cci = new CommodityChannelIndex { Length = CciPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, cci, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, cci);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue cciValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bollingerValue.IsFormed || !cciValue.IsFormed || !atrValue.IsFormed)
return;
var bands = (BollingerBandsValue)bollingerValue;
if (bands.UpBand is not decimal upper || bands.LowBand is not decimal lower || bands.MovingAverage is not decimal middle)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var atr = atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
var cci = cciValue.GetValue<decimal>();
if (close < lower && cci < CciOversold && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - StopLossAtr * atr;
}
else if (close > upper && cci > CciOverbought && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + StopLossAtr * atr;
}
else if (Position > 0 && (close >= middle || (StopLossAtr > 0 && close <= _stopPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (close <= middle || (StopLossAtr > 0 && close >= _stopPrice)))
{
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 BollingerBands, CommodityChannelIndex, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class bollinger_cci_strategy(Strategy):
"""
Bollinger CCI strategy.
A close below the lower band with CCI below CciOversold goes long and a close above the upper band with CCI above CciOverbought goes short,
reversing an opposite position. The position closes when price returns to the middle band. The stop lies StopLossAtr ATR from the entry
close and is checked on candle closes.
"""
def __init__(self):
super(bollinger_cci_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20).SetGreaterThanZero().SetDisplay("Bollinger Period", "Period of the Bollinger Bands", "Indicators")
self._bollinger_deviation = self.Param("BollingerDeviation", 2.0).SetGreaterThanZero().SetDisplay("Bollinger Deviation", "Standard deviation multiplier of the bands", "Indicators")
self._cci_period = self.Param("CciPeriod", 20).SetGreaterThanZero().SetDisplay("CCI Period", "Period of CCI", "Indicators")
self._cci_oversold = self.Param("CciOversold", -100.0).SetDisplay("CCI Oversold", "CCI level for longs", "Indicators")
self._cci_overbought = self.Param("CciOverbought", 100.0).SetDisplay("CCI Overbought", "CCI level for shorts", "Indicators")
self._stop_loss_atr = self.Param("StopLossAtr", 2.0).SetNotNegative().SetDisplay("Stop Loss ATR", "Stop distance from the entry in ATRs", "Risk")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the stop ATR", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._stop_price = Decimal(0)
def OnReseted(self):
super(bollinger_cci_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(bollinger_cci_strategy, self).OnStarted2(time)
self._reset_state()
bollinger = BollingerBands()
bollinger.Length = self._bollinger_period.Value
bollinger.Width = Decimal(self._bollinger_deviation.Value)
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, cci, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, cci)
def _process_candle(self, candle, bollinger_value, cci_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not bollinger_value.IsFormed or not cci_value.IsFormed or not atr_value.IsFormed:
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None or bollinger_value.MovingAverage is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
upper = bollinger_value.UpBand
lower = bollinger_value.LowBand
middle = bollinger_value.MovingAverage
atr = atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
cci = cci_value.GetValue[Decimal](None)
stop_atr = Decimal(self._stop_loss_atr.Value)
if close < lower and cci < Decimal(self._cci_oversold.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_atr * atr
elif close > upper and cci > Decimal(self._cci_overbought.Value) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_atr * atr
elif self.Position > 0 and (close >= middle or (stop_atr > 0 and close <= self._stop_price)):
self.SellMarket(self.Position)
elif self.Position < 0 and (close <= middle or (stop_atr > 0 and close >= self._stop_price)):
self.BuyMarket(-self.Position)
def CreateClone(self):
return bollinger_cci_strategy()