Gold RSI Divergence Strategy
The Gold RSI Divergence strategy scalps gold by identifying bullish and bearish divergences between price and the Relative Strength Index (RSI). When price makes a new low but RSI prints a higher low, the strategy looks to buy. Conversely, when price makes a new high but RSI prints a lower high, the strategy sells. Both setups are confirmed only if two pivots occur within a configurable bar range.
Details
- Entry Criteria:
- Long: Price lower low, RSI higher low, RSI < 40.
- Short: Price higher high, RSI lower high, RSI > 60.
- Long/Short: Both sides.
- Exit Criteria:
- Uses stop loss and take profit.
- Stops: Fixed stop loss and take profit in pips.
- Default Values:
RsiLength= 60StopLossPips= 11TakeProfitPips= 33
- Filters:
- Category: Divergence
- Direction: Both
- Indicators: RSI
- Stops: Yes
- Complexity: Medium
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: Yes
- 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>
/// RSI Divergence strategy.
/// Looks for price/RSI divergence for entries.
/// </summary>
public class GoldRsiDivergenceStrategy : Strategy
{
private readonly StrategyParam<int> _lookbackLeft;
private readonly StrategyParam<int> _lookbackRight;
private readonly StrategyParam<int> _rangeLower;
private readonly StrategyParam<int> _rangeUpper;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal[] _rsiBuffer = Array.Empty<decimal>();
private decimal[] _lowBuffer = Array.Empty<decimal>();
private decimal[] _highBuffer = Array.Empty<decimal>();
private int _bufferCount;
private int _barIndex;
private decimal? _lastRsiLow;
private decimal? _lastPriceLow;
private int _lastPivotLowIndex = -1;
private decimal? _lastRsiHigh;
private decimal? _lastPriceHigh;
private int _lastPivotHighIndex = -1;
private int _cooldownRemaining;
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int LookbackLeft { get => _lookbackLeft.Value; set => _lookbackLeft.Value = value; }
public int LookbackRight { get => _lookbackRight.Value; set => _lookbackRight.Value = value; }
public int RangeLower { get => _rangeLower.Value; set => _rangeLower.Value = value; }
public int RangeUpper { get => _rangeUpper.Value; set => _rangeUpper.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public GoldRsiDivergenceStrategy()
{
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI calculation length", "RSI");
_lookbackLeft = Param(nameof(LookbackLeft), 5)
.SetGreaterThanZero()
.SetDisplay("Lookback Left", "Bars to the left of pivot", "Divergence");
_lookbackRight = Param(nameof(LookbackRight), 5)
.SetGreaterThanZero()
.SetDisplay("Lookback Right", "Bars to the right of pivot", "Divergence");
_rangeLower = Param(nameof(RangeLower), 5)
.SetGreaterThanZero()
.SetDisplay("Range Lower", "Minimum bars between pivots", "Divergence");
_rangeUpper = Param(nameof(RangeUpper), 60)
.SetGreaterThanZero()
.SetDisplay("Range Upper", "Maximum bars between pivots", "Divergence");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
InitializeBuffers();
_barIndex = 0;
_lastRsiLow = null;
_lastPriceLow = null;
_lastPivotLowIndex = -1;
_lastRsiHigh = null;
_lastPriceHigh = null;
_lastPivotHighIndex = -1;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
InitializeBuffers();
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void InitializeBuffers()
{
var length = Math.Max(1, LookbackLeft + LookbackRight + 1);
_rsiBuffer = new decimal[length];
_lowBuffer = new decimal[length];
_highBuffer = new decimal[length];
_bufferCount = 0;
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
_barIndex++;
AddToBuffer(rsiValue, candle.LowPrice, candle.HighPrice);
if (_bufferCount < _rsiBuffer.Length)
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
CheckPivots(rsiValue, candle);
return;
}
var pivotIndex = LookbackRight;
var candidateRsi = _rsiBuffer[pivotIndex];
var candidateLow = _lowBuffer[pivotIndex];
var candidateHigh = _highBuffer[pivotIndex];
var candidateBar = _barIndex - LookbackRight;
var isPivotLow = IsPivotLow(candidateRsi);
var isPivotHigh = IsPivotHigh(candidateRsi);
if (isPivotLow)
{
var inRange = _lastPivotLowIndex >= 0 &&
candidateBar - _lastPivotLowIndex >= RangeLower &&
candidateBar - _lastPivotLowIndex <= RangeUpper;
var bullishDiv = inRange &&
_lastRsiLow is decimal prevRsiLow &&
_lastPriceLow is decimal prevPriceLow &&
candidateRsi > prevRsiLow &&
candidateLow < prevPriceLow;
if (bullishDiv && rsiValue < 40m && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
_lastRsiLow = candidateRsi;
_lastPriceLow = candidateLow;
_lastPivotLowIndex = candidateBar;
}
if (isPivotHigh)
{
var inRange = _lastPivotHighIndex >= 0 &&
candidateBar - _lastPivotHighIndex >= RangeLower &&
candidateBar - _lastPivotHighIndex <= RangeUpper;
var bearishDiv = inRange &&
_lastRsiHigh is decimal prevRsiHigh &&
_lastPriceHigh is decimal prevPriceHigh &&
candidateRsi < prevRsiHigh &&
candidateHigh > prevPriceHigh;
if (bearishDiv && rsiValue > 60m && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
_lastRsiHigh = candidateRsi;
_lastPriceHigh = candidateHigh;
_lastPivotHighIndex = candidateBar;
}
}
private void CheckPivots(decimal rsiValue, ICandleMessage candle)
{
// Still track pivots during cooldown
var pivotIndex = LookbackRight;
var candidateRsi = _rsiBuffer[pivotIndex];
var candidateBar = _barIndex - LookbackRight;
if (IsPivotLow(candidateRsi))
{
_lastRsiLow = candidateRsi;
_lastPriceLow = _lowBuffer[pivotIndex];
_lastPivotLowIndex = candidateBar;
}
if (IsPivotHigh(candidateRsi))
{
_lastRsiHigh = candidateRsi;
_lastPriceHigh = _highBuffer[pivotIndex];
_lastPivotHighIndex = candidateBar;
}
}
private void AddToBuffer(decimal rsi, decimal low, decimal high)
{
if (_bufferCount < _rsiBuffer.Length)
{
_rsiBuffer[_bufferCount] = rsi;
_lowBuffer[_bufferCount] = low;
_highBuffer[_bufferCount] = high;
_bufferCount++;
}
else
{
Array.Copy(_rsiBuffer, 1, _rsiBuffer, 0, _rsiBuffer.Length - 1);
Array.Copy(_lowBuffer, 1, _lowBuffer, 0, _lowBuffer.Length - 1);
Array.Copy(_highBuffer, 1, _highBuffer, 0, _highBuffer.Length - 1);
_rsiBuffer[^1] = rsi;
_lowBuffer[^1] = low;
_highBuffer[^1] = high;
}
}
private bool IsPivotLow(decimal value)
{
for (var i = 0; i < _rsiBuffer.Length; i++)
{
if (i == LookbackRight)
continue;
if (_rsiBuffer[i] <= value)
return false;
}
return true;
}
private bool IsPivotHigh(decimal value)
{
for (var i = 0; i < _rsiBuffer.Length; i++)
{
if (i == LookbackRight)
continue;
if (_rsiBuffer[i] >= value)
return false;
}
return 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
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class gold_rsi_divergence_strategy(Strategy):
def __init__(self):
super(gold_rsi_divergence_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI calculation length", "RSI")
self._lookback_left = self.Param("LookbackLeft", 5) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Left", "Bars to the left of pivot", "Divergence")
self._lookback_right = self.Param("LookbackRight", 5) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Right", "Bars to the right of pivot", "Divergence")
self._range_lower = self.Param("RangeLower", 5) \
.SetGreaterThanZero() \
.SetDisplay("Range Lower", "Minimum bars between pivots", "Divergence")
self._range_upper = self.Param("RangeUpper", 60) \
.SetGreaterThanZero() \
.SetDisplay("Range Upper", "Maximum bars between pivots", "Divergence")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._rsi_buffer = []
self._low_buffer = []
self._high_buffer = []
self._buffer_count = 0
self._bar_index = 0
self._last_rsi_low = None
self._last_price_low = None
self._last_pivot_low_index = -1
self._last_rsi_high = None
self._last_price_high = None
self._last_pivot_high_index = -1
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(gold_rsi_divergence_strategy, self).OnReseted()
self._initialize_buffers()
self._bar_index = 0
self._last_rsi_low = None
self._last_price_low = None
self._last_pivot_low_index = -1
self._last_rsi_high = None
self._last_price_high = None
self._last_pivot_high_index = -1
self._cooldown_remaining = 0
def _initialize_buffers(self):
length = max(1, int(self._lookback_left.Value) + int(self._lookback_right.Value) + 1)
self._rsi_buffer = [0.0] * length
self._low_buffer = [0.0] * length
self._high_buffer = [0.0] * length
self._buffer_count = 0
def OnStarted2(self, time):
super(gold_rsi_divergence_strategy, self).OnStarted2(time)
self._initialize_buffers()
rsi = RelativeStrengthIndex()
rsi.Length = int(self._rsi_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _add_to_buffer(self, rsi, low, high):
buf_len = len(self._rsi_buffer)
if self._buffer_count < buf_len:
self._rsi_buffer[self._buffer_count] = rsi
self._low_buffer[self._buffer_count] = low
self._high_buffer[self._buffer_count] = high
self._buffer_count += 1
else:
self._rsi_buffer = self._rsi_buffer[1:] + [rsi]
self._low_buffer = self._low_buffer[1:] + [low]
self._high_buffer = self._high_buffer[1:] + [high]
def _is_pivot_low(self, value):
lr = int(self._lookback_right.Value)
for i in range(len(self._rsi_buffer)):
if i == lr:
continue
if self._rsi_buffer[i] <= value:
return False
return True
def _is_pivot_high(self, value):
lr = int(self._lookback_right.Value)
for i in range(len(self._rsi_buffer)):
if i == lr:
continue
if self._rsi_buffer[i] >= value:
return False
return True
def _check_pivots(self, rsi_value, candle):
lr = int(self._lookback_right.Value)
candidate_rsi = self._rsi_buffer[lr]
candidate_bar = self._bar_index - lr
if self._is_pivot_low(candidate_rsi):
self._last_rsi_low = candidate_rsi
self._last_price_low = self._low_buffer[lr]
self._last_pivot_low_index = candidate_bar
if self._is_pivot_high(candidate_rsi):
self._last_rsi_high = candidate_rsi
self._last_price_high = self._high_buffer[lr]
self._last_pivot_high_index = candidate_bar
def _on_process(self, candle, rsi_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
rsi_value = float(rsi_val)
self._bar_index += 1
self._add_to_buffer(rsi_value, float(candle.LowPrice), float(candle.HighPrice))
if self._buffer_count < len(self._rsi_buffer):
return
cooldown = int(self._cooldown_bars.Value)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._check_pivots(rsi_value, candle)
return
lr = int(self._lookback_right.Value)
candidate_rsi = self._rsi_buffer[lr]
candidate_low = self._low_buffer[lr]
candidate_high = self._high_buffer[lr]
candidate_bar = self._bar_index - lr
range_lo = int(self._range_lower.Value)
range_up = int(self._range_upper.Value)
is_pivot_low = self._is_pivot_low(candidate_rsi)
is_pivot_high = self._is_pivot_high(candidate_rsi)
if is_pivot_low:
in_range = (self._last_pivot_low_index >= 0 and
candidate_bar - self._last_pivot_low_index >= range_lo and
candidate_bar - self._last_pivot_low_index <= range_up)
bullish_div = (in_range and
self._last_rsi_low is not None and
self._last_price_low is not None and
candidate_rsi > self._last_rsi_low and
candidate_low < self._last_price_low)
if bullish_div and rsi_value < 40.0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
self._last_rsi_low = candidate_rsi
self._last_price_low = candidate_low
self._last_pivot_low_index = candidate_bar
if is_pivot_high:
in_range = (self._last_pivot_high_index >= 0 and
candidate_bar - self._last_pivot_high_index >= range_lo and
candidate_bar - self._last_pivot_high_index <= range_up)
bearish_div = (in_range and
self._last_rsi_high is not None and
self._last_price_high is not None and
candidate_rsi < self._last_rsi_high and
candidate_high > self._last_price_high)
if bearish_div and rsi_value > 60.0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
self._last_rsi_high = candidate_rsi
self._last_price_high = candidate_high
self._last_pivot_high_index = candidate_bar
def CreateClone(self):
return gold_rsi_divergence_strategy()