RSI + 1200 Strategy
The RSI + 1200 Strategy seeks to capture trend reversals confirmed by
relative strength and a higher time frame trend filter. It combines a classic
14‑period Relative Strength Index with an Exponential Moving Average calculated
on a 120‑minute multi‑time frame series ("1200" refers to the higher time frame
in the original concept). Trading signals are only taken when momentum and the
trend filter align.
Backtests on liquid cryptocurrency pairs show that the method performs best in
sustained directional markets. Choppy or range‑bound periods can produce false
signals, so the strategy includes a small price slack around the EMA and a
percentage based stop‑loss to help manage risk.
A long trade is opened when the RSI crosses upward from oversold territory and
price is within one percent above the higher‑time‑frame EMA. The short setup is
the mirrored condition. Positions are closed when the RSI reaches the opposite
extreme, signalling exhaustion of the move. A protective stop is also placed at
stopLossPercent percent from the entry price.
Details
- Entry Conditions
- Long: RSI crosses above
rsiOversold and close is <= 1% above EMA.
- Short: RSI crosses below
rsiOverbought and close is >= 1% below EMA.
- Exit Conditions
- Long: RSI rises above
rsiOverbought.
- Short: RSI falls below
rsiOversold.
- Stops: Optional percentage stop‑loss via
stopLossPercent.
- Default Parameters
rsiLength = 14
rsiOverbought = 72
rsiOversold = 28
emaLength = 150
mtfTimeframe = 120 minutes
stopLossPercent = 0.10 (10%)
- Filters
- Category: Trend following
- Direction: Both
- Indicators: RSI, EMA
- Stops: Yes
- Complexity: Medium
- Timeframe: Intraday / multi‑time frame
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Moderate
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()