Стратегия CCI Support Resistance
Стратегия строит динамические уровни поддержки и сопротивления на основе пивотов CCI. Перед торговлей пробоев применяется трендовый фильтр на основе пересечения или наклона EMA.
Детали
- Условия входа:
- Лонг: цена закрывается выше уровня поддержки, построенного по CCI, после касания и тренд бычий.
- Шорт: цена закрывается ниже уровня сопротивления, построенного по CCI, после касания и тренд медвежий.
- Лонг/Шорт: Оба направления.
- Условия выхода:
- Стоп-лосс и тейк-профит на основе ATR.
- Стопы: Да, на основе ATR.
- Параметры по умолчанию:
CciLength= 50LeftPivot= 50RightPivot= 50Buffer= 10TrendMatter= trueTrendType= CrossSlowMaLength= 100FastMaLength= 50SlopeLength= 5Ksl= 1.1Ktp= 2.2
- Фильтры:
- Категория: Пробой
- Направление: Оба
- Индикаторы: CCI, EMA, ATR
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Любой
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// CCI Support Resistance strategy.
/// A CCI(CciLength) pivot low confirmed by LeftPivot lower values before it and RightPivot after it sets support at the low of its
/// candle; a CCI pivot high sets resistance at the high of its candle. A candle whose low touches support (within Buffer price steps)
/// and closes above it goes long, one whose high touches resistance and closes below it goes short, reversing an opposite position.
/// With TrendMatter the trend must agree: in Cross mode EMA(FastMaLength) against EMA(SlowMaLength), in Slope mode the change of the
/// slow EMA over SlopeLength candles. Each entry freezes a stop Ksl ATRs and a target Ktp ATRs away.
/// </summary>
public class CciSupportResistanceStrategy : Strategy
{
/// <summary>
/// Trend filter type.
/// </summary>
public enum TrendTypes
{
/// <summary>
/// Fast EMA against slow EMA.
/// </summary>
Cross,
/// <summary>
/// Slope of the slow EMA.
/// </summary>
Slope,
}
private readonly StrategyParam<int> _cciLength;
private readonly StrategyParam<int> _leftPivot;
private readonly StrategyParam<int> _rightPivot;
private readonly StrategyParam<decimal> _buffer;
private readonly StrategyParam<bool> _trendMatter;
private readonly StrategyParam<TrendTypes> _trendType;
private readonly StrategyParam<int> _slowMaLength;
private readonly StrategyParam<int> _fastMaLength;
private readonly StrategyParam<int> _slopeLength;
private readonly StrategyParam<decimal> _ksl;
private readonly StrategyParam<decimal> _ktp;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal cci, decimal high, decimal low)> _history = [];
private readonly List<decimal> _slowHistory = [];
private decimal? _support;
private decimal? _resistance;
private decimal? _stopPrice;
private decimal? _targetPrice;
/// <summary>
/// CCI period.
/// </summary>
public int CciLength
{
get => _cciLength.Value;
set => _cciLength.Value = value;
}
/// <summary>
/// CCI values before a pivot.
/// </summary>
public int LeftPivot
{
get => _leftPivot.Value;
set => _leftPivot.Value = value;
}
/// <summary>
/// CCI values after a pivot.
/// </summary>
public int RightPivot
{
get => _rightPivot.Value;
set => _rightPivot.Value = value;
}
/// <summary>
/// Touch tolerance in price steps.
/// </summary>
public decimal Buffer
{
get => _buffer.Value;
set => _buffer.Value = value;
}
/// <summary>
/// Require the trend filter.
/// </summary>
public bool TrendMatter
{
get => _trendMatter.Value;
set => _trendMatter.Value = value;
}
/// <summary>
/// Trend filter type.
/// </summary>
public TrendTypes TrendType
{
get => _trendType.Value;
set => _trendType.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowMaLength
{
get => _slowMaLength.Value;
set => _slowMaLength.Value = value;
}
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastMaLength
{
get => _fastMaLength.Value;
set => _fastMaLength.Value = value;
}
/// <summary>
/// Candles the slow EMA slope spans.
/// </summary>
public int SlopeLength
{
get => _slopeLength.Value;
set => _slopeLength.Value = value;
}
/// <summary>
/// ATR multiple of the stop.
/// </summary>
public decimal Ksl
{
get => _ksl.Value;
set => _ksl.Value = value;
}
/// <summary>
/// ATR multiple of the target.
/// </summary>
public decimal Ktp
{
get => _ktp.Value;
set => _ktp.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public CciSupportResistanceStrategy()
{
_cciLength = Param(nameof(CciLength), 50)
.SetGreaterThanZero()
.SetDisplay("CCI Length", "CCI period", "CCI");
_leftPivot = Param(nameof(LeftPivot), 50)
.SetGreaterThanZero()
.SetDisplay("Left Pivot", "CCI values before a pivot", "CCI");
_rightPivot = Param(nameof(RightPivot), 50)
.SetGreaterThanZero()
.SetDisplay("Right Pivot", "CCI values after a pivot", "CCI");
_buffer = Param(nameof(Buffer), 10m)
.SetNotNegative()
.SetDisplay("Buffer", "Touch tolerance in price steps", "Levels");
_trendMatter = Param(nameof(TrendMatter), true)
.SetDisplay("Trend Matter", "Require the trend filter", "Trend");
_trendType = Param(nameof(TrendType), TrendTypes.Cross)
.SetDisplay("Trend Type", "Trend filter type", "Trend");
_slowMaLength = Param(nameof(SlowMaLength), 100)
.SetGreaterThanZero()
.SetDisplay("Slow MA Length", "Slow EMA period", "Trend");
_fastMaLength = Param(nameof(FastMaLength), 50)
.SetGreaterThanZero()
.SetDisplay("Fast MA Length", "Fast EMA period", "Trend");
_slopeLength = Param(nameof(SlopeLength), 5)
.SetGreaterThanZero()
.SetDisplay("Slope Length", "Candles the slow EMA slope spans", "Trend");
_ksl = Param(nameof(Ksl), 1.1m)
.SetGreaterThanZero()
.SetDisplay("Ksl", "ATR multiple of the stop", "Risk");
_ktp = Param(nameof(Ktp), 2.2m)
.SetGreaterThanZero()
.SetDisplay("Ktp", "ATR multiple of the target", "Risk");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
ResetState();
}
private void ResetState()
{
_history.Clear();
_slowHistory.Clear();
_support = null;
_resistance = null;
_stopPrice = null;
_targetPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var cci = new CommodityChannelIndex { Length = CciLength };
var fastMa = new ExponentialMovingAverage { Length = FastMaLength };
var slowMa = new ExponentialMovingAverage { Length = SlowMaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(cci, fastMa, slowMa, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastMa);
DrawIndicator(area, slowMa);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, cci);
}
}
}
private void UpdateLevels(decimal cci, ICandleMessage candle)
{
_history.Add((cci, candle.HighPrice, candle.LowPrice));
var size = LeftPivot + RightPivot + 1;
if (_history.Count > size)
_history.RemoveAt(0);
if (_history.Count < size)
return;
var pivot = _history[LeftPivot];
var isHigh = true;
var isLow = true;
for (var i = 0; i < size && (isHigh || isLow); i++)
{
if (i == LeftPivot)
continue;
var value = _history[i].cci;
if (value >= pivot.cci)
isHigh = false;
if (value <= pivot.cci)
isLow = false;
}
if (isHigh)
_resistance = pivot.high;
if (isLow)
_support = pivot.low;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue cciValue, IIndicatorValue fastValue, IIndicatorValue slowValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (cciValue.IsFormed)
UpdateLevels(cciValue.GetValue<decimal>(), candle);
if (!slowValue.IsFormed)
return;
var slow = slowValue.GetValue<decimal>();
_slowHistory.Add(slow);
if (_slowHistory.Count > SlopeLength + 1)
_slowHistory.RemoveAt(0);
if (!fastValue.IsFormed || !atrValue.IsFormed || _slowHistory.Count <= SlopeLength)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Stop and target frozen at entry.
if (Position > 0 && _stopPrice is decimal longStop && _targetPrice is decimal longTarget && (candle.LowPrice <= longStop || candle.HighPrice >= longTarget))
{
SellMarket(Position);
_stopPrice = null;
_targetPrice = null;
return;
}
if (Position < 0 && _stopPrice is decimal shortStop && _targetPrice is decimal shortTarget && (candle.HighPrice >= shortStop || candle.LowPrice <= shortTarget))
{
BuyMarket(-Position);
_stopPrice = null;
_targetPrice = null;
return;
}
bool bullish;
bool bearish;
if (!TrendMatter)
{
bullish = true;
bearish = true;
}
else if (TrendType == TrendTypes.Cross)
{
var fast = fastValue.GetValue<decimal>();
bullish = fast > slow;
bearish = fast < slow;
}
else
{
var slope = slow - _slowHistory.First();
bullish = slope > 0;
bearish = slope < 0;
}
var tolerance = Buffer * (Security?.PriceStep ?? 1m);
var close = candle.ClosePrice;
var atr = atrValue.GetValue<decimal>();
if (bullish && _support is decimal support && candle.LowPrice <= support + tolerance && close > support && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - atr * Ksl;
_targetPrice = close + atr * Ktp;
}
else if (bearish && _resistance is decimal resistance && candle.HighPrice >= resistance - tolerance && close < resistance && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + atr * Ksl;
_targetPrice = close - atr * Ktp;
}
}
}
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 CommodityChannelIndex, ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
TREND_CROSS = 0
TREND_SLOPE = 1
class cci_support_resistance_strategy(Strategy):
"""
CCI Support Resistance strategy.
A CCI(CciLength) pivot low confirmed by LeftPivot lower values before it and RightPivot after it sets support at the low of its
candle; a CCI pivot high sets resistance at the high of its candle. A candle whose low touches support (within Buffer price steps)
and closes above it goes long, one whose high touches resistance and closes below it goes short, reversing an opposite position.
With TrendMatter the trend must agree: in Cross mode EMA(FastMaLength) against EMA(SlowMaLength), in Slope mode the change of the
slow EMA over SlopeLength candles. Each entry freezes a stop Ksl ATRs and a target Ktp ATRs away.
"""
def __init__(self):
super(cci_support_resistance_strategy, self).__init__()
self._cci_length = self.Param("CciLength", 50).SetGreaterThanZero().SetDisplay("CCI Length", "CCI period", "CCI")
self._left_pivot = self.Param("LeftPivot", 50).SetGreaterThanZero().SetDisplay("Left Pivot", "CCI values before a pivot", "CCI")
self._right_pivot = self.Param("RightPivot", 50).SetGreaterThanZero().SetDisplay("Right Pivot", "CCI values after a pivot", "CCI")
self._buffer = self.Param("Buffer", 10.0).SetNotNegative().SetDisplay("Buffer", "Touch tolerance in price steps", "Levels")
self._trend_matter = self.Param("TrendMatter", True).SetDisplay("Trend Matter", "Require the trend filter", "Trend")
self._trend_type = self.Param("TrendType", TREND_CROSS).SetDisplay("Trend Type", "Trend filter type (0 = Cross, 1 = Slope)", "Trend")
self._slow_ma_length = self.Param("SlowMaLength", 100).SetGreaterThanZero().SetDisplay("Slow MA Length", "Slow EMA period", "Trend")
self._fast_ma_length = self.Param("FastMaLength", 50).SetGreaterThanZero().SetDisplay("Fast MA Length", "Fast EMA period", "Trend")
self._slope_length = self.Param("SlopeLength", 5).SetGreaterThanZero().SetDisplay("Slope Length", "Candles the slow EMA slope spans", "Trend")
self._ksl = self.Param("Ksl", 1.1).SetGreaterThanZero().SetDisplay("Ksl", "ATR multiple of the stop", "Risk")
self._ktp = self.Param("Ktp", 2.2).SetGreaterThanZero().SetDisplay("Ktp", "ATR multiple of the target", "Risk")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).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._history = []
self._slow_history = []
self._support = None
self._resistance = None
self._stop_price = None
self._target_price = None
def OnReseted(self):
super(cci_support_resistance_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(cci_support_resistance_strategy, self).OnStarted2(time)
self._reset_state()
cci = CommodityChannelIndex()
cci.Length = self._cci_length.Value
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self._fast_ma_length.Value
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self._slow_ma_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(cci, fast_ma, slow_ma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, cci)
def _update_levels(self, cci, candle):
self._history.append((cci, candle.HighPrice, candle.LowPrice))
left = self._left_pivot.Value
size = left + self._right_pivot.Value + 1
if len(self._history) > size:
self._history.pop(0)
if len(self._history) < size:
return
pivot = self._history[left]
is_high = True
is_low = True
for i in range(size):
if not is_high and not is_low:
break
if i == left:
continue
value = self._history[i][0]
if value >= pivot[0]:
is_high = False
if value <= pivot[0]:
is_low = False
if is_high:
self._resistance = pivot[1]
if is_low:
self._support = pivot[2]
def _process_candle(self, candle, cci_value, fast_value, slow_value, atr_value):
if candle.State != CandleStates.Finished:
return
if cci_value.IsFormed:
self._update_levels(cci_value.GetValue[Decimal](None), candle)
if not slow_value.IsFormed:
return
slow = slow_value.GetValue[Decimal](None)
slope_length = self._slope_length.Value
self._slow_history.append(slow)
if len(self._slow_history) > slope_length + 1:
self._slow_history.pop(0)
if not fast_value.IsFormed or not atr_value.IsFormed or len(self._slow_history) <= slope_length:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
# Stop and target frozen at entry.
if self.Position > 0 and self._stop_price is not None and self._target_price is not None and (candle.LowPrice <= self._stop_price or candle.HighPrice >= self._target_price):
self.SellMarket(self.Position)
self._stop_price = None
self._target_price = None
return
if self.Position < 0 and self._stop_price is not None and self._target_price is not None and (candle.HighPrice >= self._stop_price or candle.LowPrice <= self._target_price):
self.BuyMarket(-self.Position)
self._stop_price = None
self._target_price = None
return
if not self._trend_matter.Value:
bullish = True
bearish = True
elif self._trend_type.Value == TREND_CROSS:
fast = fast_value.GetValue[Decimal](None)
bullish = fast > slow
bearish = fast < slow
else:
slope = slow - self._slow_history[0]
bullish = slope > 0
bearish = slope < 0
step = self.Security.PriceStep if self.Security is not None and self.Security.PriceStep is not None else Decimal(1)
tolerance = Decimal(self._buffer.Value) * step
close = candle.ClosePrice
atr = atr_value.GetValue[Decimal](None)
ksl = Decimal(self._ksl.Value)
ktp = Decimal(self._ktp.Value)
support = self._support
resistance = self._resistance
if bullish and support is not None and candle.LowPrice <= support + tolerance and close > support and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - atr * ksl
self._target_price = close + atr * ktp
elif bearish and resistance is not None and candle.HighPrice >= resistance - tolerance and close < resistance and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + atr * ksl
self._target_price = close - atr * ktp
def CreateClone(self):
return cci_support_resistance_strategy()