Стратегия Renko Line Break vs RSI
Стратегия повторяет логику советника «RenkoLineBreak vs RSI» из MetaTrader с использованием высокоуровневого API StockSharp. Для определения направления используется поток кирпичей Renko, фильтрация входов выполняется по RSI, а заявки выставляются в виде отложенных стопов на основе структуры из трёх свечей.
Подробности
- Условия входа:
- Покупка: тренд Renko остаётся восходящим, а RSI опускается до уровня
50 - RsiShiftили ниже. Выставляется стоп-заявка на покупку по максимуму свечи три бара назад плюсIndentFromHighLow. - Продажа: тренд Renko остаётся нисходящим, а RSI поднимается до уровня
50 + RsiShiftили выше. Выставляется стоп-заявка на продажу по минимуму свечи три бара назад минусIndentFromHighLow. - Отложенные заявки снимаются при появлении переходных состояний Renko (
ToUp/ToDown).
- Покупка: тренд Renko остаётся восходящим, а RSI опускается до уровня
- Направление: Покупка и продажа.
- Условия выхода:
- Сигнал к закрытию при обратном переходе тренда Renko (
ToDownдля лонга,ToUpдля шорта). - RSI возвращается через середину диапазона (
50 ± RsiShift). - Достижение свечой уровней запланированного стоп-лосса или тейк-профита.
- Сигнал к закрытию при обратном переходе тренда Renko (
- Стопы:
- Стоп-лосс привязан к экстремуму последних трёх свечей с поправкой
IndentFromHighLow. - Тейк-профит отстоит от предполагаемой цены входа на
TakeProfit(можно отключить, установив 0).
- Стоп-лосс привязан к экстремуму последних трёх свечей с поправкой
- Значения по умолчанию:
BoxSize= 500m.RsiPeriod= 4.RsiShift= 20m.TakeProfit= 1000m.IndentFromHighLow= 50m.Volume= 1m.CandleType= таймфрейм 5 минут.
- Фильтры:
- Категория: Следование за трендом.
- Направление: Обе стороны.
- Индикаторы: Renko, RSI.
- Стопы: Жёсткий стоп и тейк-профит.
- Сложность: Средняя.
- Таймфрейм: Гибрид (Renko + время).
- Сезонность: Нет.
- Нейросети: Нет.
- Дивергенции: Нет.
- Уровень риска: Средний.
Как это работает
- Подписка на
RenkoCandleMessageопределяет текущее направление. При смене кирпича тренд переводится в состояниеToUpилиToDown, что имитирует оригинальный индикатор. - Одновременно временные свечи рассчитывают RSI и предоставляют максимум/минимум последних трёх баров для расчёта уровней пробоя.
- Когда условия по Renko и RSI совпадают, регистрируется соответствующая стоп-заявка. Пара стоп-лосс/тейк-профит сохраняется и начинает отслеживаться после срабатывания заявки.
- После исполнения ордера уровни защиты активируются, и каждую новую свечу стратегия проверяет, достигла ли цена стопа или цели; при достижении позиция закрывается по рынку.
- Если импульс ослабевает (RSI возвращается за середину) или Renko сигнализирует разворот, позиция закрывается досрочно.
Используемые индикаторы
- Кирпичи Renko — определяют направление и переходные состояния тренда.
- Индекс относительной силы (RSI) — фильтрует входы, требуя отката против тренда.
Дополнительные замечания
IndentFromHighLowповторяет буфер оригинального советника, удерживая заявки на расстоянии от ближайших экстремумов.- При значении
TakeProfit = 0тейк-профит отключается, но стоп-лосс продолжает работать. - Одновременно активна лишь одна отложенная заявка; при изменении условий она автоматически отменяется.
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()