Big Candle RSI Divergence
Identifies unusually large candles relative to the prior five bars and compares fast and slow RSI values. Trades follow the candle direction and use a delayed trailing stop that activates only after price moves a set number of ticks in profit.
The trailing stop begins once the profit threshold is reached and then tracks price at a fixed distance, while an initial fixed stop protects the trade from the start.
Details
- Entry Criteria:
- Long: Current candle body bigger than the previous five and closes up.
- Short: Current candle body bigger than the previous five and closes down.
- Long/Short: Both directions.
- Exit Criteria: Initial stop or trailing stop hit.
- Stops: Yes, delayed trailing stop.
- Default Values:
TrailStartTicks= 200TrailDistanceTicks= 150InitialStopLossTicks= 200CandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Reversal
- Direction: Both
- Indicators: RSI
- Stops: Yes
- Complexity: Intermediate
- 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>
/// Big Candle Identifier with RSI divergence and trailing stops.
/// Enters when the current candle body is the largest of the last N candles.
/// Uses RSI fast/slow divergence as confirmation.
/// </summary>
public class BigCandleRsiDivergenceStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _trailStartPercent;
private readonly StrategyParam<decimal> _trailDistancePercent;
private readonly StrategyParam<int> _lookbackBars;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _bodies = new();
private decimal _entryPrice;
private decimal _highestSinceEntry;
private decimal _lowestSinceEntry;
private bool _trailingActive;
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public decimal TrailStartPercent { get => _trailStartPercent.Value; set => _trailStartPercent.Value = value; }
public decimal TrailDistancePercent { get => _trailDistancePercent.Value; set => _trailDistancePercent.Value = value; }
public int LookbackBars { get => _lookbackBars.Value; set => _lookbackBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BigCandleRsiDivergenceStrategy()
{
_stopLossPercent = Param(nameof(StopLossPercent), 0.3m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Initial stop loss percent", "Risk");
_trailStartPercent = Param(nameof(TrailStartPercent), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Trail Start %", "Profit percent to activate trailing", "Risk");
_trailDistancePercent = Param(nameof(TrailDistancePercent), 0.2m)
.SetGreaterThanZero()
.SetDisplay("Trail Distance %", "Trailing stop distance percent", "Risk");
_lookbackBars = Param(nameof(LookbackBars), 3)
.SetGreaterThanZero()
.SetDisplay("Lookback Bars", "Number of bars for big candle comparison", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_bodies.Clear();
_entryPrice = 0m;
_highestSinceEntry = 0m;
_lowestSinceEntry = 0m;
_trailingActive = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsiFast = new RelativeStrengthIndex { Length = 5 };
var rsiSlow = new RelativeStrengthIndex { Length = 14 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsiFast, rsiSlow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiFast, decimal rsiSlow)
{
if (candle.State != CandleStates.Finished)
return;
var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
_bodies.Add(body);
if (_bodies.Count > LookbackBars + 1)
_bodies.RemoveAt(0);
if (_bodies.Count <= LookbackBars)
return;
// Check if current body is the largest in lookback window
var isBiggest = true;
for (var i = 0; i < _bodies.Count - 1; i++)
{
if (_bodies[i] >= body)
{
isBiggest = false;
break;
}
}
var isBullish = candle.ClosePrice > candle.OpenPrice;
var isBearish = candle.ClosePrice < candle.OpenPrice;
var rsiDivergence = rsiFast - rsiSlow;
if (Position == 0)
{
if (isBiggest && isBullish && rsiDivergence > 0)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_highestSinceEntry = candle.ClosePrice;
_trailingActive = false;
}
else if (isBiggest && isBearish && rsiDivergence < 0)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_lowestSinceEntry = candle.ClosePrice;
_trailingActive = false;
}
}
else if (Position > 0 && _entryPrice > 0)
{
_highestSinceEntry = Math.Max(_highestSinceEntry, candle.ClosePrice);
var profitPercent = (candle.ClosePrice - _entryPrice) / _entryPrice * 100m;
if (!_trailingActive && profitPercent >= TrailStartPercent)
_trailingActive = true;
if (_trailingActive)
{
var stop = _highestSinceEntry * (1 - TrailDistancePercent / 100m);
if (candle.ClosePrice <= stop)
{
SellMarket();
_entryPrice = 0m;
_trailingActive = false;
}
}
else
{
var stop = _entryPrice * (1 - StopLossPercent / 100m);
if (candle.ClosePrice <= stop)
{
SellMarket();
_entryPrice = 0m;
}
}
}
else if (Position < 0 && _entryPrice > 0)
{
_lowestSinceEntry = Math.Min(_lowestSinceEntry, candle.ClosePrice);
var profitPercent = (_entryPrice - candle.ClosePrice) / _entryPrice * 100m;
if (!_trailingActive && profitPercent >= TrailStartPercent)
_trailingActive = true;
if (_trailingActive)
{
var stop = _lowestSinceEntry * (1 + TrailDistancePercent / 100m);
if (candle.ClosePrice >= stop)
{
BuyMarket();
_entryPrice = 0m;
_trailingActive = false;
}
}
else
{
var stop = _entryPrice * (1 + StopLossPercent / 100m);
if (candle.ClosePrice >= stop)
{
BuyMarket();
_entryPrice = 0m;
}
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class big_candle_rsi_divergence_strategy(Strategy):
def __init__(self):
super(big_candle_rsi_divergence_strategy, self).__init__()
self._stop_loss_percent = self.Param("StopLossPercent", 0.3) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Initial stop loss percent", "Risk")
self._trail_start_percent = self.Param("TrailStartPercent", 0.5) \
.SetGreaterThanZero() \
.SetDisplay("Trail Start %", "Profit percent to activate trailing", "Risk")
self._trail_distance_percent = self.Param("TrailDistancePercent", 0.2) \
.SetGreaterThanZero() \
.SetDisplay("Trail Distance %", "Trailing stop distance percent", "Risk")
self._lookback_bars = self.Param("LookbackBars", 3) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Bars", "Number of bars for big candle comparison", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._bodies = []
self._entry_price = 0.0
self._highest_since_entry = 0.0
self._lowest_since_entry = 0.0
self._trailing_active = False
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(big_candle_rsi_divergence_strategy, self).OnReseted()
self._bodies = []
self._entry_price = 0.0
self._highest_since_entry = 0.0
self._lowest_since_entry = 0.0
self._trailing_active = False
def OnStarted2(self, time):
super(big_candle_rsi_divergence_strategy, self).OnStarted2(time)
rsi_fast = RelativeStrengthIndex()
rsi_fast.Length = 5
rsi_slow = RelativeStrengthIndex()
rsi_slow.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi_fast, rsi_slow, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def OnProcess(self, candle, rsi_fast_val, rsi_slow_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
body = abs(close - open_p)
lookback = self._lookback_bars.Value
self._bodies.append(body)
if len(self._bodies) > lookback + 1:
self._bodies = self._bodies[1:]
if len(self._bodies) <= lookback:
return
is_biggest = True
for i in range(len(self._bodies) - 1):
if self._bodies[i] >= body:
is_biggest = False
break
is_bullish = close > open_p
is_bearish = close < open_p
rsi_divergence = float(rsi_fast_val) - float(rsi_slow_val)
sl_pct = float(self._stop_loss_percent.Value)
ts_pct = float(self._trail_start_percent.Value)
td_pct = float(self._trail_distance_percent.Value)
if self.Position == 0:
if is_biggest and is_bullish and rsi_divergence > 0:
self.BuyMarket()
self._entry_price = close
self._highest_since_entry = close
self._trailing_active = False
elif is_biggest and is_bearish and rsi_divergence < 0:
self.SellMarket()
self._entry_price = close
self._lowest_since_entry = close
self._trailing_active = False
elif self.Position > 0 and self._entry_price > 0:
if close > self._highest_since_entry:
self._highest_since_entry = close
profit_pct = (close - self._entry_price) / self._entry_price * 100.0
if not self._trailing_active and profit_pct >= ts_pct:
self._trailing_active = True
if self._trailing_active:
stop = self._highest_since_entry * (1.0 - td_pct / 100.0)
if close <= stop:
self.SellMarket()
self._entry_price = 0.0
self._trailing_active = False
else:
stop = self._entry_price * (1.0 - sl_pct / 100.0)
if close <= stop:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0 and self._entry_price > 0:
if close < self._lowest_since_entry:
self._lowest_since_entry = close
profit_pct = (self._entry_price - close) / self._entry_price * 100.0
if not self._trailing_active and profit_pct >= ts_pct:
self._trailing_active = True
if self._trailing_active:
stop = self._lowest_since_entry * (1.0 + td_pct / 100.0)
if close >= stop:
self.BuyMarket()
self._entry_price = 0.0
self._trailing_active = False
else:
stop = self._entry_price * (1.0 + sl_pct / 100.0)
if close >= stop:
self.BuyMarket()
self._entry_price = 0.0
def CreateClone(self):
return big_candle_rsi_divergence_strategy()