View on GitHub

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

  1. Subscribe to the candle series specified by CandleType (default: 5-minute candles).
  2. Track the rolling maximum and minimum over the last ReversePoint bars. These values represent the emulated RPoint levels.
  3. If price prints a new highest high, close any long position and open a short position with volume OrderVolume.
  4. If price prints a new lowest low, close any short position and open a long position with volume OrderVolume.
  5. Apply protective orders using StartProtection. The stop-loss and take-profit distances are expressed in price points via the parameters StopLossPoints and TakeProfitPoints.
  6. 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.
  7. 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

  1. Attach the strategy to a portfolio that supports the selected instrument and candle type.
  2. Adjust OrderVolume to comply with the contract size and risk management rules of your broker.
  3. Tune ReversePoint to match the volatility of the traded asset. Higher values yield fewer but more meaningful reversals.
  4. Verify that StopLossPoints, TakeProfitPoints and TrailingStopPoints are compatible with the security's PriceStep.
  5. Run a backtest in StockSharp Designer or Backtester to confirm the behaviour before trading live capital.
  6. 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;
			}
		}
	}
}