RSI Divergence
Strategy based on RSI divergence
Testing indicates an average annual return of about 85%. It performs best in the crypto market.
RSI Divergence searches for price extremes unconfirmed by the RSI oscillator. A bullish divergence leads to a buy and a bearish divergence prompts a sell. The trade lasts until RSI reverses or a stop fires.
Divergence setups often emerge near the end of long trends. By comparing the oscillator's behavior with price action, the strategy attempts to catch early reversals with controlled risk.
Details
- Entry Criteria: Signals based on RSI.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal or stop.
- Stops: Yes.
- Default Values:
RsiPeriod= 14StopLossPercent= 2mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: RSI
- Stops: Yes
- Complexity: Basic
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: Yes
- Risk Level: Medium
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>
/// Trades divergences between consecutive confirmed price pivots and their RSI values.
/// A three-bar pivot is confirmed only after the bar to its right has closed.
/// </summary>
public class RsiDivergenceStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal High, decimal Low, decimal Rsi)> _bars = new();
private decimal _previousLowPrice;
private decimal _previousLowRsi;
private decimal _previousHighPrice;
private decimal _previousHighRsi;
private bool _hasLow;
private bool _hasHigh;
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public RsiDivergenceStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetGreaterThanZero();
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetRange(0m, 100m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame());
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
ResetPivots();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetPivots();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
private void ResetPivots()
{
_bars.Clear();
_previousLowPrice = _previousLowRsi = _previousHighPrice = _previousHighRsi = 0m;
_hasLow = _hasHigh = false;
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished || !IsFormedAndOnlineAndAllowTrading())
return;
_bars.Add((candle.HighPrice, candle.LowPrice, rsiValue));
if (_bars.Count < 3)
return;
var left = _bars[0];
var pivot = _bars[1];
var right = _bars[2];
var bullish = false;
var bearish = false;
if (pivot.Low < left.Low && pivot.Low < right.Low)
{
bullish = _hasLow && pivot.Low < _previousLowPrice && pivot.Rsi > _previousLowRsi;
_previousLowPrice = pivot.Low;
_previousLowRsi = pivot.Rsi;
_hasLow = true;
}
if (pivot.High > left.High && pivot.High > right.High)
{
bearish = _hasHigh && pivot.High > _previousHighPrice && pivot.Rsi < _previousHighRsi;
_previousHighPrice = pivot.High;
_previousHighRsi = pivot.Rsi;
_hasHigh = true;
}
_bars.RemoveAt(0);
if (bullish && Position <= 0m)
BuyMarket(Volume + Math.Abs(Position));
else if (bearish && Position >= 0m)
SellMarket(Volume + Math.Abs(Position));
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class rsi_divergence_strategy(Strategy):
"""Trades confirmed three-bar price pivots unconfirmed by RSI, without future bars."""
def __init__(self):
super(rsi_divergence_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero()
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetRange(0.0, 100.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._reset_pivots()
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value)]
def OnReseted(self):
super(rsi_divergence_strategy, self).OnReseted()
self._reset_pivots()
def _reset_pivots(self):
self._bars = []
self._previous_low = None
self._previous_high = None
def OnStarted2(self, time):
super(rsi_divergence_strategy, self).OnStarted2(time)
self._reset_pivots()
self.StartProtection(Unit(), Unit(float(self._stop_loss_percent.Value), UnitTypes.Percent),
useMarketOrders=True, isLocalStop=True)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self._candle_type.Value)
subscription.Bind(rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished or not self.IsFormedAndOnlineAndAllowTrading():
return
self._bars.append((candle.HighPrice, candle.LowPrice, rsi_value))
if len(self._bars) < 3:
return
left, pivot, right = self._bars
bullish = False
bearish = False
if pivot[1] < left[1] and pivot[1] < right[1]:
bullish = (self._previous_low is not None and
pivot[1] < self._previous_low[0] and pivot[2] > self._previous_low[1])
self._previous_low = (pivot[1], pivot[2])
if pivot[0] > left[0] and pivot[0] > right[0]:
bearish = (self._previous_high is not None and
pivot[0] > self._previous_high[0] and pivot[2] < self._previous_high[1])
self._previous_high = (pivot[0], pivot[2])
self._bars.pop(0)
if bullish and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif bearish and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
def CreateClone(self):
return rsi_divergence_strategy()