Double RSI
Double RSI uses two Relative Strength Index calculations: one on the trading chart and another on a higher timeframe. Trades are taken only when both RSI readings support the same direction, aligning short‑term entries with longer‑term momentum.
The main timeframe looks for RSI crossing out of overbought or oversold zones. If the higher‑timeframe RSI confirms the move, the strategy opens a position. An optional take‑profit can lock in gains after a predefined move.
Details
- Data: Price candles on two timeframes.
- Entry Criteria:
- Long: Lower‑timeframe RSI exits oversold AND higher‑timeframe RSI is bullish.
- Short: Lower‑timeframe RSI exits overbought AND higher‑timeframe RSI is bearish.
- Exit Criteria: Opposite RSI signal or take‑profit if
UseTPis true. - Stops: None by default.
- Default Values:
CandleType= tf(5)RSILength= 14MTFTimeframe= tf(15)UseTP= False
- Filters:
- Category: Momentum
- Direction: Long & Short
- Indicators: RSI (multi‑timeframe)
- Complexity: Moderate
- Risk level: Medium
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>
/// Double RSI Strategy.
/// Uses a short RSI and a long RSI for confirmation.
/// Buys when both RSIs are oversold and sells when both are overbought.
/// </summary>
public class DoubleRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _rsiShortLength;
private readonly StrategyParam<int> _rsiLongLength;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<int> _cooldownBars;
private RelativeStrengthIndex _rsiShort;
private RelativeStrengthIndex _rsiLong;
private int _cooldownRemaining;
public DoubleRsiStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_rsiShortLength = Param(nameof(RSIShortLength), 7)
.SetGreaterThanZero()
.SetDisplay("Short RSI", "Short RSI period", "RSI");
_rsiLongLength = Param(nameof(RSILongLength), 21)
.SetGreaterThanZero()
.SetDisplay("Long RSI", "Long RSI period", "RSI");
_oversold = Param(nameof(Oversold), 35m)
.SetDisplay("Oversold", "RSI oversold level", "RSI");
_overbought = Param(nameof(Overbought), 65m)
.SetDisplay("Overbought", "RSI overbought level", "RSI");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
public int RSIShortLength
{
get => _rsiShortLength.Value;
set => _rsiShortLength.Value = value;
}
public int RSILongLength
{
get => _rsiLongLength.Value;
set => _rsiLongLength.Value = value;
}
public decimal Oversold
{
get => _oversold.Value;
set => _oversold.Value = value;
}
public decimal Overbought
{
get => _overbought.Value;
set => _overbought.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsiShort = null;
_rsiLong = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsiShort = new RelativeStrengthIndex { Length = RSIShortLength };
_rsiLong = new RelativeStrengthIndex { Length = RSILongLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsiShort, _rsiLong, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal rsiShort, decimal rsiLong)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsiShort.IsFormed || !_rsiLong.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
// Buy: both RSIs oversold
if (rsiShort < Oversold && rsiLong < Oversold && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Sell: both RSIs overbought
else if (rsiShort > Overbought && rsiLong > Overbought && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: short RSI overbought
else if (Position > 0 && rsiShort > Overbought)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: short RSI oversold
else if (Position < 0 && rsiShort < Oversold)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = 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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class double_rsi_strategy(Strategy):
"""Double RSI Strategy. Uses short and long RSI for entry/exit signals."""
def __init__(self):
super(double_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._rsi_short_length = self.Param("RSIShortLength", 7) \
.SetDisplay("Short RSI", "Short RSI period", "RSI")
self._rsi_long_length = self.Param("RSILongLength", 21) \
.SetDisplay("Long RSI", "Long RSI period", "RSI")
self._oversold = self.Param("Oversold", 35.0) \
.SetDisplay("Oversold", "RSI oversold level", "RSI")
self._overbought = self.Param("Overbought", 65.0) \
.SetDisplay("Overbought", "RSI overbought level", "RSI")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._rsi_short = None
self._rsi_long = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(double_rsi_strategy, self).OnReseted()
self._rsi_short = None
self._rsi_long = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(double_rsi_strategy, self).OnStarted2(time)
self._rsi_short = RelativeStrengthIndex()
self._rsi_short.Length = int(self._rsi_short_length.Value)
self._rsi_long = RelativeStrengthIndex()
self._rsi_long.Length = int(self._rsi_long_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi_short, self._rsi_long, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_short_val, rsi_long_val):
if candle.State != CandleStates.Finished:
return
if not self._rsi_short.IsFormed or not self._rsi_long.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
rs = float(rsi_short_val)
rl = float(rsi_long_val)
oversold = float(self._oversold.Value)
overbought = float(self._overbought.Value)
cooldown = int(self._cooldown_bars.Value)
if rs < oversold and rl < oversold and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif rs > overbought and rl > overbought 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 rs > overbought:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and rs < oversold:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
def CreateClone(self):
return double_rsi_strategy()