Открыть на GitHub

Стратегия RPoint 250 Reversal

RPoint 250 Reversal — портирование эксперта MetaTrader 4 e_RPoint_250 на платформу StockSharp. В оригинале советник использует пользовательский индикатор RPoint, который отмечает последние экстремумы цены. В StockSharp такого индикатора нет, поэтому стратегия имитирует его работу связкой стандартных индикаторов Highest и Lowest. Как только появляется новый максимум или минимум, система моментально переворачивает позицию и заново выставляет стоп-лосс, тейк-профит и трейлинг, как в версии MQL.

Логика работы

  1. Подписываемся на свечи с типом CandleType (по умолчанию пятиминутные).
  2. Рассчитываем скользящий максимум и минимум за последние ReversePoint баров — это аналог уровней RPoint.
  3. Если формируется новый максимум, закрываем все покупки и открываем продажу объёмом OrderVolume.
  4. Если формируется новый минимум, закрываем все продажи и открываем покупку объёмом OrderVolume.
  5. Защита позиции выполняется через StartProtection: расстояния StopLossPoints и TakeProfitPoints задаются в пунктах цены.
  6. При ненулевом TrailingStopPoints прибыль фиксируется трейлингом — позиция закрывается, когда цена откатывает на указанное число пунктов от лучшего значения после входа.
  7. Запоминаем время свечи с последним входом и не открываем новые сделки в ту же свечу. Это полностью повторяет защиту TimeN в исходном коде.

Стратегия поддерживает только одну позицию: перед входом в противоположную сторону текущая сделка всегда закрывается.

Параметры

Параметр Тип Значение по умолчанию Описание
OrderVolume decimal 0.1 Объём рыночных заявок, аналог параметра Lots в MetaTrader.
TakeProfitPoints decimal 15 Размер тейк-профита в пунктах. Ноль отключает фиксацию прибыли.
StopLossPoints decimal 999 Размер стоп-лосса в пунктах. Ноль позволяет торговать без фиксированного стопа.
TrailingStopPoints decimal 0 Ширина трейлинга в пунктах. При нуле трейлинг не используется.
ReversePoint int 250 Количество свечей для поиска последнего экстремума. Большие значения уменьшают шум.
CandleType DataType TimeSpan.FromMinutes(5).TimeFrame() Тип свечей для анализа. Подберите под таймфрейм в MetaTrader.

Особенности реализации

  • Highest и Lowest подключены через высокоуровневый метод Bind, поэтому дополнительные буферы не нужны.
  • StartProtection переводит параметры стопа и тейка в абсолютные цены, и StockSharp сам сопровождает позицию.
  • Трейлинг реализован на закрытых свечах: если цена откатывает на заданное количество пунктов от максимальной прибыли, позиция закрывается маркет-ордером.
  • Переменные _executedHighLevel и _executedLowLevel хранят уровень последнего срабатывания, чтобы не дублировать сделки. Это прямой аналог Reverse_High и Reverse_Low из MQL.
  • Поле _lastSignalTime повторяет переменную TimeN и блокирует повторный вход в пределах одной свечи.

Рекомендации по использованию

  1. Запустите стратегию в портфеле, где доступен нужный инструмент и тип свечей.
  2. Настройте OrderVolume под торговые ограничения брокера и собственное управление капиталом.
  3. Подберите ReversePoint под волатильность инструмента: большой горизонт сглаживает шум, но реагирует медленнее.
  4. Убедитесь, что значения StopLossPoints, TakeProfitPoints и TrailingStopPoints совместимы с PriceStep инструмента.
  5. Перед реальной торговлей выполните тест в StockSharp Designer или Backtester и убедитесь, что поведение соответствует ожиданиям.
  6. Контролируйте журналы стратегии — они помогут отследить перевороты и сравнить их с оригинальным экспертом.

Поскольку индикатор RPoint заменён стандартными компонентами, возможны незначительные расхождения с MetaTrader при разных источниках данных или правилах округления. Всегда проверяйте стратегию на собственных котировках до запуска в продакшне.

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;
			}
		}
	}
}