CCI Normalized Reversal Strategy
This strategy uses the Commodity Channel Index (CCI) to detect reversals after the indicator exits extreme zones.
Overview
The indicator is calculated on 8-hour candles with a configurable period. Two threshold levels define overbought and oversold areas. When the CCI crosses back inside these bounds after reaching an extreme, the strategy enters a position in the opposite direction, expecting a mean reversion.
Trading Rules
- Long Entry: Two bars ago the CCI was above the high level and the previous bar moved below it.
- Short Entry: Two bars ago the CCI was below the low level and the previous bar moved above it.
- Close Long: The previous bar's CCI was below the middle level.
- Close Short: The previous bar's CCI was above the middle level.
Parameters
CciPeriod– lookback period for the CCI.HighLevel– upper CCI threshold considered overbought.MiddleLevel– middle threshold used to exit positions.LowLevel– lower CCI threshold considered oversold.CandleType– candle series used for calculations (default 8 hours).
Notes
The strategy opens at most one position at a time and uses market orders. Default risk management is enabled via StartProtection.
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>
/// CCI Normalized Reversal Strategy.
/// Enters positions after the indicator leaves extreme zones.
/// </summary>
public class CciNormalizedReversalStrategy : Strategy
{
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<int> _highLevel;
private readonly StrategyParam<int> _middleLevel;
private readonly StrategyParam<int> _lowLevel;
private readonly StrategyParam<DataType> _candleType;
private int _prevColor = 2;
private int _prevPrevColor = 2;
/// <summary>
/// CCI calculation period.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Upper CCI threshold.
/// </summary>
public int HighLevel
{
get => _highLevel.Value;
set => _highLevel.Value = value;
}
/// <summary>
/// Middle CCI threshold.
/// </summary>
public int MiddleLevel
{
get => _middleLevel.Value;
set => _middleLevel.Value = value;
}
/// <summary>
/// Lower CCI threshold.
/// </summary>
public int LowLevel
{
get => _lowLevel.Value;
set => _lowLevel.Value = value;
}
/// <summary>
/// Candle type to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="CciNormalizedReversalStrategy"/>.
/// </summary>
public CciNormalizedReversalStrategy()
{
_cciPeriod = Param(nameof(CciPeriod), 10)
.SetDisplay("CCI Period", "Lookback period for CCI", "General")
.SetRange(5, 50)
;
_highLevel = Param(nameof(HighLevel), 100)
.SetDisplay("High Level", "Upper CCI threshold", "General")
.SetRange(50, 200)
;
_middleLevel = Param(nameof(MiddleLevel), 0)
.SetDisplay("Middle Level", "Middle CCI threshold", "General")
.SetRange(-50, 50)
;
_lowLevel = Param(nameof(LowLevel), -100)
.SetDisplay("Low Level", "Lower CCI threshold", "General")
.SetRange(-200, -50)
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).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();
_prevColor = 2;
_prevPrevColor = 2;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevColor = 2;
_prevPrevColor = 2;
var cci = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(cci, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var color = GetColorIndex(cciValue);
// Close short when CCI rises above middle level
if (_prevColor < 2 && Position < 0)
BuyMarket();
// Close long when CCI falls below middle level
if (_prevColor > 2 && Position > 0)
SellMarket();
// Open long after leaving high zone
if (_prevPrevColor == 0 && _prevColor > 0 && Position <= 0)
BuyMarket();
// Open short after leaving low zone
if (_prevPrevColor == 4 && _prevColor < 4 && Position >= 0)
SellMarket();
_prevPrevColor = _prevColor;
_prevColor = color;
}
private int GetColorIndex(decimal cci)
{
if (cci > MiddleLevel)
return cci > HighLevel ? 0 : 1;
if (cci < MiddleLevel)
return cci < LowLevel ? 4 : 3;
return 2;
}
}
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 CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_normalized_reversal_strategy(Strategy):
def __init__(self):
super(cci_normalized_reversal_strategy, self).__init__()
self._cci_period = self.Param("CciPeriod", 10) \
.SetDisplay("CCI Period", "Lookback period for CCI", "General")
self._high_level = self.Param("HighLevel", 100) \
.SetDisplay("High Level", "Upper CCI threshold", "General")
self._middle_level = self.Param("MiddleLevel", 0) \
.SetDisplay("Middle Level", "Middle CCI threshold", "General")
self._low_level = self.Param("LowLevel", -100) \
.SetDisplay("Low Level", "Lower CCI threshold", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_color = 2
self._prev_prev_color = 2
@property
def cci_period(self):
return self._cci_period.Value
@property
def high_level(self):
return self._high_level.Value
@property
def middle_level(self):
return self._middle_level.Value
@property
def low_level(self):
return self._low_level.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(cci_normalized_reversal_strategy, self).OnReseted()
self._prev_color = 2
self._prev_prev_color = 2
def OnStarted2(self, time):
super(cci_normalized_reversal_strategy, self).OnStarted2(time)
self._prev_color = 2
self._prev_prev_color = 2
cci = CommodityChannelIndex()
cci.Length = self.cci_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(cci, self.process_candle).Start()
def process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
cci_val = float(cci_value)
color = self._get_color_index(cci_val)
# Close short when CCI rises above middle level
if self._prev_color < 2 and self.Position < 0:
self.BuyMarket()
# Close long when CCI falls below middle level
if self._prev_color > 2 and self.Position > 0:
self.SellMarket()
# Open long after leaving high zone
if self._prev_prev_color == 0 and self._prev_color > 0 and self.Position <= 0:
self.BuyMarket()
# Open short after leaving low zone
if self._prev_prev_color == 4 and self._prev_color < 4 and self.Position >= 0:
self.SellMarket()
self._prev_prev_color = self._prev_color
self._prev_color = color
def _get_color_index(self, cci):
mid = float(self.middle_level)
high = float(self.high_level)
low = float(self.low_level)
if cci > mid:
return 0 if cci > high else 1
if cci < mid:
return 4 if cci < low else 3
return 2
def CreateClone(self):
return cci_normalized_reversal_strategy()