This strategy is a StockSharp port of the MetaTrader 5 expert advisor e_RP_250. The original system trades reversals detected by
a custom rPoint indicator. Because that indicator is not available inside StockSharp, the conversion recreates the same behaviour
with rolling highest and lowest price trackers. Whenever a new swing high or swing low appears, the strategy reverses the position
and attaches the same stop-loss, take-profit and optional trailing logic as the MQL version.
The original source did not publish verified performance results, so you should perform your own evaluation before deploying the
strategy in production.
Trading logic
Subscribe to candles defined by the CandleType parameter (5-minute candles by default).
Track the highest high and lowest low across the last ReversePoint bars (250 by default).
When the current candle sets a new highest high, close any long position and open a short position.
When the current candle sets a new lowest low, close any short position and open a long position.
Protective stop-loss and take-profit levels are expressed in price points and are reproduced through StartProtection.
Optional trailing stops lock in profits once price moves by the configured number of points.
Only one position is active at any time. The strategy also blocks duplicate orders during the same candle by remembering the last
execution time, replicating the TimeN safeguard from the MQL script.
Parameters
Parameter
Description
TakeProfitPoints
Distance in price points for the take-profit order (default 15). Set to zero to disable automatic profit taking.
StopLossPoints
Distance in price points for the stop-loss order (default 999). Set to zero to trade without a fixed stop.
TrailingStopPoints
Optional trailing stop distance in price points (default 0 disables the trailing logic).
ReversePoint
Number of candles used to detect reversal points. Larger values react slower but filter out noise.
CandleType
Candle aggregation to analyse. Default is a 5-minute time frame but you can switch to any DataType.
Position management
StartProtection applies the same stop-loss and take-profit distances as the MT5 expert.
The trailing stop tracks the most favourable price after entry and exits when price reverts by the configured amount.
Reversal signals from the opposite side immediately close the current position before opening a new one.
Usage notes
Make sure the data source supports the selected candle type, otherwise no signals will be generated.
The strategy relies on decimal prices. Verify that the security's PriceStep property correctly reflects the point value.
Test different ReversePoint values to adapt the breakout sensitivity to the volatility of the traded instrument.
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>
/// Reverse point breakout strategy converted from the e_RP_250 MQL script.
/// </summary>
public class ERp250Strategy : Strategy
{
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<int> _reversePoint;
private readonly StrategyParam<DataType> _candleType;
private Highest _highest;
private Lowest _lowest;
private decimal _latestHighSignal;
private decimal _latestLowSignal;
private decimal _lastExecutedHigh;
private decimal _lastExecutedLow;
private DateTimeOffset? _lastSignalTime;
private decimal? _bestLongPrice;
private decimal? _bestShortPrice;
private decimal _trailingDistance;
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
public int ReversePoint
{
get => _reversePoint.Value;
set => _reversePoint.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public ERp250Strategy()
{
_takeProfitPoints = Param(nameof(TakeProfitPoints), 15m)
.SetDisplay("Take Profit Points", "Take profit distance in price points", "Risk")
;
_stopLossPoints = Param(nameof(StopLossPoints), 999m)
.SetDisplay("Stop Loss Points", "Stop loss distance in price points", "Risk")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 0m)
.SetDisplay("Trailing Stop Points", "Trailing stop distance in price points", "Risk")
;
_reversePoint = Param(nameof(ReversePoint), 400)
.SetDisplay("Reverse Point Length", "Candles used to confirm reversal points", "Signals")
.SetGreaterThanZero()
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to analyse", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highest = null;
_lowest = null;
_latestHighSignal = 0m;
_latestLowSignal = 0m;
_lastExecutedHigh = 0m;
_lastExecutedLow = 0m;
_lastSignalTime = null;
_bestLongPrice = null;
_bestShortPrice = null;
_trailingDistance = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highest = new Highest { Length = ReversePoint };
_lowest = new Lowest { Length = ReversePoint };
var step = Security?.PriceStep ?? 1m;
if (step <= 0m)
step = 1m;
var takeDistance = TakeProfitPoints > 0m ? step * TakeProfitPoints : 0m;
var stopDistance = StopLossPoints > 0m ? step * StopLossPoints : 0m;
_trailingDistance = TrailingStopPoints > 0m ? step * TrailingStopPoints : 0m;
// Enable protective orders that match the original stop and take-profit distances.
StartProtection(
takeDistance > 0m ? new Unit(takeDistance, UnitTypes.Absolute) : default,
stopDistance > 0m ? new Unit(stopDistance, UnitTypes.Absolute) : default
);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var highValue = _highest.Process(new DecimalIndicatorValue(_highest, candle.HighPrice, candle.OpenTime) { IsFinal = true }).ToNullableDecimal();
var lowValue = _lowest.Process(new DecimalIndicatorValue(_lowest, candle.LowPrice, candle.OpenTime) { IsFinal = true }).ToNullableDecimal();
if (highValue is null || lowValue is null)
return;
// Update the latest reversal levels detected by the rolling highest/lowest indicators.
if (highValue.Value == candle.HighPrice)
_latestHighSignal = candle.HighPrice;
if (lowValue.Value == candle.LowPrice)
_latestLowSignal = candle.LowPrice;
// Manage an existing long position by trailing profits and reacting to opposite signals.
if (Position > 0)
{
_bestLongPrice = (_bestLongPrice is null || candle.HighPrice > _bestLongPrice) ? candle.HighPrice : _bestLongPrice;
if (_trailingDistance > 0m && _bestLongPrice is decimal bestLong && bestLong - candle.ClosePrice >= _trailingDistance)
{
SellMarket();
_bestLongPrice = null;
return;
}
if (_latestHighSignal != 0m && _latestHighSignal != _lastExecutedHigh)
{
SellMarket();
_bestLongPrice = null;
return;
}
}
else if (Position < 0)
{
_bestShortPrice = (_bestShortPrice is null || candle.LowPrice < _bestShortPrice) ? candle.LowPrice : _bestShortPrice;
if (_trailingDistance > 0m && _bestShortPrice is decimal bestShort && candle.ClosePrice - bestShort >= _trailingDistance)
{
BuyMarket();
_bestShortPrice = null;
return;
}
if (_latestLowSignal != 0m && _latestLowSignal != _lastExecutedLow)
{
BuyMarket();
_bestShortPrice = null;
return;
}
}
else
{
_bestLongPrice = null;
_bestShortPrice = null;
}
if (Position != 0)
return;
// Avoid placing more than one order within the same candle.
if (_lastSignalTime == candle.OpenTime)
return;
// Execute a new short position when a fresh reversal high is detected.
if (_latestHighSignal != 0m && _latestHighSignal != _lastExecutedHigh)
{
SellMarket();
_lastExecutedHigh = _latestHighSignal;
_lastSignalTime = candle.OpenTime;
_bestShortPrice = candle.ClosePrice;
_bestLongPrice = null;
return;
}
// Execute a new long position when a fresh reversal low is detected.
if (_latestLowSignal != 0m && _latestLowSignal != _lastExecutedLow)
{
BuyMarket();
_lastExecutedLow = _latestLowSignal;
_lastSignalTime = candle.OpenTime;
_bestLongPrice = candle.ClosePrice;
_bestShortPrice = 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, Unit, UnitTypes
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class e_rp250_strategy(Strategy):
def __init__(self):
super(e_rp250_strategy, self).__init__()
self._take_profit_points = self.Param("TakeProfitPoints", 15.0)
self._stop_loss_points = self.Param("StopLossPoints", 999.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 0.0)
self._reverse_point = self.Param("ReversePoint", 400)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._latest_high_signal = 0.0
self._latest_low_signal = 0.0
self._last_executed_high = 0.0
self._last_executed_low = 0.0
self._last_signal_time = None
self._best_long_price = None
self._best_short_price = None
self._trailing_distance = 0.0
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@TrailingStopPoints.setter
def TrailingStopPoints(self, value):
self._trailing_stop_points.Value = value
@property
def ReversePoint(self):
return self._reverse_point.Value
@ReversePoint.setter
def ReversePoint(self, value):
self._reverse_point.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(e_rp250_strategy, self).OnStarted2(time)
self._highest = Highest()
self._highest.Length = self.ReversePoint
self._lowest = Lowest()
self._lowest.Length = self.ReversePoint
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
take_distance = step * float(self.TakeProfitPoints) if float(self.TakeProfitPoints) > 0.0 else 0.0
stop_distance = step * float(self.StopLossPoints) if float(self.StopLossPoints) > 0.0 else 0.0
self._trailing_distance = step * float(self.TrailingStopPoints) if float(self.TrailingStopPoints) > 0.0 else 0.0
tp_unit = Unit(take_distance, UnitTypes.Absolute) if take_distance > 0.0 else Unit()
sl_unit = Unit(stop_distance, UnitTypes.Absolute) if stop_distance > 0.0 else Unit()
self.StartProtection(tp_unit, sl_unit)
self._latest_high_signal = 0.0
self._latest_low_signal = 0.0
self._last_executed_high = 0.0
self._last_executed_low = 0.0
self._last_signal_time = None
self._best_long_price = None
self._best_short_price = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
high_result = process_float(self._highest, candle.HighPrice, candle.OpenTime, True)
low_result = process_float(self._lowest, candle.LowPrice, candle.OpenTime, True)
if high_result.IsEmpty or low_result.IsEmpty:
return
high_value = float(high_result)
low_value = float(low_result)
if abs(high_value - high) < 1e-10:
self._latest_high_signal = high
if abs(low_value - low) < 1e-10:
self._latest_low_signal = low
# Manage existing long position
if self.Position > 0:
if self._best_long_price is None or high > self._best_long_price:
self._best_long_price = high
if self._trailing_distance > 0.0 and self._best_long_price is not None and self._best_long_price - close >= self._trailing_distance:
self.SellMarket()
self._best_long_price = None
return
if self._latest_high_signal != 0.0 and self._latest_high_signal != self._last_executed_high:
self.SellMarket()
self._best_long_price = None
return
elif self.Position < 0:
if self._best_short_price is None or low < self._best_short_price:
self._best_short_price = low
if self._trailing_distance > 0.0 and self._best_short_price is not None and close - self._best_short_price >= self._trailing_distance:
self.BuyMarket()
self._best_short_price = None
return
if self._latest_low_signal != 0.0 and self._latest_low_signal != self._last_executed_low:
self.BuyMarket()
self._best_short_price = None
return
else:
self._best_long_price = None
self._best_short_price = None
if self.Position != 0:
return
# Avoid placing more than one order within the same candle
if self._last_signal_time is not None and self._last_signal_time == candle.OpenTime:
return
# Short on fresh reversal high
if self._latest_high_signal != 0.0 and self._latest_high_signal != self._last_executed_high:
self.SellMarket()
self._last_executed_high = self._latest_high_signal
self._last_signal_time = candle.OpenTime
self._best_short_price = close
self._best_long_price = None
return
# Long on fresh reversal low
if self._latest_low_signal != 0.0 and self._latest_low_signal != self._last_executed_low:
self.BuyMarket()
self._last_executed_low = self._latest_low_signal
self._last_signal_time = candle.OpenTime
self._best_long_price = close
self._best_short_price = None
def OnReseted(self):
super(e_rp250_strategy, self).OnReseted()
self._latest_high_signal = 0.0
self._latest_low_signal = 0.0
self._last_executed_high = 0.0
self._last_executed_low = 0.0
self._last_signal_time = None
self._best_long_price = None
self._best_short_price = None
self._trailing_distance = 0.0
def CreateClone(self):
return e_rp250_strategy()