Los canales basados en volatilidad pueden destacar movimientos sobreextendidos. Este método opera en contra del precio cuando se sale del Keltner Channel, anticipando un retorno hacia la línea media. Utiliza una media móvil exponencial y el ATR para dimensionar el ancho del canal.
Las pruebas indican un rendimiento anual promedio de aproximadamente el 106%. Funciona mejor en el mercado de acciones.
A medida que se completa cada vela, la estrategia verifica si el cierre está más allá de la banda superior o inferior y si la dirección de la vela coincide. Las velas alcistas que cierran por debajo de la banda inferior activan entradas largas, mientras que las velas bajistas por encima de la banda superior impulsan cortos. Las posiciones se cierran una vez que el precio cruza la banda media o cuando se alcanza el stop basado en ATR.
Al operar en la dirección opuesta a los extremos de corto plazo, el sistema busca movimientos rápidos de reversión a la media dentro de un rango más amplio.
Detalles
Criterios de entrada: Cierre fuera del Keltner Channel en la dirección de la vela.
Largo/Corto: Ambos.
Criterios de salida: Precio cruzando la banda media o stop-loss.
Stops: Sí, basados en ATR.
Valores predeterminados:
EmaPeriod = 20
AtrPeriod = 14
AtrMultiplier = 2.0
StopLossAtrMultiplier = 2.0
CandleType = 5 minute
Filtros:
Categoría: Reversión a la media
Dirección: Ambos
Indicadores: Keltner Channel
Stops: Sí
Complejidad: Básico
Marco temporal: Intradía
Estacionalidad: No
Redes neuronales: No
Divergencia: No
Nivel de riesgo: Medio
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()