Стратегия "CCI Failure Swing"
CCI Failure Swing основана на индексе товарного канала, который формирует более низкий максимум выше +100 или более высокий минимум ниже -100. Неспособность установить новый экстремум зачастую сигнализирует о завершении предыдущего тренда.
Тестирование показывает среднегодичную доходность около 73%. Стратегию лучше запускать на крипторынке.
Стратегия открывает длинную позицию, когда CCI удерживается выше -100 и разворачивается вверх, или короткую, когда индикатор не пробивает +100 и поворачивает вниз.
Процентный стоп сохраняет небольшой риск, а выход выполняется, если CCI пересекает предыдущий уровень свинга.
Детали
- Условия входа: сигнал индикатора
- Длинная/короткая: обе
- Условия выхода: стоп-лосс или противоположный сигнал
- Стопы: да, процентные
- Значения по умолчанию:
CandleType= 15 minuteCciPeriod= 20OversoldLevel= -100OverboughtLevel= 100StopLossPercent= 2
- Фильтры:
- Категория: Разворот
- Направление: обе
- Индикаторы: CCI
- Стопы: да
- Сложность: средняя
- Таймфрейм: внутридневной
- Сезонность: нет
- Нейронные сети: нет
- Дивергенция: нет
- Уровень риска: средний
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>
/// CCI Failure Swing strategy.
/// A trough of CCI below OversoldLevel that is higher than the previous such trough arms a long, which opens as CCI turns up from it;
/// peaks above OverboughtLevel arm shorts the same way. A position closes when CCI crosses back through the swing that armed it,
/// and a percent stop limits the loss.
/// </summary>
public class CciFailureSwingStrategy : Strategy
{
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
// The last two values, the last trough and peak in the extreme zones, and the swings that arm an entry.
private decimal? _last;
private decimal? _beforeLast;
private decimal? _lastTrough;
private decimal? _lastPeak;
private decimal? _armedLong;
private decimal? _armedShort;
private decimal _exitLevel;
/// <summary>
/// Period for CCI.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// CCI level below which troughs count.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// CCI level above which peaks count.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public CciFailureSwingStrategy()
{
_cciPeriod = Param(nameof(CciPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Period for CCI", "Indicators");
_oversoldLevel = Param(nameof(OversoldLevel), -100m)
.SetDisplay("Oversold Level", "CCI level below which troughs count", "Levels");
_overboughtLevel = Param(nameof(OverboughtLevel), 100m)
.SetDisplay("Overbought Level", "CCI level above which peaks count", "Levels");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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()
{
_last = null;
_beforeLast = null;
_lastTrough = null;
_lastPeak = null;
_armedLong = null;
_armedShort = null;
_exitLevel = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var oscillator = new CommodityChannelIndex { Length = CciPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(oscillator, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, oscillator);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished || !value.IsFormed || value.IsEmpty)
return;
var current = value.GetValue<decimal>();
if (_last is decimal last)
{
// The previous value is a trough or a peak once the current one turns away from it.
if (_beforeLast is decimal beforeLast)
{
if (last < beforeLast && last < current && last < OversoldLevel)
{
_armedLong = _lastTrough is decimal trough && last > trough ? last : null;
_lastTrough = last;
}
else if (last > beforeLast && last > current && last > OverboughtLevel)
{
_armedShort = _lastPeak is decimal peak && last < peak ? last : null;
_lastPeak = last;
}
}
var longTrigger = current > last;
var shortTrigger = current < last;
var armedLong = longTrigger ? _armedLong : null;
var armedShort = shortTrigger ? _armedShort : null;
if (longTrigger)
_armedLong = null;
if (shortTrigger)
_armedShort = null;
if (IsFormedAndOnlineAndAllowTrading())
{
if (Position > 0)
{
if (current < _exitLevel)
SellMarket(Position);
}
else if (Position < 0)
{
if (current > _exitLevel)
BuyMarket(-Position);
}
else if (armedLong is decimal longSwing)
{
BuyMarket(Volume);
_exitLevel = longSwing;
}
else if (armedShort is decimal shortSwing)
{
SellMarket(Volume);
_exitLevel = shortSwing;
}
}
}
_beforeLast = _last;
_last = current;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_failure_swing_strategy(Strategy):
"""
CCI Failure Swing strategy.
A trough of CCI below OversoldLevel that is higher than the previous such trough arms a long, which opens as CCI turns up from it;
peaks above OverboughtLevel arm shorts the same way. A position closes when CCI crosses back through the swing that armed it,
and a percent stop limits the loss.
"""
def __init__(self):
super(cci_failure_swing_strategy, self).__init__()
self._cci_period = self.Param("CciPeriod", 20).SetGreaterThanZero().SetDisplay("CCI Period", "Period for CCI", "Indicators")
self._oversold_level = self.Param("OversoldLevel", -100.0).SetDisplay("Oversold Level", "CCI level below which troughs count", "Levels")
self._overbought_level = self.Param("OverboughtLevel", 100.0).SetDisplay("Overbought Level", "CCI level above which peaks count", "Levels")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).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):
# The last two values, the last trough and peak in the extreme zones, and the swings that arm an entry.
self._last = None
self._before_last = None
self._last_trough = None
self._last_peak = None
self._armed_long = None
self._armed_short = None
self._exit_level = Decimal(0)
def OnReseted(self):
super(cci_failure_swing_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(cci_failure_swing_strategy, self).OnStarted2(time)
self._reset_state()
oscillator = CommodityChannelIndex()
oscillator.Length = self._cci_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(oscillator, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, oscillator)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, value):
if candle.State != CandleStates.Finished or not value.IsFormed or value.IsEmpty:
return
current = value.GetValue[Decimal](None)
os_level = Decimal(self._oversold_level.Value)
ob_level = Decimal(self._overbought_level.Value)
last = self._last
if last is not None:
# The previous value is a trough or a peak once the current one turns away from it.
before_last = self._before_last
if before_last is not None:
if last < before_last and last < current and last < os_level:
self._armed_long = last if self._last_trough is not None and last > self._last_trough else None
self._last_trough = last
elif last > before_last and last > current and last > ob_level:
self._armed_short = last if self._last_peak is not None and last < self._last_peak else None
self._last_peak = last
long_trigger = current > last
short_trigger = current < last
armed_long = self._armed_long if long_trigger else None
armed_short = self._armed_short if short_trigger else None
if long_trigger:
self._armed_long = None
if short_trigger:
self._armed_short = None
if self.IsFormedAndOnlineAndAllowTrading():
if self.Position > 0:
if current < self._exit_level:
self.SellMarket(self.Position)
elif self.Position < 0:
if current > self._exit_level:
self.BuyMarket(-self.Position)
elif armed_long is not None:
self.BuyMarket(self.Volume)
self._exit_level = armed_long
elif armed_short is not None:
self.SellMarket(self.Volume)
self._exit_level = armed_short
self._before_last = self._last
self._last = current
def CreateClone(self):
return cci_failure_swing_strategy()