RPoint 250 Reversal Strategy
The RPoint 250 Reversal Strategy is a StockSharp port of the MetaTrader 4 expert advisor e_RPoint_250. The original robot
relies on a custom indicator called RPoint that highlights the most recent swing high and swing low. Because that indicator is
not available on StockSharp, the conversion reproduces the same behaviour with the built-in Highest and Lowest indicators.
Whenever a new extreme replaces the previously detected one, the strategy immediately flips the position and restores the same
stop-loss, take-profit and trailing logic defined in the MQL version.
Trading workflow
- Subscribe to the candle series specified by
CandleType (default: 5-minute candles).
- Track the rolling maximum and minimum over the last
ReversePoint bars. These values represent the emulated RPoint levels.
- If price prints a new highest high, close any long position and open a short position with volume
OrderVolume.
- If price prints a new lowest low, close any short position and open a long position with volume
OrderVolume.
- Apply protective orders using
StartProtection. The stop-loss and take-profit distances are expressed in price points via
the parameters StopLossPoints and TakeProfitPoints.
- Optionally trail profits by
TrailingStopPoints. The trailing engine measures how far price has moved in favour of the
position and closes it when price retraces by the configured number of points.
- Remember the candle time of the last successful entry to avoid opening multiple trades within the same bar, matching the
TimeN safeguard from the MQL script.
The strategy always maintains at most one open position. It closes existing trades before entering in the opposite direction and
never scales in.
Parameters
| Parameter |
Type |
Default |
Description |
OrderVolume |
decimal |
0.1 |
Volume sent with each market order. Mirrors the Lots input in the MetaTrader version. |
TakeProfitPoints |
decimal |
15 |
Distance to the take-profit order measured in price points. Set to 0 to disable profit targets. |
StopLossPoints |
decimal |
999 |
Distance to the protective stop expressed in price points. Set to 0 to trade without a fixed stop. |
TrailingStopPoints |
decimal |
0 |
Optional trailing distance in price points. When zero, the trailing logic is disabled. |
ReversePoint |
int |
250 |
Number of candles considered when searching for the latest swing high and swing low. Larger values smooth out noise. |
CandleType |
DataType |
TimeSpan.FromMinutes(5).TimeFrame() |
Candle aggregation analysed by the strategy. Change it to match the chart timeframe used in MetaTrader. |
Implementation notes
Highest and Lowest are bound to the candle subscription via the high-level Bind API, so no manual indicator queues are
required.
StartProtection reproduces the original stop-loss and take-profit distances in absolute price units. StockSharp handles the
order placement once a new position appears.
- Trailing stops are implemented by monitoring each completed candle. When price retreats by the configured number of points from
the best price achieved after entry, the position is closed with a market order.
- The class stores the most recent executed reversal levels (
_executedHighLevel and _executedLowLevel) to avoid duplicate
entries. This is equivalent to the Reverse_High / Reverse_Low variables in the MQL code.
- The
_lastSignalTime field mirrors the TimeN variable and blocks multiple orders inside the same candle, preventing
accidental double submissions on illiquid markets.
Usage guidelines
- Attach the strategy to a portfolio that supports the selected instrument and candle type.
- Adjust
OrderVolume to comply with the contract size and risk management rules of your broker.
- Tune
ReversePoint to match the volatility of the traded asset. Higher values yield fewer but more meaningful reversals.
- Verify that
StopLossPoints, TakeProfitPoints and TrailingStopPoints are compatible with the security's PriceStep.
- Run a backtest in StockSharp Designer or Backtester to confirm the behaviour before trading live capital.
- Monitor the log output: informational messages will highlight position changes and can help validate the conversion.
Because the RPoint indicator is approximated with built-in components, minor differences from the MetaTrader execution are
possible on historical data with gaps or different rounding rules. Always validate the results with your own market data feeds
before relying on the strategy in production.
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 MetaTrader 4 expert e_RPoint_250.
/// </summary>
public class RPoint250Strategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
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 _lastHighLevel;
private decimal _lastLowLevel;
private decimal _executedHighLevel;
private decimal _executedLowLevel;
private DateTimeOffset? _lastSignalTime;
private decimal _priceStep;
private decimal _trailingDistance;
private decimal? _bestLongPrice;
private decimal? _bestShortPrice;
public RPoint250Strategy()
{
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetDisplay("Order Volume", "Base volume for market entries.", "Trading")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
.SetDisplay("Take Profit Points", "Take profit distance expressed in price points.", "Risk")
;
_stopLossPoints = Param(nameof(StopLossPoints), 999m)
.SetDisplay("Stop Loss Points", "Stop loss distance expressed in price points.", "Risk")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 0m)
.SetDisplay("Trailing Stop Points", "Optional trailing distance in price points.", "Risk")
;
_reversePoint = Param(nameof(ReversePoint), 250)
.SetDisplay("Reverse Point Length", "Number of candles scanned for the latest reversal levels.", "Signals")
.SetGreaterThanZero()
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle aggregation used for calculations.", "General");
}
/// <summary>
/// Market order volume used for both entries and reversals.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Take-profit distance in price points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop-loss distance in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing-stop distance in price points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Number of candles used to approximate the rPoint indicator.
/// </summary>
public int ReversePoint
{
get => _reversePoint.Value;
set => _reversePoint.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highest = null;
_lowest = null;
_lastHighLevel = 0m;
_lastLowLevel = 0m;
_executedHighLevel = 0m;
_executedLowLevel = 0m;
_lastSignalTime = null;
_priceStep = 0m;
_trailingDistance = 0m;
_bestLongPrice = null;
_bestShortPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highest = new Highest { Length = Math.Max(1, ReversePoint) };
_lowest = new Lowest { Length = Math.Max(1, ReversePoint) };
_priceStep = Security?.PriceStep ?? 0m;
if (_priceStep <= 0m)
_priceStep = 1m;
var takeDistance = TakeProfitPoints > 0m ? _priceStep * TakeProfitPoints : 0m;
var stopDistance = StopLossPoints > 0m ? _priceStep * StopLossPoints : 0m;
_trailingDistance = TrailingStopPoints > 0m ? _priceStep * TrailingStopPoints : 0m;
// Apply the same static protection as in the original MQL script.
var tp = takeDistance > 0m ? new Unit(takeDistance, UnitTypes.Absolute) : (Unit)null;
var sl = stopDistance > 0m ? new Unit(stopDistance, UnitTypes.Absolute) : (Unit)null;
if (tp != null || sl != null)
StartProtection(tp, sl);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_highest, _lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _highest);
DrawIndicator(area, _lowest);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal highestValue, decimal lowestValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_highest.IsFormed || !_lowest.IsFormed)
return;
// Capture the latest swing levels as soon as they appear.
if (highestValue == candle.HighPrice && highestValue != _lastHighLevel)
_lastHighLevel = highestValue;
if (lowestValue == candle.LowPrice && lowestValue != _lastLowLevel)
_lastLowLevel = lowestValue;
if (Position > 0)
{
_bestLongPrice = _bestLongPrice is null || candle.HighPrice > _bestLongPrice
? candle.HighPrice
: _bestLongPrice;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Close the long position when price retraces by the trailing distance.
if (_trailingDistance > 0m && _bestLongPrice is decimal bestLong && bestLong - candle.LowPrice >= _trailingDistance)
{
SellMarket(Position);
_bestLongPrice = null;
return;
}
// Reverse the position when a new high reversal point appears.
if (_lastHighLevel != 0m && _lastHighLevel != _executedHighLevel)
{
SellMarket(Position);
_bestLongPrice = null;
return;
}
}
else if (Position < 0)
{
_bestShortPrice = _bestShortPrice is null || candle.LowPrice < _bestShortPrice
? candle.LowPrice
: _bestShortPrice;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Close the short position when price rallies by the trailing distance.
if (_trailingDistance > 0m && _bestShortPrice is decimal bestShort && candle.HighPrice - bestShort >= _trailingDistance)
{
BuyMarket(-Position);
_bestShortPrice = null;
return;
}
// Reverse the position when a new low reversal point appears.
if (_lastLowLevel != 0m && _lastLowLevel != _executedLowLevel)
{
BuyMarket(-Position);
_bestShortPrice = null;
return;
}
}
else
{
_bestLongPrice = null;
_bestShortPrice = null;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (OrderVolume <= 0m)
return;
if (_lastSignalTime == candle.OpenTime)
return;
// Enter short when the reversal high changes.
if (_lastHighLevel != 0m && _lastHighLevel != _executedHighLevel)
{
SellMarket(OrderVolume);
_executedHighLevel = _lastHighLevel;
_lastSignalTime = candle.OpenTime;
_bestShortPrice = candle.ClosePrice;
return;
}
// Enter long when the reversal low changes.
if (_lastLowLevel != 0m && _lastLowLevel != _executedLowLevel)
{
BuyMarket(OrderVolume);
_executedLowLevel = _lastLowLevel;
_lastSignalTime = candle.OpenTime;
_bestLongPrice = 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, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import Highest, Lowest
class r_point250_strategy(Strategy):
def __init__(self):
super(r_point250_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetDisplay("Order Volume", "Base volume for market entries", "Trading")
self._take_profit_points = self.Param("TakeProfitPoints", 500.0) \
.SetDisplay("Take Profit Points", "Take profit distance in price points", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 999.0) \
.SetDisplay("Stop Loss Points", "Stop loss distance in price points", "Risk")
self._trailing_stop_points = self.Param("TrailingStopPoints", 0.0) \
.SetDisplay("Trailing Stop Points", "Optional trailing distance in price points", "Risk")
self._reverse_point = self.Param("ReversePoint", 250) \
.SetDisplay("Reverse Point Length", "Number of candles scanned for reversal levels", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle aggregation used for calculations", "General")
self._last_high_level = 0.0
self._last_low_level = 0.0
self._executed_high_level = 0.0
self._executed_low_level = 0.0
self._last_signal_time = None
self._price_step = 1.0
self._trailing_distance = 0.0
self._best_long_price = None
self._best_short_price = None
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@property
def ReversePoint(self):
return self._reverse_point.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(r_point250_strategy, self).OnStarted2(time)
highest = Highest()
highest.Length = max(1, self.ReversePoint)
lowest = Lowest()
lowest.Length = max(1, self.ReversePoint)
ps = self.Security.PriceStep if self.Security is not None else None
self._price_step = float(ps) if ps is not None else 1.0
if self._price_step <= 0:
self._price_step = 1.0
tp_pts = float(self.TakeProfitPoints)
sl_pts = float(self.StopLossPoints)
trail_pts = float(self.TrailingStopPoints)
take_dist = self._price_step * tp_pts if tp_pts > 0 else 0.0
stop_dist = self._price_step * sl_pts if sl_pts > 0 else 0.0
self._trailing_distance = self._price_step * trail_pts if trail_pts > 0 else 0.0
tp = Unit(take_dist, UnitTypes.Absolute) if take_dist > 0 else None
sl = Unit(stop_dist, UnitTypes.Absolute) if stop_dist > 0 else None
if tp is not None or sl is not None:
self.StartProtection(tp, sl)
self._highest = highest
self._lowest = lowest
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self.ProcessCandle).Start()
def ProcessCandle(self, candle, highest_value, lowest_value):
if candle.State != CandleStates.Finished:
return
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
highest_value = float(highest_value)
lowest_value = float(lowest_value)
high_price = float(candle.HighPrice)
low_price = float(candle.LowPrice)
close_price = float(candle.ClosePrice)
if highest_value == high_price and highest_value != self._last_high_level:
self._last_high_level = highest_value
if lowest_value == low_price and lowest_value != self._last_low_level:
self._last_low_level = lowest_value
if self.Position > 0:
if self._best_long_price is None or high_price > self._best_long_price:
self._best_long_price = high_price
if (self._trailing_distance > 0 and self._best_long_price is not None
and self._best_long_price - low_price >= self._trailing_distance):
self.SellMarket(self.Position)
self._best_long_price = None
return
if self._last_high_level != 0 and self._last_high_level != self._executed_high_level:
self.SellMarket(self.Position)
self._best_long_price = None
return
elif self.Position < 0:
if self._best_short_price is None or low_price < self._best_short_price:
self._best_short_price = low_price
if (self._trailing_distance > 0 and self._best_short_price is not None
and high_price - self._best_short_price >= self._trailing_distance):
self.BuyMarket(-self.Position)
self._best_short_price = None
return
if self._last_low_level != 0 and self._last_low_level != self._executed_low_level:
self.BuyMarket(-self.Position)
self._best_short_price = None
return
else:
self._best_long_price = None
self._best_short_price = None
ov = float(self.OrderVolume)
if ov <= 0:
return
if self._last_signal_time == candle.OpenTime:
return
if self._last_high_level != 0 and self._last_high_level != self._executed_high_level:
self.SellMarket(ov)
self._executed_high_level = self._last_high_level
self._last_signal_time = candle.OpenTime
self._best_short_price = close_price
return
if self._last_low_level != 0 and self._last_low_level != self._executed_low_level:
self.BuyMarket(ov)
self._executed_low_level = self._last_low_level
self._last_signal_time = candle.OpenTime
self._best_long_price = close_price
def OnReseted(self):
super(r_point250_strategy, self).OnReseted()
self._last_high_level = 0.0
self._last_low_level = 0.0
self._executed_high_level = 0.0
self._executed_low_level = 0.0
self._last_signal_time = None
self._best_long_price = None
self._best_short_price = None
def CreateClone(self):
return r_point250_strategy()