RSI Histogram Strategy
This strategy uses the Relative Strength Index (RSI) histogram to detect reversals when the oscillator leaves extreme zones. The histogram colors the RSI value based on two thresholds: a high level marking the overbought area and a low level marking the oversold area. When the color changes from green (overbought) to gray or red, the strategy closes short positions and opens a long position. When the color changes from red (oversold) to gray or green, it closes long positions and opens a short position.
The implementation is built with the high-level StockSharp API and subscribes to candle data of a selected timeframe. An RSI indicator processes the candles and generates signals whenever its value exits the defined zones. Optional parameters allow enabling or disabling entries and exits for each side separately.
The strategy is meant for educational purposes and demonstrates how to convert an MQL expert advisor to the StockSharp framework.
Details
- Entry Criteria:
- Long: Previous bar was above the high level and the last bar moved below it.
- Short: Previous bar was below the low level and the last bar moved above it.
- Long/Short: Both sides.
- Exit Criteria:
- Opposite signal closes the current position if allowed.
- Stops: No built-in stops; the
StartProtection framework is prepared for adding them.
- Default Values:
RSI period = 14
High level = 60
Low level = 40
Timeframe = 4 hours
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: Single
- Stops: Optional
- Complexity: Simple
- Timeframe: Medium-term
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Moderate
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on RSI histogram color changes.
/// Opens long when RSI leaves the overbought zone.
/// Opens short when RSI leaves the oversold zone.
/// </summary>
public class RsiHistogramStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private readonly StrategyParam<DataType> _candleType;
private int _prevClass = -1;
private int _prevPrevClass = -1;
/// <summary>
/// RSI period length.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Overbought threshold.
/// </summary>
public decimal HighLevel
{
get => _highLevel.Value;
set => _highLevel.Value = value;
}
/// <summary>
/// Oversold threshold.
/// </summary>
public decimal LowLevel
{
get => _lowLevel.Value;
set => _lowLevel.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyPosOpen
{
get => _buyPosOpen.Value;
set => _buyPosOpen.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellPosOpen
{
get => _sellPosOpen.Value;
set => _sellPosOpen.Value = value;
}
/// <summary>
/// Allow closing long positions on opposite signal.
/// </summary>
public bool BuyPosClose
{
get => _buyPosClose.Value;
set => _buyPosClose.Value = value;
}
/// <summary>
/// Allow closing short positions on opposite signal.
/// </summary>
public bool SellPosClose
{
get => _sellPosClose.Value;
set => _sellPosClose.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public RsiHistogramStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetDisplay("RSI Period", "Length of the RSI indicator", "RSI")
.SetGreaterThanZero()
.SetOptimize(5, 30, 1);
_highLevel = Param(nameof(HighLevel), 60m)
.SetDisplay("High Level", "Overbought threshold", "RSI")
.SetOptimize(55m, 70m, 5m);
_lowLevel = Param(nameof(LowLevel), 40m)
.SetDisplay("Low Level", "Oversold threshold", "RSI")
.SetOptimize(30m, 45m, 5m);
_buyPosOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Enable Buy Entry", "Allow long entries when signal appears", "Trading");
_sellPosOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Enable Sell Entry", "Allow short entries when signal appears", "Trading");
_buyPosClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Close Buy Positions", "Allow closing longs on opposite signal", "Trading");
_sellPosClose = Param(nameof(SellPosClose), true)
.SetDisplay("Close Sell Positions", "Allow closing shorts on opposite signal", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for RSI calculation", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClass = -1;
_prevPrevClass = -1;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, (candle, rsiValue) =>
{
if (candle.State != CandleStates.Finished)
return;
var currentClass = rsiValue > HighLevel ? 0 : rsiValue < LowLevel ? 2 : 1;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevPrevClass = _prevClass;
_prevClass = currentClass;
return;
}
if (_prevPrevClass == 0 && _prevClass > 0)
{
if (SellPosClose && Position < 0)
BuyMarket();
if (BuyPosOpen && Position <= 0)
BuyMarket();
}
else if (_prevPrevClass == 2 && _prevClass < 2)
{
if (BuyPosClose && Position > 0)
SellMarket();
if (SellPosOpen && Position >= 0)
SellMarket();
}
_prevPrevClass = _prevClass;
_prevClass = currentClass;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
}
}
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 rsi_histogram_strategy(Strategy):
def __init__(self):
super(rsi_histogram_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14).SetDisplay("RSI Period", "Length of the RSI indicator", "RSI")
self._high_level = self.Param("HighLevel", 60.0).SetDisplay("High Level", "Overbought threshold", "RSI")
self._low_level = self.Param("LowLevel", 40.0).SetDisplay("Low Level", "Oversold threshold", "RSI")
self._buy_pos_open = self.Param("BuyPosOpen", True).SetDisplay("Enable Buy Entry", "Allow long entries when signal appears", "Trading")
self._sell_pos_open = self.Param("SellPosOpen", True).SetDisplay("Enable Sell Entry", "Allow short entries when signal appears", "Trading")
self._buy_pos_close = self.Param("BuyPosClose", True).SetDisplay("Close Buy Positions", "Allow closing longs on opposite signal", "Trading")
self._sell_pos_close = self.Param("SellPosClose", True).SetDisplay("Close Sell Positions", "Allow closing shorts on opposite signal", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Timeframe for RSI calculation", "General")
self._prev_class = -1
self._prev_prev_class = -1
@property
def rsi_period(self): return self._rsi_period.Value
@property
def high_level(self): return self._high_level.Value
@property
def low_level(self): return self._low_level.Value
@property
def buy_pos_open(self): return self._buy_pos_open.Value
@property
def sell_pos_open(self): return self._sell_pos_open.Value
@property
def buy_pos_close(self): return self._buy_pos_close.Value
@property
def sell_pos_close(self): return self._sell_pos_close.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(rsi_histogram_strategy, self).OnReseted()
self._prev_class = -1
self._prev_prev_class = -1
def OnStarted2(self, time):
super(rsi_histogram_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_period
subscription = self.SubscribeCandles(self.candle_type)
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: return
rv = float(rsi_value)
hl = float(self.high_level)
ll = float(self.low_level)
if rv > hl: current_class = 0
elif rv < ll: current_class = 2
else: current_class = 1
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_prev_class = self._prev_class
self._prev_class = current_class
return
if self._prev_prev_class == 0 and self._prev_class > 0:
if self.sell_pos_close and self.Position < 0:
self.BuyMarket()
if self.buy_pos_open and self.Position <= 0:
self.BuyMarket()
elif self._prev_prev_class == 2 and self._prev_class < 2:
if self.buy_pos_close and self.Position > 0:
self.SellMarket()
if self.sell_pos_open and self.Position >= 0:
self.SellMarket()
self._prev_prev_class = self._prev_class
self._prev_class = current_class
def CreateClone(self): return rsi_histogram_strategy()