Graph Style 4th Dimension RSI
Strategy combining price change with RSI levels.
Testing indicates an average annual return of about 80%. It performs well in volatile markets.
The strategy checks the direction of the last price change together with RSI extremes. It opens a position when RSI exits the overbought/oversold zones and the recent price change confirms the move. Positions are closed when RSI returns to the middle area or an opposite signal appears.
Details
- Entry Criteria: Price change direction with RSI extreme.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal or RSI back to mid.
- Stops: Percent stop loss.
- Default Values:
RsiPeriod= 14OverboughtLevel= 70mOversoldLevel= 30mStopLossPercent= 1mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Momentum
- Direction: Both
- Indicators: RSI
- Stops: Percent
- Complexity: Basic
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- 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>
/// Graph Style 4th Dimension RSI strategy.
/// Goes long when RSI leaves the oversold zone upward on a candle that closed higher than the previous one, and short when RSI
/// leaves the overbought zone downward on a candle that closed lower, reversing an opposite position. A long closes when RSI
/// returns to the middle level 50 from below and a short when it returns from above; a percent stop limits the loss.
/// </summary>
public class GraphStyle4thDimensionRSIStrategy : Strategy
{
private const decimal _middleLevel = 50m;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevRsi;
private decimal? _prevClose;
/// <summary>
/// RSI period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public GraphStyle4thDimensionRSIStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period of RSI", "Indicators");
_overboughtLevel = Param(nameof(OverboughtLevel), 70m)
.SetDisplay("Overbought", "RSI overbought level", "Indicators");
_oversoldLevel = Param(nameof(OversoldLevel), 30m)
.SetDisplay("Oversold", "RSI oversold level", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 1m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_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();
_prevRsi = null;
_prevClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = null;
_prevClose = null;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
var prevRsi = _prevRsi;
var prevClose = _prevClose;
_prevRsi = rsi;
_prevClose = candle.ClosePrice;
if (prevRsi is not decimal lastRsi || prevClose is not decimal lastClose)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var longSignal = lastRsi < OversoldLevel && rsi >= OversoldLevel && close > lastClose;
var shortSignal = lastRsi > OverboughtLevel && rsi <= OverboughtLevel && close < lastClose;
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && rsi >= _middleLevel)
SellMarket(Position);
else if (Position < 0 && rsi <= _middleLevel)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
MIDDLE_LEVEL = 50.0
class graph_style_4th_dimension_rsi_strategy(Strategy):
"""
Graph Style 4th Dimension RSI strategy.
Goes long when RSI leaves the oversold zone upward on a candle that closed higher than the previous one, and short when RSI
leaves the overbought zone downward on a candle that closed lower, reversing an opposite position. A long closes when RSI
returns to the middle level 50 from below and a short when it returns from above; a percent stop limits the loss.
"""
def __init__(self):
super(graph_style_4th_dimension_rsi_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero().SetDisplay("RSI Period", "Period of RSI", "Indicators")
self._overbought_level = self.Param("OverboughtLevel", 70.0).SetDisplay("Overbought", "RSI overbought level", "Indicators")
self._oversold_level = self.Param("OversoldLevel", 30.0).SetDisplay("Oversold", "RSI oversold level", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_rsi = None
self._prev_close = None
def OnReseted(self):
super(graph_style_4th_dimension_rsi_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(graph_style_4th_dimension_rsi_strategy, self).OnStarted2(time)
self._reset_state()
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rsi = float(rsi_value)
close = float(candle.ClosePrice)
last_rsi = self._prev_rsi
last_close = self._prev_close
self._prev_rsi = rsi
self._prev_close = close
if last_rsi is None or last_close is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
oversold = float(self._oversold_level.Value)
overbought = float(self._overbought_level.Value)
long_signal = last_rsi < oversold and rsi >= oversold and close > last_close
short_signal = last_rsi > overbought and rsi <= overbought and close < last_close
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and rsi >= MIDDLE_LEVEL:
self.SellMarket(self.Position)
elif self.Position < 0 and rsi <= MIDDLE_LEVEL:
self.BuyMarket(-self.Position)
def CreateClone(self):
return graph_style_4th_dimension_rsi_strategy()