Estratégia CCI Failure Swing
O CCI Failure Swing é baseado no Commodity Channel Index formando uma máxima mais baixa acima de +100 ou uma mínima mais alta abaixo de -100. Esta incapacidade de atingir um novo extremo frequentemente sinaliza o fim da tendência anterior.
Os testes indicam um retorno anual médio de aproximadamente 73%. Funciona melhor no mercado de criptomoedas.
A estratégia vai comprado quando o CCI se mantém acima de -100 e vira para cima, ou vendido quando falha perto de +100 e vira para baixo.
Um stop percentual mantém o risco pequeno e as operações saem se o CCI cruzar de volta pelo nível de swing anterior.
Detalhes
- Critérios de entrada: sinal do indicador
- Comprado/Vendido: Ambos
- Critérios de saída: stop-loss ou sinal oposto
- Stops: Sim, baseado em percentual
- Valores padrão:
CandleType= 15 minuteCciPeriod= 20OversoldLevel= -100OverboughtLevel= 100StopLossPercent= 2
- Filtros:
- Categoria: Reversão
- Direção: Ambos
- Indicadores: CCI
- Stops: Sim
- Complexidade: Intermediário
- Período: Intradiário
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Médio
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()