Renko Line Break vs RSI Strategy
This strategy recreates the "RenkoLineBreak vs RSI" MetaTrader expert using the StockSharp high level API. It combines Renko trend detection with an RSI pullback filter and executes trades through pending stop orders located around a three-candle price structure.
Details
- Entry Criteria:
- Long: Renko trend stays bullish and the RSI falls to or below
50 - RsiShift. A buy stop is placed at the high of the candle from three bars ago plusIndentFromHighLow. - Short: Renko trend stays bearish and the RSI rises to or above
50 + RsiShift. A sell stop is placed at the low of the candle from three bars ago minusIndentFromHighLow. - Pending orders are cancelled whenever the Renko trend switches direction (
ToUp/ToDown).
- Long: Renko trend stays bullish and the RSI falls to or below
- Long/Short: Both.
- Exit Criteria:
- Market exits when the opposite Renko transition appears (
ToDownfor longs,ToUpfor shorts). - RSI crosses back through the midpoint (
50 ± RsiShift). - Candle ranges hitting the planned stop-loss or take-profit levels.
- Market exits when the opposite Renko transition appears (
- Stops:
- Stop-loss is anchored to the extreme of the last three candles plus
IndentFromHighLow. - Take-profit is
TakeProfitprice units away from the intended entry (optional when set to zero).
- Stop-loss is anchored to the extreme of the last three candles plus
- Default Values:
BoxSize= 500m.RsiPeriod= 4.RsiShift= 20m.TakeProfit= 1000m.IndentFromHighLow= 50m.Volume= 1m.CandleType= 5-minute time frame.
- Filters:
- Category: Trend Following.
- Direction: Both.
- Indicators: Renko, RSI.
- Stops: Hard stop & take profit.
- Complexity: Intermediate.
- Timeframe: Hybrid (Renko + time candles).
- Seasonality: No.
- Neural networks: No.
- Divergence: No.
- Risk level: Moderate.
How It Works
- A Renko subscription (
RenkoCandleMessage) estimates the trend direction. When a Renko brick flips direction, the trend state is set toToUporToDownfor one bar to mimic the original indicator behaviour. - Simultaneously, a time-based candle stream feeds the RSI indicator and provides the last three highs/lows used for breakout levels.
- When both Renko trend and RSI conditions align, the strategy registers a stop order (buy or sell). Planned stop-loss and take-profit levels are stored and monitored after the order triggers.
- Upon order execution the stored protection levels become active. Subsequent candles check if price hits the stop or target ranges; if yes, the position is closed at market.
- If momentum fades (RSI crosses back through the midpoint) or the Renko trend changes, the position is closed early.
Indicators Used
- Renko bricks to infer the directional bias and detect transitions between up and down states.
- Relative Strength Index (RSI) to qualify entries by demanding pullbacks against the trend.
Additional Notes
IndentFromHighLowmodels the original expert's buffer that keeps entry and stop orders away from recent highs and lows.TakeProfitcan be set to zero to disable the profit target while leaving the stop-loss logic intact.- The strategy keeps only one pending order at a time and automatically cancels it when market conditions invalidate the setup.
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;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that combines Renko trend detection with RSI pullbacks.
/// Uses a three-bar breakout structure for entries and attaches stop-loss and take-profit levels.
/// </summary>
public class RenkoLineBreakVsRsiStrategy : Strategy
{
private enum TrendStates
{
None,
Up,
Down,
ToUp,
ToDown
}
private readonly StrategyParam<decimal> _boxSize;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiShift;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _indentFromHighLow;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private DataType _renkoType;
private TrendStates _trendState = TrendStates.None;
private bool _renkoHasPrev;
private bool _renkoPrevBull;
private decimal _prevHigh1;
private decimal _prevHigh2;
private decimal _prevHigh3;
private decimal _prevLow1;
private decimal _prevLow2;
private decimal _prevLow3;
private int _historyCount;
private bool? _pendingIsBuy;
private bool _plannedTakeProfitEnabled;
private bool _hasPlannedPrices;
private decimal _plannedEntryPrice;
private decimal _plannedStopPrice;
private decimal _plannedTakeProfitPrice;
private decimal? _activeStopPrice;
private decimal? _activeTakeProfitPrice;
private decimal _lastPosition;
/// <summary>
/// Renko brick size in price units.
/// </summary>
public decimal BoxSize
{
get => _boxSize.Value;
set => _boxSize.Value = value;
}
/// <summary>
/// RSI calculation period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Distance from the RSI midpoint (50) to generate pullback signals.
/// </summary>
public decimal RsiShift
{
get => _rsiShift.Value;
set => _rsiShift.Value = value;
}
/// <summary>
/// Take-profit distance in price units from the planned entry price.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Additional indent applied to breakout and stop-loss levels.
/// </summary>
public decimal IndentFromHighLow
{
get => _indentFromHighLow.Value;
set => _indentFromHighLow.Value = value;
}
/// <summary>
/// Time-based candle type used for RSI and breakout calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="RenkoLineBreakVsRsiStrategy"/> parameters.
/// </summary>
public RenkoLineBreakVsRsiStrategy()
{
_boxSize = Param(nameof(BoxSize), 100m)
.SetGreaterThanZero()
.SetDisplay("Renko Box Size", "Renko brick size in price units", "Renko")
.SetOptimize(100m, 1000m, 100m);
_rsiPeriod = Param(nameof(RsiPeriod), 4)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Relative Strength Index period", "Indicators")
.SetOptimize(2, 20, 1);
_rsiShift = Param(nameof(RsiShift), 10m)
.SetGreaterThanZero()
.SetDisplay("RSI Shift", "Distance from the 50 level to detect pullbacks", "Indicators")
.SetOptimize(10m, 40m, 5m);
_takeProfit = Param(nameof(TakeProfit), 1000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit distance in price units", "Risk Management")
.SetOptimize(200m, 2000m, 200m);
_indentFromHighLow = Param(nameof(IndentFromHighLow), 50m)
.SetGreaterThanZero()
.SetDisplay("Indent", "Indent applied to breakout and stop levels", "Risk Management")
.SetOptimize(10m, 200m, 10m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for RSI and breakouts", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
_renkoType ??= DataType.Create(typeof(RenkoCandleMessage), new Unit(BoxSize));
return [(Security, CandleType), (Security, _renkoType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_renkoType = null;
_trendState = TrendStates.None;
_renkoHasPrev = false;
_renkoPrevBull = false;
_prevHigh1 = 0m;
_prevHigh2 = 0m;
_prevHigh3 = 0m;
_prevLow1 = 0m;
_prevLow2 = 0m;
_prevLow3 = 0m;
_historyCount = 0;
ResetPendingPlan();
ResetActiveTargets();
_lastPosition = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex
{
Length = RsiPeriod
};
_renkoType ??= DataType.Create(typeof(RenkoCandleMessage), new Unit(BoxSize));
var timeSubscription = SubscribeCandles(CandleType);
timeSubscription
.Bind(_rsi, ProcessTimeCandle)
.Start();
var renkoSubscription = SubscribeCandles(_renkoType);
renkoSubscription
.Bind(ProcessRenkoCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, timeSubscription);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessRenkoCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var isBull = candle.ClosePrice > candle.OpenPrice;
var isBear = candle.ClosePrice < candle.OpenPrice;
if (!_renkoHasPrev)
{
// Store the very first renko brick direction and wait for the next one to define a trend state.
_renkoPrevBull = isBull;
_renkoHasPrev = true;
_trendState = TrendStates.None;
return;
}
if (isBull)
{
_trendState = _renkoPrevBull ? TrendStates.Up : TrendStates.ToUp;
_renkoPrevBull = true;
}
else if (isBear)
{
_trendState = _renkoPrevBull ? TrendStates.ToDown : TrendStates.Down;
_renkoPrevBull = false;
}
else
{
// Flat bricks keep the previous trend state.
}
}
private void ProcessTimeCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
var canTrade = true;
var hasRsi = _rsi?.IsFormed == true && rsiValue >= 0m;
CheckPendingActivation();
ManagePosition(candle, rsiValue, hasRsi);
if (canTrade && Position == 0)
{
TryPlaceEntry(rsiValue, hasRsi);
}
else if (!canTrade && Position == 0 && _pendingIsBuy != null)
{
// Cancel pending orders when trading is not allowed.
// CancelActiveOrders - not available
ResetPendingPlan();
}
UpdateHistory(candle);
_lastPosition = Position;
}
private void ManagePosition(ICandleMessage candle, decimal rsiValue, bool hasRsi)
{
var position = Position;
if (position > 0m)
{
// Long position management.
if (_pendingIsBuy != null)
ResetPendingPlan();
if (_activeTakeProfitPrice.HasValue && candle.HighPrice >= _activeTakeProfitPrice.Value)
{
SellMarket();
ResetActiveTargets();
return;
}
if (_activeStopPrice.HasValue && candle.LowPrice <= _activeStopPrice.Value)
{
SellMarket();
ResetActiveTargets();
return;
}
if (_trendState == TrendStates.ToDown)
{
SellMarket();
ResetActiveTargets();
return;
}
if (hasRsi && rsiValue > 50m + RsiShift)
{
SellMarket();
ResetActiveTargets();
}
}
else if (position < 0m)
{
// Short position management.
if (_pendingIsBuy != null)
ResetPendingPlan();
var absPosition = Math.Abs(position);
if (_activeTakeProfitPrice.HasValue && candle.LowPrice <= _activeTakeProfitPrice.Value)
{
BuyMarket();
ResetActiveTargets();
return;
}
if (_activeStopPrice.HasValue && candle.HighPrice >= _activeStopPrice.Value)
{
BuyMarket();
ResetActiveTargets();
return;
}
if (_trendState == TrendStates.ToUp)
{
BuyMarket();
ResetActiveTargets();
return;
}
if (hasRsi && rsiValue < 50m - RsiShift)
{
BuyMarket();
ResetActiveTargets();
}
}
else
{
// No position -> clear active stop/target remnants.
if (_activeStopPrice.HasValue || _activeTakeProfitPrice.HasValue)
ResetActiveTargets();
}
}
private void TryPlaceEntry(decimal rsiValue, bool hasRsi)
{
var effectiveTrend = GetEffectiveTrend();
if (effectiveTrend == TrendStates.ToDown || effectiveTrend == TrendStates.ToUp)
{
if (_pendingIsBuy != null)
{
// CancelActiveOrders - not available
ResetPendingPlan();
}
return;
}
if (_historyCount < 3 || !hasRsi)
return;
var indent = IndentFromHighLow;
var takeProfitDistance = TakeProfit;
if (effectiveTrend == TrendStates.Up && rsiValue <= 50m - RsiShift)
{
var entryPrice = _prevHigh3 + indent;
var stopPrice = Math.Min(_prevLow1, Math.Min(_prevLow2, _prevLow3)) - indent;
if (entryPrice > 0m && stopPrice > 0m && entryPrice > stopPrice)
{
var takeProfitPrice = takeProfitDistance > 0m ? entryPrice + takeProfitDistance : (decimal?)null;
PlacePendingOrder(true, entryPrice, stopPrice, takeProfitPrice);
}
}
else if (effectiveTrend == TrendStates.Down && rsiValue >= 50m + RsiShift)
{
var entryPrice = _prevLow3 - indent;
var stopPrice = Math.Max(_prevHigh1, Math.Max(_prevHigh2, _prevHigh3)) + indent;
if (entryPrice > 0m && stopPrice > 0m && entryPrice < stopPrice)
{
var takeProfitPrice = takeProfitDistance > 0m ? entryPrice - takeProfitDistance : (decimal?)null;
PlacePendingOrder(false, entryPrice, stopPrice, takeProfitPrice);
}
}
}
private TrendStates GetEffectiveTrend()
{
if (_trendState != TrendStates.None)
return _trendState;
if (_historyCount < 3)
return TrendStates.None;
if (_prevHigh1 > _prevHigh2 && _prevHigh2 > _prevHigh3)
return TrendStates.Up;
if (_prevLow1 < _prevLow2 && _prevLow2 < _prevLow3)
return TrendStates.Down;
return TrendStates.None;
}
private void PlacePendingOrder(bool isBuy, decimal entryPrice, decimal stopPrice, decimal? takeProfitPrice)
{
// Avoid duplicate registrations if the pending order already matches the desired levels.
if (_pendingIsBuy == isBuy && _hasPlannedPrices &&
entryPrice == _plannedEntryPrice && stopPrice == _plannedStopPrice &&
((takeProfitPrice == null && !_plannedTakeProfitEnabled) ||
(takeProfitPrice != null && _plannedTakeProfitEnabled && takeProfitPrice.Value == _plannedTakeProfitPrice)))
{
return;
}
CancelActiveOrders();
ResetPendingPlan();
var volume = Volume;
if (isBuy)
{
BuyMarket();
}
else
{
SellMarket();
}
_pendingIsBuy = isBuy;
_hasPlannedPrices = true;
_plannedEntryPrice = entryPrice;
_plannedStopPrice = stopPrice;
_plannedTakeProfitEnabled = takeProfitPrice != null;
_plannedTakeProfitPrice = takeProfitPrice ?? 0m;
}
private void CheckPendingActivation()
{
if (_pendingIsBuy == null || !_hasPlannedPrices)
return;
if (_pendingIsBuy.Value && _lastPosition <= 0m && Position > 0m)
{
ActivatePlannedTargets();
}
else if (!_pendingIsBuy.Value && _lastPosition >= 0m && Position < 0m)
{
ActivatePlannedTargets();
}
}
private void ActivatePlannedTargets()
{
_activeStopPrice = _plannedStopPrice;
_activeTakeProfitPrice = _plannedTakeProfitEnabled ? _plannedTakeProfitPrice : null;
ResetPendingPlan();
}
private void UpdateHistory(ICandleMessage candle)
{
_prevHigh3 = _prevHigh2;
_prevHigh2 = _prevHigh1;
_prevHigh1 = candle.HighPrice;
_prevLow3 = _prevLow2;
_prevLow2 = _prevLow1;
_prevLow1 = candle.LowPrice;
if (_historyCount < 3)
{
_historyCount++;
}
}
private void ResetPendingPlan()
{
_pendingIsBuy = null;
_hasPlannedPrices = false;
_plannedEntryPrice = 0m;
_plannedStopPrice = 0m;
_plannedTakeProfitPrice = 0m;
_plannedTakeProfitEnabled = false;
}
private void ResetActiveTargets()
{
_activeStopPrice = null;
_activeTakeProfitPrice = null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class renko_line_break_vs_rsi_strategy(Strategy):
"""Renko-inspired trend detection with RSI pullbacks using standard candles.
Uses three-bar high/low breakout structure for entries with SL/TP."""
def __init__(self):
super(renko_line_break_vs_rsi_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 4).SetGreaterThanZero().SetDisplay("RSI Period", "RSI lookback", "Indicators")
self._rsi_shift = self.Param("RsiShift", 10.0).SetGreaterThanZero().SetDisplay("RSI Shift", "Distance from 50 for pullbacks", "Indicators")
self._take_profit = self.Param("TakeProfit", 1000.0).SetGreaterThanZero().SetDisplay("Take Profit", "TP distance in price", "Risk")
self._indent = self.Param("Indent", 50.0).SetGreaterThanZero().SetDisplay("Indent", "Indent for breakout levels", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(renko_line_break_vs_rsi_strategy, self).OnReseted()
self._prev_high1 = 0
self._prev_high2 = 0
self._prev_high3 = 0
self._prev_low1 = 0
self._prev_low2 = 0
self._prev_low3 = 0
self._history_count = 0
self._active_stop = None
self._active_tp = None
self._trend = 0 # 1=up, -1=down, 0=none
self._prev_bull = None
def OnStarted2(self, time):
super(renko_line_break_vs_rsi_strategy, self).OnStarted2(time)
self._prev_high1 = 0
self._prev_high2 = 0
self._prev_high3 = 0
self._prev_low1 = 0
self._prev_low2 = 0
self._prev_low3 = 0
self._history_count = 0
self._active_stop = None
self._active_tp = None
self._trend = 0
self._prev_bull = None
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(rsi, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, rsi)
self.DrawOwnTrades(area)
def OnProcess(self, candle, rsi_val):
if candle.State != CandleStates.Finished:
return
is_bull = candle.ClosePrice > candle.OpenPrice
# Update trend based on consecutive candle direction
if self._prev_bull is not None:
if is_bull:
self._trend = 1 if self._prev_bull else 2 # 2 = ToUp
elif candle.ClosePrice < candle.OpenPrice:
self._trend = -2 if self._prev_bull else -1 # -2 = ToDown
self._prev_bull = is_bull
# Manage existing positions
if self.Position > 0:
if self._active_tp is not None and candle.HighPrice >= self._active_tp:
self.SellMarket()
self._active_stop = None
self._active_tp = None
elif self._active_stop is not None and candle.LowPrice <= self._active_stop:
self.SellMarket()
self._active_stop = None
self._active_tp = None
elif self._trend == -2: # ToDown
self.SellMarket()
self._active_stop = None
self._active_tp = None
elif rsi_val > 50 + self._rsi_shift.Value:
self.SellMarket()
self._active_stop = None
self._active_tp = None
elif self.Position < 0:
if self._active_tp is not None and candle.LowPrice <= self._active_tp:
self.BuyMarket()
self._active_stop = None
self._active_tp = None
elif self._active_stop is not None and candle.HighPrice >= self._active_stop:
self.BuyMarket()
self._active_stop = None
self._active_tp = None
elif self._trend == 2: # ToUp
self.BuyMarket()
self._active_stop = None
self._active_tp = None
elif rsi_val < 50 - self._rsi_shift.Value:
self.BuyMarket()
self._active_stop = None
self._active_tp = None
# New entries
if self.Position == 0 and self._history_count >= 3:
indent = self._indent.Value
tp_dist = self._take_profit.Value
eff_trend = self._get_effective_trend()
if eff_trend == 1 and rsi_val <= 50 - self._rsi_shift.Value:
entry = self._prev_high3 + indent
stop = min(self._prev_low1, self._prev_low2, self._prev_low3) - indent
if entry > 0 and stop > 0 and entry > stop:
self.BuyMarket()
self._active_stop = stop
self._active_tp = entry + tp_dist if tp_dist > 0 else None
elif eff_trend == -1 and rsi_val >= 50 + self._rsi_shift.Value:
entry = self._prev_low3 - indent
stop = max(self._prev_high1, self._prev_high2, self._prev_high3) + indent
if entry > 0 and stop > 0 and entry < stop:
self.SellMarket()
self._active_stop = stop
self._active_tp = entry - tp_dist if tp_dist > 0 else None
# Update history
self._prev_high3 = self._prev_high2
self._prev_high2 = self._prev_high1
self._prev_high1 = float(candle.HighPrice)
self._prev_low3 = self._prev_low2
self._prev_low2 = self._prev_low1
self._prev_low1 = float(candle.LowPrice)
if self._history_count < 3:
self._history_count += 1
def _get_effective_trend(self):
if self._trend == 1 or self._trend == -1:
return self._trend
if self._history_count >= 3:
if self._prev_high1 > self._prev_high2 and self._prev_high2 > self._prev_high3:
return 1
if self._prev_low1 < self._prev_low2 and self._prev_low2 < self._prev_low3:
return -1
return 0
def CreateClone(self):
return renko_line_break_vs_rsi_strategy()