Genie RSI Strategy
This strategy trades overbought and oversold reversals using the Relative Strength Index (RSI). When RSI rises above 80 the strategy opens a short position; when RSI falls below 20 it opens a long position. Optional take profit and trailing stop levels manage risk after entry.
The strategy is designed for oscillating markets where price frequently moves between support and resistance. It works on any timeframe, as defined by the CandleType parameter.
Details
- Entry Criteria
- Long: RSI value crosses below 20 on a finished candle and no position is open.
- Short: RSI value crosses above 80 on a finished candle and no position is open.
- Exit Criteria
- Long: RSI rises above 80, price reaches the take profit distance, or price touches the trailing stop level.
- Short: RSI falls below 20, price reaches the take profit distance, or price touches the trailing stop level.
- Indicators: RSI.
- Parameters:
RSI Period– length of the RSI indicator.Take Profit– distance in price units for profit target.Trailing Stop– distance in price units for trailing stop.Candle Type– timeframe of processed candles.
- Position Management: Uses market orders for entries and exits. Trailing stop is recalculated on each finished candle.
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>
/// RSI-based reversal strategy using fixed thresholds.
/// Sells when RSI rises above overbought level and buys when RSI falls below oversold level.
/// Includes optional take profit and trailing stop management.
/// </summary>
public class GenieRsiStrategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _trailingStop;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _entryPrice;
private decimal _trailingLevel;
private bool _isLong;
private decimal? _prevRsi;
private int _cooldownRemaining;
/// <summary>
/// Take profit distance in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Trailing stop distance in price units.
/// </summary>
public decimal TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// RSI calculation period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of completed candles to wait after a position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public GenieRsiStrategy()
{
_takeProfit = Param(nameof(TakeProfit), 500m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit distance in price units", "Risk Management")
;
_trailingStop = Param(nameof(TrailingStop), 200m)
.SetNotNegative()
.SetDisplay("Trailing Stop", "Trailing stop distance in price units", "Risk Management");
_rsiPeriod = Param(nameof(RsiPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period for RSI indicator", "Indicators")
.SetOptimize(5, 30, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_cooldownBars = Param(nameof(CooldownBars), 4)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Risk Management");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0m;
_trailingLevel = 0m;
_isLong = false;
_prevRsi = null;
_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);
DrawIndicator(area, rsi);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal rsi)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var price = candle.ClosePrice;
var crossedDown = _prevRsi is decimal prevRsi1 && prevRsi1 >= 20m && rsi < 20m;
var crossedUp = _prevRsi is decimal prevRsi2 && prevRsi2 <= 80m && rsi > 80m;
_prevRsi = rsi;
// Entry logic when flat
if (Position == 0 && _cooldownRemaining == 0)
{
if (crossedUp)
{
SellMarket();
_entryPrice = price;
_trailingLevel = price + TrailingStop;
_isLong = false;
_cooldownRemaining = CooldownBars;
}
else if (crossedDown)
{
BuyMarket();
_entryPrice = price;
_trailingLevel = price - TrailingStop;
_isLong = true;
_cooldownRemaining = CooldownBars;
}
return;
}
// Manage open position
if (_isLong)
{
// Update trailing stop for long position
if (TrailingStop > 0)
{
var newLevel = price - TrailingStop;
if (newLevel > _trailingLevel)
_trailingLevel = newLevel;
if (price <= _trailingLevel)
{
SellMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
return;
}
}
// Take profit for long position
if (TakeProfit > 0 && price - _entryPrice >= TakeProfit)
{
SellMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
return;
}
// Exit if RSI indicates overbought
if (crossedUp)
{
SellMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
}
}
else
{
// Update trailing stop for short position
if (TrailingStop > 0)
{
var newLevel = price + TrailingStop;
if (_trailingLevel == 0m || newLevel < _trailingLevel)
_trailingLevel = newLevel;
if (price >= _trailingLevel)
{
BuyMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
return;
}
}
// Take profit for short position
if (TakeProfit > 0 && _entryPrice - price >= TakeProfit)
{
BuyMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
return;
}
// Exit if RSI indicates oversold
if (crossedDown)
{
BuyMarket();
_entryPrice = 0m;
_cooldownRemaining = CooldownBars;
}
}
}
}
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 genie_rsi_strategy(Strategy):
"""
RSI-based reversal strategy.
Sells when RSI crosses above 80 (overbought), buys when RSI crosses below 20 (oversold).
Includes trailing stop, take profit, and RSI exit signals.
"""
def __init__(self):
super(genie_rsi_strategy, self).__init__()
self._take_profit = self.Param("TakeProfit", 500.0) \
.SetDisplay("Take Profit", "Take profit distance in price units", "Risk Management")
self._trailing_stop = self.Param("TrailingStop", 200.0) \
.SetDisplay("Trailing Stop", "Trailing stop distance in price units", "Risk Management")
self._rsi_period = self.Param("RsiPeriod", 15) \
.SetDisplay("RSI Period", "Period for RSI indicator", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after position change", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._entry_price = 0.0
self._trailing_level = 0.0
self._is_long = False
self._prev_rsi = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(genie_rsi_strategy, self).OnReseted()
self._entry_price = 0.0
self._trailing_level = 0.0
self._is_long = False
self._prev_rsi = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(genie_rsi_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
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_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
price = float(candle.ClosePrice)
rsi = float(rsi_val)
crossed_down = self._prev_rsi is not None and self._prev_rsi >= 20.0 and rsi < 20.0
crossed_up = self._prev_rsi is not None and self._prev_rsi <= 80.0 and rsi > 80.0
self._prev_rsi = rsi
if self.Position == 0 and self._cooldown_remaining == 0:
if crossed_up:
self.SellMarket()
self._entry_price = price
self._trailing_level = price + self._trailing_stop.Value
self._is_long = False
self._cooldown_remaining = self._cooldown_bars.Value
elif crossed_down:
self.BuyMarket()
self._entry_price = price
self._trailing_level = price - self._trailing_stop.Value
self._is_long = True
self._cooldown_remaining = self._cooldown_bars.Value
return
if self._is_long:
trailing = self._trailing_stop.Value
if trailing > 0:
new_level = price - trailing
if new_level > self._trailing_level:
self._trailing_level = new_level
if price <= self._trailing_level:
self.SellMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
return
tp = self._take_profit.Value
if tp > 0 and price - self._entry_price >= tp:
self.SellMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
return
if crossed_up:
self.SellMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
else:
trailing = self._trailing_stop.Value
if trailing > 0:
new_level = price + trailing
if self._trailing_level == 0.0 or new_level < self._trailing_level:
self._trailing_level = new_level
if price >= self._trailing_level:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
return
tp = self._take_profit.Value
if tp > 0 and self._entry_price - price >= tp:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
return
if crossed_down:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown_remaining = self._cooldown_bars.Value
def CreateClone(self):
return genie_rsi_strategy()