Открыть на GitHub

Стратегия NRTR Revers

Обзор

NRTR Revers – это перенос советника MetaTrader 5 NRTR_Revers.mq5 на платформу StockSharp. Стратегия использует подход Nick Rypock Trailing Reverse (NRTR) и меняет торговый уклон между покупкой и продажей в зависимости от того, как цена взаимодействует с диапазонами, построенными по ATR. Все расчёты выполняются на закрытии завершённых свечей выбранного таймфрейма.

Логика торговли

  1. ATR-проекция – рассчитывается средний истинный диапазон (ATR) с настраиваемым периодом и умножается на коэффициент VolatilityMultiplier, чтобы получить ширину защитной полосы.
  2. Динамические уровни – для текущего направления:
    • Определяется минимум (или максимум) в окне, полностью повторяющем оригинальный MQL-алгоритм.
    • Ищется дополнительный экстремум глубже по истории; его расстояние до основной полосы сравнивается с параметром ReversePips, чтобы подтвердить сильное движение.
  3. Переворот тренда – если предыдущая свеча закрылась за пределами ATR-полосы или расстояние до вторичного экстремума превышает заданный порог, уклон стратегии меняется (BUY → SELL или SELL → BUY). При наличии обратной позиции она закрывается, после чего открывается новая сделка в нужную сторону.
  4. Ожидание плоской позиции – после принудительного закрытия позиции стратегиия ждёт, пока позиция станет нулевой, и только затем размещает заявку в новом направлении. Такое поведение полностью воспроизводит логику исходного советника.
  5. Управление рисками – стоп-лосс, тейк-профит и трейлинг задаются в пунктах и переводятся в цену с учётом скорректированного шага (корректно для инструментов с 3 и 5 знаками). Трейлинг переносится только при приросте больше, чем TrailingStopPips + TrailingStepPips, то есть точь-в-точь как в версии для MT5.

Параметры

  • CandleType – таймфрейм, по которому подписываются свечи.
  • AtrPeriod – период сглаживания ATR.
  • VolatilityMultiplier – множитель ATR, определяющий отступ от экстремума.
  • ReversePips – дополнительное расстояние в пунктах, которое должен преодолеть вторичный экстремум для смены тренда.
  • StopLossPips – расстояние от входа до стоп-лосса (0 отключает уровень).
  • TakeProfitPips – расстояние от входа до тейк-профита (0 отключает уровень).
  • TrailingStopPips – глубина включения трейлинг-стопа (0 отключает трейлинг).
  • TrailingStepPips – минимальный дополнительный профит для переноса трейлинг-стопа; при включённом трейлинге должен быть больше нуля.
  • TradeVolume – объём заявки в единицах инструмента (лоты, контракты и т. п.).

Примечания

  • Стратегия работает только с завершёнными свечами и игнорирует незакрытые бары.
  • Получаемое через Bind значение ATR соответствует atr_array[1] из оригинального MQL-скрипта, так как обработка происходит после закрытия свечи.
  • Расчёт шага цены автоматически учитывает трёх- и пятизначные котировки, поэтому параметры в пунктах полностью совместимы с исходным советником.
  • По требованию заказчика Python-версия и соответствующая папка не создавались; доступна только реализация на C#.
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// NRTR reversal strategy using ATR channel around EMA.
/// Goes long when price breaks above EMA + ATR*multiplier.
/// Goes short when price breaks below EMA - ATR*multiplier.
/// </summary>
public class NrtrReversStrategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _volatilityMultiplier;
	private readonly StrategyParam<int> _emaPeriod;

	private AverageTrueRange _atr;
	private ExponentialMovingAverage _ema;

	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// ATR period.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// ATR multiplier for band calculation.
	/// </summary>
	public decimal VolatilityMultiplier
	{
		get => _volatilityMultiplier.Value;
		set => _volatilityMultiplier.Value = value;
	}

	/// <summary>
	/// EMA period for trend center.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public NrtrReversStrategy()
	{
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR averaging period", "Indicator");

		_volatilityMultiplier = Param(nameof(VolatilityMultiplier), 5m)
			.SetGreaterThanZero()
			.SetDisplay("Multiplier", "ATR multiplier for bands", "Indicator");

		_emaPeriod = Param(nameof(EmaPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend center period", "Indicator");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_atr = null;
		_ema = null;
		_entryPrice = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_atr = new AverageTrueRange { Length = AtrPeriod };
		_ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_atr, _ema, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal atrValue, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_atr.IsFormed || !_ema.IsFormed)
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		var close = candle.ClosePrice;
		var bandOffset = atrValue * VolatilityMultiplier;

		var upperBand = emaValue + bandOffset;
		var lowerBand = emaValue - bandOffset;

		// Buy: price breaks above upper band
		if (close > upperBand && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 50;
		}
		// Sell: price breaks below lower band
		else if (close < lowerBand && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 50;
		}
		// Exit long: price returns to EMA
		else if (Position > 0 && close < emaValue)
		{
			SellMarket();
			_entryPrice = 0;
			_cooldown = 50;
		}
		// Exit short: price returns to EMA
		else if (Position < 0 && close > emaValue)
		{
			BuyMarket();
			_entryPrice = 0;
			_cooldown = 50;
		}
	}
}