Super Simple RSI Engulfing Strategy
This strategy replicates the original SSEATwRSI MetaTrader expert advisor in StockSharp. It monitors finished candles and calculates a 7-period RSI on the candle high. A trade is triggered only when the RSI reaches an extreme and the previous two bars form a clean engulfing reversal.
A long setup requires the RSI to move above the overbought threshold while a bearish candle is fully engulfed by the next bullish candle. A short setup mirrors this logic using an oversold RSI reading and a bullish-to-bearish engulfing pattern. Position size is fixed by the Volume parameter, but any opposite exposure is flattened before opening a new trade.
Once in the market, the strategy keeps watching the global profit and loss. If floating PnL reaches the configured profit goal (in account currency) or drops below the allowed loss, it closes the entire position. There are no additional trailing stops; trades are managed solely by the pattern reversal and the account-level thresholds.
Details
- Entry Criteria:
- Long: RSI on highs >
OverboughtLevel and the last candle engulfs a bearish bar from two bars ago while price closes above that older open.
- Short: RSI on highs <
OversoldLevel and the last candle engulfs a bullish bar from two bars ago while price closes below that older open.
- Long/Short: Both.
- Exit Criteria:
- Account PnL ≥
ProfitGoal → flatten.
- Account PnL ≤
-MaxLoss → flatten.
- Opposite signal automatically offsets the previous position when a new order is placed.
- Stops: Currency-based take-profit and max-loss checks derived from total strategy PnL.
- Filters:
- RSI calculated on the candle high to emphasise exhaustion moves.
- Confirmation via a two-bar engulfing reversal.
Parameters
Volume = 0.1 – Order size in contracts. Existing exposure is offset before opening a new trade.
ProfitGoal = 190 – Currency profit target that forces a flat position once reached.
MaxLoss = 10 – Maximum allowed currency loss before the strategy closes all positions. The check uses -MaxLoss internally.
RsiPeriod = 7 – Averaging length of the RSI indicator.
RsiPrice = High – Price source used for the RSI calculation.
OverboughtLevel = 88 – RSI level that must be exceeded before taking a long reversal.
OversoldLevel = 37 – RSI level that must be undershot before taking a short reversal.
CandleType = 1-hour candles by default; adjust to match the timeframe of the original chart.
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 filter combined with engulfing candle pattern taken from the SSEATwRSI expert advisor.
/// </summary>
public class SuperSimpleRsiEngulfingStrategy : Strategy
{
public enum CandlePrices
{
Open,
High,
Low,
Close,
Median,
Typical,
Weighted
}
private readonly StrategyParam<decimal> _profitGoal;
private readonly StrategyParam<decimal> _maxLoss;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<CandlePrices> _rsiPrice;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi = null!;
private decimal? _prevOpen;
private decimal? _prevClose;
private decimal? _prevPrevOpen;
private decimal? _prevPrevClose;
/// <summary>
/// Currency profit target that forces a flatten.
/// </summary>
public decimal ProfitGoal
{
get => _profitGoal.Value;
set => _profitGoal.Value = value;
}
/// <summary>
/// Maximum allowed currency loss before closing positions.
/// </summary>
public decimal MaxLoss
{
get => _maxLoss.Value;
set => _maxLoss.Value = value;
}
/// <summary>
/// RSI averaging period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Price source used by the RSI indicator.
/// </summary>
public CandlePrices RsiPrice
{
get => _rsiPrice.Value;
set => _rsiPrice.Value = value;
}
/// <summary>
/// RSI level considered overbought.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// RSI level considered oversold.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="SuperSimpleRsiEngulfingStrategy"/>.
/// </summary>
public SuperSimpleRsiEngulfingStrategy()
{
_profitGoal = Param(nameof(ProfitGoal), 190m)
.SetGreaterThanZero()
.SetDisplay("Profit Goal", "Currency profit target to flatten", "Risk");
_maxLoss = Param(nameof(MaxLoss), 10m)
.SetGreaterThanZero()
.SetDisplay("Max Loss", "Maximum currency drawdown before flattening", "Risk");
_rsiPeriod = Param(nameof(RsiPeriod), 7)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI averaging period", "Indicators");
_rsiPrice = Param(nameof(RsiPrice), CandlePrices.High)
.SetDisplay("RSI Price", "Price source for RSI", "Indicators");
_overboughtLevel = Param(nameof(OverboughtLevel), 88m)
.SetDisplay("Overbought Level", "RSI threshold for bullish reversals", "Indicators");
_oversoldLevel = Param(nameof(OversoldLevel), 37m)
.SetDisplay("Oversold Level", "RSI threshold for bearish reversals", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle series to process", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null!;
_prevOpen = null;
_prevClose = null;
_prevPrevOpen = null;
_prevPrevClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = GetPrice(candle, RsiPrice);
var rsiResult = _rsi.Process(new DecimalIndicatorValue(_rsi, price, candle.OpenTime) { IsFinal = true });
if (!_rsi.IsFormed)
{
UpdateHistory(candle);
return;
}
var rsiValue = rsiResult.ToDecimal();
if (_prevOpen is decimal prevOpen &&
_prevClose is decimal prevClose &&
_prevPrevOpen is decimal prevPrevOpen &&
_prevPrevClose is decimal prevPrevClose)
{
// Detect the two-candle engulfing pattern from the previous bars.
var bullishEngulfing = prevPrevOpen > prevPrevClose &&
prevOpen < prevClose &&
prevPrevOpen < prevClose;
var bearishEngulfing = prevPrevOpen < prevPrevClose &&
prevOpen > prevClose &&
prevPrevOpen > prevClose;
// Only enter long if RSI indicates overbought exhaustion and pattern flips to bullish.
var longSignal = rsiValue > OverboughtLevel && bullishEngulfing && Position <= 0m;
// Only enter short if RSI indicates oversold exhaustion and pattern flips to bearish.
var shortSignal = rsiValue < OversoldLevel && bearishEngulfing && Position >= 0m;
if (longSignal)
{
BuyMarket();
}
else if (shortSignal)
{
SellMarket();
}
}
if (Position != 0m)
{
// Flatten the position once floating PnL reaches the configured thresholds.
var totalPnL = PnL;
if (totalPnL >= ProfitGoal || totalPnL <= -MaxLoss)
ClosePosition();
}
UpdateHistory(candle);
}
private void ClosePosition()
{
if (Position > 0m)
SellMarket();
else if (Position < 0m)
BuyMarket();
}
private void UpdateHistory(ICandleMessage candle)
{
// Shift the last two completed candles so pattern checks use historical data only.
_prevPrevOpen = _prevOpen;
_prevPrevClose = _prevClose;
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
}
private static decimal GetPrice(ICandleMessage candle, CandlePrices price)
{
// Support different RSI inputs without duplicating indicator logic.
return price switch
{
CandlePrices.Open => candle.OpenPrice,
CandlePrices.High => candle.HighPrice,
CandlePrices.Low => candle.LowPrice,
CandlePrices.Close => candle.ClosePrice,
CandlePrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
CandlePrices.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
CandlePrices.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
_ => candle.ClosePrice,
};
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from indicator_extensions import *
class super_simple_rsi_engulfing_strategy(Strategy):
"""RSI filter combined with engulfing candle pattern for reversals."""
def __init__(self):
super(super_simple_rsi_engulfing_strategy, self).__init__()
self._profit_goal = self.Param("ProfitGoal", 190.0) \
.SetGreaterThanZero() \
.SetDisplay("Profit Goal", "Currency profit target to flatten", "Risk")
self._max_loss = self.Param("MaxLoss", 10.0) \
.SetGreaterThanZero() \
.SetDisplay("Max Loss", "Maximum currency drawdown before flattening", "Risk")
self._rsi_period = self.Param("RsiPeriod", 7) \
.SetGreaterThanZero() \
.SetDisplay("RSI Period", "RSI averaging period", "Indicators")
# RsiPrice: 0=Open, 1=High, 2=Low, 3=Close, 4=Median, 5=Typical, 6=Weighted
self._rsi_price = self.Param("RsiPrice", 1) \
.SetDisplay("RSI Price", "Price source for RSI (0=O,1=H,2=L,3=C,4=Med,5=Typ,6=Wt)", "Indicators")
self._overbought_level = self.Param("OverboughtLevel", 88.0) \
.SetDisplay("Overbought Level", "RSI threshold for bullish reversals", "Indicators")
self._oversold_level = self.Param("OversoldLevel", 37.0) \
.SetDisplay("Oversold Level", "RSI threshold for bearish reversals", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle series to process", "General")
self._prev_open = None
self._prev_close = None
self._prev_prev_open = None
self._prev_prev_close = None
@property
def ProfitGoal(self):
return float(self._profit_goal.Value)
@property
def MaxLoss(self):
return float(self._max_loss.Value)
@property
def RsiPeriod(self):
return int(self._rsi_period.Value)
@property
def RsiPrice(self):
return int(self._rsi_price.Value)
@property
def OverboughtLevel(self):
return float(self._overbought_level.Value)
@property
def OversoldLevel(self):
return float(self._oversold_level.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(super_simple_rsi_engulfing_strategy, self).OnStarted2(time)
self._prev_open = None
self._prev_close = None
self._prev_prev_open = None
self._prev_prev_close = None
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._rsi)
self.DrawOwnTrades(area)
def _get_price(self, candle):
price_type = self.RsiPrice
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
c = float(candle.ClosePrice)
o = float(candle.OpenPrice)
if price_type == 0:
return o
elif price_type == 1:
return h
elif price_type == 2:
return lo
elif price_type == 3:
return c
elif price_type == 4:
return (h + lo) / 2.0
elif price_type == 5:
return (h + lo + c) / 3.0
elif price_type == 6:
return (h + lo + 2.0 * c) / 4.0
return c
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = self._get_price(candle)
rsi_result = process_float(self._rsi, Decimal(price), candle.ServerTime, True)
if not self._rsi.IsFormed:
self._update_history(candle)
return
rsi_value = float(rsi_result.Value)
if (self._prev_open is not None and self._prev_close is not None
and self._prev_prev_open is not None and self._prev_prev_close is not None):
prev_open = self._prev_open
prev_close = self._prev_close
prev_prev_open = self._prev_prev_open
prev_prev_close = self._prev_prev_close
bullish_engulfing = (prev_prev_open > prev_prev_close
and prev_open < prev_close
and prev_prev_open < prev_close)
bearish_engulfing = (prev_prev_open < prev_prev_close
and prev_open > prev_close
and prev_prev_open > prev_close)
long_signal = rsi_value > self.OverboughtLevel and bullish_engulfing and self.Position <= 0
short_signal = rsi_value < self.OversoldLevel and bearish_engulfing and self.Position >= 0
if long_signal:
self.BuyMarket()
elif short_signal:
self.SellMarket()
if self.Position != 0:
total_pnl = float(self.PnL)
if total_pnl >= self.ProfitGoal or total_pnl <= -self.MaxLoss:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
self._update_history(candle)
def _update_history(self, candle):
self._prev_prev_open = self._prev_open
self._prev_prev_close = self._prev_close
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
def OnReseted(self):
super(super_simple_rsi_engulfing_strategy, self).OnReseted()
self._prev_open = None
self._prev_close = None
self._prev_prev_open = None
self._prev_prev_close = None
def CreateClone(self):
return super_simple_rsi_engulfing_strategy()