Estrategia Keltner Rsi
Estrategia que combina los indicadores Keltner Channels y RSI. Busca oportunidades de reversión a la media cuando el precio toca los límites del canal y el RSI confirma condiciones de sobreventa/sobrecompra.
Las pruebas indican un rendimiento anual promedio de aproximadamente 88%. Funciona mejor en el mercado de acciones.
Los Keltner Channels mapean la volatilidad reciente mientras el RSI mide los extremos del impulso. Las entradas ocurren cuando el RSI respalda un movimiento más allá del canal.
Ideal para traders de rebote en torno a envolventes de volatilidad. Los stops dependen de un multiplicador de ATR.
Detalles
- Criterios de entrada:
- Largo:
Close < LowerBand && RSI < RsiOversoldLevel - Corto:
Close > UpperBand && RSI > RsiOverboughtLevel
- Largo:
- Largo/Corto: Ambos
- Criterios de salida:
- El precio regresa a la EMA
- Stops: Basados en porcentaje usando
StopLossPercent - Valores predeterminados:
EmaPeriod= 20AtrPeriod= 14AtrMultiplier= 2.0mRsiPeriod= 14RsiOverboughtLevel= 70mRsiOversoldLevel= 30mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filtros:
- Categoría: Reversión a la media
- Dirección: Ambos
- Indicadores: Keltner Channel, RSI
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Medio plazo
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy combining Keltner Channels and RSI indicators.
/// Looks for mean reversion opportunities when price touches channel boundaries
/// and RSI confirms oversold/overbought conditions.
/// </summary>
public class KeltnerRsiStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOverboughtLevel;
private readonly StrategyParam<decimal> _rsiOversoldLevel;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// EMA period for Keltner Channels.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// ATR period for Keltner Channels.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for Keltner Channels width.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Period for RSI calculation.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal RsiOverboughtLevel
{
get => _rsiOverboughtLevel.Value;
set => _rsiOverboughtLevel.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal RsiOversoldLevel
{
get => _rsiOversoldLevel.Value;
set => _rsiOversoldLevel.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
// Fields for indicators
private ExponentialMovingAverage _ema;
private ATR _atr;
private RSI _rsi;
private int _cooldown;
/// <summary>
/// Initialize strategy.
/// </summary>
public KeltnerRsiStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Period for EMA in Keltner Channels", "Indicators")
.SetOptimize(10, 30, 5);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR in Keltner Channels", "Indicators")
.SetOptimize(7, 21, 7);
_atrMultiplier = Param(nameof(AtrMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Multiplier for ATR to set channel width", "Indicators")
.SetOptimize(1.0m, 3.0m, 0.5m);
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicators")
.SetOptimize(7, 21, 7);
_rsiOverboughtLevel = Param(nameof(RsiOverboughtLevel), 60m)
.SetRange(50, 90)
.SetDisplay("RSI Overbought", "RSI level considered overbought", "Trading Levels")
.SetOptimize(65, 80, 5);
_rsiOversoldLevel = Param(nameof(RsiOversoldLevel), 40m)
.SetRange(10, 50)
.SetDisplay("RSI Oversold", "RSI level considered oversold", "Trading Levels")
.SetOptimize(20, 35, 5);
_cooldownBars = Param(nameof(CooldownBars), 120)
.SetRange(5, 500)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_ema = null;
_atr = null;
_rsi = null;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_ema = new EMA { Length = EmaPeriod };
_atr = new ATR { Length = AtrPeriod };
_rsi = new RSI { Length = RsiPeriod };
// Create subscription
var subscription = SubscribeCandles(CandleType);
// Use WhenCandlesFinished to process candles manually
subscription
.Bind(_ema, _atr, _rsi, ProcessCandle)
.Start();
// Setup chart if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
// Add indicators to chart
DrawIndicator(area, _ema);
// Create second area for RSI
var rsiArea = CreateChartArea();
DrawIndicator(rsiArea, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal atrValue, decimal rsiValue)
{
// Skip if indicators are not formed yet
if (!_ema.IsFormed || !_atr.IsFormed || !_rsi.IsFormed)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Calculate Keltner Channels
var upperBand = emaValue + (atrValue * AtrMultiplier);
var lowerBand = emaValue - (atrValue * AtrMultiplier);
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Trading logic
if (candle.ClosePrice < emaValue && rsiValue < 45m && Position == 0)
{
// Mean-reversion long in lower zone.
BuyMarket();
_cooldown = CooldownBars;
}
else if (candle.ClosePrice > emaValue && rsiValue > 55m && Position == 0)
{
// Mean-reversion short in upper zone.
SellMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && candle.ClosePrice >= emaValue && rsiValue > 50)
{
// Exit long position when price crosses above EMA (middle band)
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice <= emaValue && rsiValue < 50)
{
// Exit short position when price crosses below EMA (middle band)
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class keltner_rsi_strategy(Strategy):
def __init__(self):
super(keltner_rsi_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "Period for EMA in Keltner Channels", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR in Keltner Channels", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0) \
.SetDisplay("ATR Multiplier", "Multiplier for ATR to set channel width", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicators")
self._rsi_overbought_level = self.Param("RsiOverboughtLevel", 60.0) \
.SetDisplay("RSI Overbought", "RSI level considered overbought", "Trading Levels")
self._rsi_oversold_level = self.Param("RsiOversoldLevel", 40.0) \
.SetDisplay("RSI Oversold", "RSI level considered oversold", "Trading Levels")
self._cooldown_bars = self.Param("CooldownBars", 120) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._ema = None
self._atr = None
self._rsi = None
self._cooldown = 0
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def AtrMultiplier(self):
return self._atr_multiplier.Value
@AtrMultiplier.setter
def AtrMultiplier(self, value):
self._atr_multiplier.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiOverboughtLevel(self):
return self._rsi_overbought_level.Value
@RsiOverboughtLevel.setter
def RsiOverboughtLevel(self, value):
self._rsi_overbought_level.Value = value
@property
def RsiOversoldLevel(self):
return self._rsi_oversold_level.Value
@RsiOversoldLevel.setter
def RsiOversoldLevel(self, value):
self._rsi_oversold_level.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
@StopLossPercent.setter
def StopLossPercent(self, value):
self._stop_loss_percent.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(keltner_rsi_strategy, self).OnStarted2(time)
self._cooldown = 0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaPeriod
self._atr = AverageTrueRange()
self._atr.Length = self.AtrPeriod
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
self.SubscribeCandles(self.CandleType) \
.Bind(self._ema, self._atr, self._rsi, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, ema_value, atr_value, rsi_value):
if candle.State != CandleStates.Finished:
return
if not self._ema.IsFormed or not self._atr.IsFormed or not self._rsi.IsFormed:
return
ema_f = float(ema_value)
atr_f = float(atr_value)
rsi_f = float(rsi_value)
close = float(candle.ClosePrice)
cooldown_bars = int(self.CooldownBars)
if self._cooldown > 0:
self._cooldown -= 1
return
if close < ema_f and rsi_f < 45.0 and self.Position == 0:
self.BuyMarket()
self._cooldown = cooldown_bars
elif close > ema_f and rsi_f > 55.0 and self.Position == 0:
self.SellMarket()
self._cooldown = cooldown_bars
elif self.Position > 0 and close >= ema_f and rsi_f > 50:
self.SellMarket()
self._cooldown = cooldown_bars
elif self.Position < 0 and close <= ema_f and rsi_f < 50:
self.BuyMarket()
self._cooldown = cooldown_bars
def OnReseted(self):
super(keltner_rsi_strategy, self).OnReseted()
self._ema = None
self._atr = None
self._rsi = None
self._cooldown = 0
def CreateClone(self):
return keltner_rsi_strategy()