Parabolic SAR RSI Divergence
The Parabolic SAR RSI Divergence strategy is built around that trades based on Parabolic SAR signals when RSI shows divergence from price.
Testing indicates an average annual return of about 103%. It performs best in the stocks market.
Signals trigger when Parabolic confirms divergence setups on intraday (5m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like SarAccelerationFactor, SarMaxAccelerationFactor. Adjust these defaults to balance risk and reward.
Details
- Entry Criteria: see implementation for indicator conditions.
- Long/Short: Both directions.
- Exit Criteria: opposite signal or stop logic.
- Stops: Yes, using indicator-based calculations.
- Default Values:
SarAccelerationFactor = 0.02mSarMaxAccelerationFactor = 0.2mRsiPeriod = 14CandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Parabolic, Divergence
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: Yes
- Risk Level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades Parabolic SAR trend direction with RSI divergence-style reversals.
/// </summary>
public class ParabolicSarRsiDivergenceStrategy : Strategy
{
private readonly StrategyParam<decimal> _sarAccelerationFactor;
private readonly StrategyParam<decimal> _sarMaxAccelerationFactor;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevRsi;
private decimal _prevPrice;
private bool _hasPrevValues;
private int _cooldownRemaining;
/// <summary>
/// Strategy parameter: Parabolic SAR acceleration factor.
/// </summary>
public decimal SarAccelerationFactor
{
get => _sarAccelerationFactor.Value;
set => _sarAccelerationFactor.Value = value;
}
/// <summary>
/// Strategy parameter: Parabolic SAR maximum acceleration factor.
/// </summary>
public decimal SarMaxAccelerationFactor
{
get => _sarMaxAccelerationFactor.Value;
set => _sarMaxAccelerationFactor.Value = value;
}
/// <summary>
/// Strategy parameter: RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Strategy parameter: RSI oversold level.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// Strategy parameter: RSI overbought level.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// Strategy parameter: Number of closed candles between position changes.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Strategy parameter: Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ParabolicSarRsiDivergenceStrategy()
{
_sarAccelerationFactor = Param(nameof(SarAccelerationFactor), 0.02m)
.SetRange(0.01m, 0.25m)
.SetDisplay("SAR Acceleration Factor", "Initial acceleration factor for Parabolic SAR", "Indicator Settings");
_sarMaxAccelerationFactor = Param(nameof(SarMaxAccelerationFactor), 0.2m)
.SetRange(0.1m, 0.5m)
.SetDisplay("SAR Max Acceleration Factor", "Maximum acceleration factor for Parabolic SAR", "Indicator Settings");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicator Settings");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetDisplay("RSI Oversold", "RSI oversold level for bullish reversal detection", "Indicator Settings");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetDisplay("RSI Overbought", "RSI overbought level for bearish reversal detection", "Indicator Settings");
_cooldownBars = Param(nameof(CooldownBars), 24)
.SetNotNegative()
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).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 OnReseted()
{
base.OnReseted();
_prevRsi = 0;
_prevPrice = 0;
_hasPrevValues = false;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex
{
Length = RsiPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var rsiArea = CreateChartArea();
if (rsiArea != null)
DrawIndicator(rsiArea, rsi);
}
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_hasPrevValues)
{
StoreState(candle.ClosePrice, rsiValue);
return;
}
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var bullishDivergence = candle.ClosePrice < _prevPrice && rsiValue > _prevRsi;
var bearishDivergence = candle.ClosePrice > _prevPrice && rsiValue < _prevRsi;
var bullishReversal = _prevRsi < RsiOversold && rsiValue >= RsiOversold;
var bearishReversal = _prevRsi > RsiOverbought && rsiValue <= RsiOverbought;
var canTrade = _cooldownRemaining == 0;
if (canTrade && (bullishDivergence || bullishReversal) && Position <= 0)
{
BuyMarket(Volume + (Position < 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = CooldownBars;
}
else if (canTrade && (bearishDivergence || bearishReversal) && Position >= 0)
{
SellMarket(Volume + (Position > 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = CooldownBars;
}
StoreState(candle.ClosePrice, rsiValue);
}
private void StoreState(decimal price, decimal rsi)
{
_prevPrice = price;
_prevRsi = rsi;
_hasPrevValues = true;
}
}
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 parabolic_sar_rsi_divergence_strategy(Strategy):
"""
Strategy that trades Parabolic SAR trend direction with RSI divergence-style reversals.
"""
def __init__(self):
super(parabolic_sar_rsi_divergence_strategy, self).__init__()
self._sar_acceleration_factor = self.Param("SarAccelerationFactor", 0.02) \
.SetDisplay("SAR Acceleration Factor", "Initial acceleration factor for Parabolic SAR", "Indicator Settings")
self._sar_max_acceleration_factor = self.Param("SarMaxAccelerationFactor", 0.2) \
.SetDisplay("SAR Max Acceleration Factor", "Maximum acceleration factor for Parabolic SAR", "Indicator Settings")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicator Settings")
self._rsi_oversold = self.Param("RsiOversold", 30.0) \
.SetDisplay("RSI Oversold", "RSI oversold level for bullish reversal detection", "Indicator Settings")
self._rsi_overbought = self.Param("RsiOverbought", 70.0) \
.SetDisplay("RSI Overbought", "RSI overbought level for bearish reversal detection", "Indicator Settings")
self._cooldown_bars = self.Param("CooldownBars", 24) \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_rsi = 0.0
self._prev_price = 0.0
self._has_prev_values = False
self._cooldown_remaining = 0
@property
def SarAccelerationFactor(self):
return self._sar_acceleration_factor.Value
@SarAccelerationFactor.setter
def SarAccelerationFactor(self, value):
self._sar_acceleration_factor.Value = value
@property
def SarMaxAccelerationFactor(self):
return self._sar_max_acceleration_factor.Value
@SarMaxAccelerationFactor.setter
def SarMaxAccelerationFactor(self, value):
self._sar_max_acceleration_factor.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiOversold(self):
return self._rsi_oversold.Value
@RsiOversold.setter
def RsiOversold(self, value):
self._rsi_oversold.Value = value
@property
def RsiOverbought(self):
return self._rsi_overbought.Value
@RsiOverbought.setter
def RsiOverbought(self, value):
self._rsi_overbought.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(parabolic_sar_rsi_divergence_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._prev_price = 0.0
self._has_prev_values = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(parabolic_sar_rsi_divergence_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
rsi_area = self.CreateChartArea()
if rsi_area is not None:
self.DrawIndicator(rsi_area, rsi)
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
def ProcessCandle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
rsi_val = float(rsi_value)
close_price = float(candle.ClosePrice)
if not self._has_prev_values:
self._prev_price = close_price
self._prev_rsi = rsi_val
self._has_prev_values = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
bullish_divergence = close_price < self._prev_price and rsi_val > self._prev_rsi
bearish_divergence = close_price > self._prev_price and rsi_val < self._prev_rsi
bullish_reversal = self._prev_rsi < self.RsiOversold and rsi_val >= self.RsiOversold
bearish_reversal = self._prev_rsi > self.RsiOverbought and rsi_val <= self.RsiOverbought
can_trade = self._cooldown_remaining == 0
if can_trade and (bullish_divergence or bullish_reversal) and self.Position <= 0:
vol = self.Volume
if self.Position < 0:
vol = self.Volume + abs(self.Position)
self.BuyMarket(vol)
self._cooldown_remaining = self.CooldownBars
elif can_trade and (bearish_divergence or bearish_reversal) and self.Position >= 0:
vol = self.Volume
if self.Position > 0:
vol = self.Volume + abs(self.Position)
self.SellMarket(vol)
self._cooldown_remaining = self.CooldownBars
self._prev_price = close_price
self._prev_rsi = rsi_val
def CreateClone(self):
return parabolic_sar_rsi_divergence_strategy()