Estratégia Keltner Rsi
Estratégia que combina os indicadores Keltner Channels e RSI. Busca oportunidades de reversão à média quando o preço toca os limites do canal e o RSI confirma condições de sobrevenda/sobrecompra.
Os testes indicam um retorno anual médio de aproximadamente 88%. Funciona melhor no mercado de ações.
Os Keltner Channels mapeiam a volatilidade recente enquanto o RSI mede os extremos do momentum. As entradas ocorrem quando o RSI apoia um movimento além do canal.
Ótimo para traders de rebote em torno de envelopes de volatilidade. O multiplicador de ATR define a largura do canal, enquanto o stop é uma porcentagem fixa do preço de entrada.
Detalhes
- Critérios de entrada:
- Comprado:
Close < LowerBand && RSI < RsiOversoldLevel - Vendido:
Close > UpperBand && RSI > RsiOverboughtLevel
- Comprado:
- Comprado/Vendido: Ambos
- Critérios de saída:
- O preço retorna à EMA
- Stops: Baseados em porcentagem usando
StopLossPercent - Valores padrão:
EmaPeriod= 20AtrPeriod= 14AtrMultiplier= 2.0mRsiPeriod= 14RsiOverboughtLevel= 70mRsiOversoldLevel= 30mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame() O canal é a EMA mais e menos AtrMultiplier ATR; uma compra é encerrada quando o preço fecha na EMA ou acima e uma venda na EMA ou abaixo. Um sinal de entrada contra uma posição aberta a inverte.
- Filtros:
- Categoria: Reversão à média
- Direção: Ambos
- Indicadores: Keltner Channel, RSI
- Stops: Sim
- Complexidade: Intermediário
- Período: Médio prazo
- 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 RSI strategy.
/// The Keltner Channel is the EmaPeriod EMA plus and minus AtrMultiplier times the AtrPeriod ATR. A close below the lower band with RSI
/// below RsiOversoldLevel goes long and a close above the upper band with RSI above RsiOverboughtLevel goes short, reversing an opposite
/// position. The position closes once price returns to the EMA, and a percent stop limits the loss.
/// </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<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Period of the channel EMA.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Period of the channel ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of the channel width.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Period of RSI.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI level for shorts.
/// </summary>
public decimal RsiOverboughtLevel
{
get => _rsiOverboughtLevel.Value;
set => _rsiOverboughtLevel.Value = value;
}
/// <summary>
/// RSI level for longs.
/// </summary>
public decimal RsiOversoldLevel
{
get => _rsiOversoldLevel.Value;
set => _rsiOversoldLevel.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </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 KeltnerRsiStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Period of the channel EMA", "Keltner");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the channel ATR", "Keltner");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR multiplier of the channel width", "Keltner");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period of RSI", "RSI");
_rsiOverboughtLevel = Param(nameof(RsiOverboughtLevel), 70m)
.SetDisplay("RSI Overbought", "RSI level for shorts", "RSI");
_rsiOversoldLevel = Param(nameof(RsiOversoldLevel), 30m)
.SetDisplay("RSI Oversold", "RSI level for longs", "RSI");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_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 OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ema, atr, rsi, 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, ema);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, rsi);
}
}
}
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 emaValue, IIndicatorValue atrValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!emaValue.IsFormed || !atrValue.IsFormed || !rsiValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var middle = emaValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var rsi = rsiValue.GetValue<decimal>();
var upper = middle + AtrMultiplier * atr;
var lower = middle - AtrMultiplier * atr;
var close = candle.ClosePrice;
if (close < lower && rsi < RsiOversoldLevel && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (close > upper && rsi > RsiOverboughtLevel && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && close >= middle)
SellMarket(Position);
else if (Position < 0 && close <= middle)
BuyMarket(-Position);
}
}
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 ExponentialMovingAverage, AverageTrueRange, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class keltner_rsi_strategy(Strategy):
"""
Keltner RSI strategy.
The Keltner Channel is the EmaPeriod EMA plus and minus AtrMultiplier times the AtrPeriod ATR. A close below the lower band with RSI
below RsiOversoldLevel goes long and a close above the upper band with RSI above RsiOverboughtLevel goes short, reversing an opposite
position. The position closes once price returns to the EMA, and a percent stop limits the loss.
"""
def __init__(self):
super(keltner_rsi_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetGreaterThanZero().SetDisplay("EMA Period", "Period of the channel EMA", "Keltner")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the channel ATR", "Keltner")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetGreaterThanZero().SetDisplay("ATR Multiplier", "ATR multiplier of the channel width", "Keltner")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero().SetDisplay("RSI Period", "Period of RSI", "RSI")
self._rsi_overbought_level = self.Param("RsiOverboughtLevel", 70.0).SetDisplay("RSI Overbought", "RSI level for shorts", "RSI")
self._rsi_oversold_level = self.Param("RsiOversoldLevel", 30.0).SetDisplay("RSI Oversold", "RSI level for longs", "RSI")
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(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(keltner_rsi_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema, atr, rsi, 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, ema)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
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, ema_value, atr_value, rsi_value):
if candle.State != CandleStates.Finished:
return
if not ema_value.IsFormed or not atr_value.IsFormed or not rsi_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
middle = ema_value.GetValue[Decimal](None)
atr = atr_value.GetValue[Decimal](None)
rsi = rsi_value.GetValue[Decimal](None)
multiplier = Decimal(self._atr_multiplier.Value)
upper = middle + multiplier * atr
lower = middle - multiplier * atr
close = candle.ClosePrice
if close < lower and rsi < Decimal(self._rsi_oversold_level.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif close > upper and rsi > Decimal(self._rsi_overbought_level.Value) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and close >= middle:
self.SellMarket(self.Position)
elif self.Position < 0 and close <= middle:
self.BuyMarket(-self.Position)
def CreateClone(self):
return keltner_rsi_strategy()