View on GitHub

NRTR Extr Strategy

This strategy implements the Nick Rypock Trailing Reverse (NRTR) algorithm with additional signal arrows. It is a conversion of the original MQL5 example "Exp_NRTR_extr" to the StockSharp high level API.

How It Works

  • The custom NrtrExtrIndicator computes an average range over a configurable period and draws a trailing level that follows price.
  • When the price reverses beyond this level the indicator flips direction and emits a buy or sell signal.
  • The strategy opens a long position on a buy signal and a short position on a sell signal.
  • Existing positions are closed on the opposite signal or when the defined stop loss or take profit levels are hit.

Parameters

Name Description
Period Number of candles used for average range calculation.
Digits Shift Additional precision adjustment applied to the range factor.
Stop Loss Protective stop in price points.
Take Profit Profit target in price points.
Enable Buy Open / Enable Sell Open Allow opening long or short positions.
Enable Buy Close / Enable Sell Close Allow closing existing positions on opposite signals.
Candle Type Timeframe of candles used for the indicator.

Notes

The indicator is based on the Average True Range to estimate market volatility. For visualization the strategy automatically draws candles and executed trades on the chart area.

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>
/// NRTR Extr strategy - trend following based on ATR-based trailing levels.
/// Opens long when trend turns up, short when trend turns down.
/// </summary>
public class NrtrExtrStrategy : Strategy
{
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _price;
	private decimal _value;
	private int _trend;
	private int _trendPrev;
	private bool _initialized;

	public int Period { get => _period.Value; set => _period.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public NrtrExtrStrategy()
	{
		_period = Param(nameof(Period), 10)
			.SetGreaterThanZero()
			.SetDisplay("Period", "ATR period for NRTR", "Indicator")
			.SetOptimize(5, 20, 5);

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Time frame", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_price = 0;
		_value = 0;
		_trend = 0;
		_trendPrev = 0;
		_initialized = false;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var atr = new AverageTrueRange { Length = Period };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

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

		if (!atrValue.IsFormed)
			return;

		var atr = atrValue.GetValue<decimal>();

		if (atr <= 0)
			return;

		if (!_initialized)
		{
			_price = candle.ClosePrice;
			_value = candle.ClosePrice;
			_trend = 1;
			_trendPrev = 1;
			_initialized = true;
			return;
		}

		var dK = atr / Period;

		if (_trend >= 0)
		{
			_price = Math.Max(_price, candle.HighPrice);
			_value = Math.Max(_value, _price * (1m - dK));

			if (candle.ClosePrice < _value)
			{
				_price = candle.LowPrice;
				_value = _price * (1m + dK);
				_trend = -1;
			}
		}
		else
		{
			_price = Math.Min(_price, candle.LowPrice);
			_value = Math.Min(_value, _price * (1m + dK));

			if (candle.ClosePrice > _value)
			{
				_price = candle.HighPrice;
				_value = _price * (1m - dK);
				_trend = 1;
			}
		}

		var buySignal = _trendPrev <= 0 && _trend > 0;
		var sellSignal = _trendPrev >= 0 && _trend < 0;

		if (IsFormedAndOnlineAndAllowTrading())
		{
			if (buySignal && Position <= 0)
				BuyMarket();
			else if (sellSignal && Position >= 0)
				SellMarket();
		}

		_trendPrev = _trend;
	}
}