Стратегия возврата по CCI
Индекс товарного канала (CCI) показывает, насколько далеко цена ушла от своего статистического среднего. Стратегия входит в рынок, когда CCI сильно отклоняется от собственной средней, предполагая возврат после ослабления импульса.
Тестирование показывает среднегодичную доходность около 151%. Стратегию лучше запускать на фондовом рынке.
Длинная сделка осуществляется, когда CCI опускается ниже среднего минус DeviationMultiplier стандартных отклонений. Короткая сделка открывается, когда CCI поднимается выше среднего плюс тот же множитель. Позиция закрывается, когда CCI снова пересекает среднее значение.
Система подходит краткосрочным трейдерам, предпочитающим контртрендовые сделки. Стоп‑лосс на основе процентного движения помогает ограничить риск, если возврат не происходит быстро.
Подробности
- Условия входа:
- Long: CCI < Avg − DeviationMultiplier * StdDev
- Short: CCI > Avg + DeviationMultiplier * StdDev
- Long/Short: обе стороны.
- Условия выхода:
- Long: выход при CCI > Avg
- Short: выход при CCI < Avg
- Стопы: да, процентный стоп‑лосс.
- Параметры по умолчанию:
CciPeriod= 20AveragePeriod= 20DeviationMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Возврат к среднему
- Направление: Обе стороны
- Индикаторы: CCI
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// CCI Mean Reversion strategy.
/// This strategy enters positions when CCI is significantly below or above its average value.
/// </summary>
public class CciMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<int> _averagePeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private decimal _prevCci;
private decimal _avgCci;
private decimal _stdDevCci;
private decimal _sumCci;
private decimal _sumSquaresCci;
private int _count;
private readonly Queue<decimal> _cciValues = [];
/// <summary>
/// CCI Period.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Period for calculating mean and standard deviation of CCI.
/// </summary>
public int AveragePeriod
{
get => _averagePeriod.Value;
set => _averagePeriod.Value = value;
}
/// <summary>
/// Deviation multiplier for entry signals.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public CciMeanReversionStrategy()
{
_cciPeriod = Param(nameof(CciPeriod), 20)
.SetGreaterThanZero()
.SetOptimize(10, 30, 5)
.SetDisplay("CCI Period", "Period for Commodity Channel Index", "Indicators");
_averagePeriod = Param(nameof(AveragePeriod), 20)
.SetGreaterThanZero()
.SetOptimize(10, 50, 10)
.SetDisplay("Average Period", "Period for calculating CCI average and standard deviation", "Settings");
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2m)
.SetGreaterThanZero()
.SetOptimize(1.5m, 3m, 0.5m)
.SetDisplay("Deviation Multiplier", "Multiplier for standard deviation", "Settings");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetOptimize(1m, 3m, 0.5m)
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Risk Management");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCci = 0;
_avgCci = 0;
_stdDevCci = 0;
_sumCci = 0;
_sumSquaresCci = 0;
_count = 0;
_cciValues.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
// Reset variables
// Create CCI indicator
var cci = new CommodityChannelIndex { Length = CciPeriod };
// Create subscription and bind indicator
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(cci, ProcessCandle)
.Start();
// Setup chart visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, cci);
DrawOwnTrades(area);
}
// Enable position protection
StartProtection(
takeProfit: new Unit(0m), // We'll manage exits ourselves based on CCI
stopLoss: new Unit(StopLossPercent, UnitTypes.Percent)
);
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue cciValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Extract CCI value
var currentCci = cciValue.ToDecimal();
// Update CCI statistics
UpdateCciStatistics(currentCci);
// Save current CCI for next iteration
_prevCci = currentCci;
// If we don't have enough data yet for statistics
if (_count < AveragePeriod)
return;
// Check for entry conditions
if (Position == 0)
{
// Long entry - CCI is significantly below its average
if (currentCci < _avgCci - DeviationMultiplier * _stdDevCci)
{
BuyMarket(Volume);
LogInfo($"Long entry: CCI = {currentCci}, CCI Avg = {_avgCci}, CCI StdDev = {_stdDevCci}");
}
// Short entry - CCI is significantly above its average
else if (currentCci > _avgCci + DeviationMultiplier * _stdDevCci)
{
SellMarket(Volume);
LogInfo($"Short entry: CCI = {currentCci}, CCI Avg = {_avgCci}, CCI StdDev = {_stdDevCci}");
}
}
// Check for exit conditions
else if (Position > 0) // Long position
{
if (currentCci > _avgCci)
{
ClosePosition();
LogInfo($"Long exit: CCI = {currentCci}, CCI Avg = {_avgCci}");
}
}
else if (Position < 0) // Short position
{
if (currentCci < _avgCci)
{
ClosePosition();
LogInfo($"Short exit: CCI = {currentCci}, CCI Avg = {_avgCci}");
}
}
}
private void UpdateCciStatistics(decimal currentCci)
{
// Add current value to the queue
_cciValues.Enqueue(currentCci);
_sumCci += currentCci;
_sumSquaresCci += currentCci * currentCci;
_count++;
// If queue is larger than period, remove oldest value
if (_cciValues.Count > AveragePeriod)
{
var oldestCci = _cciValues.Dequeue();
_sumCci -= oldestCci;
_sumSquaresCci -= oldestCci * oldestCci;
_count--;
}
// Calculate average and standard deviation
if (_count > 0)
{
_avgCci = _sumCci / _count;
if (_count > 1)
{
var variance = (_sumSquaresCci - (_sumCci * _sumCci) / _count) / (_count - 1);
_stdDevCci = variance <= 0 ? 0 : (decimal)Math.Sqrt((double)variance);
}
else
{
_stdDevCci = 0;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_mean_reversion_strategy(Strategy):
"""
CCI Mean Reversion strategy.
Enters positions when CCI is significantly below or above its average value.
"""
def __init__(self):
super(cci_mean_reversion_strategy, self).__init__()
self._cci_period = self.Param("CciPeriod", 20) \
.SetDisplay("CCI Period", "Period for Commodity Channel Index", "Indicators")
self._average_period = self.Param("AveragePeriod", 20) \
.SetDisplay("Average Period", "Period for calculating CCI average and standard deviation", "Settings")
self._deviation_multiplier = self.Param("DeviationMultiplier", 2.0) \
.SetDisplay("Deviation Multiplier", "Multiplier for standard deviation", "Settings")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Risk Management")
self._prev_cci = 0.0
self._avg_cci = 0.0
self._std_dev_cci = 0.0
self._sum_cci = 0.0
self._sum_squares_cci = 0.0
self._count = 0
self._cci_values = []
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(cci_mean_reversion_strategy, self).OnReseted()
self._prev_cci = 0.0
self._avg_cci = 0.0
self._std_dev_cci = 0.0
self._sum_cci = 0.0
self._sum_squares_cci = 0.0
self._count = 0
self._cci_values = []
def OnStarted2(self, time):
super(cci_mean_reversion_strategy, self).OnStarted2(time)
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(cci, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, cci)
self.DrawOwnTrades(area)
self.StartProtection(
takeProfit=Unit(0),
stopLoss=Unit(self._stop_loss_percent.Value, UnitTypes.Percent)
)
def on_process(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
current_cci = float(cci_value)
self._update_cci_statistics(current_cci)
self._prev_cci = current_cci
if self._count < self._average_period.Value:
return
if self.Position == 0:
if current_cci < self._avg_cci - self._deviation_multiplier.Value * self._std_dev_cci:
self.BuyMarket(self.Volume)
elif current_cci > self._avg_cci + self._deviation_multiplier.Value * self._std_dev_cci:
self.SellMarket(self.Volume)
elif self.Position > 0:
if current_cci > self._avg_cci:
self.ClosePosition()
elif self.Position < 0:
if current_cci < self._avg_cci:
self.ClosePosition()
def _update_cci_statistics(self, current_cci):
self._cci_values.append(current_cci)
self._sum_cci += current_cci
self._sum_squares_cci += current_cci * current_cci
self._count += 1
if len(self._cci_values) > self._average_period.Value:
oldest_cci = self._cci_values.pop(0)
self._sum_cci -= oldest_cci
self._sum_squares_cci -= oldest_cci * oldest_cci
self._count -= 1
if self._count > 0:
self._avg_cci = self._sum_cci / self._count
if self._count > 1:
variance = (self._sum_squares_cci - (self._sum_cci * self._sum_cci) / self._count) / (self._count - 1)
self._std_dev_cci = 0 if variance <= 0 else Math.Sqrt(float(variance))
else:
self._std_dev_cci = 0
def CreateClone(self):
return cci_mean_reversion_strategy()