Estratégia RSI + 1200
A Estratégia RSI + 1200 busca capturar reversões de tendência confirmadas por
força relativa e um filtro de tendência de período superior. Combina um Relative
Strength Index clássico de 14 períodos com uma Média Móvel Exponencial calculada
em uma série de múltiplos períodos de 120 minutos ("1200" refere-se ao período superior
no conceito original). Os sinais de trading só são capturados quando o momentum e o
filtro de tendência se alinham.
Backtests em pares de criptomoedas líquidas mostram que o método funciona melhor em
mercados direcionais sustentados. Períodos agitados ou de consolidação podem produzir sinais falsos,
portanto a estratégia inclui uma pequena folga de preço ao redor da EMA e um
stop‑loss baseado em percentual para ajudar a gerenciar o risco.
Uma operação comprada é aberta quando RSI cruza para cima a partir do território de sobrevenda e
o preço está dentro de um por cento acima da EMA do período superior. A configuração vendida é
a condição espelhada. As posições são fechadas quando RSI atinge o extremo oposto, sinalizando
o esgotamento do movimento. Um stop protetor também é colocado a
stopLossPercent por cento do preço de entrada.
Detalhes
- Condições de entrada
- Comprado: RSI cruza acima de
rsiOversold e o fechamento é <= 1% acima da EMA.
- Vendido: RSI cruza abaixo de
rsiOverbought e o fechamento é >= 1% abaixo da EMA.
- Condições de saída
- Comprado: RSI sobe acima de
rsiOverbought.
- Vendido: RSI cai abaixo de
rsiOversold.
- Stops: Stop‑loss percentual opcional via
stopLossPercent.
- Parâmetros padrão
rsiLength = 14
rsiOverbought = 72
rsiOversold = 28
emaLength = 150
mtfTimeframe = 120 minutos
stopLossPercent = 0.10 (10%)
- Filtros
- Categoria: Seguidor de tendência
- Direção: Ambos
- Indicadores: RSI, EMA
- Stops: Sim
- Complexidade: Médio
- Período: Intradiário / multi‑período
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Moderado
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// RSI + 1200 Strategy.
/// Uses RSI crossover signals with EMA trend filter.
/// Buys when RSI crosses above oversold level while price is above EMA.
/// Sells when RSI crosses below overbought level while price is below EMA.
/// </summary>
public class RsiPlus1200Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _rsiOverbought;
private readonly StrategyParam<int> _rsiOversold;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _cooldownBars;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ema;
private decimal _prevRsi;
private int _cooldownRemaining;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
public int RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public RsiPlus1200Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI calculation length", "RSI");
_rsiOverbought = Param(nameof(RsiOverbought), 70)
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI");
_rsiOversold = Param(nameof(RsiOversold), 30)
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI");
_emaLength = Param(nameof(EmaLength), 100)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period for trend filter", "Moving Average");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_ema = null;
_prevRsi = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _ema, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal rsiVal, decimal emaVal)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_ema.IsFormed)
{
_prevRsi = rsiVal;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevRsi = rsiVal;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevRsi = rsiVal;
return;
}
if (_prevRsi == 0)
{
_prevRsi = rsiVal;
return;
}
// RSI crossovers
var rsiCrossUpOversold = rsiVal > RsiOversold && _prevRsi <= RsiOversold;
var rsiCrossDownOverbought = rsiVal < RsiOverbought && _prevRsi >= RsiOverbought;
// Buy: RSI crosses above oversold + price above EMA (uptrend)
if (rsiCrossUpOversold && candle.ClosePrice > emaVal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: RSI crosses below overbought + price below EMA (downtrend)
else if (rsiCrossDownOverbought && candle.ClosePrice < emaVal && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: RSI overbought
else if (Position > 0 && rsiVal > RsiOverbought)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: RSI oversold
else if (Position < 0 && rsiVal < RsiOversold)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevRsi = rsiVal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class rsi_plus_1200_strategy(Strategy):
"""RSI + 1200 Strategy."""
def __init__(self):
super(rsi_plus_1200_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI calculation length", "RSI")
self._rsi_overbought = self.Param("RsiOverbought", 70) \
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI")
self._rsi_oversold = self.Param("RsiOversold", 30) \
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI")
self._ema_length = self.Param("EmaLength", 100) \
.SetDisplay("EMA Length", "EMA period for trend filter", "Moving Average")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._rsi = None
self._ema = None
self._prev_rsi = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(rsi_plus_1200_strategy, self).OnReseted()
self._rsi = None
self._ema = None
self._prev_rsi = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(rsi_plus_1200_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._ema = ExponentialMovingAverage()
self._ema.Length = int(self._ema_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_val, ema_val):
if candle.State != CandleStates.Finished:
return
if not self._rsi.IsFormed or not self._ema.IsFormed:
self._prev_rsi = float(rsi_val)
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = float(rsi_val)
return
rsi = float(rsi_val)
ema = float(ema_val)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_rsi = rsi
return
if self._prev_rsi == 0.0:
self._prev_rsi = rsi
return
rsi_ob = int(self._rsi_overbought.Value)
rsi_os = int(self._rsi_oversold.Value)
cooldown = int(self._cooldown_bars.Value)
close = float(candle.ClosePrice)
rsi_cross_up_oversold = rsi > rsi_os and self._prev_rsi <= rsi_os
rsi_cross_down_overbought = rsi < rsi_ob and self._prev_rsi >= rsi_ob
if rsi_cross_up_oversold and close > ema and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif rsi_cross_down_overbought and close < ema and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and rsi > rsi_ob:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and rsi < rsi_os:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_rsi = rsi
def CreateClone(self):
return rsi_plus_1200_strategy()