Canais baseados em volatilidade podem destacar movimentos sobreextendidos. Este método opera contra o preço quando ele ultrapassa o Keltner Channel, antecipando um retorno em direção à linha média. Utiliza uma média móvel exponencial e o ATR para dimensionar a largura do canal.
Os testes indicam um retorno anual médio de aproximadamente 106%. Funciona melhor no mercado de ações.
A cada vela concluída, a estratégia verifica se o fechamento está além da banda superior ou inferior e se a direção da vela está de acordo. Velas de alta fechando abaixo da banda inferior disparam entradas compradas, enquanto velas de baixa acima da banda superior geram vendas. As posições são encerradas quando o preço cruza a banda média ou quando o stop baseado em ATR é atingido.
Ao operar na direção oposta dos extremos de curto prazo, o sistema busca movimentos rápidos de reversão à média dentro de uma faixa mais ampla.
Detalhes
Critérios de entrada: Fechamento fora do Keltner Channel na direção da vela.
Comprado/Vendido: Ambos.
Critérios de saída: Preço cruzando a banda média ou stop-loss.
Stops: Sim, baseados em ATR.
Valores padrão:
EmaPeriod = 20
AtrPeriod = 14
AtrMultiplier = 2.0
StopLossAtrMultiplier = 2.0
CandleType = 5 minute
Filtros:
Categoria: Reversão à média
Direção: Ambos
Indicadores: Keltner Channel
Stops: Sim
Complexidade: Básico
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>
/// Keltner Channel Reversal strategy.
/// Enters long when price is below lower Keltner Channel with a bullish candle.
/// Enters short when price is above upper Keltner Channel with a bearish candle.
/// Exits at middle band.
/// </summary>
public class KeltnerChannelReversalStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private int _cooldown;
/// <summary>
/// EMA period for Keltner Channel.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for Keltner Channel.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public KeltnerChannelReversalStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Period for EMA in Keltner Channel", "Indicators");
_atrMultiplier = Param(nameof(AtrMultiplier), 2.0m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "Multiplier for ATR in Keltner Channel", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_cooldown = 0;
var keltner = new KeltnerChannels
{
Length = EmaPeriod,
Multiplier = AtrMultiplier,
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(keltner, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, keltner);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue keltnerValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!keltnerValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var kc = (IKeltnerChannelsValue)keltnerValue;
var upper = kc.Upper;
var lower = kc.Lower;
var middle = kc.Middle;
if (upper == null || lower == null || middle == null)
return;
var isBullish = candle.ClosePrice > candle.OpenPrice;
var isBearish = candle.ClosePrice < candle.OpenPrice;
if (Position == 0 && candle.ClosePrice < lower.Value && isBullish)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && candle.ClosePrice > upper.Value && isBearish)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && candle.ClosePrice > middle.Value)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice < middle.Value)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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 KeltnerChannels
from StockSharp.Algo.Strategies import Strategy
class keltner_channel_reversal_strategy(Strategy):
"""
Keltner Channel Reversal strategy.
Enters long when price is below lower Keltner Channel with a bullish candle.
Enters short when price is above upper Keltner Channel with a bearish candle.
Exits at middle band.
"""
def __init__(self):
super(keltner_channel_reversal_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetDisplay("EMA Period", "Period for EMA in Keltner Channel", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetDisplay("ATR Multiplier", "Multiplier for ATR in Keltner Channel", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(keltner_channel_reversal_strategy, self).OnReseted()
self._cooldown = 0
def OnStarted2(self, time):
super(keltner_channel_reversal_strategy, self).OnStarted2(time)
self._cooldown = 0
keltner = KeltnerChannels()
keltner.Length = self._ema_period.Value
keltner.Multiplier = self._atr_multiplier.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(keltner, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, keltner)
self.DrawOwnTrades(area)
def _process_candle(self, candle, keltner_value):
if candle.State != CandleStates.Finished:
return
if not keltner_value.IsFormed:
return
if self._cooldown > 0:
self._cooldown -= 1
return
upper = keltner_value.Upper
lower = keltner_value.Lower
middle = keltner_value.Middle
if upper is None or lower is None or middle is None:
return
close = float(candle.ClosePrice)
ub = float(upper)
lb = float(lower)
mb = float(middle)
cd = self._cooldown_bars.Value
is_bullish = candle.ClosePrice > candle.OpenPrice
is_bearish = candle.ClosePrice < candle.OpenPrice
if self.Position == 0 and close < lb and is_bullish:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and close > ub and is_bearish:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and close > mb:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close < mb:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return keltner_channel_reversal_strategy()